Skip to content
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e6fa339
Add scroll marker fake scrollbar implementation
arogl Mar 28, 2026
9e2ff52
Update Japanese locale, and fix formatting changes
arogl Mar 28, 2026
b305601
Remove period mark from ja strings
pzhlkj6612 Apr 8, 2026
e4d4f64
Fix wrong level of content in table
pzhlkj6612 Apr 8, 2026
d7892ea
Revert code style changes
pzhlkj6612 Apr 8, 2026
b3a6649
Change names and translations to match existing style
pzhlkj6612 Apr 8, 2026
4e5c7ff
Remove useless tr-catch and null-check statements
pzhlkj6612 Apr 8, 2026
f8cc623
Merge pull request #1 from pzhlkj6612/arogl/cleanup
arogl Apr 10, 2026
90a1b6a
Ensure highlights add and remove simultaneously
arogl May 1, 2026
db50cbe
Remove all try-catch
pzhlkj6612 Apr 23, 2026
b705b0e
Remove unused parameters
pzhlkj6612 May 11, 2026
e9296d7
Remove an empty line
pzhlkj6612 May 28, 2026
6e7538c
Make scrollbar width const
pzhlkj6612 May 28, 2026
d9cddc1
Remove unreliable page color check; use only one style
pzhlkj6612 May 28, 2026
9ab9bf0
Throttle down "scroll" by "setTimeout" not "requestAnimationFrame"
pzhlkj6612 May 29, 2026
bd73d98
Use local variables
pzhlkj6612 May 30, 2026
65da005
Extract common things from the code about scrollbar; readability
pzhlkj6612 Jun 1, 2026
5e83b02
Process existing elements directly instead; reduce fallbacks
pzhlkj6612 Jun 1, 2026
32cbbb0
Query markers one time only when updating style for the active one
pzhlkj6612 Jun 2, 2026
e42a39d
Add or remove injected "find-ext-scrollbar-style" directly
pzhlkj6612 Jun 2, 2026
c6efb60
Remove a fallback from "createScrollMarker"
pzhlkj6612 Jun 2, 2026
6f0b50e
Simplify by using returned element from "appendChild()"
pzhlkj6612 Aug 4, 2026
f96cc3f
Manage merged styles in ShadowRoot
pzhlkj6612 Aug 4, 2026
bddcd36
Reduce DOM traversal by calculating markerTop during matching checking
pzhlkj6612 Aug 5, 2026
d29c894
Drop meaningless fallbacks or checks
pzhlkj6612 Aug 5, 2026
5bf97ff
Restore dark-mode detection by media query; hard-code colors for now
pzhlkj6612 Aug 5, 2026
9905f25
Object-Oriented Programming: ScrollbarHighlightMaker
pzhlkj6612 Aug 5, 2026
4602a4b
Minor refactors for variable naming and comments
pzhlkj6612 Aug 5, 2026
c4a5c0a
Merge pull request #2 from pzhlkj6612/arogl/cleanup
arogl Aug 6, 2026
2f047ec
Break scrollbar into separate files
arogl Aug 9, 2026
ac6826f
Update manifest for new files
arogl Aug 9, 2026
bb8795a
cur/paste error :sad:
arogl Aug 9, 2026
62e44a7
Restore unneeded changes from JS and JSON files
pzhlkj6612 Aug 12, 2026
0fe26e5
Merge pull request #3 from pzhlkj6612/arogl/cleanup
arogl Aug 13, 2026
06be275
Address scrollbar comments
arogl Aug 14, 2026
b85f94d
I think this fixes the last comments.
arogl Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@
"message": "Hide Find and Replace Pane Toggle Button",
"description": "Text displayed beside the hide find and replace toggle button option."
},
"extension_option_scroll_markers_text": {
"message": "Show Scroll Markers",
"description": "Text displayed beside the scroll markers toggle option."
},
"extension_option_scroll_markers_title": {
"message": "If enabled, scroll markers will appear on the right edge of the page indicating the position of all matches in the document.",
"description": "Tooltip text for the scroll markers option description icon."
},
"search_option_max_results_text": {
"message": "Max Highlighted Results",
"description": "Text displayed above the max highlighted results option."
Expand Down
8 changes: 8 additions & 0 deletions _locales/ja/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@
"message": "検索と置換ボタンを非表示",
"description": "Text displayed beside the hide find and replace toggle button option."
},
"extension_option_scroll_markers_text": {
"message": "スクロールマーカーを表示",
"description": "Text displayed beside the scroll markers toggle option."
},
"extension_option_scroll_markers_title": {
"message": "有効時:すべての一致箇所の位置を示すスクロールマーカーがページ右端に表示されます",
"description": "Tooltip text for the scroll markers option description icon."
},
"search_option_max_results_text": {
"message": "最大ハイライト数",
"description": "Text displayed above the max highlighted results option."
Expand Down
8 changes: 8 additions & 0 deletions _locales/zh/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@
"message": "隐藏“查找并替换”按钮",
"description": "Text displayed beside the hide find and replace toggle button option."
},
"extension_option_scroll_markers_text": {
"message": "显示滚动标记",
"description": "Text displayed beside the scroll markers toggle option."
},
"extension_option_scroll_markers_title": {
"message": "若启用,滚动标记将出现在页面的右边缘,指示文档中所有匹配项的位置。",
"description": "Tooltip text for the scroll markers option description icon."
},
"search_option_max_results_text": {
"message": "最大高亮数量",
"description": "Text displayed above the max highlighted results option."
Expand Down
256 changes: 255 additions & 1 deletion content/highlighter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Find.register('Content.Highlighter', function(self) {

const indexHighlight = 'find-ext-index-highlight';
const allHighlight = 'find-ext-all-highlight';
let scrollbarMaker = null;

/**
* Highlight all occurrences of a regex in the page, using an occurrence map and regex.
Expand All @@ -18,6 +19,11 @@ Find.register('Content.Highlighter', function(self) {
* @param {object} options - The search and highlight options
* */
self.highlightAll = function(occurrenceMap, regex, options) {
if (options && options.scroll_markers) {
Comment thread
brandon1024 marked this conversation as resolved.
scrollbarMaker?.destroy();
scrollbarMaker = new ScrollbarHighlightMaker(options);
}

const tags = {
occIndex: null,
maxIndex: null,
Expand Down Expand Up @@ -185,6 +191,9 @@ Find.register('Content.Highlighter', function(self) {
inMatch = charMap[key].matched;
matchGroup.text += tags.openingMarkup;
}
if (options && options.scroll_markers) {
scrollbarMaker.addOccurrence(occIndex, document.getElementById(matchGroup.groupUUID));
}
} else {
if (inMatch) {
inMatch = charMap[key].matched;
Expand Down Expand Up @@ -217,6 +226,12 @@ Find.register('Content.Highlighter', function(self) {
}
}
}

// Collect occurrence IDs from highlight spans
if (options && options.scroll_markers) {
scrollbarMaker.mount();
scrollbarMaker.createMarkers();
}
};

/**
Expand Down Expand Up @@ -261,6 +276,10 @@ Find.register('Content.Highlighter', function(self) {
window.scrollBy(0, -100);
}
}

if (options && options.scroll_markers) {
scrollbarMaker.setActive(index);
}
};

/**
Expand Down Expand Up @@ -331,6 +350,9 @@ Find.register('Content.Highlighter', function(self) {
* @private
* */
self.restore = function() {
scrollbarMaker?.destroy();
scrollbarMaker = null;

let classes = [indexHighlight, allHighlight];
for (let classIndex = 0; classIndex < classes.length; classIndex++) {
let els = Array.from(document.querySelectorAll('.' + classes[classIndex]));
Expand All @@ -349,6 +371,238 @@ Find.register('Content.Highlighter', function(self) {
}
};

class ScrollbarHighlightMaker {
Comment thread
brandon1024 marked this conversation as resolved.
Outdated
scrollbarWidth = (function () {
const w = window.innerWidth - document.documentElement.clientWidth;
// Using OS-native overlay scrollbars produces w=0 here
return w > 0 ? w : 13;
})();

constructor(options) {
this.options = options;

this.globalStyle = null;

this.overlay = null;
this.track = null;
this.thumb = null;
this.markerContainer = null;

this.currentScrollY = 0;
this.docInvisibleHeight = 0;
this.scrollListener = null;

this.occTopPositionMap = new Map();
}

addComponents() {
// Suppress the native scrollbar
this.globalStyle = document.head.appendChild(document.createElement('style'));
this.globalStyle.textContent =
'::-webkit-scrollbar { width: 0px !important; height: 0px !important; }' +
'html { scrollbar-width: none !important; }';

this.overlay = document.body.appendChild(document.createElement('div'));
const shadowRoot = this.overlay.attachShadow({mode: 'open'});

shadowRoot.appendChild(document.createElement('style')).textContent = `
#find-ext-scrollbar-track {
Comment thread
brandon1024 marked this conversation as resolved.
Outdated
position: fixed;
top: 0;
right: 0;
width: ${this.scrollbarWidth}px;
height: 100vh;
z-index: 2147483647;
pointer-events: auto;
background: #f1f1f1;
box-sizing: border-box;
overflow: hidden;
}
@media (prefers-color-scheme: dark) {
#find-ext-scrollbar-track {
background: #2b2b2b;
}
}

#find-ext-scroll-thumb {
position: absolute;
right: 0;
width: 100%;
min-height: 30px;
background: #aaaaaa;
border-radius: 3px;
cursor: pointer;
box-sizing: border-box;
transition: background 0.15s;
}
@media (prefers-color-scheme: dark) {
#find-ext-scroll-thumb {
background: #6b6b6b;
}
}
#find-ext-scroll-thumb:hover {
background: #888888;
}

[id^="find-ext-marker-"] {
display: block;
position: absolute;
left: 0;
right: 0;
width: 100%;
height: 4px;
min-height: 4px;
background-color: ${this.options.all_highlight_color.hexColor};
opacity: 0.85;
z-index: 2;
box-sizing: border-box;
pointer-events: none;
margin: 0;
padding: 0;
border: none;
border-radius: 1px;
}

[id^="find-ext-marker-"].index_highlight {
background-color: ${this.options.index_highlight_color.hexColor};
z-index: 3;
}
`;

// Scroll track, sits exactly where the native scrollbar was
this.track = shadowRoot.appendChild(document.createElement('div'));
this.track.id = 'find-ext-scrollbar-track';

// Scroll thumb
this.thumb = this.track.appendChild(document.createElement('div'));
this.thumb.id = 'find-ext-scroll-thumb';

// Highlight markers
this.markerContainer = this.track.appendChild(document.createElement('div'));
}

updateThumb() {
const scrollElement = document.scrollingElement;
const docHeight = scrollElement.scrollHeight;
const viewHeight = window.innerHeight;

if (docHeight <= viewHeight) {
this.thumb.style.display = 'none';
return;
}
this.thumb.style.display = 'block';

const thumbHeight = Math.max(30, (viewHeight / docHeight) * viewHeight);
const maxThumbTop = viewHeight - thumbHeight;
this.currentScrollY = window.scrollY || scrollElement.scrollTop;
this.docInvisibleHeight = docHeight - viewHeight;
const scrollRatio = this.currentScrollY / this.docInvisibleHeight;
this.thumb.style.height = thumbHeight + 'px';
this.thumb.style.top = Math.min(maxThumbTop, scrollRatio * maxThumbTop) + 'px';
}

bindScroll() {
// Throttle down here
// Do not use "requestAnimationFrame()", see MDN document for "scroll event"
this.ticking = false;
this.scrollListener = () => {
if (!this.ticking) {
this.ticking = true;
setTimeout(() => {
this.updateThumb();
this.ticking = false;
}, (1000 / 60) /* 60 FPS */);
}
};
window.addEventListener('scroll', this.scrollListener);
}

bindTrackClick() {
// Click on track to jump
this.track.addEventListener('click', (e) => {
if (e.target === this.thumb) return;
const ratio = (e.clientY - this.track.clientTop) / this.track.clientHeight;
const targetY = ratio * this.docInvisibleHeight;
window.scrollTo({ top: targetY, behavior: 'smooth' });
});
}

bindThumbDrag() {
this.thumb.addEventListener('mousedown', (e) => {
e.preventDefault();
const dragStartScrollY = this.currentScrollY;
const dragStartY = e.clientY;
const onMove = (e) => {
const ratio = (e.clientY - dragStartY) / (this.track.clientHeight - this.thumb.clientHeight /* exclude the thumb itself */);
const targetY = dragStartScrollY + ratio * this.docInvisibleHeight;
window.scrollTo(0, targetY);
};
const onUp = function () {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
}


/**
* @private
* */
calculateMarkerPosition(el) {
const clientRect = el.getBoundingClientRect();
const docHeight = document.scrollingElement.scrollHeight;
const elementAbsoluteTop = window.scrollY + clientRect.top + (0.5 * clientRect.height);
const proportion = elementAbsoluteTop / docHeight;
const markerTop = proportion * window.innerHeight;
return Math.max(0, Math.min(window.innerHeight - 4, markerTop));
}

createMarker(occurrenceId, topPosition) {
const container = document.createDocumentFragment();
const marker = container.appendChild(document.createElement('div'));
marker.id = 'find-ext-marker-' + occurrenceId;
marker.style.top = topPosition + 'px';
return container;
}

setActive(occIndex) {
const markers = this.markerContainer.children;
Array.from(markers).forEach((el, index) => {
el.className = index === occIndex ? 'index_highlight' : '';
});
}

destroy() {
window.removeEventListener('scroll', this.scrollListener);
// Other event listeners will be removed by GC
this.overlay?.parentNode?.removeChild(this.overlay);
this.globalStyle?.parentNode?.removeChild(this.globalStyle);
}

createMarkers() {
this.occTopPositionMap.forEach((markerTop, occIndex) => {
this.markerContainer.appendChild(this.createMarker(occIndex, markerTop));
});
}

addOccurrence(occIndex, el) {
if (!this.occTopPositionMap.has(occIndex)) {
const markerTop = this.calculateMarkerPosition(el);
this.occTopPositionMap.set(occIndex, markerTop);
}
}

mount() {
Comment thread
brandon1024 marked this conversation as resolved.
Outdated
this.addComponents();
this.bindTrackClick();
this.bindThumbDrag();
this.updateThumb();
this.bindScroll();
}
}

function isElementInViewport(element) {
let elementBoundingRect = element.getBoundingClientRect();
if (elementBoundingRect.top < 0 || elementBoundingRect.left < 0) {
Expand All @@ -365,4 +619,4 @@ Find.register('Content.Highlighter', function(self) {

return true;
}
});
});
7 changes: 7 additions & 0 deletions popup/js/options-pane.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Find.register('Popup.OptionsPane', function (self) {
hide_saved_expressions_button: false,
hide_clipboard_button: true,
hide_find_replace_button: true,
scroll_markers: false,
max_results: 0,
index_highlight_color: Object.freeze({
hue: 34,
Expand Down Expand Up @@ -95,6 +96,11 @@ Find.register('Popup.OptionsPane', function (self) {
Find.Popup.Storage.saveOptions(options);
Find.Popup.SearchPane.hideFindReplacePaneToggleButton(options.hide_find_replace_button);
});
document.getElementById('scroll-markers-enable-toggle').addEventListener('change', (e) => {
options.scroll_markers = e.target.checked;
Find.Popup.Storage.saveOptions(options);
Find.Popup.BrowserAction.updateSearch();
});

//Add max results slider event listeners
let maxResultsSlider = document.getElementById('max-results-slider');
Expand Down Expand Up @@ -354,6 +360,7 @@ Find.register('Popup.OptionsPane', function (self) {
document.getElementById('hide-saved-expressions-pane-toggle-option-toggle').checked = options.hide_saved_expressions_button;
document.getElementById('hide-copy-to-clipboard-option-toggle').checked = options.hide_clipboard_button;
document.getElementById('find-replace-toggle-option-toggle').checked = options.hide_find_replace_button;
document.getElementById('scroll-markers-enable-toggle').checked = options.scroll_markers;

Find.Popup.SearchPane.hideOptionsPaneToggleButton(options.hide_options_button);
Find.Popup.SearchPane.hideSavedExpressionsPaneToggleButton(options.hide_saved_expressions_button);
Expand Down
Loading