| 461 | |
| 462 | // Highlights text starting from the given startIndex with the specified length. |
| 463 | const highlight = (textNode, startIndex, length) => { |
| 464 | if (startIndex === -1) { |
| 465 | return false; |
| 466 | } |
| 467 | const selection = globalThis.getSelection(); |
| 468 | const range = document.createRange(); |
| 469 | range.setStart(textNode, startIndex); |
| 470 | range.setEnd(textNode, startIndex + length); |
| 471 | selection.removeAllRanges(); |
| 472 | selection.addRange(range); |
| 473 | |
| 474 | // Ensure the highlighted element is visible within the viewport. |
| 475 | const rect = range.getBoundingClientRect(); |
| 476 | if (rect.top < 0 || rect.bottom > globalThis.innerHeight) { |
| 477 | const screenHeight = globalThis.innerHeight; |
| 478 | globalThis.scrollTo({ |
| 479 | top: globalThis.scrollY + rect.top + rect.height / 2 - screenHeight / 2, |
| 480 | // Scroll instantly when we find a search result. This matches the behavior of Chrome and |
| 481 | // Firefox's native search UI. See #4661. |
| 482 | behavior: "instant", |
| 483 | }); |
| 484 | } |
| 485 | |
| 486 | return true; |
| 487 | }; |
| 488 | |
| 489 | const getAllTextNodes = () => { |
| 490 | const textNodes = []; |