Skip to content

Commit 328c121

Browse files
Handle older browsers that don't fully support the CSSStyleSheet API
1 parent d2f0e46 commit 328c121

File tree

1 file changed

+37
-18
lines changed

1 file changed

+37
-18
lines changed

highlight-helper.js

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function Highlighter(options = hhDefaultOptions) {
88
options[key] = options[key] ?? hhDefaultOptions[key];
99
}
1010

11-
this.annotatableContainer, this.relativeAncestorElement, this.annotatableParagraphs;
11+
this.annotatableContainer, this.relativeAncestorElement, this.annotatableParagraphs, this.stylesheets;
1212
let generalStylesheet, appearanceStylesheet, highlightApiStylesheet, selectionStylesheet;
1313
let annotatableParagraphIds, hyperlinkElements;
1414
let svgBackground, svgActiveOverlay, selectionHandles;
@@ -50,14 +50,11 @@ function Highlighter(options = hhDefaultOptions) {
5050
document.body.tabIndex = -1;
5151

5252
// Set up stylesheets
53-
generalStylesheet = new CSSStyleSheet();
54-
appearanceStylesheet = new CSSStyleSheet();
55-
highlightApiStylesheet = new CSSStyleSheet();
56-
selectionStylesheet = new CSSStyleSheet();
57-
document.adoptedStyleSheets.push(generalStylesheet);
58-
document.adoptedStyleSheets.push(appearanceStylesheet);
59-
document.adoptedStyleSheets.push(highlightApiStylesheet);
60-
document.adoptedStyleSheets.push(selectionStylesheet);
53+
this.stylesheets = {}
54+
generalStylesheet = createStylesheet(this.stylesheets, 'general');
55+
appearanceStylesheet = createStylesheet(this.stylesheets, 'appearance');
56+
highlightApiStylesheet = createStylesheet(this.stylesheets, 'highlight-api');
57+
selectionStylesheet = createStylesheet(this.stylesheets, 'selection');
6158
generalStylesheet.replaceSync(`
6259
${options.containerSelector} {
6360
-webkit-tap-highlight-color: transparent;
@@ -559,11 +556,7 @@ function Highlighter(options = hhDefaultOptions) {
559556

560557
// Remove this Highlighter instance and its highlights
561558
this.removeHighlighter = () => {
562-
generalStylesheet.replaceSync('');
563-
appearanceStylesheet.replaceSync('');
564-
highlightApiStylesheet.replaceSync('');
565-
selectionStylesheet.replaceSync('');
566-
559+
for (const stylesheet of Object.values(this.stylesheets)) if (stylesheet.parentElement) stylesheet.remove();
567560
this.loadHighlights([]);
568561
this.annotatableContainer.querySelectorAll('.hh-svg-background, .hh-selection-handle').forEach(el => el.remove())
569562
controller.abort();
@@ -1055,6 +1048,31 @@ function Highlighter(options = hhDefaultOptions) {
10551048
return styleTemplate;
10561049
}
10571050

1051+
// Create a CSS stylesheet
1052+
function createStylesheet(stylesheets, stylesheetKey) {
1053+
let stylesheet = stylesheets[stylesheetKey];
1054+
if (!stylesheet) {
1055+
if (supportsCssStylesheetApi) {
1056+
stylesheet = new CSSStyleSheet();
1057+
document.adoptedStyleSheets.push(stylesheet);
1058+
} else {
1059+
// For browsers that don't fully support the CSSStyleSheet API, such as Safari < 16.4.
1060+
// See https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet#browser_compatibility
1061+
stylesheet = document.createElement('style');
1062+
stylesheet.appendChild(document.createTextNode(''));
1063+
stylesheet.replaceSync = (newContent) => {
1064+
stylesheet.textContent = newContent;
1065+
}
1066+
stylesheet.insertRule = (newContent) => {
1067+
stylesheet.textContent += newContent;
1068+
}
1069+
document.head.appendChild(stylesheet);
1070+
}
1071+
stylesheets[stylesheetKey] = stylesheet;
1072+
}
1073+
return stylesheet;
1074+
}
1075+
10581076
// Restore the previous selection range in case the browser clears the selection
10591077
const getRestoredSelectionOrCaret = (selection, pointerEvent = null) => {
10601078
if (selection.type === 'None') {
@@ -1091,14 +1109,12 @@ function Highlighter(options = hhDefaultOptions) {
10911109
// Adapted from https://stackoverflow.com/a/12924488/1349044
10921110
const getCaretFromCoordinates = (clientX, clientY, checkAnnotatable = false, checkXDistance = false, checkYDistance = false) => {
10931111
let range;
1094-
if (document.caretPositionFromPoint) {
1095-
// Most browsers
1112+
if (supportsCaretPositionFromPoint) {
10961113
let caretPosition = document.caretPositionFromPoint(clientX, clientY);
10971114
range = document.createRange();
10981115
range.setStart(caretPosition.offsetNode, caretPosition.offset);
10991116
range.collapse(true);
1100-
} else if (document.caretRangeFromPoint) {
1101-
// Safari
1117+
} else if (supportsCaretRangeFromPoint) {
11021118
range = document.caretRangeFromPoint(clientX, clientY);
11031119
}
11041120
if (!range) return;
@@ -1227,6 +1243,9 @@ let hhHighlighters = [];
12271243
const isTouchDevice = navigator.maxTouchPoints > 0;
12281244
const isWebKit = /^((?!Chrome|Firefox|Android|Samsung).)*AppleWebKit/i.test(navigator.userAgent);
12291245
const isWKWebView = isWebKit && window.webkit?.messageHandlers;
1246+
const supportsCaretPositionFromPoint = document.caretPositionFromPoint;
1247+
const supportsCaretRangeFromPoint = document.caretRangeFromPoint;
1248+
const supportsCssStylesheetApi = CSSStyleSheet?.prototype?.replaceSync;
12301249
const supportsHighlightApi = CSS.highlights;
12311250

12321251
// Workaround to allow programmatic text selection on tap in iOS Safari

0 commit comments

Comments
 (0)