Skip to content

TypeError: null.offsetTop #390

@dingshaohua-com

Description

@dingshaohua-com

getIsHeaderBottomMode passes CSS-escaped ID to getElementById, causing TypeError: null.offsetTop

Bug Description

When a heading has an ID containing CSS special characters (e.g. . in 3.1-introduction), tocbot throws:

Uncaught TypeError: Cannot read properties of null (reading 'offsetTop')
    at getIsHeaderBottomMode (build-html.js:354)
    at Object.updateToc (build-html.js:222)

Root Cause

In build-html.js, topHeaderId is CSS-escaped at line 207-210:

const topHeaderId = topHeader.id.replace(
  /([ #;&,.+*~':"!^$[\]()=>|/\\@])/g,
  "\\$1",
)

This is correct for CSS selectors (line 233):

const activeTocLink = tocElement.querySelector(
  `.${options.linkClass}[href="${options.basePath}#${activeId}"]`,
)

But the escaped ID is also passed to getIsHeaderBottomMode at line 222:

(getIsHeaderBottomMode(topHeaderId) || eventCount <= 2)

Where it's used with getElementById (line 352):

const activeHeading = document?.getElementById(headerId)
const isBottomMode = activeHeading.offsetTop > ...  // activeHeading is null!

getElementById expects the raw ID (3.1-introduction), not the CSS-escaped version (3\.1-introduction), so it returns null.

Steps to Reproduce

  1. Have a heading with . in its ID, e.g. <h2 id="3.1-introduction">3.1 Introduction</h2>
  2. Initialize tocbot with scrollContainer set
  3. Click a TOC link → URL gets #3.1-introduction
  4. Scroll or click another TOC link → crash

Suggested Fix

Unescape the ID before passing to getElementById, or keep a separate unescaped copy:

// Option A: pass raw ID to getIsHeaderBottomMode
const rawTopHeaderId = topHeader.id
const topHeaderId = rawTopHeaderId.replace(
  /([ #;&,.+*~':"!^$[\]()=>|/\\@])/g,
  "\\$1",
)
// ...
(getIsHeaderBottomMode(rawTopHeaderId) || eventCount <= 2)

Also add null check in getIsHeaderBottomMode:

function getIsHeaderBottomMode(headerId) {
  const scrollEl = getScrollEl()
  const activeHeading = document?.getElementById(headerId)
  if (!activeHeading) return false
  // ...
}

Environment

  • tocbot: 4.36.4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions