Просмотр исходного кода

Add support to specify content width of formatted page

Eduardo Grajeda 10 месяцев назад
Родитель
Сommit
6c5aba843a

+ 4 - 0
_locales/en/messages.json

@@ -579,6 +579,10 @@
 		"message": "apply the system theme when formatting a page",
 		"message": "apply the system theme when formatting a page",
 		"description": "Title of the button 'apply the system theme when formatting a page'"
 		"description": "Title of the button 'apply the system theme when formatting a page'"
 	},
 	},
+	"optionContentWidth": {
+		"message": "content width when formatting a page (em)",
+		"description": "Options page label: 'content width when formatting a page (em)'"
+	},
 	"optionWarnUnsavedPage": {
 	"optionWarnUnsavedPage": {
 		"message": "warn if leaving page with unsaved changes",
 		"message": "warn if leaving page with unsaved changes",
 		"description": "Title of the button 'warn if leaving page with unsaved changes'"
 		"description": "Title of the button 'warn if leaving page with unsaved changes'"

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
lib/single-file-extension-background.js


+ 2013 - 2011
lib/single-file-extension-editor.js

@@ -1092,2236 +1092,2238 @@
 		const COMMENT_HEADER = "Page saved with SingleFile";
 		const COMMENT_HEADER = "Page saved with SingleFile";
 		const COMMENT_HEADER_LEGACY = "Archive processed by SingleFile";
 		const COMMENT_HEADER_LEGACY = "Archive processed by SingleFile";
 
 
-		const STYLE_FORMATTED_PAGE = `
-	/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+		let NOTES_WEB_STYLESHEET, MASK_WEB_STYLESHEET, HIGHLIGHTS_WEB_STYLESHEET;
+		let selectedNote, anchorElement, maskNoteElement, maskPageElement, highlightSelectionMode, removeHighlightMode, resizingNoteMode, movingNoteMode, highlightColor, collapseNoteTimeout, cuttingOuterMode, cuttingMode, cuttingTouchTarget, cuttingPath, cuttingPathIndex, previousContent;
+		let removedElements = [], removedElementIndex = 0, initScriptContent, pageResources, pageUrl, pageCompressContent, includeInfobar, openInfobar, infobarPositionAbsolute, infobarPositionTop, infobarPositionBottom, infobarPositionLeft, infobarPositionRight;
 
 
-/* Avoid adding ID selector rules in this style sheet, since they could
- * inadvertently match elements in the article content. */
+		globalThis.zip = singlefile.helper.zip;
+		initEventListeners();
+		new MutationObserver(initEventListeners).observe(document, { childList: true });
 
 
-:root {
-  --grey-90-a10: rgba(12, 12, 13, 0.1);
-  --grey-90-a20: rgba(12, 12, 13, 0.2);
-  --grey-90-a30: rgba(12, 12, 13, 0.3);
-  --grey-90-a80: rgba(12, 12, 13, 0.8);
-  --grey-30: #d7d7db;
-  --blue-40: #45a1ff;
-  --blue-40-a30: rgba(69, 161, 255, 0.3);
-  --blue-60: #0060df;
-  --body-padding: 64px;
-  --font-size: 12;
-  --content-width: 70em;
-  --line-height: 1.6em;
-}
+		function initEventListeners() {
+			window.onmessage = async event => {
+				const message = JSON.parse(event.data);
+				if (message.method == "init") {
+					await init(message);
+				}
+				if (message.method == "addNote") {
+					addNote(message);
+				}
+				if (message.method == "displayNotes") {
+					document.querySelectorAll(NOTE_TAGNAME).forEach(noteElement => noteElement.shadowRoot.querySelector("." + NOTE_CLASS).classList.remove(NOTE_HIDDEN_CLASS));
+				}
+				if (message.method == "hideNotes") {
+					document.querySelectorAll(NOTE_TAGNAME).forEach(noteElement => noteElement.shadowRoot.querySelector("." + NOTE_CLASS).classList.add(NOTE_HIDDEN_CLASS));
+				}
+				if (message.method == "enableHighlight") {
+					if (highlightColor) {
+						document.documentElement.classList.remove(highlightColor + "-mode");
+					}
+					highlightColor = message.color;
+					highlightSelectionMode = true;
+					document.documentElement.classList.add(message.color + "-mode");
+				}
+				if (message.method == "disableHighlight") {
+					disableHighlight();
+					highlightSelectionMode = false;
+				}
+				if (message.method == "displayHighlights") {
+					document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.remove(HIGHLIGHT_HIDDEN_CLASS));
+				}
+				if (message.method == "hideHighlights") {
+					document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.add(HIGHLIGHT_HIDDEN_CLASS));
+				}
+				if (message.method == "enableRemoveHighlights") {
+					removeHighlightMode = true;
+					document.documentElement.classList.add("single-file-remove-highlights-mode");
+				}
+				if (message.method == "disableRemoveHighlights") {
+					removeHighlightMode = false;
+					document.documentElement.classList.remove("single-file-remove-highlights-mode");
+				}
+				if (message.method == "enableEditPage") {
+					document.body.contentEditable = true;
+					onUpdate(false);
+				}
+				if (message.method == "formatPage") {
+					formatPage(!message.applySystemTheme, message.contentWidth);
+				}
+				if (message.method == "cancelFormatPage") {
+					cancelFormatPage();
+				}
+				if (message.method == "disableEditPage") {
+					document.body.contentEditable = false;
+				}
+				if (message.method == "enableCutInnerPage") {
+					cuttingMode = true;
+					document.documentElement.classList.add(CUT_MODE_CLASS);
+				}
+				if (message.method == "enableCutOuterPage") {
+					cuttingOuterMode = true;
+					document.documentElement.classList.add(CUT_MODE_CLASS);
+				}
+				if (message.method == "disableCutInnerPage" || message.method == "disableCutOuterPage") {
+					if (message.method == "disableCutInnerPage") {
+						cuttingMode = false;
+					} else {
+						cuttingOuterMode = false;
+					}
+					document.documentElement.classList.remove(CUT_MODE_CLASS);
+					resetSelectedElements();
+					if (cuttingPath) {
+						unhighlightCutElement();
+						cuttingPath = null;
+					}
+				}
+				if (message.method == "undoCutPage") {
+					undoCutPage();
+				}
+				if (message.method == "undoAllCutPage") {
+					while (removedElementIndex) {
+						removedElements[removedElementIndex - 1].forEach(element => element.classList.remove(REMOVED_CONTENT_CLASS));
+						removedElementIndex--;
+					}
+				}
+				if (message.method == "redoCutPage") {
+					redoCutPage();
+				}
+				if (message.method == "getContent") {
+					onUpdate(true);
+					includeInfobar = message.includeInfobar;
+					openInfobar = message.openInfobar;
+					infobarPositionAbsolute = message.infobarPositionAbsolute;
+					infobarPositionTop = message.infobarPositionTop;
+					infobarPositionBottom = message.infobarPositionBottom;
+					infobarPositionLeft = message.infobarPositionLeft;
+					infobarPositionRight = message.infobarPositionRight;
+					let content = getContent(message.compressHTML, message.updatedResources);
+					if (initScriptContent) {
+						content = content.replace(/<script data-template-shadow-root src.*?<\/script>/g, initScriptContent);
+					}
+					let filename;
+					const pageOptions = loadOptionsFromPage(document);
+					if (pageOptions) {
+						pageOptions.backgroundSave = message.backgroundSave;
+						pageOptions.saveDate = new Date(pageOptions.saveDate);
+						pageOptions.visitDate = new Date(pageOptions.visitDate);
+						filename = await singlefile.helper.formatFilename(content, document, pageOptions);
+					}
+					if (message.sharePage) {
+						setLabels(message.labels);
+					}
+					if (pageCompressContent) {
+						const viewport = document.head.querySelector("meta[name=viewport]");
+						window.parent.postMessage(JSON.stringify({
+							method: "setContent",
+							content,
+							filename,
+							title: document.title,
+							doctype: singlefile.helper.getDoctypeString(document),
+							url: pageUrl,
+							viewport: viewport ? viewport.content : null,
+							compressContent: true,
+							foregroundSave: message.foregroundSave,
+							sharePage: message.sharePage,
+							documentHeight: document.documentElement.offsetHeight
+						}), "*");
+					} else {
+						if (message.foregroundSave || message.sharePage) {
+							try {
+								await downloadPageForeground({
+									content,
+									filename: filename || message.filename,
+									mimeType: "text/html"
+								}, { sharePage: message.sharePage });
+							} catch (error) {
+								console.log(error); // eslint-disable-line no-console
+								window.parent.postMessage(JSON.stringify({ method: "onError", error: error.message }), "*");
+							}
+						} else {
+							window.parent.postMessage(JSON.stringify({
+								method: "setContent",
+								content,
+								filename
+							}), "*");
+						}
+					}
+				}
+				if (message.method == "printPage") {
+					printPage();
+				}
+				if (message.method == "displayInfobar") {
+					singlefile.helper.displayIcon(document, true, {
+						openInfobar: message.openInfobar,
+						infobarPositionAbsolute: message.infobarPositionAbsolute,
+						infobarPositionTop: message.infobarPositionTop,
+						infobarPositionBottom: message.infobarPositionBottom,
+						infobarPositionLeft: message.infobarPositionLeft,
+						infobarPositionRight: message.infobarPositionRight
+					});
+					const infobarDoc = document.implementation.createHTMLDocument();
+					infobarDoc.body.appendChild(document.querySelector(singlefile.helper.INFOBAR_TAGNAME));
+					serializeShadowRoots(infobarDoc.body);
+					const content = singlefile.helper.serialize(infobarDoc, true);
+					window.parent.postMessage(JSON.stringify({
+						method: "displayInfobar",
+						content
+					}), "*");
+				}
+				if (message.method == "download") {
+					try {
+						await downloadPageForeground({
+							content: message.content,
+							filename: message.filename,
+							mimeType: message.mimeType
+						}, { sharePage: message.sharePage });
+					} catch (error) {
+						console.log(error); // eslint-disable-line no-console
+						window.parent.postMessage(JSON.stringify({ method: "onError", error: error.message }), "*");
+					}
+				}
+			};
+			window.onresize = reflowNotes;
+			document.ondragover = event => event.preventDefault();
+			document.ondrop = async event => {
+				if (event.dataTransfer.files && event.dataTransfer.files[0]) {
+					const file = event.dataTransfer.files[0];
+					event.preventDefault();
+					const content = new TextDecoder().decode(await file.arrayBuffer());
+					const compressContent = /<html[^>]* data-sfz[^>]*>/i.test(content);
+					if (compressContent) {
+						await init({ content: file, compressContent }, { filename: file.name });
+					} else {
+						await init({ content }, { filename: file.name });
+					}
+				}
+			};
+		}
 
 
-body {
-  --main-background: #fff;
-  --main-foreground: #333;
-  --font-color: #000000;
-  --primary-color: #0B83FF;
-  --toolbar-border: var(--grey-90-a20);
-  --toolbar-transparent-border: transparent;
-  --toolbar-box-shadow: var(--grey-90-a10);
-  --toolbar-button-background: transparent;
-  --toolbar-button-background-hover: var(--grey-90-a10);
-  --toolbar-button-foreground-hover: var(--font-color);
-  --toolbar-button-background-active: var(--grey-90-a20);
-  --toolbar-button-foreground-active: var(--primary-color);
-  --toolbar-button-border: transparent;
-  --toolbar-button-border-hover: transparent;
-  --toolbar-button-border-active: transparent;
-  --tooltip-background: var(--grey-90-a80);
-  --tooltip-foreground: white;
-  --tooltip-border: transparent;
-  --popup-background: white;
-  --popup-border: rgba(0, 0, 0, 0.12);
-  --opaque-popup-border: #e0e0e0;
-  --popup-line: var(--grey-30);
-  --popup-shadow: rgba(49, 49, 49, 0.3);
-  --popup-button-background: #edecf0;
-  --popup-button-background-hover: hsla(0,0%,70%,.4);
-  --popup-button-foreground-hover: var(--font-color);
-  --popup-button-background-active: hsla(240,5%,5%,.15);
-  --selected-background: var(--blue-40-a30);
-  --selected-border: var(--blue-40);
-  --font-value-border: var(--grey-30);
-  --icon-fill: #3b3b3c;
-  --icon-disabled-fill: #8080807F;
-  --link-foreground: var(--blue-60);
-  --link-selected-foreground: #333;
-  --visited-link-foreground: #b5007f;
-  /* light colours */
-}
-
-body.sepia {
-  --main-background: #f4ecd8;
-  --main-foreground: #5b4636;
-  --toolbar-border: #5b4636;
-}
-
-body.dark {
-  --main-background: rgb(28, 27, 34);
-  --main-foreground: #eee;
-  --font-color: #fff;
-  --toolbar-border: #4a4a4b;
-  --toolbar-box-shadow: black;
-  --toolbar-button-background-hover: var(--grey-90-a30);
-  --toolbar-button-background-active: var(--grey-90-a80);
-  --tooltip-background: black;
-  --tooltip-foreground: white;
-  --popup-background: rgb(66,65,77);
-  --opaque-popup-border: #434146;
-  --popup-line: rgb(82, 82, 94);
-  --popup-button-background: #5c5c61;
-  --popup-button-background-active: hsla(0,0%,70%,.6);
-  --selected-background: #3E6D9A;
-  --font-value-border: #656468;
-  --icon-fill: #fff;
-  --icon-disabled-fill: #ffffff66;
-  --link-foreground: #45a1ff;
-  --link-selected-foreground: #fff;
-  --visited-link-foreground: #e675fd;
-  /* dark colours */
-}
-
-body.hcm {
-  --main-background: Canvas;
-  --main-foreground: CanvasText;
-  --font-color: CanvasText;
-  --primary-color: SelectedItem;
-  --toolbar-border: CanvasText;
-   /* We need a true transparent but in HCM this would compute to an actual color,
-      so select the page's background color instead: */
-  --toolbar-transparent-border: Canvas;
-  --toolbar-box-shadow: Canvas;
-  --toolbar-button-background: ButtonFace;
-  --toolbar-button-background-hover: ButtonText;
-  --toolbar-button-foreground-hover: ButtonFace;
-  --toolbar-button-background-active: SelectedItem;
-  --toolbar-button-foreground-active: SelectedItemText;
-  --toolbar-button-border: ButtonText;
-  --toolbar-button-border-hover: ButtonText;
-  --toolbar-button-border-active: ButtonText;
-  --tooltip-background: Canvas;
-  --tooltip-foreground: CanvasText;
-  --tooltip-border: CanvasText;
-  --popup-background: Canvas;
-  --popup-border: CanvasText;
-  --opaque-popup-border: CanvasText;
-  --popup-line: CanvasText;
-  --popup-button-background: ButtonFace;
-  --popup-button-background-hover: ButtonText;
-  --popup-button-foreground-hover: ButtonFace;
-  --popup-button-background-active: ButtonText;
-  --selected-background: Canvas;
-  --selected-border: SelectedItem;
-  --font-value-border: CanvasText;
-  --icon-fill: ButtonText;
-  --icon-disabled-fill: GrayText;
-  --link-foreground: LinkText;
-  --link-selected-foreground: ActiveText;
-  --visited-link-foreground: VisitedText;
-}
-
-body {
-  margin: 0;
-  padding: var(--body-padding);
-  background-color: var(--main-background);
-  color: var(--main-foreground);
-}
-
-body.loaded {
-  transition: color 0.4s, background-color 0.4s;
-}
-
-body.dark *::-moz-selection {
-  background-color: var(--selected-background);
-}
-
-a::-moz-selection {
-  color: var(--link-selected-foreground);
-}
-
-body.sans-serif,
-body.sans-serif .remove-button {
-  font-family: Helvetica, Arial, sans-serif;
-}
-
-body.serif,
-body.serif .remove-button {
-  font-family: Georgia, "Times New Roman", serif;
-}
+		async function init({ content, password, compressContent }, { filename, reset } = {}) {
+			await initConstants();
+			if (compressContent) {
+				const zipOptions = {
+					workerScripts: { inflate: ["/lib/single-file-z-worker.js"] }
+				};
+				try {
+					const worker = new Worker(zipOptions.workerScripts.inflate[0]);
+					worker.terminate();
+					// eslint-disable-next-line no-unused-vars
+				} catch (error) {
+					delete zipOptions.workerScripts;
+				}
+				zipOptions.useWebWorkers = IS_NOT_SAFARI;
+				const { docContent, origDocContent, resources, url } = await singlefile.helper.extract(content, {
+					password,
+					prompt,
+					shadowRootScriptURL: new URL("/lib/single-file-extension-editor-init.js", document.baseURI).href,
+					zipOptions
+				});
+				pageResources = resources;
+				pageUrl = url;
+				pageCompressContent = true;
+				const contentDocument = (new DOMParser()).parseFromString(docContent, "text/html");
+				if (detectSavedPage(contentDocument)) {
+					await singlefile.helper.display(document, docContent, { disableFramePointerEvents: true });
+					const infobarElement = document.querySelector(singlefile.helper.INFOBAR_TAGNAME);
+					if (infobarElement) {
+						infobarElement.remove();
+					}
+					await initPage();
+					let icon;
+					const origContentDocument = (new DOMParser()).parseFromString(origDocContent, "text/html");
+					const iconElement = origContentDocument.querySelector("link[rel*=icon]");
+					if (iconElement) {
+						const iconResource = resources.find(resource => resource.filename == iconElement.getAttribute("href"));
+						if (iconResource && iconResource.content) {
+							const reader = new FileReader();
+							reader.readAsDataURL(await (await fetch(iconResource.content)).blob());
+							icon = await new Promise((resolve, reject) => {
+								reader.addEventListener("load", () => resolve(reader.result), false);
+								reader.addEventListener("error", reject, false);
+							});
+						} else {
+							icon = iconElement.href;
+						}
+					}
+					window.parent.postMessage(JSON.stringify({
+						method: "onInit",
+						title: document.title,
+						icon,
+						filename,
+						reset,
+						formatPageEnabled: isProbablyReaderable(document)
+					}), "*");
+				}
+			} else {
+				const initScriptContentMatch = content.match(/<script data-template-shadow-root.*<\/script>/);
+				if (initScriptContentMatch && initScriptContentMatch[0]) {
+					initScriptContent = initScriptContentMatch[0];
+				}
+				content = content.replace(/<script data-template-shadow-root.*<\/script>/g, "<script data-template-shadow-root src=/lib/single-file-extension-editor-init.js></script>");
+				const contentDocument = (new DOMParser()).parseFromString(content, "text/html");
+				if (detectSavedPage(contentDocument)) {
+					if (contentDocument.doctype) {
+						if (document.doctype) {
+							document.replaceChild(contentDocument.doctype, document.doctype);
+						} else {
+							document.insertBefore(contentDocument.doctype, document.documentElement);
+						}
+					} else if (document.doctype) {
+						document.doctype.remove();
+					}
+					const infobarElement = contentDocument.querySelector(singlefile.helper.INFOBAR_TAGNAME);
+					if (infobarElement) {
+						infobarElement.remove();
+					}
+					contentDocument.querySelectorAll("noscript").forEach(element => {
+						element.setAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME, element.innerHTML);
+						element.textContent = "";
+					});
+					contentDocument.querySelectorAll("iframe").forEach(element => {
+						const pointerEvents = "pointer-events";
+						element.style.setProperty("-sf-" + pointerEvents, element.style.getPropertyValue(pointerEvents), element.style.getPropertyPriority(pointerEvents));
+						element.style.setProperty(pointerEvents, "none", "important");
+					});
+					document.replaceChild(contentDocument.documentElement, document.documentElement);
+					document.querySelectorAll("[data-single-file-note-refs]").forEach(noteRefElement => noteRefElement.dataset.singleFileNoteRefs = noteRefElement.dataset.singleFileNoteRefs.replace(/,/g, " "));
+					deserializeShadowRoots(document);
+					document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => attachNoteListeners(containerElement, true));
+					insertHighlightStylesheet(document);
+					maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
+					maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
+					document.documentElement.onmousedown = onMouseDown;
+					document.documentElement.onmouseup = document.documentElement.ontouchend = onMouseUp;
+					document.documentElement.onmouseover = onMouseOver;
+					document.documentElement.onmouseout = onMouseOut;
+					document.documentElement.onkeydown = onKeyDown;
+					document.documentElement.ontouchstart = document.documentElement.ontouchmove = onTouchMove;
+					window.onclick = event => event.preventDefault();
+					const iconElement = document.querySelector("link[rel*=icon]");
+					window.parent.postMessage(JSON.stringify({
+						method: "onInit",
+						title: document.title,
+						icon: iconElement && iconElement.href,
+						filename,
+						reset,
+						formatPageEnabled: isProbablyReaderable(document)
+					}), "*");
+				}
+			}
+		}
 
 
-/* Override some controls and content styles based on color scheme */
+		function loadOptionsFromPage(doc) {
+			const optionsElement = doc.body.querySelector("script[type=\"application/json\"][" + SCRIPT_OPTIONS + "]");
+			if (optionsElement) {
+				return JSON.parse(optionsElement.textContent);
+			}
+		}
 
 
-body.light > .container > .header > .domain {
-  border-bottom-color: #333333 !important;
-}
+		async function initPage() {
+			document.querySelectorAll("iframe").forEach(element => {
+				const pointerEvents = "pointer-events";
+				element.style.setProperty("-sf-" + pointerEvents, element.style.getPropertyValue(pointerEvents), element.style.getPropertyPriority(pointerEvents));
+				element.style.setProperty(pointerEvents, "none", "important");
+			});
+			document.querySelectorAll("[data-single-file-note-refs]").forEach(noteRefElement => noteRefElement.dataset.singleFileNoteRefs = noteRefElement.dataset.singleFileNoteRefs.replace(/,/g, " "));
+			deserializeShadowRoots(document);
+			reflowNotes();
+			await waitResourcesLoad();
+			reflowNotes();
+			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => attachNoteListeners(containerElement, true));
+			insertHighlightStylesheet(document);
+			maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
+			maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
+			document.documentElement.onmousedown = onMouseDown;
+			document.documentElement.onmouseup = document.documentElement.ontouchend = onMouseUp;
+			document.documentElement.onmouseover = onMouseOver;
+			document.documentElement.onmouseout = onMouseOut;
+			document.documentElement.onkeydown = onKeyDown;
+			document.documentElement.ontouchstart = document.documentElement.ontouchmove = onTouchMove;
+			window.onclick = event => event.preventDefault();
+		}
 
 
-body.sepia > .container > .header > .domain {
-  border-bottom-color: #5b4636 !important;
-}
+		async function initConstants() {
+			[NOTES_WEB_STYLESHEET, MASK_WEB_STYLESHEET, HIGHLIGHTS_WEB_STYLESHEET] = await Promise.all([
+				minifyText(await ((await fetch("../pages/editor-note-web.css")).text())),
+				minifyText(await ((await fetch("../pages/editor-mask-web.css")).text())),
+				minifyText(await ((await fetch("../pages/editor-frame-web.css")).text()))
+			]);
+		}
 
 
-body.dark > .container > .header > .domain {
-  border-bottom-color: #eeeeee !important;
-}
-
-body.light blockquote {
-  border-inline-start: 2px solid #333333 !important;
-}
+		function addNote({ color }) {
+			const containerElement = document.createElement(NOTE_TAGNAME);
+			const noteElement = document.createElement("div");
+			const headerElement = document.createElement("header");
+			const blockquoteElement = document.createElement("blockquote");
+			const mainElement = document.createElement("textarea");
+			const resizeElement = document.createElement("div");
+			const removeNoteElement = document.createElement("img");
+			const anchorIconElement = document.createElement("img");
+			const noteShadow = containerElement.attachShadow({ mode: "open" });
+			headerElement.appendChild(anchorIconElement);
+			headerElement.appendChild(removeNoteElement);
+			blockquoteElement.appendChild(mainElement);
+			noteElement.appendChild(headerElement);
+			noteElement.appendChild(blockquoteElement);
+			noteElement.appendChild(resizeElement);
+			noteShadow.appendChild(getStyleElement(NOTES_WEB_STYLESHEET));
+			noteShadow.appendChild(noteElement);
+			const notesElements = Array.from(document.querySelectorAll(NOTE_TAGNAME));
+			const noteId = Math.max.call(Math, 0, ...notesElements.map(noteElement => Number(noteElement.dataset.noteId))) + 1;
+			blockquoteElement.cite = "https://www.w3.org/TR/annotation-model/#selector(type=CssSelector,value=[data-single-file-note-refs~=\"" + noteId + "\"])";
+			noteElement.classList.add(NOTE_CLASS);
+			noteElement.classList.add(NOTE_ANCHORED_CLASS);
+			noteElement.classList.add(color);
+			noteElement.dataset.color = color;
+			mainElement.dir = "auto";
+			const boundingRectDocument = document.documentElement.getBoundingClientRect();
+			let positionX = NOTE_INITIAL_WIDTH + NOTE_INITIAL_POSITION_X - 1 - boundingRectDocument.x;
+			let positionY = NOTE_INITIAL_HEIGHT + NOTE_INITIAL_POSITION_Y - 1 - boundingRectDocument.y;
+			while (Array.from(document.elementsFromPoint(positionX - window.scrollX, positionY - window.scrollY)).find(element => element.tagName.toLowerCase() == NOTE_TAGNAME)) {
+				positionX += NOTE_INITIAL_POSITION_X;
+				positionY += NOTE_INITIAL_POSITION_Y;
+			}
+			noteElement.style.setProperty("left", (positionX - NOTE_INITIAL_WIDTH - 1) + "px");
+			noteElement.style.setProperty("top", (positionY - NOTE_INITIAL_HEIGHT - 1) + "px");
+			resizeElement.className = "note-resize";
+			resizeElement.ondragstart = event => event.preventDefault();
+			removeNoteElement.className = "note-remove";
+			removeNoteElement.src = BUTTON_CLOSE_URL;
+			removeNoteElement.ondragstart = event => event.preventDefault();
+			anchorIconElement.className = "note-anchor";
+			anchorIconElement.src = BUTTON_ANCHOR_URL;
+			anchorIconElement.ondragstart = event => event.preventDefault();
+			containerElement.dataset.noteId = noteId;
+			addNoteRef(document.documentElement, noteId);
+			attachNoteListeners(containerElement, true);
+			document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
+			noteElement.classList.add(NOTE_SELECTED_CLASS);
+			selectedNote = noteElement;
+			onUpdate(false);
+		}
 
 
-body.sepia blockquote {
-  border-inline-start: 2px solid #5b4636 !important;
-}
+		function attachNoteListeners(containerElement, editable = false) {
+			const SELECT_PX_THRESHOLD = 4;
+			const COLLAPSING_NOTE_DELAY = 750;
+			const noteShadow = containerElement.shadowRoot;
+			const noteElement = noteShadow.childNodes[1];
+			const headerElement = noteShadow.querySelector("header");
+			const mainElement = noteShadow.querySelector("textarea");
+			const noteId = containerElement.dataset.noteId;
+			const resizeElement = noteShadow.querySelector(".note-resize");
+			const anchorIconElement = noteShadow.querySelector(".note-anchor");
+			const removeNoteElement = noteShadow.querySelector(".note-remove");
+			mainElement.readOnly = !editable;
+			if (!editable) {
+				anchorIconElement.style.setProperty("display", "none", "important");
+			} else {
+				anchorIconElement.style.removeProperty("display");
+			}
+			headerElement.ontouchstart = headerElement.onmousedown = event => {
+				if (event.target == headerElement) {
+					collapseNoteTimeout = setTimeout(() => {
+						noteElement.classList.toggle("note-collapsed");
+						hideMaskNote();
+					}, COLLAPSING_NOTE_DELAY);
+					event.preventDefault();
+					const position = getPosition(event);
+					const clientX = position.clientX;
+					const clientY = position.clientY;
+					const boundingRect = noteElement.getBoundingClientRect();
+					const deltaX = clientX - boundingRect.left;
+					const deltaY = clientY - boundingRect.top;
+					maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS);
+					document.documentElement.style.setProperty("user-select", "none", "important");
+					anchorElement = getAnchorElement(containerElement);
+					displayMaskNote();
+					selectNote(noteElement);
+					moveNote(event, deltaX, deltaY);
+					movingNoteMode = { event, deltaX, deltaY };
+					document.documentElement.ontouchmove = document.documentElement.onmousemove = event => {
+						clearTimeout(collapseNoteTimeout);
+						if (!movingNoteMode) {
+							movingNoteMode = { deltaX, deltaY };
+						}
+						movingNoteMode.event = event;
+						moveNote(event, deltaX, deltaY);
+					};
+				}
+			};
+			resizeElement.ontouchstart = resizeElement.onmousedown = event => {
+				event.preventDefault();
+				resizingNoteMode = true;
+				selectNote(noteElement);
+				maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS);
+				document.documentElement.style.setProperty("user-select", "none", "important");
+				document.documentElement.ontouchmove = document.documentElement.onmousemove = event => {
+					event.preventDefault();
+					const { clientX, clientY } = getPosition(event);
+					const boundingRectNote = noteElement.getBoundingClientRect();
+					noteElement.style.width = clientX - boundingRectNote.left + "px";
+					noteElement.style.height = clientY - boundingRectNote.top + "px";
+				};
+			};
+			anchorIconElement.ontouchend = anchorIconElement.onclick = event => {
+				event.preventDefault();
+				noteElement.classList.toggle(NOTE_ANCHORED_CLASS);
+				if (!noteElement.classList.contains(NOTE_ANCHORED_CLASS)) {
+					deleteNoteRef(containerElement, noteId);
+					addNoteRef(document.documentElement, noteId);
+				}
+				onUpdate(false);
+			};
+			removeNoteElement.ontouchend = removeNoteElement.onclick = event => {
+				event.preventDefault();
+				deleteNoteRef(containerElement, noteId);
+				containerElement.remove();
+			};
+			noteElement.onmousedown = () => {
+				selectNote(noteElement);
+			};
 
 
-body.dark blockquote {
-  border-inline-start: 2px solid #eeeeee !important;
-}
+			function moveNote(event, deltaX, deltaY) {
+				event.preventDefault();
+				const { clientX, clientY } = getPosition(event);
+				noteElement.classList.add(NOTE_MOVING_CLASS);
+				if (editable) {
+					if (noteElement.classList.contains(NOTE_ANCHORED_CLASS)) {
+						deleteNoteRef(containerElement, noteId);
+						anchorElement = getTarget(clientX, clientY) || document.documentElement;
+						addNoteRef(anchorElement, noteId);
+					} else {
+						anchorElement = document.documentElement;
+					}
+				}
+				document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
+				noteElement.style.setProperty("left", (clientX - deltaX) + "px");
+				noteElement.style.setProperty("top", (clientY - deltaY) + "px");
+				noteElement.style.setProperty("position", "fixed");
+				displayMaskNote();
+			}
 
 
-.light-button {
-  color: #333333;
-  background-color: #ffffff;
-}
+			function displayMaskNote() {
+				if (anchorElement == document.documentElement || anchorElement == document.documentElement) {
+					hideMaskNote();
+				} else {
+					const boundingRectAnchor = anchorElement.getBoundingClientRect();
+					maskNoteElement.classList.add(NOTE_MASK_MOVING_CLASS);
+					if (selectedNote) {
+						maskNoteElement.classList.add(selectedNote.dataset.color);
+					}
+					maskNoteElement.style.setProperty("top", (boundingRectAnchor.y - 3) + "px");
+					maskNoteElement.style.setProperty("left", (boundingRectAnchor.x - 3) + "px");
+					maskNoteElement.style.setProperty("width", (boundingRectAnchor.width + 3) + "px");
+					maskNoteElement.style.setProperty("height", (boundingRectAnchor.height + 3) + "px");
+				}
+			}
 
 
-.dark-button {
-  color: #eeeeee;
-  background-color: #1c1b22;
-}
+			function hideMaskNote() {
+				maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS);
+				if (selectedNote) {
+					maskNoteElement.classList.remove(selectedNote.dataset.color);
+				}
+			}
 
 
-.sepia-button {
-  color: #5b4636;
-  background-color: #f4ecd8;
-}
+			function selectNote(noteElement) {
+				if (selectedNote) {
+					selectedNote.classList.remove(NOTE_SELECTED_CLASS);
+					maskNoteElement.classList.remove(selectedNote.dataset.color);
+				}
+				noteElement.classList.add(NOTE_SELECTED_CLASS);
+				noteElement.classList.add(noteElement.dataset.color);
+				selectedNote = noteElement;
+			}
 
 
-.auto-button {
-  text-align: center;
-}
+			function getTarget(clientX, clientY) {
+				const targets = Array.from(document.elementsFromPoint(clientX, clientY)).filter(element => element.matches("html *:not(" + NOTE_TAGNAME + "):not(." + MASK_CLASS + ")"));
+				if (!targets.includes(document.documentElement)) {
+					targets.push(document.documentElement);
+				}
+				let newTarget, target = targets[0], boundingRect = target.getBoundingClientRect();
+				newTarget = determineTargetElement("floor", target, clientX - boundingRect.left, getMatchedParents(target, "left"));
+				if (newTarget == target) {
+					newTarget = determineTargetElement("ceil", target, boundingRect.left + boundingRect.width - clientX, getMatchedParents(target, "right"));
+				}
+				if (newTarget == target) {
+					newTarget = determineTargetElement("floor", target, clientY - boundingRect.top, getMatchedParents(target, "top"));
+				}
+				if (newTarget == target) {
+					newTarget = determineTargetElement("ceil", target, boundingRect.top + boundingRect.height - clientY, getMatchedParents(target, "bottom"));
+				}
+				target = newTarget;
+				while (boundingRect = target && target.getBoundingClientRect(), boundingRect && boundingRect.width <= SELECT_PX_THRESHOLD && boundingRect.height <= SELECT_PX_THRESHOLD) {
+					target = target.parentElement;
+				}
+				return target;
+			}
 
 
-@media (prefers-color-scheme: dark) {
-  .auto-button {
-    background-color: #1c1b22;
-    color: #eeeeee;
-  }
-}
+			function getMatchedParents(target, property) {
+				let element = target, matchedParent, parents = [];
+				do {
+					const boundingRect = element.getBoundingClientRect();
+					if (element.parentElement && !element.parentElement.tagName.toLowerCase() != NOTE_TAGNAME && !element.classList.contains(MASK_CLASS)) {
+						const parentBoundingRect = element.parentElement.getBoundingClientRect();
+						matchedParent = Math.abs(parentBoundingRect[property] - boundingRect[property]) <= SELECT_PX_THRESHOLD;
+						if (matchedParent) {
+							if (element.parentElement.clientWidth > SELECT_PX_THRESHOLD && element.parentElement.clientHeight > SELECT_PX_THRESHOLD &&
+								((element.parentElement.clientWidth - element.clientWidth > SELECT_PX_THRESHOLD) || (element.parentElement.clientHeight - element.clientHeight > SELECT_PX_THRESHOLD))) {
+								parents.push(element.parentElement);
+							}
+							element = element.parentElement;
+						}
+					} else {
+						matchedParent = false;
+					}
+				} while (matchedParent && element);
+				return parents;
+			}
 
 
-@media not (prefers-color-scheme: dark) {
-  .auto-button {
-    background-color: #ffffff;
-    color: #333333;
-  }
-}
+			function determineTargetElement(roundingMethod, target, widthDistance, parents) {
+				if (Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) <= parents.length) {
+					target = parents[parents.length - Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) - 1];
+				}
+				return target;
+			}
+		}
 
 
-/* Loading/error message */
+		function onMouseDown(event) {
+			if ((cuttingMode || cuttingOuterMode) && cuttingPath) {
+				event.preventDefault();
+			}
+		}
 
 
-.reader-message {
-  margin-top: 40px;
-  display: none;
-  text-align: center;
-  width: 100%;
-  font-size: 0.9em;
-}
+		function onMouseUp(event) {
+			if (highlightSelectionMode) {
+				event.preventDefault();
+				highlightSelection();
+				onUpdate(false);
+			}
+			if (removeHighlightMode) {
+				event.preventDefault();
+				let element = event.target, done;
+				while (element && !done) {
+					if (element.classList.contains(HIGHLIGHT_CLASS)) {
+						document.querySelectorAll("." + HIGHLIGHT_CLASS + "[data-singlefile-highlight-id=" + JSON.stringify(element.dataset.singlefileHighlightId) + "]").forEach(highlightedElement => {
+							resetHighlightedElement(highlightedElement);
+							onUpdate(false);
+						});
+						done = true;
+					}
+					element = element.parentElement;
+				}
+			}
+			if (resizingNoteMode) {
+				event.preventDefault();
+				resizingNoteMode = false;
+				document.documentElement.style.removeProperty("user-select");
+				maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS);
+				document.documentElement.ontouchmove = onTouchMove;
+				document.documentElement.onmousemove = null;
+				onUpdate(false);
+			}
+			if (movingNoteMode) {
+				event.preventDefault();
+				anchorNote(movingNoteMode.event || event, selectedNote, movingNoteMode.deltaX, movingNoteMode.deltaY);
+				movingNoteMode = null;
+				document.documentElement.ontouchmove = onTouchMove;
+				document.documentElement.onmousemove = null;
+				onUpdate(false);
+			}
+			if ((cuttingMode || cuttingOuterMode) && cuttingPath) {
+				event.preventDefault();
+				if (event.ctrlKey) {
+					const element = cuttingPath[cuttingPathIndex];
+					element.classList.toggle(cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS);
+				} else {
+					validateCutElement(event.shiftKey);
+				}
+			}
+			if (collapseNoteTimeout) {
+				clearTimeout(collapseNoteTimeout);
+				collapseNoteTimeout = null;
+			}
+		}
 
 
-/* Detector element to see if we're at the top of the doc or not. */
-.top-anchor {
-  position: absolute;
-  top: 0;
-  width: 0;
-  height: 5px;
-  pointer-events: none;
-}
+		function onMouseOver(event) {
+			if (cuttingMode || cuttingOuterMode) {
+				const target = event.target;
+				if (target.classList) {
+					let ancestorFound;
+					document.querySelectorAll("." + (cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS)).forEach(element => {
+						if (element == target || isAncestor(element, target) || isAncestor(target, element)) {
+							ancestorFound = element;
+						}
+					});
+					if (ancestorFound) {
+						cuttingPath = [ancestorFound];
+					} else {
+						cuttingPath = getParents(event.target);
+					}
+					cuttingPathIndex = 0;
+					highlightCutElement();
+				}
+			}
+		}
 
 
-/* Header */
+		function onMouseOut() {
+			if (cuttingMode || cuttingOuterMode) {
+				if (cuttingPath) {
+					unhighlightCutElement();
+					cuttingPath = null;
+				}
+			}
+		}
 
 
-.header {
-  text-align: start;
-  display: none;
-}
+		function onTouchMove(event) {
+			if (cuttingMode || cuttingOuterMode) {
+				event.preventDefault();
+				const { clientX, clientY } = getPosition(event);
+				const target = document.elementFromPoint(clientX, clientY);
+				if (cuttingTouchTarget != target) {
+					onMouseOut();
+					if (target) {
+						cuttingTouchTarget = target;
+						onMouseOver({ target });
+					}
+				}
+			}
+		}
 
 
-.domain {
-  font-size: 0.9em;
-  line-height: 1.48em;
-  padding-bottom: 4px;
-  font-family: Helvetica, Arial, sans-serif;
-  text-decoration: none;
-  border-bottom: 1px solid;
-  color: var(--link-foreground);
-}
+		function onKeyDown(event) {
+			if (cuttingMode || cuttingOuterMode) {
+				if (event.code == "Tab") {
+					if (cuttingPath) {
+						const delta = event.shiftKey ? -1 : 1;
+						let element = cuttingPath[cuttingPathIndex];
+						let nextElement = cuttingPath[cuttingPathIndex + delta];
+						if (nextElement) {
+							let pathIndex = cuttingPathIndex + delta;
+							while (
+								nextElement &&
+								(
+									(delta == 1 &&
+										element.getBoundingClientRect().width >= nextElement.getBoundingClientRect().width &&
+										element.getBoundingClientRect().height >= nextElement.getBoundingClientRect().height) ||
+									(delta == -1 &&
+										element.getBoundingClientRect().width <= nextElement.getBoundingClientRect().width &&
+										element.getBoundingClientRect().height <= nextElement.getBoundingClientRect().height))) {
+								pathIndex += delta;
+								nextElement = cuttingPath[pathIndex];
+							}
+							if (nextElement && nextElement.classList && nextElement != document.body && nextElement != document.documentElement) {
+								unhighlightCutElement();
+								cuttingPathIndex = pathIndex;
+								highlightCutElement();
+							}
+						}
+					}
+					event.preventDefault();
+				}
+				if (event.code == "Space") {
+					if (cuttingPath) {
+						if (event.ctrlKey) {
+							const element = cuttingPath[cuttingPathIndex];
+							element.classList.add(cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS);
+						} else {
+							validateCutElement(event.shiftKey);
+						}
+						event.preventDefault();
+					}
+				}
+				if (event.code == "Escape") {
+					resetSelectedElements();
+					event.preventDefault();
+				}
+				if (event.key.toLowerCase() == "z" && event.ctrlKey) {
+					if (event.shiftKey) {
+						redoCutPage();
+					} else {
+						undoCutPage();
+					}
+					event.preventDefault();
+				}
+			}
+			if (event.key.toLowerCase() == "s" && event.ctrlKey) {
+				window.parent.postMessage(JSON.stringify({ "method": "savePage" }), "*");
+				event.preventDefault();
+			}
+			if (event.key.toLowerCase() == "p" && event.ctrlKey) {
+				printPage();
+				event.preventDefault();
+			}
+		}
 
 
-.header > h1 {
-  font-size: 1.6em;
-  line-height: 1.25em;
-  width: 100%;
-  margin: 30px 0;
-  padding: 0;
-}
+		function printPage() {
+			unhighlightCutElement();
+			resetSelectedElements();
+			window.print();
+		}
 
 
-.header > .credits {
-  font-size: 0.9em;
-  line-height: 1.48em;
-  margin: 0 0 10px;
-  padding: 0;
-  font-style: italic;
-}
+		function highlightCutElement() {
+			const element = cuttingPath[cuttingPathIndex];
+			element.classList.add(cuttingMode ? CUT_HOVER_CLASS : CUT_OUTER_HOVER_CLASS);
+		}
 
 
-.header > .meta-data {
-  font-size: 0.65em;
-  margin: 0 0 15px;
-}
+		function unhighlightCutElement() {
+			if (cuttingPath) {
+				const element = cuttingPath[cuttingPathIndex];
+				element.classList.remove(CUT_HOVER_CLASS);
+				element.classList.remove(CUT_OUTER_HOVER_CLASS);
+			}
+		}
 
 
-.reader-estimated-time {
-  text-align: match-parent;
-}
+		function disableHighlight(doc = document) {
+			if (highlightColor) {
+				doc.documentElement.classList.remove(highlightColor + "-mode");
+			}
+		}
 
 
-/* Controls toolbar */
+		function undoCutPage() {
+			if (removedElementIndex) {
+				removedElements[removedElementIndex - 1].forEach(element => element.classList.remove(REMOVED_CONTENT_CLASS));
+				removedElementIndex--;
+			}
+		}
 
 
-.toolbar-container {
-  position: sticky;
-  z-index: 2;
-  top: 32px;
-  height: 0; /* take up no space, so body is at the top. */
-
-  /* As a stick container, we're positioned relative to the body. Move us to
-   * the edge of the viewport using margins, and take the width of
-   * the body padding into account for calculating our width.
-   */
-  margin-inline-start: calc(-1 * var(--body-padding));
-  width: max(var(--body-padding), calc((100% - var(--content-width)) / 2 + var(--body-padding)));
-  font-size: var(--font-size); /* Needed to ensure 'em' units match, is reset for .reader-controls */
-}
+		function redoCutPage() {
+			if (removedElementIndex < removedElements.length) {
+				removedElements[removedElementIndex].forEach(element => element.classList.add(REMOVED_CONTENT_CLASS));
+				removedElementIndex++;
+			}
+		}
 
 
-.toolbar {
-  padding-block: 16px;
-  border: 1px solid var(--toolbar-border);
-  border-radius: 6px;
-  box-shadow: 0 2px 8px var(--toolbar-box-shadow);
+		function validateCutElement(invert) {
+			const selectedElement = cuttingPath[cuttingPathIndex];
+			if ((cuttingMode && !invert) || (cuttingOuterMode && invert)) {
+				if (document.documentElement != selectedElement && selectedElement.tagName.toLowerCase() != NOTE_TAGNAME) {
+					const elementsRemoved = [selectedElement].concat(...document.querySelectorAll("." + CUT_SELECTED_CLASS + ",." + CUT_SELECTED_CLASS + " *,." + CUT_HOVER_CLASS + " *"));
+					resetSelectedElements();
+					if (elementsRemoved.length) {
+						elementsRemoved.forEach(element => {
+							unhighlightCutElement();
+							if (element.tagName.toLowerCase() == NOTE_TAGNAME) {
+								resetAnchorNote(element);
+							} else {
+								element.classList.add(REMOVED_CONTENT_CLASS);
+							}
+						});
+						removedElements[removedElementIndex] = elementsRemoved;
+						removedElementIndex++;
+						removedElements.length = removedElementIndex;
+						onUpdate(false);
+					}
+				}
+			} else {
+				if (document.documentElement != selectedElement && selectedElement.tagName.toLowerCase() != NOTE_TAGNAME) {
+					const elements = [];
+					const searchSelector = "*:not(style):not(meta):not(." + REMOVED_CONTENT_CLASS + ")";
+					const elementsKept = [selectedElement].concat(...document.querySelectorAll("." + CUT_OUTER_SELECTED_CLASS));
+					document.body.querySelectorAll(searchSelector).forEach(element => {
+						let removed = true;
+						elementsKept.forEach(elementKept => removed = removed && (elementKept != element && !isAncestor(elementKept, element) && !isAncestor(element, elementKept)));
+						if (removed) {
+							if (element.tagName.toLowerCase() == NOTE_TAGNAME) {
+								resetAnchorNote(element);
+							} else {
+								elements.push(element);
+							}
+						}
+					});
+					elementsKept.forEach(elementKept => {
+						unhighlightCutElement();
+						const elementKeptRect = elementKept.getBoundingClientRect();
+						elementKept.querySelectorAll(searchSelector).forEach(descendant => {
+							const descendantRect = descendant.getBoundingClientRect();
+							if (descendantRect.width && descendantRect.height && (
+								descendantRect.left + descendantRect.width < elementKeptRect.left ||
+								descendantRect.right > elementKeptRect.right + elementKeptRect.width ||
+								descendantRect.top + descendantRect.height < elementKeptRect.top ||
+								descendantRect.bottom > elementKeptRect.bottom + elementKeptRect.height
+							)) {
+								elements.push(descendant);
+							}
+						});
+					});
+					resetSelectedElements();
+					if (elements.length) {
+						elements.forEach(element => element.classList.add(REMOVED_CONTENT_CLASS));
+						removedElements[removedElementIndex] = elements;
+						removedElementIndex++;
+						removedElements.length = removedElementIndex;
+						onUpdate(false);
+					}
+				}
+			}
+		}
 
 
-  width: 32px; /* basic width, without padding/border */
+		function resetSelectedElements(doc = document) {
+			doc.querySelectorAll("." + CUT_OUTER_SELECTED_CLASS + ",." + CUT_SELECTED_CLASS).forEach(element => {
+				element.classList.remove(CUT_OUTER_SELECTED_CLASS);
+				element.classList.remove(CUT_SELECTED_CLASS);
+			});
+		}
 
 
-  /* padding should be 16px, except if there's not enough space for that, in
-   * which case use half the available space for padding (=25% on each side).
-   * The 34px here is the width + borders. We use a variable because we need
-   * to know this size for the margin calculation.
-   */
-  --inline-padding: min(16px, calc(25% - 0.25 * 34px));
-  padding-inline: var(--inline-padding);
+		function anchorNote(event, noteElement, deltaX, deltaY) {
+			event.preventDefault();
+			const { clientX, clientY } = getPosition(event);
+			document.documentElement.style.removeProperty("user-select");
+			noteElement.classList.remove(NOTE_MOVING_CLASS);
+			maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS);
+			maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS);
+			maskNoteElement.classList.remove(noteElement.dataset.color);
+			const headerElement = noteElement.querySelector("header");
+			headerElement.ontouchmove = document.documentElement.onmousemove = null;
+			let currentElement = anchorElement;
+			let positionedElement;
+			while (currentElement.parentElement && !positionedElement) {
+				if (!FORBIDDEN_TAG_NAMES.includes(currentElement.tagName.toLowerCase())) {
+					const currentElementStyle = getComputedStyle(currentElement);
+					if (currentElementStyle.position != "static") {
+						positionedElement = currentElement;
+					}
+				}
+				currentElement = currentElement.parentElement;
+			}
+			if (!positionedElement) {
+				positionedElement = document.documentElement;
+			}
+			const containerElement = noteElement.getRootNode().host;
+			if (positionedElement == document.documentElement) {
+				const firstMaskElement = document.querySelector("." + MASK_CLASS);
+				firstMaskElement.parentElement.insertBefore(containerElement, firstMaskElement);
+			} else {
+				positionedElement.appendChild(containerElement);
+			}
+			const boundingRectPositionedElement = positionedElement.getBoundingClientRect();
+			const stylePositionedElement = window.getComputedStyle(positionedElement);
+			const borderX = parseInt(stylePositionedElement.getPropertyValue("border-left-width"));
+			const borderY = parseInt(stylePositionedElement.getPropertyValue("border-top-width"));
+			noteElement.style.setProperty("position", "absolute");
+			noteElement.style.setProperty("left", (clientX - boundingRectPositionedElement.x - deltaX - borderX) + "px");
+			noteElement.style.setProperty("top", (clientY - boundingRectPositionedElement.y - deltaY - borderY) + "px");
+		}
 
 
-  /* Keep a maximum of 96px distance to the body, but center once the margin
-   * gets too small. We need to set the start margin, however...
-   * To center, we'd want 50% of the container, but we'd subtract half our
-   * own width (16px) and half the border (1px) and the inline padding.
-   * When the other margin would be 96px, we want 100% - 96px - the complete
-   * width of the actual toolbar (34px + 2 * padding)
-   */
-  margin-inline-start: max(calc(50% - 17px - var(--inline-padding)), calc(100% - 96px - 34px - 2 * var(--inline-padding)));
+		function resetAnchorNote(containerElement) {
+			const noteId = containerElement.dataset.noteId;
+			const noteElement = containerElement.shadowRoot.childNodes[1];
+			noteElement.classList.remove(NOTE_ANCHORED_CLASS);
+			deleteNoteRef(containerElement, noteId);
+			addNoteRef(document.documentElement, noteId);
+			document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
+		}
 
 
-  font-family: Helvetica, Arial, sans-serif;
-  list-style: none;
-  user-select: none;
-}
+		function getPosition(event) {
+			if (event.touches && event.touches.length) {
+				const touch = event.touches[0];
+				return touch;
+			} else {
+				return event;
+			}
+		}
 
 
-@media (prefers-reduced-motion: no-preference) {
-  .toolbar {
-    transition-property: border-color, box-shadow;
-    transition-duration: 250ms;
-  }
+		function highlightSelection() {
+			let highlightId = 0;
+			document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(highlightedElement => highlightId = Math.max(highlightId, highlightedElement.dataset.singlefileHighlightId));
+			highlightId++;
+			const selection = window.getSelection();
+			const highlightedNodes = new Set();
+			for (let indexRange = 0; indexRange < selection.rangeCount; indexRange++) {
+				const range = selection.getRangeAt(indexRange);
+				if (!range.collapsed) {
+					if (range.commonAncestorContainer.nodeType == range.commonAncestorContainer.TEXT_NODE) {
+						let contentText = range.startContainer.splitText(range.startOffset);
+						contentText = contentText.splitText(range.endOffset);
+						addHighLightedNode(contentText.previousSibling);
+					} else {
+						const treeWalker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
+						let highlightNodes;
+						while (treeWalker.nextNode()) {
+							if (highlightNodes && !treeWalker.currentNode.contains(range.endContainer)) {
+								addHighLightedNode(treeWalker.currentNode);
+							}
+							if (treeWalker.currentNode == range.startContainer) {
+								if (range.startContainer.nodeType == range.startContainer.TEXT_NODE) {
+									const contentText = range.startContainer.splitText(range.startOffset);
+									treeWalker.nextNode();
+									addHighLightedNode(contentText);
+								} else {
+									addHighLightedNode(range.startContainer.childNodes[range.startOffset]);
+								}
+								highlightNodes = true;
+							}
+							if (treeWalker.currentNode == range.endContainer) {
+								if (range.endContainer.nodeType == range.endContainer.TEXT_NODE) {
+									const contentText = range.endContainer.splitText(range.endOffset);
+									treeWalker.nextNode();
+									addHighLightedNode(contentText.previousSibling);
+								} else {
+									addHighLightedNode(range.endContainer.childNodes[range.endOffset]);
+								}
+								highlightNodes = false;
+							}
+						}
+						range.collapse();
+					}
+				}
+			}
+			highlightedNodes.forEach(node => highlightNode(node));
 
 
-  .toolbar .toolbar-button {
-    transition-property: opacity;
-    transition-duration: 250ms;
-  }
+			function addHighLightedNode(node) {
+				if (node && node.textContent.trim()) {
+					if (node.nodeType == node.TEXT_NODE && node.parentElement.childNodes.length == 1 && node.parentElement.classList.contains(HIGHLIGHT_CLASS)) {
+						highlightedNodes.add(node.parentElement);
+					} else {
+						highlightedNodes.add(node);
+					}
+				}
+			}
 
 
-  .toolbar-container.scrolled .toolbar:not(:hover, :focus-within) {
-    border-color: var(--toolbar-transparent-border);
-    box-shadow: 0 2px 8px transparent;
-  }
+			function highlightNode(node) {
+				if (node.nodeType == node.ELEMENT_NODE) {
+					resetHighlightedElement(node);
+					node.classList.add(HIGHLIGHT_CLASS);
+					node.classList.add(highlightColor);
+					node.dataset.singlefileHighlightId = highlightId;
+				} else if (node.parentElement) {
+					highlightTextNode(node);
+				}
+			}
 
 
-  .toolbar-container.scrolled .toolbar:not(:hover, :focus-within) .toolbar-button {
-    opacity: 0.6;
-  }
+			function highlightTextNode(node) {
+				const spanElement = document.createElement("span");
+				spanElement.classList.add(HIGHLIGHT_CLASS);
+				spanElement.classList.add(highlightColor);
+				spanElement.textContent = node.textContent;
+				spanElement.dataset.singlefileHighlightId = highlightId;
+				node.parentNode.replaceChild(spanElement, node);
+				return spanElement;
+			}
+		}
 
 
-  .toolbar-container.transition-location {
-    transition-duration: 250ms;
-    transition-property: width;
-  }
-}
-
-.toolbar-container.overlaps .toolbar-button {
-  opacity: 0.1;
-}
-
-.dropdown-open .toolbar {
-  border-color: var(--toolbar-transparent-border);
-  box-shadow: 0 2px 8px transparent;
-}
+		function getParents(element) {
+			const path = [];
+			while (element) {
+				path.push(element);
+				element = element.parentElement;
+			}
+			return path;
+		}
 
 
-.reader-controls {
-  /* We use 'em's above this node to get it to the right size. However,
-   * the UI inside the toolbar should use a fixed, smaller size. */
-  font-size: 11px;
-}
+		function formatPage(applySystemTheme, contentWidth) {
+			if (pageCompressContent) {
+				serializeShadowRoots(document);
+				previousContent = document.documentElement.cloneNode(true);
+				deserializeShadowRoots(document);
+			} else {
+				previousContent = getContent(false, []);
+			}
+			const shadowRoots = {};
+			const classesToPreserve = ["single-file-highlight", "single-file-highlight-yellow", "single-file-highlight-green", "single-file-highlight-pink", "single-file-highlight-blue"];
+			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
+				shadowRoots[containerElement.dataset.noteId] = containerElement.shadowRoot;
+				const className = "singlefile-note-id-" + containerElement.dataset.noteId;
+				containerElement.classList.add(className);
+				classesToPreserve.push(className);
+			});
+			const optionsElement = document.querySelector("script[type=\"application/json\"][" + SCRIPT_OPTIONS + "]");
+			const article = new Readability(document, { classesToPreserve }).parse();
+			const articleMetadata = Object.assign({}, article);
+			delete articleMetadata.content;
+			delete articleMetadata.textContent;
+			for (const key in articleMetadata) {
+				if (articleMetadata[key] == null) {
+					delete articleMetadata[key];
+				}
+			}
+			removedElements = [];
+			removedElementIndex = 0;
+			document.body.innerHTML = "";
+			const domParser = new DOMParser();
+			const doc = domParser.parseFromString(article.content, "text/html");
+			const contentEditable = document.body.contentEditable;
+			document.documentElement.replaceChild(doc.body, document.body);
+			if (optionsElement) {
+				document.body.appendChild(optionsElement);
+			}
+			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
+				const noteId = (Array.from(containerElement.classList).find(className => /singlefile-note-id-\d+/.test(className))).split("singlefile-note-id-")[1];
+				containerElement.classList.remove("singlefile-note-id-" + noteId);
+				containerElement.dataset.noteId = noteId;
+				if (!containerElement.shadowRoot) {
+					containerElement.attachShadow({ mode: "open" });
+					containerElement.shadowRoot.appendChild(shadowRoots[noteId]);
+				}
+			});
+			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => shadowRoots[containerElement.dataset.noteId].childNodes.forEach(node => containerElement.shadowRoot.appendChild(node)));
+			document.body.contentEditable = contentEditable;
+			document.head.querySelectorAll("style").forEach(styleElement => styleElement.remove());
+			const styleElement = document.createElement("style");
+			styleElement.textContent = getStyleFormattedPage(contentWidth);
+			document.head.appendChild(styleElement);
+			document.body.classList.add("moz-reader-content");
+			document.body.classList.add("content-width6");
+			document.body.classList.add("reader-show-element");
+			document.body.classList.add("sans-serif");
+			document.body.classList.add("container");
+			document.body.classList.add("line-height4");
+			const prefersColorSchemeDark = matchMedia("(prefers-color-scheme: dark)");
+			if (applySystemTheme && prefersColorSchemeDark && prefersColorSchemeDark.matches) {
+				document.body.classList.add("dark");
+			}
+			document.body.style.setProperty("display", "block");
+			document.body.style.setProperty("padding", "24px");
+			const titleElement = document.createElement("h1");
+			titleElement.classList.add("reader-title");
+			titleElement.textContent = article.title;
+			document.body.insertBefore(titleElement, document.body.firstChild);
+			const existingMetaDataElement = document.querySelector("script[id=singlefile-readability-metadata]");
+			if (existingMetaDataElement) {
+				existingMetaDataElement.remove();
+			}
+			const metaDataElement = document.createElement("script");
+			metaDataElement.type = "application/json";
+			metaDataElement.id = "singlefile-readability-metadata";
+			metaDataElement.textContent = JSON.stringify(articleMetadata, null, 2);
+			document.head.appendChild(metaDataElement);
+			document.querySelectorAll("a[href]").forEach(element => {
+				const href = element.getAttribute("href").trim();
+				if (href.startsWith(document.baseURI + "#")) {
+					element.setAttribute("href", href.substring(document.baseURI.length));
+				}
+			});
+			insertHighlightStylesheet(document);
+			maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
+			maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
+			reflowNotes();
+			onUpdate(false);
+		}
 
 
-button {
-  -moz-context-properties: fill;
-  color: var(--font-color);
-  fill: var(--icon-fill);
-}
+		async function cancelFormatPage() {
+			if (previousContent) {
+				const contentEditable = document.body.contentEditable;
+				if (pageCompressContent) {
+					document.replaceChild(previousContent, document.documentElement);
+					deserializeShadowRoots(document);
+					await initPage();
+				} else {
+					await init({ content: previousContent }, { reset: true });
+				}
+				document.body.contentEditable = contentEditable;
+				onUpdate(false);
+				previousContent = null;
+			}
+		}
 
 
-button:disabled {
-  fill: var(--icon-disabled-fill);
-}
+		function insertHighlightStylesheet(doc) {
+			if (!doc.querySelector("." + HIGHLIGHTS_STYLESHEET_CLASS)) {
+				const styleheetHighlights = getStyleElement(HIGHLIGHTS_WEB_STYLESHEET);
+				styleheetHighlights.classList.add(HIGHLIGHTS_STYLESHEET_CLASS);
+				doc.documentElement.appendChild(styleheetHighlights);
+			}
+		}
 
 
-.toolbar button::-moz-focus-inner {
-  border: 0;
-}
+		function getContent(compressHTML, updatedResources) {
+			unhighlightCutElement();
+			serializeShadowRoots(document);
+			const doc = document.cloneNode(true);
+			disableHighlight(doc);
+			resetSelectedElements(doc);
+			deserializeShadowRoots(doc);
+			deserializeShadowRoots(document);
+			doc.documentElement.classList.remove(CUT_MODE_CLASS);
+			doc.querySelectorAll("[" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "]").forEach(element => {
+				element.textContent = element.getAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
+				element.removeAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
+			});
+			doc.querySelectorAll("." + MASK_CLASS + ", " + singlefile.helper.INFOBAR_TAGNAME + ", ." + REMOVED_CONTENT_CLASS).forEach(element => element.remove());
+			if (includeInfobar) {
+				const options = singlefile.helper.extractInfobarData(doc);
+				options.openInfobar = openInfobar;
+				options.infobarPositionAbsolute = infobarPositionAbsolute;
+				options.infobarPositionTop = infobarPositionTop;
+				options.infobarPositionRight = infobarPositionRight;
+				options.infobarPositionBottom = infobarPositionBottom;
+				options.infobarPositionLeft = infobarPositionLeft;
+				singlefile.helper.appendInfobar(doc, options);
+			}
+			doc.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.remove(HIGHLIGHT_HIDDEN_CLASS));
+			doc.querySelectorAll(`template[${SHADOWROOT_ATTRIBUTE_NAME}]`).forEach(templateElement => {
+				const noteElement = templateElement.querySelector("." + NOTE_CLASS);
+				if (noteElement) {
+					noteElement.classList.remove(NOTE_HIDDEN_CLASS);
+				}
+				const mainElement = templateElement.querySelector("textarea");
+				if (mainElement) {
+					mainElement.textContent = mainElement.value;
+				}
+			});
+			doc.querySelectorAll("iframe").forEach(element => {
+				const pointerEvents = "pointer-events";
+				element.style.setProperty(pointerEvents, element.style.getPropertyValue("-sf-" + pointerEvents), element.style.getPropertyPriority("-sf-" + pointerEvents));
+				element.style.removeProperty("-sf-" + pointerEvents);
+			});
+			doc.body.removeAttribute("contentEditable");
+			const newResources = Object.keys(updatedResources).filter(url => updatedResources[url].type == "stylesheet").map(url => updatedResources[url]);
+			newResources.forEach(resource => {
+				const element = doc.createElement("style");
+				doc.body.appendChild(element);
+				element.textContent = resource.content;
+			});
+			if (pageCompressContent) {
+				const pageFilename = pageResources
+					.filter(resource => resource.filename.endsWith("index.html"))
+					.sort((resourceLeft, resourceRight) => resourceLeft.filename.length - resourceRight.filename.length)[0].filename;
+				const resources = pageResources.filter(resource => resource.parentResources.includes(pageFilename));
+				doc.querySelectorAll("[src]").forEach(element => resources.forEach(resource => {
+					if (element.src == resource.content) {
+						element.src = resource.name;
+					}
+				}));
+				let content = singlefile.helper.serialize(doc, compressHTML);
+				resources.sort((resourceLeft, resourceRight) => resourceRight.content.length - resourceLeft.content.length);
+				resources.forEach(resource => content = content.replaceAll(resource.content, resource.name));
+				return content + "<script " + SCRIPT_TEMPLATE_SHADOW_ROOT + ">" + getEmbedScript() + "</script>";
+			} else {
+				return singlefile.helper.serialize(doc, compressHTML) + "<script " + SCRIPT_TEMPLATE_SHADOW_ROOT + ">" + getEmbedScript() + "</script>";
+			}
+		}
 
 
-.toolbar-button {
-  position: relative;
-  width: 32px;
-  height: 32px;
-  padding: 0;
-  border: 1px solid var(--toolbar-button-border);
-  border-radius: 4px;
-  margin: 4px 0;
-  background-color: var(--toolbar-button-background);
-  background-size: 16px 16px;
-  background-position: center;
-  background-repeat: no-repeat;
-}
+		function onUpdate(saved) {
+			window.parent.postMessage(JSON.stringify({ "method": "onUpdate", saved }), "*");
+		}
 
 
-.toolbar-button:hover,
-.toolbar-button:focus-visible {
-  background-color: var(--toolbar-button-background-hover);
-  border-color: var(--toolbar-button-border-hover);
-  fill: var(--toolbar-button-foreground-hover);
-}
+		function waitResourcesLoad() {
+			return new Promise(resolve => {
+				let counterMutations = 0;
+				const done = () => {
+					observer.disconnect();
+					resolve();
+				};
+				let timeoutInit = setTimeout(done, 100);
+				const observer = new MutationObserver(() => {
+					if (counterMutations < 20) {
+						counterMutations++;
+						clearTimeout(timeoutInit);
+						timeoutInit = setTimeout(done, 100);
+					} else {
+						done();
+					}
+				});
+				observer.observe(document, { subtree: true, childList: true, attributes: true });
+			});
+		}
 
 
-.open .toolbar-button,
-.toolbar-button:hover:active {
-  background-color: var(--toolbar-button-background-active);
-  border-color: var(--toolbar-button-border-active);
-  color: var(--toolbar-button-foreground-active);
-  fill: var(--toolbar-button-foreground-active);
-}
+		function reflowNotes() {
+			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
+				const noteElement = containerElement.shadowRoot.querySelector("." + NOTE_CLASS);
+				const noteBoundingRect = noteElement.getBoundingClientRect();
+				const anchorElement = getAnchorElement(containerElement);
+				const anchorBoundingRect = anchorElement.getBoundingClientRect();
+				const maxX = anchorBoundingRect.x + Math.max(0, anchorBoundingRect.width - noteBoundingRect.width);
+				const minX = anchorBoundingRect.x;
+				const maxY = anchorBoundingRect.y + Math.max(0, anchorBoundingRect.height - NOTE_HEADER_HEIGHT);
+				const minY = anchorBoundingRect.y;
+				let left = parseInt(noteElement.style.getPropertyValue("left"));
+				let top = parseInt(noteElement.style.getPropertyValue("top"));
+				if (noteBoundingRect.x > maxX) {
+					left -= noteBoundingRect.x - maxX;
+				}
+				if (noteBoundingRect.x < minX) {
+					left += minX - noteBoundingRect.x;
+				}
+				if (noteBoundingRect.y > maxY) {
+					top -= noteBoundingRect.y - maxY;
+				}
+				if (noteBoundingRect.y < minY) {
+					top += minY - noteBoundingRect.y;
+				}
+				noteElement.style.setProperty("position", "absolute");
+				noteElement.style.setProperty("left", left + "px");
+				noteElement.style.setProperty("top", top + "px");
+			});
+		}
 
 
-.hover-label {
-  position: absolute;
-  top: 4px;
-  inset-inline-start: 36px;
-  line-height: 16px;
-  white-space: pre; /* make sure we don't wrap */
-  background-color: var(--tooltip-background);
-  color: var(--tooltip-foreground);
-  padding: 4px 8px;
-  border: 1px solid var(--tooltip-border);
-  border-radius: 2px;
-  visibility: hidden;
-  pointer-events: none;
-  /* Put above .dropdown .dropdown-popup, which has z-index: 1000. */
-  z-index: 1001;
-}
+		function resetHighlightedElement(element) {
+			element.classList.remove(HIGHLIGHT_CLASS);
+			element.classList.remove("single-file-highlight-yellow");
+			element.classList.remove("single-file-highlight-pink");
+			element.classList.remove("single-file-highlight-blue");
+			element.classList.remove("single-file-highlight-green");
+			delete element.dataset.singlefileHighlightId;
+		}
 
 
-/* Show the hover tooltip on non-dropdown buttons. */
-.toolbar-button:not(.dropdown-toggle):hover > .hover-label,
-.toolbar-button:not(.dropdown-toggle):focus-visible > .hover-label,
-/* Show the hover tooltip for dropdown buttons unless its dropdown is open. */
-:not(.open) > li > .dropdown-toggle:hover > .hover-label,
-:not(.open) > li > .dropdown-toggle:focus-visible > .hover-label {
-  visibility: visible;
-}
+		function serializeShadowRoots(node) {
+			node.querySelectorAll("*").forEach(element => {
+				const shadowRoot = getShadowRoot(element);
+				if (shadowRoot) {
+					serializeShadowRoots(shadowRoot);
+					const templateElement = document.createElement("template");
+					templateElement.setAttribute(SHADOWROOT_ATTRIBUTE_NAME, "open");
+					Array.from(shadowRoot.childNodes).forEach(childNode => templateElement.appendChild(childNode));
+					element.appendChild(templateElement);
+				}
+			});
+		}
 
 
-.dropdown {
-  text-align: center;
-  list-style: none;
-  margin: 0;
-  padding: 0;
-  position: relative;
-}
+		function deserializeShadowRoots(node) {
+			node.querySelectorAll(`template[${SHADOWROOT_ATTRIBUTE_NAME}]`).forEach(element => {
+				if (element.parentElement) {
+					let shadowRoot = getShadowRoot(element.parentElement);
+					if (shadowRoot) {
+						Array.from(element.childNodes).forEach(node => shadowRoot.appendChild(node));
+						element.remove();
+					} else {
+						try {
+							shadowRoot = element.parentElement.attachShadow({ mode: "open" });
+							const contentDocument = (new DOMParser()).parseFromString(element.innerHTML, "text/html");
+							Array.from(contentDocument.head.childNodes).forEach(node => shadowRoot.appendChild(node));
+							Array.from(contentDocument.body.childNodes).forEach(node => shadowRoot.appendChild(node));
+							// eslint-disable-next-line no-unused-vars
+						} catch (error) {
+							// ignored
+						}
+					}
+					if (shadowRoot) {
+						deserializeShadowRoots(shadowRoot);
+					}
+				}
+			});
+		}
 
 
-.dropdown li {
-  margin: 0;
-  padding: 0;
-}
+		function getMaskElement(className, containerClassName) {
+			let maskElement = document.documentElement.querySelector("." + className);
+			if (!maskElement) {
+				maskElement = document.createElement("div");
+				const maskContainerElement = document.createElement("div");
+				if (containerClassName) {
+					maskContainerElement.classList.add(containerClassName);
+				}
+				maskContainerElement.classList.add(MASK_CLASS);
+				const firstNote = document.querySelector(NOTE_TAGNAME);
+				if (firstNote && firstNote.parentElement == document.documentElement) {
+					document.documentElement.insertBefore(maskContainerElement, firstNote);
+				} else {
+					document.documentElement.appendChild(maskContainerElement);
+				}
+				maskElement.classList.add(className);
+				const maskShadow = maskContainerElement.attachShadow({ mode: "open" });
+				maskShadow.appendChild(getStyleElement(MASK_WEB_STYLESHEET));
+				maskShadow.appendChild(maskElement);
+				return maskElement;
+			}
+		}
 
 
-/* Popup */
+		function getStyleFormattedPage(contentWidth) {
+			return `
+	/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
-.dropdown .dropdown-popup {
-  text-align: start;
-  position: absolute;
-  inset-inline-start: 40px;
-  z-index: 1000;
-  background-color: var(--popup-background);
-  visibility: hidden;
-  border-radius: 4px;
-  border: 1px solid var(--popup-border);
-  box-shadow: 0 0 10px 0 var(--popup-shadow);
-  top: 0;
-}
+/* Avoid adding ID selector rules in this style sheet, since they could
+ * inadvertently match elements in the article content. */
 
 
-.open > .dropdown-popup {
-  visibility: visible;
+:root {
+  --grey-90-a10: rgba(12, 12, 13, 0.1);
+  --grey-90-a20: rgba(12, 12, 13, 0.2);
+  --grey-90-a30: rgba(12, 12, 13, 0.3);
+  --grey-90-a80: rgba(12, 12, 13, 0.8);
+  --grey-30: #d7d7db;
+  --blue-40: #45a1ff;
+  --blue-40-a30: rgba(69, 161, 255, 0.3);
+  --blue-60: #0060df;
+  --body-padding: 64px;
+  --font-size: 12;
+  --content-width: ${contentWidth}em;
+  --line-height: 1.6em;
 }
 }
 
 
-.dropdown-arrow {
-  position: absolute;
-  height: 24px;
-  width: 16px;
-  inset-inline-start: -16px;
-  background-image: url("chrome://global/skin/reader/RM-Type-Controls-Arrow.svg");
-  display: block;
-  -moz-context-properties: fill, stroke;
-  fill: var(--popup-background);
-  stroke: var(--opaque-popup-border);
-  pointer-events: none;
+body {
+  --main-background: #fff;
+  --main-foreground: #333;
+  --font-color: #000000;
+  --primary-color: #0B83FF;
+  --toolbar-border: var(--grey-90-a20);
+  --toolbar-transparent-border: transparent;
+  --toolbar-box-shadow: var(--grey-90-a10);
+  --toolbar-button-background: transparent;
+  --toolbar-button-background-hover: var(--grey-90-a10);
+  --toolbar-button-foreground-hover: var(--font-color);
+  --toolbar-button-background-active: var(--grey-90-a20);
+  --toolbar-button-foreground-active: var(--primary-color);
+  --toolbar-button-border: transparent;
+  --toolbar-button-border-hover: transparent;
+  --toolbar-button-border-active: transparent;
+  --tooltip-background: var(--grey-90-a80);
+  --tooltip-foreground: white;
+  --tooltip-border: transparent;
+  --popup-background: white;
+  --popup-border: rgba(0, 0, 0, 0.12);
+  --opaque-popup-border: #e0e0e0;
+  --popup-line: var(--grey-30);
+  --popup-shadow: rgba(49, 49, 49, 0.3);
+  --popup-button-background: #edecf0;
+  --popup-button-background-hover: hsla(0,0%,70%,.4);
+  --popup-button-foreground-hover: var(--font-color);
+  --popup-button-background-active: hsla(240,5%,5%,.15);
+  --selected-background: var(--blue-40-a30);
+  --selected-border: var(--blue-40);
+  --font-value-border: var(--grey-30);
+  --icon-fill: #3b3b3c;
+  --icon-disabled-fill: #8080807F;
+  --link-foreground: var(--blue-60);
+  --link-selected-foreground: #333;
+  --visited-link-foreground: #b5007f;
+  /* light colours */
 }
 }
 
 
-.dropdown-arrow:dir(rtl) {
-  transform: scaleX(-1);
+body.sepia {
+  --main-background: #f4ecd8;
+  --main-foreground: #5b4636;
+  --toolbar-border: #5b4636;
 }
 }
 
 
-/* Align the style dropdown arrow (narrate does its own) */
-.style-dropdown .dropdown-arrow {
-  top: 7px;
+body.dark {
+  --main-background: rgb(28, 27, 34);
+  --main-foreground: #eee;
+  --font-color: #fff;
+  --toolbar-border: #4a4a4b;
+  --toolbar-box-shadow: black;
+  --toolbar-button-background-hover: var(--grey-90-a30);
+  --toolbar-button-background-active: var(--grey-90-a80);
+  --tooltip-background: black;
+  --tooltip-foreground: white;
+  --popup-background: rgb(66,65,77);
+  --opaque-popup-border: #434146;
+  --popup-line: rgb(82, 82, 94);
+  --popup-button-background: #5c5c61;
+  --popup-button-background-active: hsla(0,0%,70%,.6);
+  --selected-background: #3E6D9A;
+  --font-value-border: #656468;
+  --icon-fill: #fff;
+  --icon-disabled-fill: #ffffff66;
+  --link-foreground: #45a1ff;
+  --link-selected-foreground: #fff;
+  --visited-link-foreground: #e675fd;
+  /* dark colours */
 }
 }
 
 
-/* Font style popup */
-
-.radio-button {
-  /* We visually hide these, but we keep them around so they can be focused
-   * and changed by interacting with them via the label, or the keyboard, or
-   * assistive technology.
-   */
-  opacity: 0;
-  pointer-events: none;
-  position: absolute;
-}
-
-.radiorow,
-.buttonrow {
-  display: flex;
-  align-content: center;
-  justify-content: center;
+body.hcm {
+  --main-background: Canvas;
+  --main-foreground: CanvasText;
+  --font-color: CanvasText;
+  --primary-color: SelectedItem;
+  --toolbar-border: CanvasText;
+   /* We need a true transparent but in HCM this would compute to an actual color,
+      so select the page's background color instead: */
+  --toolbar-transparent-border: Canvas;
+  --toolbar-box-shadow: Canvas;
+  --toolbar-button-background: ButtonFace;
+  --toolbar-button-background-hover: ButtonText;
+  --toolbar-button-foreground-hover: ButtonFace;
+  --toolbar-button-background-active: SelectedItem;
+  --toolbar-button-foreground-active: SelectedItemText;
+  --toolbar-button-border: ButtonText;
+  --toolbar-button-border-hover: ButtonText;
+  --toolbar-button-border-active: ButtonText;
+  --tooltip-background: Canvas;
+  --tooltip-foreground: CanvasText;
+  --tooltip-border: CanvasText;
+  --popup-background: Canvas;
+  --popup-border: CanvasText;
+  --opaque-popup-border: CanvasText;
+  --popup-line: CanvasText;
+  --popup-button-background: ButtonFace;
+  --popup-button-background-hover: ButtonText;
+  --popup-button-foreground-hover: ButtonFace;
+  --popup-button-background-active: ButtonText;
+  --selected-background: Canvas;
+  --selected-border: SelectedItem;
+  --font-value-border: CanvasText;
+  --icon-fill: ButtonText;
+  --icon-disabled-fill: GrayText;
+  --link-foreground: LinkText;
+  --link-selected-foreground: ActiveText;
+  --visited-link-foreground: VisitedText;
 }
 }
 
 
-.content-width-value,
-.font-size-value,
-.line-height-value {
-  box-sizing: border-box;
-  width: 36px;
-  height: 20px;
-  line-height: 20px;
-  display: flex;
-  justify-content: center;
-  align-content: center;
-  margin: auto;
-  border-radius: 10px;
-  border: 1px solid var(--font-value-border);
-  background-color: var(--popup-button-background);
+body {
+  margin: 0;
+  padding: var(--body-padding);
+  background-color: var(--main-background);
+  color: var(--main-foreground);
 }
 }
 
 
-.buttonrow > button {
-  border: 0;
-  height: 60px;
-  width: 90px;
-  background-color: transparent;
-  background-repeat: no-repeat;
-  background-position: center;
+body.loaded {
+  transition: color 0.4s, background-color 0.4s;
 }
 }
 
 
-.buttonrow > button:enabled:hover,
-.buttonrow > button:enabled:focus-visible {
-  background-color: var(--popup-button-background-hover);
-  color: var(--popup-button-foreground-hover);
-  fill: var(--popup-button-foreground-hover);
+body.dark *::-moz-selection {
+  background-color: var(--selected-background);
 }
 }
 
 
-.buttonrow > button:enabled:hover:active {
-  background-color: var(--popup-button-background-active);
+a::-moz-selection {
+  color: var(--link-selected-foreground);
 }
 }
 
 
-.radiorow:not(:last-child),
-.buttonrow:not(:last-child) {
-  border-bottom: 1px solid var(--popup-line);
+body.sans-serif,
+body.sans-serif .remove-button {
+  font-family: Helvetica, Arial, sans-serif;
 }
 }
 
 
-body.hcm .buttonrow.line-height-buttons {
-  /* On HCM the .color-scheme-buttons row is hidden, so remove the border from the row above it */
-  border-bottom: none;
+body.serif,
+body.serif .remove-button {
+  font-family: Georgia, "Times New Roman", serif;
 }
 }
 
 
-.radiorow > label {
-  position: relative;
-  box-sizing: border-box;
-  border-radius: 2px;
-  border: 2px solid var(--popup-border);
-}
+/* Override some controls and content styles based on color scheme */
 
 
-.radiorow > label[checked] {
-  border-color: var(--selected-border);
+body.light > .container > .header > .domain {
+  border-bottom-color: #333333 !important;
 }
 }
 
 
-/* For the hover style, we draw a line under the item by means of a
- * pseudo-element. Because these items are variable height, and
- * because their contents are variable height, position it absolutely,
- * and give it a width of 100% (the content width) + 4px for the 2 * 2px
- * border width.
- */
-.radiorow > input[type=radio]:focus-visible + label::after,
-.radiorow > label:hover::after {
-  content: "";
-  display: block;
-  border-bottom: 2px solid var(--selected-border);
-  width: calc(100% + 4px);
-  position: absolute;
-  /* to skip the 2 * 2px border + 2px spacing. */
-  bottom: -6px;
-  /* Match the start of the 2px border of the element: */
-  inset-inline-start: -2px;
+body.sepia > .container > .header > .domain {
+  border-bottom-color: #5b4636 !important;
 }
 }
 
 
-.font-type-buttons > label {
-  height: 64px;
-  width: 105px;
-  /* Slightly more space between these items. */
-  margin: 10px;
-  /* Center the Sans-serif / Serif labels */
-  text-align: center;
-  background-size: 63px 39px;
-  background-repeat: no-repeat;
-  background-position: center 18px;
-  background-color: var(--popup-button-background);
-  fill: currentColor;
-  -moz-context-properties: fill;
-  /* This mostly matches baselines, but because of differences in font
-   * baselines between serif and sans-serif, this isn't always enough. */
-  line-height: 1;
-  padding-top: 2px;
+body.dark > .container > .header > .domain {
+  border-bottom-color: #eeeeee !important;
 }
 }
 
 
-.font-type-buttons > label[checked] {
-  background-color: var(--selected-background);
+body.light blockquote {
+  border-inline-start: 2px solid #333333 !important;
 }
 }
 
 
-.sans-serif-button {
-  font-family: Helvetica, Arial, sans-serif;
-  background-image: url("chrome://global/skin/reader/RM-Sans-Serif.svg");
+body.sepia blockquote {
+  border-inline-start: 2px solid #5b4636 !important;
 }
 }
 
 
-/* Tweak padding to match the baseline on mac */
-:root[platform=macosx] .sans-serif-button {
-  padding-top: 3px;
+body.dark blockquote {
+  border-inline-start: 2px solid #eeeeee !important;
 }
 }
 
 
-.serif-button {
-  font-family: Georgia, "Times New Roman", serif;
-  background-image: url("chrome://global/skin/reader/RM-Serif.svg");
+.light-button {
+  color: #333333;
+  background-color: #ffffff;
 }
 }
 
 
-body.hcm .color-scheme-buttons {
-  /* Disallow selecting themes when HCM is on. */
-  display: none;
+.dark-button {
+  color: #eeeeee;
+  background-color: #1c1b22;
 }
 }
 
 
-.color-scheme-buttons > label {
-  padding: 12px;
-  height: 34px;
-  font-size: 12px;
-  /* Center the labels horizontally as well as vertically */
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  /* We want 10px between items, but there's no margin collapsing in flexbox. */
-  margin: 10px 5px;
+.sepia-button {
+  color: #5b4636;
+  background-color: #f4ecd8;
 }
 }
 
 
-.color-scheme-buttons > input:first-child + label {
-  margin-inline-start: 10px;
+.auto-button {
+  text-align: center;
 }
 }
 
 
-.color-scheme-buttons > label:last-child {
-  margin-inline-end: 10px;
+@media (prefers-color-scheme: dark) {
+  .auto-button {
+    background-color: #1c1b22;
+    color: #eeeeee;
+  }
 }
 }
 
 
-/* Toolbar icons */
-
-.close-button {
-  background-image: url("chrome://global/skin/icons/close.svg");
+@media not (prefers-color-scheme: dark) {
+  .auto-button {
+    background-color: #ffffff;
+    color: #333333;
+  }
 }
 }
 
 
-.style-button {
-  background-image: url("chrome://global/skin/reader/RM-Type-Controls-24x24.svg");
-}
+/* Loading/error message */
 
 
-.minus-button {
-  background-size: 18px 18px;
-  background-image: url("chrome://global/skin/reader/RM-Minus-24x24.svg");
+.reader-message {
+  margin-top: 40px;
+  display: none;
+  text-align: center;
+  width: 100%;
+  font-size: 0.9em;
 }
 }
 
 
-.plus-button {
-  background-size: 18px 18px;
-  background-image: url("chrome://global/skin/reader/RM-Plus-24x24.svg");
+/* Detector element to see if we're at the top of the doc or not. */
+.top-anchor {
+  position: absolute;
+  top: 0;
+  width: 0;
+  height: 5px;
+  pointer-events: none;
 }
 }
 
 
-.content-width-minus-button {
-  background-size: 42px 16px;
-  background-image: url("chrome://global/skin/reader/RM-Content-Width-Minus-42x16.svg");
-}
+/* Header */
 
 
-.content-width-plus-button {
-  background-size: 44px 16px;
-  background-image: url("chrome://global/skin/reader/RM-Content-Width-Plus-44x16.svg");
+.header {
+  text-align: start;
+  display: none;
 }
 }
 
 
-.line-height-minus-button {
-  background-size: 34px 14px;
-  background-image: url("chrome://global/skin/reader/RM-Line-Height-Minus-38x14.svg");
+.domain {
+  font-size: 0.9em;
+  line-height: 1.48em;
+  padding-bottom: 4px;
+  font-family: Helvetica, Arial, sans-serif;
+  text-decoration: none;
+  border-bottom: 1px solid;
+  color: var(--link-foreground);
 }
 }
 
 
-.line-height-plus-button {
-  background-size: 34px 24px;
-  background-image: url("chrome://global/skin/reader/RM-Line-Height-Plus-38x24.svg");
+.header > h1 {
+  font-size: 1.6em;
+  line-height: 1.25em;
+  width: 100%;
+  margin: 30px 0;
+  padding: 0;
 }
 }
 
 
-/* Mirror the line height buttons if the article is RTL. */
-.reader-controls[articledir="rtl"] .line-height-minus-button,
-.reader-controls[articledir="rtl"] .line-height-plus-button {
-  transform: scaleX(-1);
+.header > .credits {
+  font-size: 0.9em;
+  line-height: 1.48em;
+  margin: 0 0 10px;
+  padding: 0;
+  font-style: italic;
 }
 }
 
 
-@media print {
-  .toolbar {
-    display: none !important;
-  }
+.header > .meta-data {
+  font-size: 0.65em;
+  margin: 0 0 15px;
 }
 }
 
 
-/* Article content */
+.reader-estimated-time {
+  text-align: match-parent;
+}
 
 
-/* Note that any class names from the original article that we want to match on
- * must be added to CLASSES_TO_PRESERVE in ReaderMode.jsm, so that
- * Readability.js doesn't strip them out */
+/* Controls toolbar */
 
 
-.container {
-  margin: 0 auto;
-  font-size: var(--font-size);
-  max-width: var(--content-width);
-  line-height: var(--line-height);
-}
+.toolbar-container {
+  position: sticky;
+  z-index: 2;
+  top: 32px;
+  height: 0; /* take up no space, so body is at the top. */
 
 
-pre {
-  font-family: inherit;
+  /* As a stick container, we're positioned relative to the body. Move us to
+   * the edge of the viewport using margins, and take the width of
+   * the body padding into account for calculating our width.
+   */
+  margin-inline-start: calc(-1 * var(--body-padding));
+  width: max(var(--body-padding), calc((100% - var(--content-width)) / 2 + var(--body-padding)));
+  font-size: var(--font-size); /* Needed to ensure 'em' units match, is reset for .reader-controls */
 }
 }
 
 
-.moz-reader-content {
-  display: none;
-  font-size: 1em;
+.toolbar {
+  padding-block: 16px;
+  border: 1px solid var(--toolbar-border);
+  border-radius: 6px;
+  box-shadow: 0 2px 8px var(--toolbar-box-shadow);
+
+  width: 32px; /* basic width, without padding/border */
+
+  /* padding should be 16px, except if there's not enough space for that, in
+   * which case use half the available space for padding (=25% on each side).
+   * The 34px here is the width + borders. We use a variable because we need
+   * to know this size for the margin calculation.
+   */
+  --inline-padding: min(16px, calc(25% - 0.25 * 34px));
+  padding-inline: var(--inline-padding);
+
+  /* Keep a maximum of 96px distance to the body, but center once the margin
+   * gets too small. We need to set the start margin, however...
+   * To center, we'd want 50% of the container, but we'd subtract half our
+   * own width (16px) and half the border (1px) and the inline padding.
+   * When the other margin would be 96px, we want 100% - 96px - the complete
+   * width of the actual toolbar (34px + 2 * padding)
+   */
+  margin-inline-start: max(calc(50% - 17px - var(--inline-padding)), calc(100% - 96px - 34px - 2 * var(--inline-padding)));
+
+  font-family: Helvetica, Arial, sans-serif;
+  list-style: none;
+  user-select: none;
 }
 }
 
 
-@media print {
-  .moz-reader-content p,
-  .moz-reader-content code,
-  .moz-reader-content pre,
-  .moz-reader-content blockquote,
-  .moz-reader-content ul,
-  .moz-reader-content ol,
-  .moz-reader-content li,
-  .moz-reader-content figure,
-  .moz-reader-content .wp-caption {
-    margin: 0 0 10px !important;
-    padding: 0 !important;
+@media (prefers-reduced-motion: no-preference) {
+  .toolbar {
+    transition-property: border-color, box-shadow;
+    transition-duration: 250ms;
   }
   }
-}
 
 
-.moz-reader-content h1,
-.moz-reader-content h2,
-.moz-reader-content h3 {
-  font-weight: bold;
-}
+  .toolbar .toolbar-button {
+    transition-property: opacity;
+    transition-duration: 250ms;
+  }
 
 
-.moz-reader-content h1 {
-  font-size: 1.6em;
-  line-height: 1.25em;
-}
+  .toolbar-container.scrolled .toolbar:not(:hover, :focus-within) {
+    border-color: var(--toolbar-transparent-border);
+    box-shadow: 0 2px 8px transparent;
+  }
 
 
-.moz-reader-content h2 {
-  font-size: 1.2em;
-  line-height: 1.51em;
-}
+  .toolbar-container.scrolled .toolbar:not(:hover, :focus-within) .toolbar-button {
+    opacity: 0.6;
+  }
 
 
-.moz-reader-content h3 {
-  font-size: 1em;
-  line-height: 1.66em;
+  .toolbar-container.transition-location {
+    transition-duration: 250ms;
+    transition-property: width;
+  }
 }
 }
 
 
-.moz-reader-content a:link {
-  text-decoration: underline;
-  font-weight: normal;
+.toolbar-container.overlaps .toolbar-button {
+  opacity: 0.1;
 }
 }
 
 
-.moz-reader-content a:link,
-.moz-reader-content a:link:hover,
-.moz-reader-content a:link:active {
-  color: var(--link-foreground);
+.dropdown-open .toolbar {
+  border-color: var(--toolbar-transparent-border);
+  box-shadow: 0 2px 8px transparent;
 }
 }
 
 
-.moz-reader-content a:visited {
-  color: var(--visited-link-foreground);
+.reader-controls {
+  /* We use 'em's above this node to get it to the right size. However,
+   * the UI inside the toolbar should use a fixed, smaller size. */
+  font-size: 11px;
 }
 }
 
 
-.moz-reader-content * {
-  max-width: 100%;
-  height: auto;
+button {
+  -moz-context-properties: fill;
+  color: var(--font-color);
+  fill: var(--icon-fill);
 }
 }
 
 
-.moz-reader-content p,
-.moz-reader-content p,
-.moz-reader-content code,
-.moz-reader-content pre,
-.moz-reader-content blockquote,
-.moz-reader-content ul,
-.moz-reader-content ol,
-.moz-reader-content li,
-.moz-reader-content figure,
-.moz-reader-content .wp-caption {
-  margin: -10px -10px 20px;
-  padding: 10px;
-  border-radius: 5px;
+button:disabled {
+  fill: var(--icon-disabled-fill);
 }
 }
 
 
-.moz-reader-content li {
-  margin-bottom: 0;
+.toolbar button::-moz-focus-inner {
+  border: 0;
 }
 }
 
 
-.moz-reader-content li > ul,
-.moz-reader-content li > ol {
-  margin-bottom: -10px;
+.toolbar-button {
+  position: relative;
+  width: 32px;
+  height: 32px;
+  padding: 0;
+  border: 1px solid var(--toolbar-button-border);
+  border-radius: 4px;
+  margin: 4px 0;
+  background-color: var(--toolbar-button-background);
+  background-size: 16px 16px;
+  background-position: center;
+  background-repeat: no-repeat;
 }
 }
 
 
-.moz-reader-content p > img:only-child,
-.moz-reader-content p > a:only-child > img:only-child,
-.moz-reader-content .wp-caption img,
-.moz-reader-content figure img {
-  display: block;
+.toolbar-button:hover,
+.toolbar-button:focus-visible {
+  background-color: var(--toolbar-button-background-hover);
+  border-color: var(--toolbar-button-border-hover);
+  fill: var(--toolbar-button-foreground-hover);
 }
 }
 
 
-.moz-reader-content img[moz-reader-center] {
-  margin-inline: auto;
+.open .toolbar-button,
+.toolbar-button:hover:active {
+  background-color: var(--toolbar-button-background-active);
+  border-color: var(--toolbar-button-border-active);
+  color: var(--toolbar-button-foreground-active);
+  fill: var(--toolbar-button-foreground-active);
 }
 }
 
 
-.moz-reader-content .caption,
-.moz-reader-content .wp-caption-text
-.moz-reader-content figcaption {
-  font-size: 0.9em;
-  line-height: 1.48em;
-  font-style: italic;
+.hover-label {
+  position: absolute;
+  top: 4px;
+  inset-inline-start: 36px;
+  line-height: 16px;
+  white-space: pre; /* make sure we don't wrap */
+  background-color: var(--tooltip-background);
+  color: var(--tooltip-foreground);
+  padding: 4px 8px;
+  border: 1px solid var(--tooltip-border);
+  border-radius: 2px;
+  visibility: hidden;
+  pointer-events: none;
+  /* Put above .dropdown .dropdown-popup, which has z-index: 1000. */
+  z-index: 1001;
 }
 }
 
 
-.moz-reader-content pre {
-  white-space: pre-wrap;
+/* Show the hover tooltip on non-dropdown buttons. */
+.toolbar-button:not(.dropdown-toggle):hover > .hover-label,
+.toolbar-button:not(.dropdown-toggle):focus-visible > .hover-label,
+/* Show the hover tooltip for dropdown buttons unless its dropdown is open. */
+:not(.open) > li > .dropdown-toggle:hover > .hover-label,
+:not(.open) > li > .dropdown-toggle:focus-visible > .hover-label {
+  visibility: visible;
 }
 }
 
 
-.moz-reader-content blockquote {
+.dropdown {
+  text-align: center;
+  list-style: none;
+  margin: 0;
   padding: 0;
   padding: 0;
-  padding-inline-start: 16px;
+  position: relative;
 }
 }
 
 
-.moz-reader-content ul,
-.moz-reader-content ol {
+.dropdown li {
+  margin: 0;
   padding: 0;
   padding: 0;
 }
 }
 
 
-.moz-reader-content ul {
-  padding-inline-start: 30px;
-  list-style: disc;
-}
-
-.moz-reader-content ol {
-  padding-inline-start: 30px;
-}
+/* Popup */
 
 
-table,
-th,
-td {
-  border: 1px solid currentColor;
-  border-collapse: collapse;
-  padding: 6px;
-  vertical-align: top;
+.dropdown .dropdown-popup {
+  text-align: start;
+  position: absolute;
+  inset-inline-start: 40px;
+  z-index: 1000;
+  background-color: var(--popup-background);
+  visibility: hidden;
+  border-radius: 4px;
+  border: 1px solid var(--popup-border);
+  box-shadow: 0 0 10px 0 var(--popup-shadow);
+  top: 0;
 }
 }
 
 
-table {
-  margin: 5px;
+.open > .dropdown-popup {
+  visibility: visible;
 }
 }
 
 
-/* Visually hide (but don't display: none) screen reader elements */
-.moz-reader-content .visually-hidden,
-.moz-reader-content .visuallyhidden,
-.moz-reader-content .sr-only {
-  display: inline-block;
-  width: 1px;
-  height: 1px;
-  margin: -1px;
-  overflow: hidden;
-  padding: 0;
-  border-width: 0;
+.dropdown-arrow {
+  position: absolute;
+  height: 24px;
+  width: 16px;
+  inset-inline-start: -16px;
+  background-image: url("chrome://global/skin/reader/RM-Type-Controls-Arrow.svg");
+  display: block;
+  -moz-context-properties: fill, stroke;
+  fill: var(--popup-background);
+  stroke: var(--opaque-popup-border);
+  pointer-events: none;
 }
 }
 
 
-/* Hide elements with common "hidden" class names */
-.moz-reader-content .hidden,
-.moz-reader-content .invisible {
-  display: none;
+.dropdown-arrow:dir(rtl) {
+  transform: scaleX(-1);
 }
 }
 
 
-/* Enforce wordpress and similar emoji/smileys aren't sized to be full-width,
- * see bug 1399616 for context. */
-.moz-reader-content img.wp-smiley,
-.moz-reader-content img.emoji {
-  display: inline-block;
-  border-width: 0;
-  /* height: auto is implied from '.moz-reader-content *' rule. */
-  width: 1em;
-  margin: 0 .07em;
-  padding: 0;
+/* Align the style dropdown arrow (narrate does its own) */
+.style-dropdown .dropdown-arrow {
+  top: 7px;
 }
 }
 
 
-.reader-show-element {
-  display: initial;
-}
+/* Font style popup */
 
 
-/* Provide extra spacing for images that may be aided with accompanying element such as <figcaption> */
-.moz-reader-block-img:not(:last-child) {
-  margin-block-end: 12px;
+.radio-button {
+  /* We visually hide these, but we keep them around so they can be focused
+   * and changed by interacting with them via the label, or the keyboard, or
+   * assistive technology.
+   */
+  opacity: 0;
+  pointer-events: none;
+  position: absolute;
 }
 }
 
 
-.moz-reader-wide-table {
-  overflow-x: auto;
-  display: block;
+.radiorow,
+.buttonrow {
+  display: flex;
+  align-content: center;
+  justify-content: center;
 }
 }
 
 
-pre code {
-  background-color: var(--main-background);
-  border: 1px solid var(--font-color);
-  display: block;
-  overflow: auto;
-}`;
-
-		let NOTES_WEB_STYLESHEET, MASK_WEB_STYLESHEET, HIGHLIGHTS_WEB_STYLESHEET;
-		let selectedNote, anchorElement, maskNoteElement, maskPageElement, highlightSelectionMode, removeHighlightMode, resizingNoteMode, movingNoteMode, highlightColor, collapseNoteTimeout, cuttingOuterMode, cuttingMode, cuttingTouchTarget, cuttingPath, cuttingPathIndex, previousContent;
-		let removedElements = [], removedElementIndex = 0, initScriptContent, pageResources, pageUrl, pageCompressContent, includeInfobar, openInfobar, infobarPositionAbsolute, infobarPositionTop, infobarPositionBottom, infobarPositionLeft, infobarPositionRight;
-
-		globalThis.zip = singlefile.helper.zip;
-		initEventListeners();
-		new MutationObserver(initEventListeners).observe(document, { childList: true });
+.content-width-value,
+.font-size-value,
+.line-height-value {
+  box-sizing: border-box;
+  width: 36px;
+  height: 20px;
+  line-height: 20px;
+  display: flex;
+  justify-content: center;
+  align-content: center;
+  margin: auto;
+  border-radius: 10px;
+  border: 1px solid var(--font-value-border);
+  background-color: var(--popup-button-background);
+}
 
 
-		function initEventListeners() {
-			window.onmessage = async event => {
-				const message = JSON.parse(event.data);
-				if (message.method == "init") {
-					await init(message);
-				}
-				if (message.method == "addNote") {
-					addNote(message);
-				}
-				if (message.method == "displayNotes") {
-					document.querySelectorAll(NOTE_TAGNAME).forEach(noteElement => noteElement.shadowRoot.querySelector("." + NOTE_CLASS).classList.remove(NOTE_HIDDEN_CLASS));
-				}
-				if (message.method == "hideNotes") {
-					document.querySelectorAll(NOTE_TAGNAME).forEach(noteElement => noteElement.shadowRoot.querySelector("." + NOTE_CLASS).classList.add(NOTE_HIDDEN_CLASS));
-				}
-				if (message.method == "enableHighlight") {
-					if (highlightColor) {
-						document.documentElement.classList.remove(highlightColor + "-mode");
-					}
-					highlightColor = message.color;
-					highlightSelectionMode = true;
-					document.documentElement.classList.add(message.color + "-mode");
-				}
-				if (message.method == "disableHighlight") {
-					disableHighlight();
-					highlightSelectionMode = false;
-				}
-				if (message.method == "displayHighlights") {
-					document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.remove(HIGHLIGHT_HIDDEN_CLASS));
-				}
-				if (message.method == "hideHighlights") {
-					document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.add(HIGHLIGHT_HIDDEN_CLASS));
-				}
-				if (message.method == "enableRemoveHighlights") {
-					removeHighlightMode = true;
-					document.documentElement.classList.add("single-file-remove-highlights-mode");
-				}
-				if (message.method == "disableRemoveHighlights") {
-					removeHighlightMode = false;
-					document.documentElement.classList.remove("single-file-remove-highlights-mode");
-				}
-				if (message.method == "enableEditPage") {
-					document.body.contentEditable = true;
-					onUpdate(false);
-				}
-				if (message.method == "formatPage") {
-					formatPage(!message.applySystemTheme);
-				}
-				if (message.method == "cancelFormatPage") {
-					cancelFormatPage();
-				}
-				if (message.method == "disableEditPage") {
-					document.body.contentEditable = false;
-				}
-				if (message.method == "enableCutInnerPage") {
-					cuttingMode = true;
-					document.documentElement.classList.add(CUT_MODE_CLASS);
-				}
-				if (message.method == "enableCutOuterPage") {
-					cuttingOuterMode = true;
-					document.documentElement.classList.add(CUT_MODE_CLASS);
-				}
-				if (message.method == "disableCutInnerPage" || message.method == "disableCutOuterPage") {
-					if (message.method == "disableCutInnerPage") {
-						cuttingMode = false;
-					} else {
-						cuttingOuterMode = false;
-					}
-					document.documentElement.classList.remove(CUT_MODE_CLASS);
-					resetSelectedElements();
-					if (cuttingPath) {
-						unhighlightCutElement();
-						cuttingPath = null;
-					}
-				}
-				if (message.method == "undoCutPage") {
-					undoCutPage();
-				}
-				if (message.method == "undoAllCutPage") {
-					while (removedElementIndex) {
-						removedElements[removedElementIndex - 1].forEach(element => element.classList.remove(REMOVED_CONTENT_CLASS));
-						removedElementIndex--;
-					}
-				}
-				if (message.method == "redoCutPage") {
-					redoCutPage();
-				}
-				if (message.method == "getContent") {
-					onUpdate(true);
-					includeInfobar = message.includeInfobar;
-					openInfobar = message.openInfobar;
-					infobarPositionAbsolute = message.infobarPositionAbsolute;
-					infobarPositionTop = message.infobarPositionTop;
-					infobarPositionBottom = message.infobarPositionBottom;
-					infobarPositionLeft = message.infobarPositionLeft;
-					infobarPositionRight = message.infobarPositionRight;
-					let content = getContent(message.compressHTML, message.updatedResources);
-					if (initScriptContent) {
-						content = content.replace(/<script data-template-shadow-root src.*?<\/script>/g, initScriptContent);
-					}
-					let filename;
-					const pageOptions = loadOptionsFromPage(document);
-					if (pageOptions) {
-						pageOptions.backgroundSave = message.backgroundSave;
-						pageOptions.saveDate = new Date(pageOptions.saveDate);
-						pageOptions.visitDate = new Date(pageOptions.visitDate);
-						filename = await singlefile.helper.formatFilename(content, document, pageOptions);
-					}
-					if (message.sharePage) {
-						setLabels(message.labels);
-					}
-					if (pageCompressContent) {
-						const viewport = document.head.querySelector("meta[name=viewport]");
-						window.parent.postMessage(JSON.stringify({
-							method: "setContent",
-							content,
-							filename,
-							title: document.title,
-							doctype: singlefile.helper.getDoctypeString(document),
-							url: pageUrl,
-							viewport: viewport ? viewport.content : null,
-							compressContent: true,
-							foregroundSave: message.foregroundSave,
-							sharePage: message.sharePage,
-							documentHeight: document.documentElement.offsetHeight
-						}), "*");
-					} else {
-						if (message.foregroundSave || message.sharePage) {
-							try {
-								await downloadPageForeground({
-									content,
-									filename: filename || message.filename,
-									mimeType: "text/html"
-								}, { sharePage: message.sharePage });
-							} catch (error) {
-								console.log(error); // eslint-disable-line no-console
-								window.parent.postMessage(JSON.stringify({ method: "onError", error: error.message }), "*");
-							}
-						} else {
-							window.parent.postMessage(JSON.stringify({
-								method: "setContent",
-								content,
-								filename
-							}), "*");
-						}
-					}
-				}
-				if (message.method == "printPage") {
-					printPage();
-				}
-				if (message.method == "displayInfobar") {
-					singlefile.helper.displayIcon(document, true, {
-						openInfobar: message.openInfobar,
-						infobarPositionAbsolute: message.infobarPositionAbsolute,
-						infobarPositionTop: message.infobarPositionTop,
-						infobarPositionBottom: message.infobarPositionBottom,
-						infobarPositionLeft: message.infobarPositionLeft,
-						infobarPositionRight: message.infobarPositionRight
-					});
-					const infobarDoc = document.implementation.createHTMLDocument();
-					infobarDoc.body.appendChild(document.querySelector(singlefile.helper.INFOBAR_TAGNAME));
-					serializeShadowRoots(infobarDoc.body);
-					const content = singlefile.helper.serialize(infobarDoc, true);
-					window.parent.postMessage(JSON.stringify({
-						method: "displayInfobar",
-						content
-					}), "*");
-				}
-				if (message.method == "download") {
-					try {
-						await downloadPageForeground({
-							content: message.content,
-							filename: message.filename,
-							mimeType: message.mimeType
-						}, { sharePage: message.sharePage });
-					} catch (error) {
-						console.log(error); // eslint-disable-line no-console
-						window.parent.postMessage(JSON.stringify({ method: "onError", error: error.message }), "*");
-					}
-				}
-			};
-			window.onresize = reflowNotes;
-			document.ondragover = event => event.preventDefault();
-			document.ondrop = async event => {
-				if (event.dataTransfer.files && event.dataTransfer.files[0]) {
-					const file = event.dataTransfer.files[0];
-					event.preventDefault();
-					const content = new TextDecoder().decode(await file.arrayBuffer());
-					const compressContent = /<html[^>]* data-sfz[^>]*>/i.test(content);
-					if (compressContent) {
-						await init({ content: file, compressContent }, { filename: file.name });
-					} else {
-						await init({ content }, { filename: file.name });
-					}
-				}
-			};
-		}
+.buttonrow > button {
+  border: 0;
+  height: 60px;
+  width: 90px;
+  background-color: transparent;
+  background-repeat: no-repeat;
+  background-position: center;
+}
 
 
-		async function init({ content, password, compressContent }, { filename, reset } = {}) {
-			await initConstants();
-			if (compressContent) {
-				const zipOptions = {
-					workerScripts: { inflate: ["/lib/single-file-z-worker.js"] }
-				};
-				try {
-					const worker = new Worker(zipOptions.workerScripts.inflate[0]);
-					worker.terminate();
-					// eslint-disable-next-line no-unused-vars
-				} catch (error) {
-					delete zipOptions.workerScripts;
-				}
-				zipOptions.useWebWorkers = IS_NOT_SAFARI;
-				const { docContent, origDocContent, resources, url } = await singlefile.helper.extract(content, {
-					password,
-					prompt,
-					shadowRootScriptURL: new URL("/lib/single-file-extension-editor-init.js", document.baseURI).href,
-					zipOptions
-				});
-				pageResources = resources;
-				pageUrl = url;
-				pageCompressContent = true;
-				const contentDocument = (new DOMParser()).parseFromString(docContent, "text/html");
-				if (detectSavedPage(contentDocument)) {
-					await singlefile.helper.display(document, docContent, { disableFramePointerEvents: true });
-					const infobarElement = document.querySelector(singlefile.helper.INFOBAR_TAGNAME);
-					if (infobarElement) {
-						infobarElement.remove();
-					}
-					await initPage();
-					let icon;
-					const origContentDocument = (new DOMParser()).parseFromString(origDocContent, "text/html");
-					const iconElement = origContentDocument.querySelector("link[rel*=icon]");
-					if (iconElement) {
-						const iconResource = resources.find(resource => resource.filename == iconElement.getAttribute("href"));
-						if (iconResource && iconResource.content) {
-							const reader = new FileReader();
-							reader.readAsDataURL(await (await fetch(iconResource.content)).blob());
-							icon = await new Promise((resolve, reject) => {
-								reader.addEventListener("load", () => resolve(reader.result), false);
-								reader.addEventListener("error", reject, false);
-							});
-						} else {
-							icon = iconElement.href;
-						}
-					}
-					window.parent.postMessage(JSON.stringify({
-						method: "onInit",
-						title: document.title,
-						icon,
-						filename,
-						reset,
-						formatPageEnabled: isProbablyReaderable(document)
-					}), "*");
-				}
-			} else {
-				const initScriptContentMatch = content.match(/<script data-template-shadow-root.*<\/script>/);
-				if (initScriptContentMatch && initScriptContentMatch[0]) {
-					initScriptContent = initScriptContentMatch[0];
-				}
-				content = content.replace(/<script data-template-shadow-root.*<\/script>/g, "<script data-template-shadow-root src=/lib/single-file-extension-editor-init.js></script>");
-				const contentDocument = (new DOMParser()).parseFromString(content, "text/html");
-				if (detectSavedPage(contentDocument)) {
-					if (contentDocument.doctype) {
-						if (document.doctype) {
-							document.replaceChild(contentDocument.doctype, document.doctype);
-						} else {
-							document.insertBefore(contentDocument.doctype, document.documentElement);
-						}
-					} else if (document.doctype) {
-						document.doctype.remove();
-					}
-					const infobarElement = contentDocument.querySelector(singlefile.helper.INFOBAR_TAGNAME);
-					if (infobarElement) {
-						infobarElement.remove();
-					}
-					contentDocument.querySelectorAll("noscript").forEach(element => {
-						element.setAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME, element.innerHTML);
-						element.textContent = "";
-					});
-					contentDocument.querySelectorAll("iframe").forEach(element => {
-						const pointerEvents = "pointer-events";
-						element.style.setProperty("-sf-" + pointerEvents, element.style.getPropertyValue(pointerEvents), element.style.getPropertyPriority(pointerEvents));
-						element.style.setProperty(pointerEvents, "none", "important");
-					});
-					document.replaceChild(contentDocument.documentElement, document.documentElement);
-					document.querySelectorAll("[data-single-file-note-refs]").forEach(noteRefElement => noteRefElement.dataset.singleFileNoteRefs = noteRefElement.dataset.singleFileNoteRefs.replace(/,/g, " "));
-					deserializeShadowRoots(document);
-					document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => attachNoteListeners(containerElement, true));
-					insertHighlightStylesheet(document);
-					maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
-					maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
-					document.documentElement.onmousedown = onMouseDown;
-					document.documentElement.onmouseup = document.documentElement.ontouchend = onMouseUp;
-					document.documentElement.onmouseover = onMouseOver;
-					document.documentElement.onmouseout = onMouseOut;
-					document.documentElement.onkeydown = onKeyDown;
-					document.documentElement.ontouchstart = document.documentElement.ontouchmove = onTouchMove;
-					window.onclick = event => event.preventDefault();
-					const iconElement = document.querySelector("link[rel*=icon]");
-					window.parent.postMessage(JSON.stringify({
-						method: "onInit",
-						title: document.title,
-						icon: iconElement && iconElement.href,
-						filename,
-						reset,
-						formatPageEnabled: isProbablyReaderable(document)
-					}), "*");
-				}
-			}
-		}
+.buttonrow > button:enabled:hover,
+.buttonrow > button:enabled:focus-visible {
+  background-color: var(--popup-button-background-hover);
+  color: var(--popup-button-foreground-hover);
+  fill: var(--popup-button-foreground-hover);
+}
 
 
-		function loadOptionsFromPage(doc) {
-			const optionsElement = doc.body.querySelector("script[type=\"application/json\"][" + SCRIPT_OPTIONS + "]");
-			if (optionsElement) {
-				return JSON.parse(optionsElement.textContent);
-			}
-		}
+.buttonrow > button:enabled:hover:active {
+  background-color: var(--popup-button-background-active);
+}
 
 
-		async function initPage() {
-			document.querySelectorAll("iframe").forEach(element => {
-				const pointerEvents = "pointer-events";
-				element.style.setProperty("-sf-" + pointerEvents, element.style.getPropertyValue(pointerEvents), element.style.getPropertyPriority(pointerEvents));
-				element.style.setProperty(pointerEvents, "none", "important");
-			});
-			document.querySelectorAll("[data-single-file-note-refs]").forEach(noteRefElement => noteRefElement.dataset.singleFileNoteRefs = noteRefElement.dataset.singleFileNoteRefs.replace(/,/g, " "));
-			deserializeShadowRoots(document);
-			reflowNotes();
-			await waitResourcesLoad();
-			reflowNotes();
-			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => attachNoteListeners(containerElement, true));
-			insertHighlightStylesheet(document);
-			maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
-			maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
-			document.documentElement.onmousedown = onMouseDown;
-			document.documentElement.onmouseup = document.documentElement.ontouchend = onMouseUp;
-			document.documentElement.onmouseover = onMouseOver;
-			document.documentElement.onmouseout = onMouseOut;
-			document.documentElement.onkeydown = onKeyDown;
-			document.documentElement.ontouchstart = document.documentElement.ontouchmove = onTouchMove;
-			window.onclick = event => event.preventDefault();
-		}
+.radiorow:not(:last-child),
+.buttonrow:not(:last-child) {
+  border-bottom: 1px solid var(--popup-line);
+}
 
 
-		async function initConstants() {
-			[NOTES_WEB_STYLESHEET, MASK_WEB_STYLESHEET, HIGHLIGHTS_WEB_STYLESHEET] = await Promise.all([
-				minifyText(await ((await fetch("../pages/editor-note-web.css")).text())),
-				minifyText(await ((await fetch("../pages/editor-mask-web.css")).text())),
-				minifyText(await ((await fetch("../pages/editor-frame-web.css")).text()))
-			]);
-		}
+body.hcm .buttonrow.line-height-buttons {
+  /* On HCM the .color-scheme-buttons row is hidden, so remove the border from the row above it */
+  border-bottom: none;
+}
 
 
-		function addNote({ color }) {
-			const containerElement = document.createElement(NOTE_TAGNAME);
-			const noteElement = document.createElement("div");
-			const headerElement = document.createElement("header");
-			const blockquoteElement = document.createElement("blockquote");
-			const mainElement = document.createElement("textarea");
-			const resizeElement = document.createElement("div");
-			const removeNoteElement = document.createElement("img");
-			const anchorIconElement = document.createElement("img");
-			const noteShadow = containerElement.attachShadow({ mode: "open" });
-			headerElement.appendChild(anchorIconElement);
-			headerElement.appendChild(removeNoteElement);
-			blockquoteElement.appendChild(mainElement);
-			noteElement.appendChild(headerElement);
-			noteElement.appendChild(blockquoteElement);
-			noteElement.appendChild(resizeElement);
-			noteShadow.appendChild(getStyleElement(NOTES_WEB_STYLESHEET));
-			noteShadow.appendChild(noteElement);
-			const notesElements = Array.from(document.querySelectorAll(NOTE_TAGNAME));
-			const noteId = Math.max.call(Math, 0, ...notesElements.map(noteElement => Number(noteElement.dataset.noteId))) + 1;
-			blockquoteElement.cite = "https://www.w3.org/TR/annotation-model/#selector(type=CssSelector,value=[data-single-file-note-refs~=\"" + noteId + "\"])";
-			noteElement.classList.add(NOTE_CLASS);
-			noteElement.classList.add(NOTE_ANCHORED_CLASS);
-			noteElement.classList.add(color);
-			noteElement.dataset.color = color;
-			mainElement.dir = "auto";
-			const boundingRectDocument = document.documentElement.getBoundingClientRect();
-			let positionX = NOTE_INITIAL_WIDTH + NOTE_INITIAL_POSITION_X - 1 - boundingRectDocument.x;
-			let positionY = NOTE_INITIAL_HEIGHT + NOTE_INITIAL_POSITION_Y - 1 - boundingRectDocument.y;
-			while (Array.from(document.elementsFromPoint(positionX - window.scrollX, positionY - window.scrollY)).find(element => element.tagName.toLowerCase() == NOTE_TAGNAME)) {
-				positionX += NOTE_INITIAL_POSITION_X;
-				positionY += NOTE_INITIAL_POSITION_Y;
-			}
-			noteElement.style.setProperty("left", (positionX - NOTE_INITIAL_WIDTH - 1) + "px");
-			noteElement.style.setProperty("top", (positionY - NOTE_INITIAL_HEIGHT - 1) + "px");
-			resizeElement.className = "note-resize";
-			resizeElement.ondragstart = event => event.preventDefault();
-			removeNoteElement.className = "note-remove";
-			removeNoteElement.src = BUTTON_CLOSE_URL;
-			removeNoteElement.ondragstart = event => event.preventDefault();
-			anchorIconElement.className = "note-anchor";
-			anchorIconElement.src = BUTTON_ANCHOR_URL;
-			anchorIconElement.ondragstart = event => event.preventDefault();
-			containerElement.dataset.noteId = noteId;
-			addNoteRef(document.documentElement, noteId);
-			attachNoteListeners(containerElement, true);
-			document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
-			noteElement.classList.add(NOTE_SELECTED_CLASS);
-			selectedNote = noteElement;
-			onUpdate(false);
-		}
+.radiorow > label {
+  position: relative;
+  box-sizing: border-box;
+  border-radius: 2px;
+  border: 2px solid var(--popup-border);
+}
 
 
-		function attachNoteListeners(containerElement, editable = false) {
-			const SELECT_PX_THRESHOLD = 4;
-			const COLLAPSING_NOTE_DELAY = 750;
-			const noteShadow = containerElement.shadowRoot;
-			const noteElement = noteShadow.childNodes[1];
-			const headerElement = noteShadow.querySelector("header");
-			const mainElement = noteShadow.querySelector("textarea");
-			const noteId = containerElement.dataset.noteId;
-			const resizeElement = noteShadow.querySelector(".note-resize");
-			const anchorIconElement = noteShadow.querySelector(".note-anchor");
-			const removeNoteElement = noteShadow.querySelector(".note-remove");
-			mainElement.readOnly = !editable;
-			if (!editable) {
-				anchorIconElement.style.setProperty("display", "none", "important");
-			} else {
-				anchorIconElement.style.removeProperty("display");
-			}
-			headerElement.ontouchstart = headerElement.onmousedown = event => {
-				if (event.target == headerElement) {
-					collapseNoteTimeout = setTimeout(() => {
-						noteElement.classList.toggle("note-collapsed");
-						hideMaskNote();
-					}, COLLAPSING_NOTE_DELAY);
-					event.preventDefault();
-					const position = getPosition(event);
-					const clientX = position.clientX;
-					const clientY = position.clientY;
-					const boundingRect = noteElement.getBoundingClientRect();
-					const deltaX = clientX - boundingRect.left;
-					const deltaY = clientY - boundingRect.top;
-					maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS);
-					document.documentElement.style.setProperty("user-select", "none", "important");
-					anchorElement = getAnchorElement(containerElement);
-					displayMaskNote();
-					selectNote(noteElement);
-					moveNote(event, deltaX, deltaY);
-					movingNoteMode = { event, deltaX, deltaY };
-					document.documentElement.ontouchmove = document.documentElement.onmousemove = event => {
-						clearTimeout(collapseNoteTimeout);
-						if (!movingNoteMode) {
-							movingNoteMode = { deltaX, deltaY };
-						}
-						movingNoteMode.event = event;
-						moveNote(event, deltaX, deltaY);
-					};
-				}
-			};
-			resizeElement.ontouchstart = resizeElement.onmousedown = event => {
-				event.preventDefault();
-				resizingNoteMode = true;
-				selectNote(noteElement);
-				maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS);
-				document.documentElement.style.setProperty("user-select", "none", "important");
-				document.documentElement.ontouchmove = document.documentElement.onmousemove = event => {
-					event.preventDefault();
-					const { clientX, clientY } = getPosition(event);
-					const boundingRectNote = noteElement.getBoundingClientRect();
-					noteElement.style.width = clientX - boundingRectNote.left + "px";
-					noteElement.style.height = clientY - boundingRectNote.top + "px";
-				};
-			};
-			anchorIconElement.ontouchend = anchorIconElement.onclick = event => {
-				event.preventDefault();
-				noteElement.classList.toggle(NOTE_ANCHORED_CLASS);
-				if (!noteElement.classList.contains(NOTE_ANCHORED_CLASS)) {
-					deleteNoteRef(containerElement, noteId);
-					addNoteRef(document.documentElement, noteId);
-				}
-				onUpdate(false);
-			};
-			removeNoteElement.ontouchend = removeNoteElement.onclick = event => {
-				event.preventDefault();
-				deleteNoteRef(containerElement, noteId);
-				containerElement.remove();
-			};
-			noteElement.onmousedown = () => {
-				selectNote(noteElement);
-			};
+.radiorow > label[checked] {
+  border-color: var(--selected-border);
+}
+
+/* For the hover style, we draw a line under the item by means of a
+ * pseudo-element. Because these items are variable height, and
+ * because their contents are variable height, position it absolutely,
+ * and give it a width of 100% (the content width) + 4px for the 2 * 2px
+ * border width.
+ */
+.radiorow > input[type=radio]:focus-visible + label::after,
+.radiorow > label:hover::after {
+  content: "";
+  display: block;
+  border-bottom: 2px solid var(--selected-border);
+  width: calc(100% + 4px);
+  position: absolute;
+  /* to skip the 2 * 2px border + 2px spacing. */
+  bottom: -6px;
+  /* Match the start of the 2px border of the element: */
+  inset-inline-start: -2px;
+}
+
+.font-type-buttons > label {
+  height: 64px;
+  width: 105px;
+  /* Slightly more space between these items. */
+  margin: 10px;
+  /* Center the Sans-serif / Serif labels */
+  text-align: center;
+  background-size: 63px 39px;
+  background-repeat: no-repeat;
+  background-position: center 18px;
+  background-color: var(--popup-button-background);
+  fill: currentColor;
+  -moz-context-properties: fill;
+  /* This mostly matches baselines, but because of differences in font
+   * baselines between serif and sans-serif, this isn't always enough. */
+  line-height: 1;
+  padding-top: 2px;
+}
+
+.font-type-buttons > label[checked] {
+  background-color: var(--selected-background);
+}
+
+.sans-serif-button {
+  font-family: Helvetica, Arial, sans-serif;
+  background-image: url("chrome://global/skin/reader/RM-Sans-Serif.svg");
+}
+
+/* Tweak padding to match the baseline on mac */
+:root[platform=macosx] .sans-serif-button {
+  padding-top: 3px;
+}
+
+.serif-button {
+  font-family: Georgia, "Times New Roman", serif;
+  background-image: url("chrome://global/skin/reader/RM-Serif.svg");
+}
+
+body.hcm .color-scheme-buttons {
+  /* Disallow selecting themes when HCM is on. */
+  display: none;
+}
+
+.color-scheme-buttons > label {
+  padding: 12px;
+  height: 34px;
+  font-size: 12px;
+  /* Center the labels horizontally as well as vertically */
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  /* We want 10px between items, but there's no margin collapsing in flexbox. */
+  margin: 10px 5px;
+}
 
 
-			function moveNote(event, deltaX, deltaY) {
-				event.preventDefault();
-				const { clientX, clientY } = getPosition(event);
-				noteElement.classList.add(NOTE_MOVING_CLASS);
-				if (editable) {
-					if (noteElement.classList.contains(NOTE_ANCHORED_CLASS)) {
-						deleteNoteRef(containerElement, noteId);
-						anchorElement = getTarget(clientX, clientY) || document.documentElement;
-						addNoteRef(anchorElement, noteId);
-					} else {
-						anchorElement = document.documentElement;
-					}
-				}
-				document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
-				noteElement.style.setProperty("left", (clientX - deltaX) + "px");
-				noteElement.style.setProperty("top", (clientY - deltaY) + "px");
-				noteElement.style.setProperty("position", "fixed");
-				displayMaskNote();
-			}
+.color-scheme-buttons > input:first-child + label {
+  margin-inline-start: 10px;
+}
 
 
-			function displayMaskNote() {
-				if (anchorElement == document.documentElement || anchorElement == document.documentElement) {
-					hideMaskNote();
-				} else {
-					const boundingRectAnchor = anchorElement.getBoundingClientRect();
-					maskNoteElement.classList.add(NOTE_MASK_MOVING_CLASS);
-					if (selectedNote) {
-						maskNoteElement.classList.add(selectedNote.dataset.color);
-					}
-					maskNoteElement.style.setProperty("top", (boundingRectAnchor.y - 3) + "px");
-					maskNoteElement.style.setProperty("left", (boundingRectAnchor.x - 3) + "px");
-					maskNoteElement.style.setProperty("width", (boundingRectAnchor.width + 3) + "px");
-					maskNoteElement.style.setProperty("height", (boundingRectAnchor.height + 3) + "px");
-				}
-			}
+.color-scheme-buttons > label:last-child {
+  margin-inline-end: 10px;
+}
 
 
-			function hideMaskNote() {
-				maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS);
-				if (selectedNote) {
-					maskNoteElement.classList.remove(selectedNote.dataset.color);
-				}
-			}
+/* Toolbar icons */
 
 
-			function selectNote(noteElement) {
-				if (selectedNote) {
-					selectedNote.classList.remove(NOTE_SELECTED_CLASS);
-					maskNoteElement.classList.remove(selectedNote.dataset.color);
-				}
-				noteElement.classList.add(NOTE_SELECTED_CLASS);
-				noteElement.classList.add(noteElement.dataset.color);
-				selectedNote = noteElement;
-			}
+.close-button {
+  background-image: url("chrome://global/skin/icons/close.svg");
+}
 
 
-			function getTarget(clientX, clientY) {
-				const targets = Array.from(document.elementsFromPoint(clientX, clientY)).filter(element => element.matches("html *:not(" + NOTE_TAGNAME + "):not(." + MASK_CLASS + ")"));
-				if (!targets.includes(document.documentElement)) {
-					targets.push(document.documentElement);
-				}
-				let newTarget, target = targets[0], boundingRect = target.getBoundingClientRect();
-				newTarget = determineTargetElement("floor", target, clientX - boundingRect.left, getMatchedParents(target, "left"));
-				if (newTarget == target) {
-					newTarget = determineTargetElement("ceil", target, boundingRect.left + boundingRect.width - clientX, getMatchedParents(target, "right"));
-				}
-				if (newTarget == target) {
-					newTarget = determineTargetElement("floor", target, clientY - boundingRect.top, getMatchedParents(target, "top"));
-				}
-				if (newTarget == target) {
-					newTarget = determineTargetElement("ceil", target, boundingRect.top + boundingRect.height - clientY, getMatchedParents(target, "bottom"));
-				}
-				target = newTarget;
-				while (boundingRect = target && target.getBoundingClientRect(), boundingRect && boundingRect.width <= SELECT_PX_THRESHOLD && boundingRect.height <= SELECT_PX_THRESHOLD) {
-					target = target.parentElement;
-				}
-				return target;
-			}
+.style-button {
+  background-image: url("chrome://global/skin/reader/RM-Type-Controls-24x24.svg");
+}
 
 
-			function getMatchedParents(target, property) {
-				let element = target, matchedParent, parents = [];
-				do {
-					const boundingRect = element.getBoundingClientRect();
-					if (element.parentElement && !element.parentElement.tagName.toLowerCase() != NOTE_TAGNAME && !element.classList.contains(MASK_CLASS)) {
-						const parentBoundingRect = element.parentElement.getBoundingClientRect();
-						matchedParent = Math.abs(parentBoundingRect[property] - boundingRect[property]) <= SELECT_PX_THRESHOLD;
-						if (matchedParent) {
-							if (element.parentElement.clientWidth > SELECT_PX_THRESHOLD && element.parentElement.clientHeight > SELECT_PX_THRESHOLD &&
-								((element.parentElement.clientWidth - element.clientWidth > SELECT_PX_THRESHOLD) || (element.parentElement.clientHeight - element.clientHeight > SELECT_PX_THRESHOLD))) {
-								parents.push(element.parentElement);
-							}
-							element = element.parentElement;
-						}
-					} else {
-						matchedParent = false;
-					}
-				} while (matchedParent && element);
-				return parents;
-			}
+.minus-button {
+  background-size: 18px 18px;
+  background-image: url("chrome://global/skin/reader/RM-Minus-24x24.svg");
+}
 
 
-			function determineTargetElement(roundingMethod, target, widthDistance, parents) {
-				if (Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) <= parents.length) {
-					target = parents[parents.length - Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) - 1];
-				}
-				return target;
-			}
-		}
+.plus-button {
+  background-size: 18px 18px;
+  background-image: url("chrome://global/skin/reader/RM-Plus-24x24.svg");
+}
 
 
-		function onMouseDown(event) {
-			if ((cuttingMode || cuttingOuterMode) && cuttingPath) {
-				event.preventDefault();
-			}
-		}
+.content-width-minus-button {
+  background-size: 42px 16px;
+  background-image: url("chrome://global/skin/reader/RM-Content-Width-Minus-42x16.svg");
+}
 
 
-		function onMouseUp(event) {
-			if (highlightSelectionMode) {
-				event.preventDefault();
-				highlightSelection();
-				onUpdate(false);
-			}
-			if (removeHighlightMode) {
-				event.preventDefault();
-				let element = event.target, done;
-				while (element && !done) {
-					if (element.classList.contains(HIGHLIGHT_CLASS)) {
-						document.querySelectorAll("." + HIGHLIGHT_CLASS + "[data-singlefile-highlight-id=" + JSON.stringify(element.dataset.singlefileHighlightId) + "]").forEach(highlightedElement => {
-							resetHighlightedElement(highlightedElement);
-							onUpdate(false);
-						});
-						done = true;
-					}
-					element = element.parentElement;
-				}
-			}
-			if (resizingNoteMode) {
-				event.preventDefault();
-				resizingNoteMode = false;
-				document.documentElement.style.removeProperty("user-select");
-				maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS);
-				document.documentElement.ontouchmove = onTouchMove;
-				document.documentElement.onmousemove = null;
-				onUpdate(false);
-			}
-			if (movingNoteMode) {
-				event.preventDefault();
-				anchorNote(movingNoteMode.event || event, selectedNote, movingNoteMode.deltaX, movingNoteMode.deltaY);
-				movingNoteMode = null;
-				document.documentElement.ontouchmove = onTouchMove;
-				document.documentElement.onmousemove = null;
-				onUpdate(false);
-			}
-			if ((cuttingMode || cuttingOuterMode) && cuttingPath) {
-				event.preventDefault();
-				if (event.ctrlKey) {
-					const element = cuttingPath[cuttingPathIndex];
-					element.classList.toggle(cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS);
-				} else {
-					validateCutElement(event.shiftKey);
-				}
-			}
-			if (collapseNoteTimeout) {
-				clearTimeout(collapseNoteTimeout);
-				collapseNoteTimeout = null;
-			}
-		}
+.content-width-plus-button {
+  background-size: 44px 16px;
+  background-image: url("chrome://global/skin/reader/RM-Content-Width-Plus-44x16.svg");
+}
 
 
-		function onMouseOver(event) {
-			if (cuttingMode || cuttingOuterMode) {
-				const target = event.target;
-				if (target.classList) {
-					let ancestorFound;
-					document.querySelectorAll("." + (cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS)).forEach(element => {
-						if (element == target || isAncestor(element, target) || isAncestor(target, element)) {
-							ancestorFound = element;
-						}
-					});
-					if (ancestorFound) {
-						cuttingPath = [ancestorFound];
-					} else {
-						cuttingPath = getParents(event.target);
-					}
-					cuttingPathIndex = 0;
-					highlightCutElement();
-				}
-			}
-		}
+.line-height-minus-button {
+  background-size: 34px 14px;
+  background-image: url("chrome://global/skin/reader/RM-Line-Height-Minus-38x14.svg");
+}
 
 
-		function onMouseOut() {
-			if (cuttingMode || cuttingOuterMode) {
-				if (cuttingPath) {
-					unhighlightCutElement();
-					cuttingPath = null;
-				}
-			}
-		}
+.line-height-plus-button {
+  background-size: 34px 24px;
+  background-image: url("chrome://global/skin/reader/RM-Line-Height-Plus-38x24.svg");
+}
 
 
-		function onTouchMove(event) {
-			if (cuttingMode || cuttingOuterMode) {
-				event.preventDefault();
-				const { clientX, clientY } = getPosition(event);
-				const target = document.elementFromPoint(clientX, clientY);
-				if (cuttingTouchTarget != target) {
-					onMouseOut();
-					if (target) {
-						cuttingTouchTarget = target;
-						onMouseOver({ target });
-					}
-				}
-			}
-		}
+/* Mirror the line height buttons if the article is RTL. */
+.reader-controls[articledir="rtl"] .line-height-minus-button,
+.reader-controls[articledir="rtl"] .line-height-plus-button {
+  transform: scaleX(-1);
+}
+
+@media print {
+  .toolbar {
+    display: none !important;
+  }
+}
+
+/* Article content */
+
+/* Note that any class names from the original article that we want to match on
+ * must be added to CLASSES_TO_PRESERVE in ReaderMode.jsm, so that
+ * Readability.js doesn't strip them out */
 
 
-		function onKeyDown(event) {
-			if (cuttingMode || cuttingOuterMode) {
-				if (event.code == "Tab") {
-					if (cuttingPath) {
-						const delta = event.shiftKey ? -1 : 1;
-						let element = cuttingPath[cuttingPathIndex];
-						let nextElement = cuttingPath[cuttingPathIndex + delta];
-						if (nextElement) {
-							let pathIndex = cuttingPathIndex + delta;
-							while (
-								nextElement &&
-								(
-									(delta == 1 &&
-										element.getBoundingClientRect().width >= nextElement.getBoundingClientRect().width &&
-										element.getBoundingClientRect().height >= nextElement.getBoundingClientRect().height) ||
-									(delta == -1 &&
-										element.getBoundingClientRect().width <= nextElement.getBoundingClientRect().width &&
-										element.getBoundingClientRect().height <= nextElement.getBoundingClientRect().height))) {
-								pathIndex += delta;
-								nextElement = cuttingPath[pathIndex];
-							}
-							if (nextElement && nextElement.classList && nextElement != document.body && nextElement != document.documentElement) {
-								unhighlightCutElement();
-								cuttingPathIndex = pathIndex;
-								highlightCutElement();
-							}
-						}
-					}
-					event.preventDefault();
-				}
-				if (event.code == "Space") {
-					if (cuttingPath) {
-						if (event.ctrlKey) {
-							const element = cuttingPath[cuttingPathIndex];
-							element.classList.add(cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS);
-						} else {
-							validateCutElement(event.shiftKey);
-						}
-						event.preventDefault();
-					}
-				}
-				if (event.code == "Escape") {
-					resetSelectedElements();
-					event.preventDefault();
-				}
-				if (event.key.toLowerCase() == "z" && event.ctrlKey) {
-					if (event.shiftKey) {
-						redoCutPage();
-					} else {
-						undoCutPage();
-					}
-					event.preventDefault();
-				}
-			}
-			if (event.key.toLowerCase() == "s" && event.ctrlKey) {
-				window.parent.postMessage(JSON.stringify({ "method": "savePage" }), "*");
-				event.preventDefault();
-			}
-			if (event.key.toLowerCase() == "p" && event.ctrlKey) {
-				printPage();
-				event.preventDefault();
-			}
-		}
+.container {
+  margin: 0 auto;
+  font-size: var(--font-size);
+  max-width: var(--content-width);
+  line-height: var(--line-height);
+}
 
 
-		function printPage() {
-			unhighlightCutElement();
-			resetSelectedElements();
-			window.print();
-		}
+pre {
+  font-family: inherit;
+}
 
 
-		function highlightCutElement() {
-			const element = cuttingPath[cuttingPathIndex];
-			element.classList.add(cuttingMode ? CUT_HOVER_CLASS : CUT_OUTER_HOVER_CLASS);
-		}
+.moz-reader-content {
+  display: none;
+  font-size: 1em;
+}
 
 
-		function unhighlightCutElement() {
-			if (cuttingPath) {
-				const element = cuttingPath[cuttingPathIndex];
-				element.classList.remove(CUT_HOVER_CLASS);
-				element.classList.remove(CUT_OUTER_HOVER_CLASS);
-			}
-		}
+@media print {
+  .moz-reader-content p,
+  .moz-reader-content code,
+  .moz-reader-content pre,
+  .moz-reader-content blockquote,
+  .moz-reader-content ul,
+  .moz-reader-content ol,
+  .moz-reader-content li,
+  .moz-reader-content figure,
+  .moz-reader-content .wp-caption {
+    margin: 0 0 10px !important;
+    padding: 0 !important;
+  }
+}
 
 
-		function disableHighlight(doc = document) {
-			if (highlightColor) {
-				doc.documentElement.classList.remove(highlightColor + "-mode");
-			}
-		}
+.moz-reader-content h1,
+.moz-reader-content h2,
+.moz-reader-content h3 {
+  font-weight: bold;
+}
 
 
-		function undoCutPage() {
-			if (removedElementIndex) {
-				removedElements[removedElementIndex - 1].forEach(element => element.classList.remove(REMOVED_CONTENT_CLASS));
-				removedElementIndex--;
-			}
-		}
+.moz-reader-content h1 {
+  font-size: 1.6em;
+  line-height: 1.25em;
+}
 
 
-		function redoCutPage() {
-			if (removedElementIndex < removedElements.length) {
-				removedElements[removedElementIndex].forEach(element => element.classList.add(REMOVED_CONTENT_CLASS));
-				removedElementIndex++;
-			}
-		}
+.moz-reader-content h2 {
+  font-size: 1.2em;
+  line-height: 1.51em;
+}
 
 
-		function validateCutElement(invert) {
-			const selectedElement = cuttingPath[cuttingPathIndex];
-			if ((cuttingMode && !invert) || (cuttingOuterMode && invert)) {
-				if (document.documentElement != selectedElement && selectedElement.tagName.toLowerCase() != NOTE_TAGNAME) {
-					const elementsRemoved = [selectedElement].concat(...document.querySelectorAll("." + CUT_SELECTED_CLASS + ",." + CUT_SELECTED_CLASS + " *,." + CUT_HOVER_CLASS + " *"));
-					resetSelectedElements();
-					if (elementsRemoved.length) {
-						elementsRemoved.forEach(element => {
-							unhighlightCutElement();
-							if (element.tagName.toLowerCase() == NOTE_TAGNAME) {
-								resetAnchorNote(element);
-							} else {
-								element.classList.add(REMOVED_CONTENT_CLASS);
-							}
-						});
-						removedElements[removedElementIndex] = elementsRemoved;
-						removedElementIndex++;
-						removedElements.length = removedElementIndex;
-						onUpdate(false);
-					}
-				}
-			} else {
-				if (document.documentElement != selectedElement && selectedElement.tagName.toLowerCase() != NOTE_TAGNAME) {
-					const elements = [];
-					const searchSelector = "*:not(style):not(meta):not(." + REMOVED_CONTENT_CLASS + ")";
-					const elementsKept = [selectedElement].concat(...document.querySelectorAll("." + CUT_OUTER_SELECTED_CLASS));
-					document.body.querySelectorAll(searchSelector).forEach(element => {
-						let removed = true;
-						elementsKept.forEach(elementKept => removed = removed && (elementKept != element && !isAncestor(elementKept, element) && !isAncestor(element, elementKept)));
-						if (removed) {
-							if (element.tagName.toLowerCase() == NOTE_TAGNAME) {
-								resetAnchorNote(element);
-							} else {
-								elements.push(element);
-							}
-						}
-					});
-					elementsKept.forEach(elementKept => {
-						unhighlightCutElement();
-						const elementKeptRect = elementKept.getBoundingClientRect();
-						elementKept.querySelectorAll(searchSelector).forEach(descendant => {
-							const descendantRect = descendant.getBoundingClientRect();
-							if (descendantRect.width && descendantRect.height && (
-								descendantRect.left + descendantRect.width < elementKeptRect.left ||
-								descendantRect.right > elementKeptRect.right + elementKeptRect.width ||
-								descendantRect.top + descendantRect.height < elementKeptRect.top ||
-								descendantRect.bottom > elementKeptRect.bottom + elementKeptRect.height
-							)) {
-								elements.push(descendant);
-							}
-						});
-					});
-					resetSelectedElements();
-					if (elements.length) {
-						elements.forEach(element => element.classList.add(REMOVED_CONTENT_CLASS));
-						removedElements[removedElementIndex] = elements;
-						removedElementIndex++;
-						removedElements.length = removedElementIndex;
-						onUpdate(false);
-					}
-				}
-			}
-		}
+.moz-reader-content h3 {
+  font-size: 1em;
+  line-height: 1.66em;
+}
 
 
-		function resetSelectedElements(doc = document) {
-			doc.querySelectorAll("." + CUT_OUTER_SELECTED_CLASS + ",." + CUT_SELECTED_CLASS).forEach(element => {
-				element.classList.remove(CUT_OUTER_SELECTED_CLASS);
-				element.classList.remove(CUT_SELECTED_CLASS);
-			});
-		}
+.moz-reader-content a:link {
+  text-decoration: underline;
+  font-weight: normal;
+}
 
 
-		function anchorNote(event, noteElement, deltaX, deltaY) {
-			event.preventDefault();
-			const { clientX, clientY } = getPosition(event);
-			document.documentElement.style.removeProperty("user-select");
-			noteElement.classList.remove(NOTE_MOVING_CLASS);
-			maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS);
-			maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS);
-			maskNoteElement.classList.remove(noteElement.dataset.color);
-			const headerElement = noteElement.querySelector("header");
-			headerElement.ontouchmove = document.documentElement.onmousemove = null;
-			let currentElement = anchorElement;
-			let positionedElement;
-			while (currentElement.parentElement && !positionedElement) {
-				if (!FORBIDDEN_TAG_NAMES.includes(currentElement.tagName.toLowerCase())) {
-					const currentElementStyle = getComputedStyle(currentElement);
-					if (currentElementStyle.position != "static") {
-						positionedElement = currentElement;
-					}
-				}
-				currentElement = currentElement.parentElement;
-			}
-			if (!positionedElement) {
-				positionedElement = document.documentElement;
-			}
-			const containerElement = noteElement.getRootNode().host;
-			if (positionedElement == document.documentElement) {
-				const firstMaskElement = document.querySelector("." + MASK_CLASS);
-				firstMaskElement.parentElement.insertBefore(containerElement, firstMaskElement);
-			} else {
-				positionedElement.appendChild(containerElement);
-			}
-			const boundingRectPositionedElement = positionedElement.getBoundingClientRect();
-			const stylePositionedElement = window.getComputedStyle(positionedElement);
-			const borderX = parseInt(stylePositionedElement.getPropertyValue("border-left-width"));
-			const borderY = parseInt(stylePositionedElement.getPropertyValue("border-top-width"));
-			noteElement.style.setProperty("position", "absolute");
-			noteElement.style.setProperty("left", (clientX - boundingRectPositionedElement.x - deltaX - borderX) + "px");
-			noteElement.style.setProperty("top", (clientY - boundingRectPositionedElement.y - deltaY - borderY) + "px");
-		}
+.moz-reader-content a:link,
+.moz-reader-content a:link:hover,
+.moz-reader-content a:link:active {
+  color: var(--link-foreground);
+}
 
 
-		function resetAnchorNote(containerElement) {
-			const noteId = containerElement.dataset.noteId;
-			const noteElement = containerElement.shadowRoot.childNodes[1];
-			noteElement.classList.remove(NOTE_ANCHORED_CLASS);
-			deleteNoteRef(containerElement, noteId);
-			addNoteRef(document.documentElement, noteId);
-			document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
-		}
+.moz-reader-content a:visited {
+  color: var(--visited-link-foreground);
+}
 
 
-		function getPosition(event) {
-			if (event.touches && event.touches.length) {
-				const touch = event.touches[0];
-				return touch;
-			} else {
-				return event;
-			}
-		}
+.moz-reader-content * {
+  max-width: 100%;
+  height: auto;
+}
 
 
-		function highlightSelection() {
-			let highlightId = 0;
-			document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(highlightedElement => highlightId = Math.max(highlightId, highlightedElement.dataset.singlefileHighlightId));
-			highlightId++;
-			const selection = window.getSelection();
-			const highlightedNodes = new Set();
-			for (let indexRange = 0; indexRange < selection.rangeCount; indexRange++) {
-				const range = selection.getRangeAt(indexRange);
-				if (!range.collapsed) {
-					if (range.commonAncestorContainer.nodeType == range.commonAncestorContainer.TEXT_NODE) {
-						let contentText = range.startContainer.splitText(range.startOffset);
-						contentText = contentText.splitText(range.endOffset);
-						addHighLightedNode(contentText.previousSibling);
-					} else {
-						const treeWalker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
-						let highlightNodes;
-						while (treeWalker.nextNode()) {
-							if (highlightNodes && !treeWalker.currentNode.contains(range.endContainer)) {
-								addHighLightedNode(treeWalker.currentNode);
-							}
-							if (treeWalker.currentNode == range.startContainer) {
-								if (range.startContainer.nodeType == range.startContainer.TEXT_NODE) {
-									const contentText = range.startContainer.splitText(range.startOffset);
-									treeWalker.nextNode();
-									addHighLightedNode(contentText);
-								} else {
-									addHighLightedNode(range.startContainer.childNodes[range.startOffset]);
-								}
-								highlightNodes = true;
-							}
-							if (treeWalker.currentNode == range.endContainer) {
-								if (range.endContainer.nodeType == range.endContainer.TEXT_NODE) {
-									const contentText = range.endContainer.splitText(range.endOffset);
-									treeWalker.nextNode();
-									addHighLightedNode(contentText.previousSibling);
-								} else {
-									addHighLightedNode(range.endContainer.childNodes[range.endOffset]);
-								}
-								highlightNodes = false;
-							}
-						}
-						range.collapse();
-					}
-				}
-			}
-			highlightedNodes.forEach(node => highlightNode(node));
+.moz-reader-content p,
+.moz-reader-content p,
+.moz-reader-content code,
+.moz-reader-content pre,
+.moz-reader-content blockquote,
+.moz-reader-content ul,
+.moz-reader-content ol,
+.moz-reader-content li,
+.moz-reader-content figure,
+.moz-reader-content .wp-caption {
+  margin: -10px -10px 20px;
+  padding: 10px;
+  border-radius: 5px;
+}
 
 
-			function addHighLightedNode(node) {
-				if (node && node.textContent.trim()) {
-					if (node.nodeType == node.TEXT_NODE && node.parentElement.childNodes.length == 1 && node.parentElement.classList.contains(HIGHLIGHT_CLASS)) {
-						highlightedNodes.add(node.parentElement);
-					} else {
-						highlightedNodes.add(node);
-					}
-				}
-			}
+.moz-reader-content li {
+  margin-bottom: 0;
+}
 
 
-			function highlightNode(node) {
-				if (node.nodeType == node.ELEMENT_NODE) {
-					resetHighlightedElement(node);
-					node.classList.add(HIGHLIGHT_CLASS);
-					node.classList.add(highlightColor);
-					node.dataset.singlefileHighlightId = highlightId;
-				} else if (node.parentElement) {
-					highlightTextNode(node);
-				}
-			}
+.moz-reader-content li > ul,
+.moz-reader-content li > ol {
+  margin-bottom: -10px;
+}
 
 
-			function highlightTextNode(node) {
-				const spanElement = document.createElement("span");
-				spanElement.classList.add(HIGHLIGHT_CLASS);
-				spanElement.classList.add(highlightColor);
-				spanElement.textContent = node.textContent;
-				spanElement.dataset.singlefileHighlightId = highlightId;
-				node.parentNode.replaceChild(spanElement, node);
-				return spanElement;
-			}
-		}
+.moz-reader-content p > img:only-child,
+.moz-reader-content p > a:only-child > img:only-child,
+.moz-reader-content .wp-caption img,
+.moz-reader-content figure img {
+  display: block;
+}
 
 
-		function getParents(element) {
-			const path = [];
-			while (element) {
-				path.push(element);
-				element = element.parentElement;
-			}
-			return path;
-		}
+.moz-reader-content img[moz-reader-center] {
+  margin-inline: auto;
+}
 
 
-		function formatPage(applySystemTheme) {
-			if (pageCompressContent) {
-				serializeShadowRoots(document);
-				previousContent = document.documentElement.cloneNode(true);
-				deserializeShadowRoots(document);
-			} else {
-				previousContent = getContent(false, []);
-			}
-			const shadowRoots = {};
-			const classesToPreserve = ["single-file-highlight", "single-file-highlight-yellow", "single-file-highlight-green", "single-file-highlight-pink", "single-file-highlight-blue"];
-			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
-				shadowRoots[containerElement.dataset.noteId] = containerElement.shadowRoot;
-				const className = "singlefile-note-id-" + containerElement.dataset.noteId;
-				containerElement.classList.add(className);
-				classesToPreserve.push(className);
-			});
-			const optionsElement = document.querySelector("script[type=\"application/json\"][" + SCRIPT_OPTIONS + "]");
-			const article = new Readability(document, { classesToPreserve }).parse();
-			const articleMetadata = Object.assign({}, article);
-			delete articleMetadata.content;
-			delete articleMetadata.textContent;
-			for (const key in articleMetadata) {
-				if (articleMetadata[key] == null) {
-					delete articleMetadata[key];
-				}
-			}
-			removedElements = [];
-			removedElementIndex = 0;
-			document.body.innerHTML = "";
-			const domParser = new DOMParser();
-			const doc = domParser.parseFromString(article.content, "text/html");
-			const contentEditable = document.body.contentEditable;
-			document.documentElement.replaceChild(doc.body, document.body);
-			if (optionsElement) {
-				document.body.appendChild(optionsElement);
-			}
-			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
-				const noteId = (Array.from(containerElement.classList).find(className => /singlefile-note-id-\d+/.test(className))).split("singlefile-note-id-")[1];
-				containerElement.classList.remove("singlefile-note-id-" + noteId);
-				containerElement.dataset.noteId = noteId;
-				if (!containerElement.shadowRoot) {
-					containerElement.attachShadow({ mode: "open" });
-					containerElement.shadowRoot.appendChild(shadowRoots[noteId]);
-				}
-			});
-			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => shadowRoots[containerElement.dataset.noteId].childNodes.forEach(node => containerElement.shadowRoot.appendChild(node)));
-			document.body.contentEditable = contentEditable;
-			document.head.querySelectorAll("style").forEach(styleElement => styleElement.remove());
-			const styleElement = document.createElement("style");
-			styleElement.textContent = STYLE_FORMATTED_PAGE;
-			document.head.appendChild(styleElement);
-			document.body.classList.add("moz-reader-content");
-			document.body.classList.add("content-width6");
-			document.body.classList.add("reader-show-element");
-			document.body.classList.add("sans-serif");
-			document.body.classList.add("container");
-			document.body.classList.add("line-height4");
-			const prefersColorSchemeDark = matchMedia("(prefers-color-scheme: dark)");
-			if (applySystemTheme && prefersColorSchemeDark && prefersColorSchemeDark.matches) {
-				document.body.classList.add("dark");
-			}
-			document.body.style.setProperty("display", "block");
-			document.body.style.setProperty("padding", "24px");
-			const titleElement = document.createElement("h1");
-			titleElement.classList.add("reader-title");
-			titleElement.textContent = article.title;
-			document.body.insertBefore(titleElement, document.body.firstChild);
-			const existingMetaDataElement = document.querySelector("script[id=singlefile-readability-metadata]");
-			if (existingMetaDataElement) {
-				existingMetaDataElement.remove();
-			}
-			const metaDataElement = document.createElement("script");
-			metaDataElement.type = "application/json";
-			metaDataElement.id = "singlefile-readability-metadata";
-			metaDataElement.textContent = JSON.stringify(articleMetadata, null, 2);
-			document.head.appendChild(metaDataElement);
-			document.querySelectorAll("a[href]").forEach(element => {
-				const href = element.getAttribute("href").trim();
-				if (href.startsWith(document.baseURI + "#")) {
-					element.setAttribute("href", href.substring(document.baseURI.length));
-				}
-			});
-			insertHighlightStylesheet(document);
-			maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
-			maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
-			reflowNotes();
-			onUpdate(false);
-		}
+.moz-reader-content .caption,
+.moz-reader-content .wp-caption-text
+.moz-reader-content figcaption {
+  font-size: 0.9em;
+  line-height: 1.48em;
+  font-style: italic;
+}
 
 
-		async function cancelFormatPage() {
-			if (previousContent) {
-				const contentEditable = document.body.contentEditable;
-				if (pageCompressContent) {
-					document.replaceChild(previousContent, document.documentElement);
-					deserializeShadowRoots(document);
-					await initPage();
-				} else {
-					await init({ content: previousContent }, { reset: true });
-				}
-				document.body.contentEditable = contentEditable;
-				onUpdate(false);
-				previousContent = null;
-			}
-		}
+.moz-reader-content pre {
+  white-space: pre-wrap;
+}
+
+.moz-reader-content blockquote {
+  padding: 0;
+  padding-inline-start: 16px;
+}
+
+.moz-reader-content ul,
+.moz-reader-content ol {
+  padding: 0;
+}
 
 
-		function insertHighlightStylesheet(doc) {
-			if (!doc.querySelector("." + HIGHLIGHTS_STYLESHEET_CLASS)) {
-				const styleheetHighlights = getStyleElement(HIGHLIGHTS_WEB_STYLESHEET);
-				styleheetHighlights.classList.add(HIGHLIGHTS_STYLESHEET_CLASS);
-				doc.documentElement.appendChild(styleheetHighlights);
-			}
-		}
+.moz-reader-content ul {
+  padding-inline-start: 30px;
+  list-style: disc;
+}
 
 
-		function getContent(compressHTML, updatedResources) {
-			unhighlightCutElement();
-			serializeShadowRoots(document);
-			const doc = document.cloneNode(true);
-			disableHighlight(doc);
-			resetSelectedElements(doc);
-			deserializeShadowRoots(doc);
-			deserializeShadowRoots(document);
-			doc.documentElement.classList.remove(CUT_MODE_CLASS);
-			doc.querySelectorAll("[" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "]").forEach(element => {
-				element.textContent = element.getAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
-				element.removeAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
-			});
-			doc.querySelectorAll("." + MASK_CLASS + ", " + singlefile.helper.INFOBAR_TAGNAME + ", ." + REMOVED_CONTENT_CLASS).forEach(element => element.remove());
-			if (includeInfobar) {
-				const options = singlefile.helper.extractInfobarData(doc);
-				options.openInfobar = openInfobar;
-				options.infobarPositionAbsolute = infobarPositionAbsolute;
-				options.infobarPositionTop = infobarPositionTop;
-				options.infobarPositionRight = infobarPositionRight;
-				options.infobarPositionBottom = infobarPositionBottom;
-				options.infobarPositionLeft = infobarPositionLeft;
-				singlefile.helper.appendInfobar(doc, options);
-			}
-			doc.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.remove(HIGHLIGHT_HIDDEN_CLASS));
-			doc.querySelectorAll(`template[${SHADOWROOT_ATTRIBUTE_NAME}]`).forEach(templateElement => {
-				const noteElement = templateElement.querySelector("." + NOTE_CLASS);
-				if (noteElement) {
-					noteElement.classList.remove(NOTE_HIDDEN_CLASS);
-				}
-				const mainElement = templateElement.querySelector("textarea");
-				if (mainElement) {
-					mainElement.textContent = mainElement.value;
-				}
-			});
-			doc.querySelectorAll("iframe").forEach(element => {
-				const pointerEvents = "pointer-events";
-				element.style.setProperty(pointerEvents, element.style.getPropertyValue("-sf-" + pointerEvents), element.style.getPropertyPriority("-sf-" + pointerEvents));
-				element.style.removeProperty("-sf-" + pointerEvents);
-			});
-			doc.body.removeAttribute("contentEditable");
-			const newResources = Object.keys(updatedResources).filter(url => updatedResources[url].type == "stylesheet").map(url => updatedResources[url]);
-			newResources.forEach(resource => {
-				const element = doc.createElement("style");
-				doc.body.appendChild(element);
-				element.textContent = resource.content;
-			});
-			if (pageCompressContent) {
-				const pageFilename = pageResources
-					.filter(resource => resource.filename.endsWith("index.html"))
-					.sort((resourceLeft, resourceRight) => resourceLeft.filename.length - resourceRight.filename.length)[0].filename;
-				const resources = pageResources.filter(resource => resource.parentResources.includes(pageFilename));
-				doc.querySelectorAll("[src]").forEach(element => resources.forEach(resource => {
-					if (element.src == resource.content) {
-						element.src = resource.name;
-					}
-				}));
-				let content = singlefile.helper.serialize(doc, compressHTML);
-				resources.sort((resourceLeft, resourceRight) => resourceRight.content.length - resourceLeft.content.length);
-				resources.forEach(resource => content = content.replaceAll(resource.content, resource.name));
-				return content + "<script " + SCRIPT_TEMPLATE_SHADOW_ROOT + ">" + getEmbedScript() + "</script>";
-			} else {
-				return singlefile.helper.serialize(doc, compressHTML) + "<script " + SCRIPT_TEMPLATE_SHADOW_ROOT + ">" + getEmbedScript() + "</script>";
-			}
-		}
+.moz-reader-content ol {
+  padding-inline-start: 30px;
+}
 
 
-		function onUpdate(saved) {
-			window.parent.postMessage(JSON.stringify({ "method": "onUpdate", saved }), "*");
-		}
+table,
+th,
+td {
+  border: 1px solid currentColor;
+  border-collapse: collapse;
+  padding: 6px;
+  vertical-align: top;
+}
 
 
-		function waitResourcesLoad() {
-			return new Promise(resolve => {
-				let counterMutations = 0;
-				const done = () => {
-					observer.disconnect();
-					resolve();
-				};
-				let timeoutInit = setTimeout(done, 100);
-				const observer = new MutationObserver(() => {
-					if (counterMutations < 20) {
-						counterMutations++;
-						clearTimeout(timeoutInit);
-						timeoutInit = setTimeout(done, 100);
-					} else {
-						done();
-					}
-				});
-				observer.observe(document, { subtree: true, childList: true, attributes: true });
-			});
-		}
+table {
+  margin: 5px;
+}
 
 
-		function reflowNotes() {
-			document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
-				const noteElement = containerElement.shadowRoot.querySelector("." + NOTE_CLASS);
-				const noteBoundingRect = noteElement.getBoundingClientRect();
-				const anchorElement = getAnchorElement(containerElement);
-				const anchorBoundingRect = anchorElement.getBoundingClientRect();
-				const maxX = anchorBoundingRect.x + Math.max(0, anchorBoundingRect.width - noteBoundingRect.width);
-				const minX = anchorBoundingRect.x;
-				const maxY = anchorBoundingRect.y + Math.max(0, anchorBoundingRect.height - NOTE_HEADER_HEIGHT);
-				const minY = anchorBoundingRect.y;
-				let left = parseInt(noteElement.style.getPropertyValue("left"));
-				let top = parseInt(noteElement.style.getPropertyValue("top"));
-				if (noteBoundingRect.x > maxX) {
-					left -= noteBoundingRect.x - maxX;
-				}
-				if (noteBoundingRect.x < minX) {
-					left += minX - noteBoundingRect.x;
-				}
-				if (noteBoundingRect.y > maxY) {
-					top -= noteBoundingRect.y - maxY;
-				}
-				if (noteBoundingRect.y < minY) {
-					top += minY - noteBoundingRect.y;
-				}
-				noteElement.style.setProperty("position", "absolute");
-				noteElement.style.setProperty("left", left + "px");
-				noteElement.style.setProperty("top", top + "px");
-			});
-		}
+/* Visually hide (but don't display: none) screen reader elements */
+.moz-reader-content .visually-hidden,
+.moz-reader-content .visuallyhidden,
+.moz-reader-content .sr-only {
+  display: inline-block;
+  width: 1px;
+  height: 1px;
+  margin: -1px;
+  overflow: hidden;
+  padding: 0;
+  border-width: 0;
+}
 
 
-		function resetHighlightedElement(element) {
-			element.classList.remove(HIGHLIGHT_CLASS);
-			element.classList.remove("single-file-highlight-yellow");
-			element.classList.remove("single-file-highlight-pink");
-			element.classList.remove("single-file-highlight-blue");
-			element.classList.remove("single-file-highlight-green");
-			delete element.dataset.singlefileHighlightId;
-		}
+/* Hide elements with common "hidden" class names */
+.moz-reader-content .hidden,
+.moz-reader-content .invisible {
+  display: none;
+}
 
 
-		function serializeShadowRoots(node) {
-			node.querySelectorAll("*").forEach(element => {
-				const shadowRoot = getShadowRoot(element);
-				if (shadowRoot) {
-					serializeShadowRoots(shadowRoot);
-					const templateElement = document.createElement("template");
-					templateElement.setAttribute(SHADOWROOT_ATTRIBUTE_NAME, "open");
-					Array.from(shadowRoot.childNodes).forEach(childNode => templateElement.appendChild(childNode));
-					element.appendChild(templateElement);
-				}
-			});
-		}
+/* Enforce wordpress and similar emoji/smileys aren't sized to be full-width,
+ * see bug 1399616 for context. */
+.moz-reader-content img.wp-smiley,
+.moz-reader-content img.emoji {
+  display: inline-block;
+  border-width: 0;
+  /* height: auto is implied from '.moz-reader-content *' rule. */
+  width: 1em;
+  margin: 0 .07em;
+  padding: 0;
+}
 
 
-		function deserializeShadowRoots(node) {
-			node.querySelectorAll(`template[${SHADOWROOT_ATTRIBUTE_NAME}]`).forEach(element => {
-				if (element.parentElement) {
-					let shadowRoot = getShadowRoot(element.parentElement);
-					if (shadowRoot) {
-						Array.from(element.childNodes).forEach(node => shadowRoot.appendChild(node));
-						element.remove();
-					} else {
-						try {
-							shadowRoot = element.parentElement.attachShadow({ mode: "open" });
-							const contentDocument = (new DOMParser()).parseFromString(element.innerHTML, "text/html");
-							Array.from(contentDocument.head.childNodes).forEach(node => shadowRoot.appendChild(node));
-							Array.from(contentDocument.body.childNodes).forEach(node => shadowRoot.appendChild(node));
-							// eslint-disable-next-line no-unused-vars
-						} catch (error) {
-							// ignored
-						}
-					}
-					if (shadowRoot) {
-						deserializeShadowRoots(shadowRoot);
-					}
-				}
-			});
-		}
+.reader-show-element {
+  display: initial;
+}
 
 
-		function getMaskElement(className, containerClassName) {
-			let maskElement = document.documentElement.querySelector("." + className);
-			if (!maskElement) {
-				maskElement = document.createElement("div");
-				const maskContainerElement = document.createElement("div");
-				if (containerClassName) {
-					maskContainerElement.classList.add(containerClassName);
-				}
-				maskContainerElement.classList.add(MASK_CLASS);
-				const firstNote = document.querySelector(NOTE_TAGNAME);
-				if (firstNote && firstNote.parentElement == document.documentElement) {
-					document.documentElement.insertBefore(maskContainerElement, firstNote);
-				} else {
-					document.documentElement.appendChild(maskContainerElement);
-				}
-				maskElement.classList.add(className);
-				const maskShadow = maskContainerElement.attachShadow({ mode: "open" });
-				maskShadow.appendChild(getStyleElement(MASK_WEB_STYLESHEET));
-				maskShadow.appendChild(maskElement);
-				return maskElement;
-			}
+/* Provide extra spacing for images that may be aided with accompanying element such as <figcaption> */
+.moz-reader-block-img:not(:last-child) {
+  margin-block-end: 12px;
+}
+
+.moz-reader-wide-table {
+  overflow-x: auto;
+  display: block;
+}
+
+pre code {
+  background-color: var(--main-background);
+  border: 1px solid var(--font-color);
+  display: block;
+  overflow: auto;
+}`;
 		}
 		}
 
 
 		function getEmbedScript() {
 		function getEmbedScript() {

+ 1 - 0
src/core/bg/config.js

@@ -93,6 +93,7 @@ const DEFAULT_CONFIG = {
 	backgroundSave: BACKGROUND_SAVE_SUPPORTED,
 	backgroundSave: BACKGROUND_SAVE_SUPPORTED,
 	defaultEditorMode: "normal",
 	defaultEditorMode: "normal",
 	applySystemTheme: true,
 	applySystemTheme: true,
+	contentWidth: 70,
 	autoSaveDelay: 1,
 	autoSaveDelay: 1,
 	autoSaveLoad: false,
 	autoSaveLoad: false,
 	autoSaveUnload: false,
 	autoSaveUnload: false,

+ 2 - 1
src/ui/bg/ui-editor.js

@@ -533,7 +533,8 @@ function formatPage() {
 	updatedResources = {};
 	updatedResources = {};
 	editorElement.contentWindow.postMessage(JSON.stringify({
 	editorElement.contentWindow.postMessage(JSON.stringify({
 		method: "formatPage",
 		method: "formatPage",
-		applySystemTheme: tabData.options.applySystemTheme
+		applySystemTheme: tabData.options.applySystemTheme,
+		contentWidth: tabData.options.contentWidth
 	}), "*");
 	}), "*");
 }
 }
 
 

+ 5 - 0
src/ui/bg/ui-options.js

@@ -191,6 +191,7 @@ const openSavedPageLabel = document.getElementById("openSavedPageLabel");
 const autoOpenEditorLabel = document.getElementById("autoOpenEditorLabel");
 const autoOpenEditorLabel = document.getElementById("autoOpenEditorLabel");
 const defaultEditorModeLabel = document.getElementById("defaultEditorModeLabel");
 const defaultEditorModeLabel = document.getElementById("defaultEditorModeLabel");
 const applySystemThemeLabel = document.getElementById("applySystemThemeLabel");
 const applySystemThemeLabel = document.getElementById("applySystemThemeLabel");
+const contentWidthLabel = document.getElementById("contentWidthLabel");
 const warnUnsavedPageLabel = document.getElementById("warnUnsavedPageLabel");
 const warnUnsavedPageLabel = document.getElementById("warnUnsavedPageLabel");
 const displayInfobarInEditorLabel = document.getElementById("displayInfobarInEditorLabel");
 const displayInfobarInEditorLabel = document.getElementById("displayInfobarInEditorLabel");
 const infobarTemplateLabel = document.getElementById("infobarTemplateLabel");
 const infobarTemplateLabel = document.getElementById("infobarTemplateLabel");
@@ -317,6 +318,7 @@ const defaultEditorModeFormatLabel = document.getElementById("defaultEditorModeF
 const defaultEditorModeCutLabel = document.getElementById("defaultEditorModeCutLabel");
 const defaultEditorModeCutLabel = document.getElementById("defaultEditorModeCutLabel");
 const defaultEditorModeCutExternalLabel = document.getElementById("defaultEditorModeCutExternalLabel");
 const defaultEditorModeCutExternalLabel = document.getElementById("defaultEditorModeCutExternalLabel");
 const applySystemThemeInput = document.getElementById("applySystemThemeInput");
 const applySystemThemeInput = document.getElementById("applySystemThemeInput");
+const contentWidthInput = document.getElementById("contentWidthInput");
 const warnUnsavedPageInput = document.getElementById("warnUnsavedPageInput");
 const warnUnsavedPageInput = document.getElementById("warnUnsavedPageInput");
 const displayInfobarInEditorInput = document.getElementById("displayInfobarInEditorInput");
 const displayInfobarInEditorInput = document.getElementById("displayInfobarInEditorInput");
 const expandAllButton = document.getElementById("expandAllButton");
 const expandAllButton = document.getElementById("expandAllButton");
@@ -796,6 +798,7 @@ defaultEditorModeFormatLabel.textContent = browser.i18n.getMessage("optionDefaul
 defaultEditorModeCutLabel.textContent = browser.i18n.getMessage("optionDefaultEditorModeCut");
 defaultEditorModeCutLabel.textContent = browser.i18n.getMessage("optionDefaultEditorModeCut");
 defaultEditorModeCutExternalLabel.textContent = browser.i18n.getMessage("optionDefaultEditorModeCutExternal");
 defaultEditorModeCutExternalLabel.textContent = browser.i18n.getMessage("optionDefaultEditorModeCutExternal");
 applySystemThemeLabel.textContent = browser.i18n.getMessage("optionApplySystemTheme");
 applySystemThemeLabel.textContent = browser.i18n.getMessage("optionApplySystemTheme");
+contentWidthLabel.textContent = browser.i18n.getMessage("optionContentWidth");
 warnUnsavedPageLabel.textContent = browser.i18n.getMessage("optionWarnUnsavedPage");
 warnUnsavedPageLabel.textContent = browser.i18n.getMessage("optionWarnUnsavedPage");
 displayInfobarInEditorLabel.textContent = browser.i18n.getMessage("optiondisplayInfobarInEditor");
 displayInfobarInEditorLabel.textContent = browser.i18n.getMessage("optiondisplayInfobarInEditor");
 resetButton.textContent = browser.i18n.getMessage("optionsResetButton");
 resetButton.textContent = browser.i18n.getMessage("optionsResetButton");
@@ -1126,6 +1129,7 @@ async function refresh(profileName) {
 	autoOpenEditorInput.checked = profileOptions.autoOpenEditor;
 	autoOpenEditorInput.checked = profileOptions.autoOpenEditor;
 	defaultEditorModeInput.value = profileOptions.defaultEditorMode;
 	defaultEditorModeInput.value = profileOptions.defaultEditorMode;
 	applySystemThemeInput.checked = profileOptions.applySystemTheme;
 	applySystemThemeInput.checked = profileOptions.applySystemTheme;
+	contentWidthInput.value = profileOptions.contentWidth;
 	warnUnsavedPageInput.checked = profileOptions.warnUnsavedPage;
 	warnUnsavedPageInput.checked = profileOptions.warnUnsavedPage;
 	displayInfobarInEditorInput.checked = profileOptions.displayInfobarInEditor;
 	displayInfobarInEditorInput.checked = profileOptions.displayInfobarInEditor;
 }
 }
@@ -1247,6 +1251,7 @@ async function update() {
 			autoOpenEditor: autoOpenEditorInput.checked,
 			autoOpenEditor: autoOpenEditorInput.checked,
 			defaultEditorMode: defaultEditorModeInput.value,
 			defaultEditorMode: defaultEditorModeInput.value,
 			applySystemTheme: applySystemThemeInput.checked,
 			applySystemTheme: applySystemThemeInput.checked,
+			contentWidth: contentWidthInput.value,
 			warnUnsavedPage: warnUnsavedPageInput.checked,
 			warnUnsavedPage: warnUnsavedPageInput.checked,
 			displayInfobarInEditor: displayInfobarInEditorInput.checked,
 			displayInfobarInEditor: displayInfobarInEditorInput.checked,
 			saveToRestFormApi: saveToRestFormApiInput.checked,
 			saveToRestFormApi: saveToRestFormApiInput.checked,

+ 2013 - 2011
src/ui/content/content-ui-editor-web.js

@@ -68,2236 +68,2238 @@ import { downloadPageForeground } from "../../core/common/download.js";
 	const COMMENT_HEADER = "Page saved with SingleFile";
 	const COMMENT_HEADER = "Page saved with SingleFile";
 	const COMMENT_HEADER_LEGACY = "Archive processed by SingleFile";
 	const COMMENT_HEADER_LEGACY = "Archive processed by SingleFile";
 
 
-	const STYLE_FORMATTED_PAGE = `
-	/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+	let NOTES_WEB_STYLESHEET, MASK_WEB_STYLESHEET, HIGHLIGHTS_WEB_STYLESHEET;
+	let selectedNote, anchorElement, maskNoteElement, maskPageElement, highlightSelectionMode, removeHighlightMode, resizingNoteMode, movingNoteMode, highlightColor, collapseNoteTimeout, cuttingOuterMode, cuttingMode, cuttingTouchTarget, cuttingPath, cuttingPathIndex, previousContent;
+	let removedElements = [], removedElementIndex = 0, initScriptContent, pageResources, pageUrl, pageCompressContent, includeInfobar, openInfobar, infobarPositionAbsolute, infobarPositionTop, infobarPositionBottom, infobarPositionLeft, infobarPositionRight;
 
 
-/* Avoid adding ID selector rules in this style sheet, since they could
- * inadvertently match elements in the article content. */
+	globalThis.zip = singlefile.helper.zip;
+	initEventListeners();
+	new MutationObserver(initEventListeners).observe(document, { childList: true });
 
 
-:root {
-  --grey-90-a10: rgba(12, 12, 13, 0.1);
-  --grey-90-a20: rgba(12, 12, 13, 0.2);
-  --grey-90-a30: rgba(12, 12, 13, 0.3);
-  --grey-90-a80: rgba(12, 12, 13, 0.8);
-  --grey-30: #d7d7db;
-  --blue-40: #45a1ff;
-  --blue-40-a30: rgba(69, 161, 255, 0.3);
-  --blue-60: #0060df;
-  --body-padding: 64px;
-  --font-size: 12;
-  --content-width: 70em;
-  --line-height: 1.6em;
-}
+	function initEventListeners() {
+		window.onmessage = async event => {
+			const message = JSON.parse(event.data);
+			if (message.method == "init") {
+				await init(message);
+			}
+			if (message.method == "addNote") {
+				addNote(message);
+			}
+			if (message.method == "displayNotes") {
+				document.querySelectorAll(NOTE_TAGNAME).forEach(noteElement => noteElement.shadowRoot.querySelector("." + NOTE_CLASS).classList.remove(NOTE_HIDDEN_CLASS));
+			}
+			if (message.method == "hideNotes") {
+				document.querySelectorAll(NOTE_TAGNAME).forEach(noteElement => noteElement.shadowRoot.querySelector("." + NOTE_CLASS).classList.add(NOTE_HIDDEN_CLASS));
+			}
+			if (message.method == "enableHighlight") {
+				if (highlightColor) {
+					document.documentElement.classList.remove(highlightColor + "-mode");
+				}
+				highlightColor = message.color;
+				highlightSelectionMode = true;
+				document.documentElement.classList.add(message.color + "-mode");
+			}
+			if (message.method == "disableHighlight") {
+				disableHighlight();
+				highlightSelectionMode = false;
+			}
+			if (message.method == "displayHighlights") {
+				document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.remove(HIGHLIGHT_HIDDEN_CLASS));
+			}
+			if (message.method == "hideHighlights") {
+				document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.add(HIGHLIGHT_HIDDEN_CLASS));
+			}
+			if (message.method == "enableRemoveHighlights") {
+				removeHighlightMode = true;
+				document.documentElement.classList.add("single-file-remove-highlights-mode");
+			}
+			if (message.method == "disableRemoveHighlights") {
+				removeHighlightMode = false;
+				document.documentElement.classList.remove("single-file-remove-highlights-mode");
+			}
+			if (message.method == "enableEditPage") {
+				document.body.contentEditable = true;
+				onUpdate(false);
+			}
+			if (message.method == "formatPage") {
+				formatPage(!message.applySystemTheme, message.contentWidth);
+			}
+			if (message.method == "cancelFormatPage") {
+				cancelFormatPage();
+			}
+			if (message.method == "disableEditPage") {
+				document.body.contentEditable = false;
+			}
+			if (message.method == "enableCutInnerPage") {
+				cuttingMode = true;
+				document.documentElement.classList.add(CUT_MODE_CLASS);
+			}
+			if (message.method == "enableCutOuterPage") {
+				cuttingOuterMode = true;
+				document.documentElement.classList.add(CUT_MODE_CLASS);
+			}
+			if (message.method == "disableCutInnerPage" || message.method == "disableCutOuterPage") {
+				if (message.method == "disableCutInnerPage") {
+					cuttingMode = false;
+				} else {
+					cuttingOuterMode = false;
+				}
+				document.documentElement.classList.remove(CUT_MODE_CLASS);
+				resetSelectedElements();
+				if (cuttingPath) {
+					unhighlightCutElement();
+					cuttingPath = null;
+				}
+			}
+			if (message.method == "undoCutPage") {
+				undoCutPage();
+			}
+			if (message.method == "undoAllCutPage") {
+				while (removedElementIndex) {
+					removedElements[removedElementIndex - 1].forEach(element => element.classList.remove(REMOVED_CONTENT_CLASS));
+					removedElementIndex--;
+				}
+			}
+			if (message.method == "redoCutPage") {
+				redoCutPage();
+			}
+			if (message.method == "getContent") {
+				onUpdate(true);
+				includeInfobar = message.includeInfobar;
+				openInfobar = message.openInfobar;
+				infobarPositionAbsolute = message.infobarPositionAbsolute;
+				infobarPositionTop = message.infobarPositionTop;
+				infobarPositionBottom = message.infobarPositionBottom;
+				infobarPositionLeft = message.infobarPositionLeft;
+				infobarPositionRight = message.infobarPositionRight;
+				let content = getContent(message.compressHTML, message.updatedResources);
+				if (initScriptContent) {
+					content = content.replace(/<script data-template-shadow-root src.*?<\/script>/g, initScriptContent);
+				}
+				let filename;
+				const pageOptions = loadOptionsFromPage(document);
+				if (pageOptions) {
+					pageOptions.backgroundSave = message.backgroundSave;
+					pageOptions.saveDate = new Date(pageOptions.saveDate);
+					pageOptions.visitDate = new Date(pageOptions.visitDate);
+					filename = await singlefile.helper.formatFilename(content, document, pageOptions);
+				}
+				if (message.sharePage) {
+					setLabels(message.labels);
+				}
+				if (pageCompressContent) {
+					const viewport = document.head.querySelector("meta[name=viewport]");
+					window.parent.postMessage(JSON.stringify({
+						method: "setContent",
+						content,
+						filename,
+						title: document.title,
+						doctype: singlefile.helper.getDoctypeString(document),
+						url: pageUrl,
+						viewport: viewport ? viewport.content : null,
+						compressContent: true,
+						foregroundSave: message.foregroundSave,
+						sharePage: message.sharePage,
+						documentHeight: document.documentElement.offsetHeight
+					}), "*");
+				} else {
+					if (message.foregroundSave || message.sharePage) {
+						try {
+							await downloadPageForeground({
+								content,
+								filename: filename || message.filename,
+								mimeType: "text/html"
+							}, { sharePage: message.sharePage });
+						} catch (error) {
+							console.log(error); // eslint-disable-line no-console
+							window.parent.postMessage(JSON.stringify({ method: "onError", error: error.message }), "*");
+						}
+					} else {
+						window.parent.postMessage(JSON.stringify({
+							method: "setContent",
+							content,
+							filename
+						}), "*");
+					}
+				}
+			}
+			if (message.method == "printPage") {
+				printPage();
+			}
+			if (message.method == "displayInfobar") {
+				singlefile.helper.displayIcon(document, true, {
+					openInfobar: message.openInfobar,
+					infobarPositionAbsolute: message.infobarPositionAbsolute,
+					infobarPositionTop: message.infobarPositionTop,
+					infobarPositionBottom: message.infobarPositionBottom,
+					infobarPositionLeft: message.infobarPositionLeft,
+					infobarPositionRight: message.infobarPositionRight
+				});
+				const infobarDoc = document.implementation.createHTMLDocument();
+				infobarDoc.body.appendChild(document.querySelector(singlefile.helper.INFOBAR_TAGNAME));
+				serializeShadowRoots(infobarDoc.body);
+				const content = singlefile.helper.serialize(infobarDoc, true);
+				window.parent.postMessage(JSON.stringify({
+					method: "displayInfobar",
+					content
+				}), "*");
+			}
+			if (message.method == "download") {
+				try {
+					await downloadPageForeground({
+						content: message.content,
+						filename: message.filename,
+						mimeType: message.mimeType
+					}, { sharePage: message.sharePage });
+				} catch (error) {
+					console.log(error); // eslint-disable-line no-console
+					window.parent.postMessage(JSON.stringify({ method: "onError", error: error.message }), "*");
+				}
+			}
+		};
+		window.onresize = reflowNotes;
+		document.ondragover = event => event.preventDefault();
+		document.ondrop = async event => {
+			if (event.dataTransfer.files && event.dataTransfer.files[0]) {
+				const file = event.dataTransfer.files[0];
+				event.preventDefault();
+				const content = new TextDecoder().decode(await file.arrayBuffer());
+				const compressContent = /<html[^>]* data-sfz[^>]*>/i.test(content);
+				if (compressContent) {
+					await init({ content: file, compressContent }, { filename: file.name });
+				} else {
+					await init({ content }, { filename: file.name });
+				}
+			}
+		};
+	}
 
 
-body {
-  --main-background: #fff;
-  --main-foreground: #333;
-  --font-color: #000000;
-  --primary-color: #0B83FF;
-  --toolbar-border: var(--grey-90-a20);
-  --toolbar-transparent-border: transparent;
-  --toolbar-box-shadow: var(--grey-90-a10);
-  --toolbar-button-background: transparent;
-  --toolbar-button-background-hover: var(--grey-90-a10);
-  --toolbar-button-foreground-hover: var(--font-color);
-  --toolbar-button-background-active: var(--grey-90-a20);
-  --toolbar-button-foreground-active: var(--primary-color);
-  --toolbar-button-border: transparent;
-  --toolbar-button-border-hover: transparent;
-  --toolbar-button-border-active: transparent;
-  --tooltip-background: var(--grey-90-a80);
-  --tooltip-foreground: white;
-  --tooltip-border: transparent;
-  --popup-background: white;
-  --popup-border: rgba(0, 0, 0, 0.12);
-  --opaque-popup-border: #e0e0e0;
-  --popup-line: var(--grey-30);
-  --popup-shadow: rgba(49, 49, 49, 0.3);
-  --popup-button-background: #edecf0;
-  --popup-button-background-hover: hsla(0,0%,70%,.4);
-  --popup-button-foreground-hover: var(--font-color);
-  --popup-button-background-active: hsla(240,5%,5%,.15);
-  --selected-background: var(--blue-40-a30);
-  --selected-border: var(--blue-40);
-  --font-value-border: var(--grey-30);
-  --icon-fill: #3b3b3c;
-  --icon-disabled-fill: #8080807F;
-  --link-foreground: var(--blue-60);
-  --link-selected-foreground: #333;
-  --visited-link-foreground: #b5007f;
-  /* light colours */
-}
-
-body.sepia {
-  --main-background: #f4ecd8;
-  --main-foreground: #5b4636;
-  --toolbar-border: #5b4636;
-}
-
-body.dark {
-  --main-background: rgb(28, 27, 34);
-  --main-foreground: #eee;
-  --font-color: #fff;
-  --toolbar-border: #4a4a4b;
-  --toolbar-box-shadow: black;
-  --toolbar-button-background-hover: var(--grey-90-a30);
-  --toolbar-button-background-active: var(--grey-90-a80);
-  --tooltip-background: black;
-  --tooltip-foreground: white;
-  --popup-background: rgb(66,65,77);
-  --opaque-popup-border: #434146;
-  --popup-line: rgb(82, 82, 94);
-  --popup-button-background: #5c5c61;
-  --popup-button-background-active: hsla(0,0%,70%,.6);
-  --selected-background: #3E6D9A;
-  --font-value-border: #656468;
-  --icon-fill: #fff;
-  --icon-disabled-fill: #ffffff66;
-  --link-foreground: #45a1ff;
-  --link-selected-foreground: #fff;
-  --visited-link-foreground: #e675fd;
-  /* dark colours */
-}
-
-body.hcm {
-  --main-background: Canvas;
-  --main-foreground: CanvasText;
-  --font-color: CanvasText;
-  --primary-color: SelectedItem;
-  --toolbar-border: CanvasText;
-   /* We need a true transparent but in HCM this would compute to an actual color,
-      so select the page's background color instead: */
-  --toolbar-transparent-border: Canvas;
-  --toolbar-box-shadow: Canvas;
-  --toolbar-button-background: ButtonFace;
-  --toolbar-button-background-hover: ButtonText;
-  --toolbar-button-foreground-hover: ButtonFace;
-  --toolbar-button-background-active: SelectedItem;
-  --toolbar-button-foreground-active: SelectedItemText;
-  --toolbar-button-border: ButtonText;
-  --toolbar-button-border-hover: ButtonText;
-  --toolbar-button-border-active: ButtonText;
-  --tooltip-background: Canvas;
-  --tooltip-foreground: CanvasText;
-  --tooltip-border: CanvasText;
-  --popup-background: Canvas;
-  --popup-border: CanvasText;
-  --opaque-popup-border: CanvasText;
-  --popup-line: CanvasText;
-  --popup-button-background: ButtonFace;
-  --popup-button-background-hover: ButtonText;
-  --popup-button-foreground-hover: ButtonFace;
-  --popup-button-background-active: ButtonText;
-  --selected-background: Canvas;
-  --selected-border: SelectedItem;
-  --font-value-border: CanvasText;
-  --icon-fill: ButtonText;
-  --icon-disabled-fill: GrayText;
-  --link-foreground: LinkText;
-  --link-selected-foreground: ActiveText;
-  --visited-link-foreground: VisitedText;
-}
-
-body {
-  margin: 0;
-  padding: var(--body-padding);
-  background-color: var(--main-background);
-  color: var(--main-foreground);
-}
-
-body.loaded {
-  transition: color 0.4s, background-color 0.4s;
-}
-
-body.dark *::-moz-selection {
-  background-color: var(--selected-background);
-}
-
-a::-moz-selection {
-  color: var(--link-selected-foreground);
-}
-
-body.sans-serif,
-body.sans-serif .remove-button {
-  font-family: Helvetica, Arial, sans-serif;
-}
-
-body.serif,
-body.serif .remove-button {
-  font-family: Georgia, "Times New Roman", serif;
-}
+	async function init({ content, password, compressContent }, { filename, reset } = {}) {
+		await initConstants();
+		if (compressContent) {
+			const zipOptions = {
+				workerScripts: { inflate: ["/lib/single-file-z-worker.js"] }
+			};
+			try {
+				const worker = new Worker(zipOptions.workerScripts.inflate[0]);
+				worker.terminate();
+				// eslint-disable-next-line no-unused-vars
+			} catch (error) {
+				delete zipOptions.workerScripts;
+			}
+			zipOptions.useWebWorkers = IS_NOT_SAFARI;
+			const { docContent, origDocContent, resources, url } = await singlefile.helper.extract(content, {
+				password,
+				prompt,
+				shadowRootScriptURL: new URL("/lib/single-file-extension-editor-init.js", document.baseURI).href,
+				zipOptions
+			});
+			pageResources = resources;
+			pageUrl = url;
+			pageCompressContent = true;
+			const contentDocument = (new DOMParser()).parseFromString(docContent, "text/html");
+			if (detectSavedPage(contentDocument)) {
+				await singlefile.helper.display(document, docContent, { disableFramePointerEvents: true });
+				const infobarElement = document.querySelector(singlefile.helper.INFOBAR_TAGNAME);
+				if (infobarElement) {
+					infobarElement.remove();
+				}
+				await initPage();
+				let icon;
+				const origContentDocument = (new DOMParser()).parseFromString(origDocContent, "text/html");
+				const iconElement = origContentDocument.querySelector("link[rel*=icon]");
+				if (iconElement) {
+					const iconResource = resources.find(resource => resource.filename == iconElement.getAttribute("href"));
+					if (iconResource && iconResource.content) {
+						const reader = new FileReader();
+						reader.readAsDataURL(await (await fetch(iconResource.content)).blob());
+						icon = await new Promise((resolve, reject) => {
+							reader.addEventListener("load", () => resolve(reader.result), false);
+							reader.addEventListener("error", reject, false);
+						});
+					} else {
+						icon = iconElement.href;
+					}
+				}
+				window.parent.postMessage(JSON.stringify({
+					method: "onInit",
+					title: document.title,
+					icon,
+					filename,
+					reset,
+					formatPageEnabled: isProbablyReaderable(document)
+				}), "*");
+			}
+		} else {
+			const initScriptContentMatch = content.match(/<script data-template-shadow-root.*<\/script>/);
+			if (initScriptContentMatch && initScriptContentMatch[0]) {
+				initScriptContent = initScriptContentMatch[0];
+			}
+			content = content.replace(/<script data-template-shadow-root.*<\/script>/g, "<script data-template-shadow-root src=/lib/single-file-extension-editor-init.js></script>");
+			const contentDocument = (new DOMParser()).parseFromString(content, "text/html");
+			if (detectSavedPage(contentDocument)) {
+				if (contentDocument.doctype) {
+					if (document.doctype) {
+						document.replaceChild(contentDocument.doctype, document.doctype);
+					} else {
+						document.insertBefore(contentDocument.doctype, document.documentElement);
+					}
+				} else if (document.doctype) {
+					document.doctype.remove();
+				}
+				const infobarElement = contentDocument.querySelector(singlefile.helper.INFOBAR_TAGNAME);
+				if (infobarElement) {
+					infobarElement.remove();
+				}
+				contentDocument.querySelectorAll("noscript").forEach(element => {
+					element.setAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME, element.innerHTML);
+					element.textContent = "";
+				});
+				contentDocument.querySelectorAll("iframe").forEach(element => {
+					const pointerEvents = "pointer-events";
+					element.style.setProperty("-sf-" + pointerEvents, element.style.getPropertyValue(pointerEvents), element.style.getPropertyPriority(pointerEvents));
+					element.style.setProperty(pointerEvents, "none", "important");
+				});
+				document.replaceChild(contentDocument.documentElement, document.documentElement);
+				document.querySelectorAll("[data-single-file-note-refs]").forEach(noteRefElement => noteRefElement.dataset.singleFileNoteRefs = noteRefElement.dataset.singleFileNoteRefs.replace(/,/g, " "));
+				deserializeShadowRoots(document);
+				document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => attachNoteListeners(containerElement, true));
+				insertHighlightStylesheet(document);
+				maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
+				maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
+				document.documentElement.onmousedown = onMouseDown;
+				document.documentElement.onmouseup = document.documentElement.ontouchend = onMouseUp;
+				document.documentElement.onmouseover = onMouseOver;
+				document.documentElement.onmouseout = onMouseOut;
+				document.documentElement.onkeydown = onKeyDown;
+				document.documentElement.ontouchstart = document.documentElement.ontouchmove = onTouchMove;
+				window.onclick = event => event.preventDefault();
+				const iconElement = document.querySelector("link[rel*=icon]");
+				window.parent.postMessage(JSON.stringify({
+					method: "onInit",
+					title: document.title,
+					icon: iconElement && iconElement.href,
+					filename,
+					reset,
+					formatPageEnabled: isProbablyReaderable(document)
+				}), "*");
+			}
+		}
+	}
 
 
-/* Override some controls and content styles based on color scheme */
+	function loadOptionsFromPage(doc) {
+		const optionsElement = doc.body.querySelector("script[type=\"application/json\"][" + SCRIPT_OPTIONS + "]");
+		if (optionsElement) {
+			return JSON.parse(optionsElement.textContent);
+		}
+	}
 
 
-body.light > .container > .header > .domain {
-  border-bottom-color: #333333 !important;
-}
+	async function initPage() {
+		document.querySelectorAll("iframe").forEach(element => {
+			const pointerEvents = "pointer-events";
+			element.style.setProperty("-sf-" + pointerEvents, element.style.getPropertyValue(pointerEvents), element.style.getPropertyPriority(pointerEvents));
+			element.style.setProperty(pointerEvents, "none", "important");
+		});
+		document.querySelectorAll("[data-single-file-note-refs]").forEach(noteRefElement => noteRefElement.dataset.singleFileNoteRefs = noteRefElement.dataset.singleFileNoteRefs.replace(/,/g, " "));
+		deserializeShadowRoots(document);
+		reflowNotes();
+		await waitResourcesLoad();
+		reflowNotes();
+		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => attachNoteListeners(containerElement, true));
+		insertHighlightStylesheet(document);
+		maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
+		maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
+		document.documentElement.onmousedown = onMouseDown;
+		document.documentElement.onmouseup = document.documentElement.ontouchend = onMouseUp;
+		document.documentElement.onmouseover = onMouseOver;
+		document.documentElement.onmouseout = onMouseOut;
+		document.documentElement.onkeydown = onKeyDown;
+		document.documentElement.ontouchstart = document.documentElement.ontouchmove = onTouchMove;
+		window.onclick = event => event.preventDefault();
+	}
 
 
-body.sepia > .container > .header > .domain {
-  border-bottom-color: #5b4636 !important;
-}
+	async function initConstants() {
+		[NOTES_WEB_STYLESHEET, MASK_WEB_STYLESHEET, HIGHLIGHTS_WEB_STYLESHEET] = await Promise.all([
+			minifyText(await ((await fetch("../pages/editor-note-web.css")).text())),
+			minifyText(await ((await fetch("../pages/editor-mask-web.css")).text())),
+			minifyText(await ((await fetch("../pages/editor-frame-web.css")).text()))
+		]);
+	}
 
 
-body.dark > .container > .header > .domain {
-  border-bottom-color: #eeeeee !important;
-}
-
-body.light blockquote {
-  border-inline-start: 2px solid #333333 !important;
-}
+	function addNote({ color }) {
+		const containerElement = document.createElement(NOTE_TAGNAME);
+		const noteElement = document.createElement("div");
+		const headerElement = document.createElement("header");
+		const blockquoteElement = document.createElement("blockquote");
+		const mainElement = document.createElement("textarea");
+		const resizeElement = document.createElement("div");
+		const removeNoteElement = document.createElement("img");
+		const anchorIconElement = document.createElement("img");
+		const noteShadow = containerElement.attachShadow({ mode: "open" });
+		headerElement.appendChild(anchorIconElement);
+		headerElement.appendChild(removeNoteElement);
+		blockquoteElement.appendChild(mainElement);
+		noteElement.appendChild(headerElement);
+		noteElement.appendChild(blockquoteElement);
+		noteElement.appendChild(resizeElement);
+		noteShadow.appendChild(getStyleElement(NOTES_WEB_STYLESHEET));
+		noteShadow.appendChild(noteElement);
+		const notesElements = Array.from(document.querySelectorAll(NOTE_TAGNAME));
+		const noteId = Math.max.call(Math, 0, ...notesElements.map(noteElement => Number(noteElement.dataset.noteId))) + 1;
+		blockquoteElement.cite = "https://www.w3.org/TR/annotation-model/#selector(type=CssSelector,value=[data-single-file-note-refs~=\"" + noteId + "\"])";
+		noteElement.classList.add(NOTE_CLASS);
+		noteElement.classList.add(NOTE_ANCHORED_CLASS);
+		noteElement.classList.add(color);
+		noteElement.dataset.color = color;
+		mainElement.dir = "auto";
+		const boundingRectDocument = document.documentElement.getBoundingClientRect();
+		let positionX = NOTE_INITIAL_WIDTH + NOTE_INITIAL_POSITION_X - 1 - boundingRectDocument.x;
+		let positionY = NOTE_INITIAL_HEIGHT + NOTE_INITIAL_POSITION_Y - 1 - boundingRectDocument.y;
+		while (Array.from(document.elementsFromPoint(positionX - window.scrollX, positionY - window.scrollY)).find(element => element.tagName.toLowerCase() == NOTE_TAGNAME)) {
+			positionX += NOTE_INITIAL_POSITION_X;
+			positionY += NOTE_INITIAL_POSITION_Y;
+		}
+		noteElement.style.setProperty("left", (positionX - NOTE_INITIAL_WIDTH - 1) + "px");
+		noteElement.style.setProperty("top", (positionY - NOTE_INITIAL_HEIGHT - 1) + "px");
+		resizeElement.className = "note-resize";
+		resizeElement.ondragstart = event => event.preventDefault();
+		removeNoteElement.className = "note-remove";
+		removeNoteElement.src = BUTTON_CLOSE_URL;
+		removeNoteElement.ondragstart = event => event.preventDefault();
+		anchorIconElement.className = "note-anchor";
+		anchorIconElement.src = BUTTON_ANCHOR_URL;
+		anchorIconElement.ondragstart = event => event.preventDefault();
+		containerElement.dataset.noteId = noteId;
+		addNoteRef(document.documentElement, noteId);
+		attachNoteListeners(containerElement, true);
+		document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
+		noteElement.classList.add(NOTE_SELECTED_CLASS);
+		selectedNote = noteElement;
+		onUpdate(false);
+	}
 
 
-body.sepia blockquote {
-  border-inline-start: 2px solid #5b4636 !important;
-}
+	function attachNoteListeners(containerElement, editable = false) {
+		const SELECT_PX_THRESHOLD = 4;
+		const COLLAPSING_NOTE_DELAY = 750;
+		const noteShadow = containerElement.shadowRoot;
+		const noteElement = noteShadow.childNodes[1];
+		const headerElement = noteShadow.querySelector("header");
+		const mainElement = noteShadow.querySelector("textarea");
+		const noteId = containerElement.dataset.noteId;
+		const resizeElement = noteShadow.querySelector(".note-resize");
+		const anchorIconElement = noteShadow.querySelector(".note-anchor");
+		const removeNoteElement = noteShadow.querySelector(".note-remove");
+		mainElement.readOnly = !editable;
+		if (!editable) {
+			anchorIconElement.style.setProperty("display", "none", "important");
+		} else {
+			anchorIconElement.style.removeProperty("display");
+		}
+		headerElement.ontouchstart = headerElement.onmousedown = event => {
+			if (event.target == headerElement) {
+				collapseNoteTimeout = setTimeout(() => {
+					noteElement.classList.toggle("note-collapsed");
+					hideMaskNote();
+				}, COLLAPSING_NOTE_DELAY);
+				event.preventDefault();
+				const position = getPosition(event);
+				const clientX = position.clientX;
+				const clientY = position.clientY;
+				const boundingRect = noteElement.getBoundingClientRect();
+				const deltaX = clientX - boundingRect.left;
+				const deltaY = clientY - boundingRect.top;
+				maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS);
+				document.documentElement.style.setProperty("user-select", "none", "important");
+				anchorElement = getAnchorElement(containerElement);
+				displayMaskNote();
+				selectNote(noteElement);
+				moveNote(event, deltaX, deltaY);
+				movingNoteMode = { event, deltaX, deltaY };
+				document.documentElement.ontouchmove = document.documentElement.onmousemove = event => {
+					clearTimeout(collapseNoteTimeout);
+					if (!movingNoteMode) {
+						movingNoteMode = { deltaX, deltaY };
+					}
+					movingNoteMode.event = event;
+					moveNote(event, deltaX, deltaY);
+				};
+			}
+		};
+		resizeElement.ontouchstart = resizeElement.onmousedown = event => {
+			event.preventDefault();
+			resizingNoteMode = true;
+			selectNote(noteElement);
+			maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS);
+			document.documentElement.style.setProperty("user-select", "none", "important");
+			document.documentElement.ontouchmove = document.documentElement.onmousemove = event => {
+				event.preventDefault();
+				const { clientX, clientY } = getPosition(event);
+				const boundingRectNote = noteElement.getBoundingClientRect();
+				noteElement.style.width = clientX - boundingRectNote.left + "px";
+				noteElement.style.height = clientY - boundingRectNote.top + "px";
+			};
+		};
+		anchorIconElement.ontouchend = anchorIconElement.onclick = event => {
+			event.preventDefault();
+			noteElement.classList.toggle(NOTE_ANCHORED_CLASS);
+			if (!noteElement.classList.contains(NOTE_ANCHORED_CLASS)) {
+				deleteNoteRef(containerElement, noteId);
+				addNoteRef(document.documentElement, noteId);
+			}
+			onUpdate(false);
+		};
+		removeNoteElement.ontouchend = removeNoteElement.onclick = event => {
+			event.preventDefault();
+			deleteNoteRef(containerElement, noteId);
+			containerElement.remove();
+		};
+		noteElement.onmousedown = () => {
+			selectNote(noteElement);
+		};
 
 
-body.dark blockquote {
-  border-inline-start: 2px solid #eeeeee !important;
-}
+		function moveNote(event, deltaX, deltaY) {
+			event.preventDefault();
+			const { clientX, clientY } = getPosition(event);
+			noteElement.classList.add(NOTE_MOVING_CLASS);
+			if (editable) {
+				if (noteElement.classList.contains(NOTE_ANCHORED_CLASS)) {
+					deleteNoteRef(containerElement, noteId);
+					anchorElement = getTarget(clientX, clientY) || document.documentElement;
+					addNoteRef(anchorElement, noteId);
+				} else {
+					anchorElement = document.documentElement;
+				}
+			}
+			document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
+			noteElement.style.setProperty("left", (clientX - deltaX) + "px");
+			noteElement.style.setProperty("top", (clientY - deltaY) + "px");
+			noteElement.style.setProperty("position", "fixed");
+			displayMaskNote();
+		}
 
 
-.light-button {
-  color: #333333;
-  background-color: #ffffff;
-}
+		function displayMaskNote() {
+			if (anchorElement == document.documentElement || anchorElement == document.documentElement) {
+				hideMaskNote();
+			} else {
+				const boundingRectAnchor = anchorElement.getBoundingClientRect();
+				maskNoteElement.classList.add(NOTE_MASK_MOVING_CLASS);
+				if (selectedNote) {
+					maskNoteElement.classList.add(selectedNote.dataset.color);
+				}
+				maskNoteElement.style.setProperty("top", (boundingRectAnchor.y - 3) + "px");
+				maskNoteElement.style.setProperty("left", (boundingRectAnchor.x - 3) + "px");
+				maskNoteElement.style.setProperty("width", (boundingRectAnchor.width + 3) + "px");
+				maskNoteElement.style.setProperty("height", (boundingRectAnchor.height + 3) + "px");
+			}
+		}
 
 
-.dark-button {
-  color: #eeeeee;
-  background-color: #1c1b22;
-}
+		function hideMaskNote() {
+			maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS);
+			if (selectedNote) {
+				maskNoteElement.classList.remove(selectedNote.dataset.color);
+			}
+		}
 
 
-.sepia-button {
-  color: #5b4636;
-  background-color: #f4ecd8;
-}
+		function selectNote(noteElement) {
+			if (selectedNote) {
+				selectedNote.classList.remove(NOTE_SELECTED_CLASS);
+				maskNoteElement.classList.remove(selectedNote.dataset.color);
+			}
+			noteElement.classList.add(NOTE_SELECTED_CLASS);
+			noteElement.classList.add(noteElement.dataset.color);
+			selectedNote = noteElement;
+		}
 
 
-.auto-button {
-  text-align: center;
-}
+		function getTarget(clientX, clientY) {
+			const targets = Array.from(document.elementsFromPoint(clientX, clientY)).filter(element => element.matches("html *:not(" + NOTE_TAGNAME + "):not(." + MASK_CLASS + ")"));
+			if (!targets.includes(document.documentElement)) {
+				targets.push(document.documentElement);
+			}
+			let newTarget, target = targets[0], boundingRect = target.getBoundingClientRect();
+			newTarget = determineTargetElement("floor", target, clientX - boundingRect.left, getMatchedParents(target, "left"));
+			if (newTarget == target) {
+				newTarget = determineTargetElement("ceil", target, boundingRect.left + boundingRect.width - clientX, getMatchedParents(target, "right"));
+			}
+			if (newTarget == target) {
+				newTarget = determineTargetElement("floor", target, clientY - boundingRect.top, getMatchedParents(target, "top"));
+			}
+			if (newTarget == target) {
+				newTarget = determineTargetElement("ceil", target, boundingRect.top + boundingRect.height - clientY, getMatchedParents(target, "bottom"));
+			}
+			target = newTarget;
+			while (boundingRect = target && target.getBoundingClientRect(), boundingRect && boundingRect.width <= SELECT_PX_THRESHOLD && boundingRect.height <= SELECT_PX_THRESHOLD) {
+				target = target.parentElement;
+			}
+			return target;
+		}
 
 
-@media (prefers-color-scheme: dark) {
-  .auto-button {
-    background-color: #1c1b22;
-    color: #eeeeee;
-  }
-}
+		function getMatchedParents(target, property) {
+			let element = target, matchedParent, parents = [];
+			do {
+				const boundingRect = element.getBoundingClientRect();
+				if (element.parentElement && !element.parentElement.tagName.toLowerCase() != NOTE_TAGNAME && !element.classList.contains(MASK_CLASS)) {
+					const parentBoundingRect = element.parentElement.getBoundingClientRect();
+					matchedParent = Math.abs(parentBoundingRect[property] - boundingRect[property]) <= SELECT_PX_THRESHOLD;
+					if (matchedParent) {
+						if (element.parentElement.clientWidth > SELECT_PX_THRESHOLD && element.parentElement.clientHeight > SELECT_PX_THRESHOLD &&
+							((element.parentElement.clientWidth - element.clientWidth > SELECT_PX_THRESHOLD) || (element.parentElement.clientHeight - element.clientHeight > SELECT_PX_THRESHOLD))) {
+							parents.push(element.parentElement);
+						}
+						element = element.parentElement;
+					}
+				} else {
+					matchedParent = false;
+				}
+			} while (matchedParent && element);
+			return parents;
+		}
 
 
-@media not (prefers-color-scheme: dark) {
-  .auto-button {
-    background-color: #ffffff;
-    color: #333333;
-  }
-}
+		function determineTargetElement(roundingMethod, target, widthDistance, parents) {
+			if (Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) <= parents.length) {
+				target = parents[parents.length - Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) - 1];
+			}
+			return target;
+		}
+	}
 
 
-/* Loading/error message */
+	function onMouseDown(event) {
+		if ((cuttingMode || cuttingOuterMode) && cuttingPath) {
+			event.preventDefault();
+		}
+	}
 
 
-.reader-message {
-  margin-top: 40px;
-  display: none;
-  text-align: center;
-  width: 100%;
-  font-size: 0.9em;
-}
+	function onMouseUp(event) {
+		if (highlightSelectionMode) {
+			event.preventDefault();
+			highlightSelection();
+			onUpdate(false);
+		}
+		if (removeHighlightMode) {
+			event.preventDefault();
+			let element = event.target, done;
+			while (element && !done) {
+				if (element.classList.contains(HIGHLIGHT_CLASS)) {
+					document.querySelectorAll("." + HIGHLIGHT_CLASS + "[data-singlefile-highlight-id=" + JSON.stringify(element.dataset.singlefileHighlightId) + "]").forEach(highlightedElement => {
+						resetHighlightedElement(highlightedElement);
+						onUpdate(false);
+					});
+					done = true;
+				}
+				element = element.parentElement;
+			}
+		}
+		if (resizingNoteMode) {
+			event.preventDefault();
+			resizingNoteMode = false;
+			document.documentElement.style.removeProperty("user-select");
+			maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS);
+			document.documentElement.ontouchmove = onTouchMove;
+			document.documentElement.onmousemove = null;
+			onUpdate(false);
+		}
+		if (movingNoteMode) {
+			event.preventDefault();
+			anchorNote(movingNoteMode.event || event, selectedNote, movingNoteMode.deltaX, movingNoteMode.deltaY);
+			movingNoteMode = null;
+			document.documentElement.ontouchmove = onTouchMove;
+			document.documentElement.onmousemove = null;
+			onUpdate(false);
+		}
+		if ((cuttingMode || cuttingOuterMode) && cuttingPath) {
+			event.preventDefault();
+			if (event.ctrlKey) {
+				const element = cuttingPath[cuttingPathIndex];
+				element.classList.toggle(cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS);
+			} else {
+				validateCutElement(event.shiftKey);
+			}
+		}
+		if (collapseNoteTimeout) {
+			clearTimeout(collapseNoteTimeout);
+			collapseNoteTimeout = null;
+		}
+	}
 
 
-/* Detector element to see if we're at the top of the doc or not. */
-.top-anchor {
-  position: absolute;
-  top: 0;
-  width: 0;
-  height: 5px;
-  pointer-events: none;
-}
+	function onMouseOver(event) {
+		if (cuttingMode || cuttingOuterMode) {
+			const target = event.target;
+			if (target.classList) {
+				let ancestorFound;
+				document.querySelectorAll("." + (cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS)).forEach(element => {
+					if (element == target || isAncestor(element, target) || isAncestor(target, element)) {
+						ancestorFound = element;
+					}
+				});
+				if (ancestorFound) {
+					cuttingPath = [ancestorFound];
+				} else {
+					cuttingPath = getParents(event.target);
+				}
+				cuttingPathIndex = 0;
+				highlightCutElement();
+			}
+		}
+	}
 
 
-/* Header */
+	function onMouseOut() {
+		if (cuttingMode || cuttingOuterMode) {
+			if (cuttingPath) {
+				unhighlightCutElement();
+				cuttingPath = null;
+			}
+		}
+	}
 
 
-.header {
-  text-align: start;
-  display: none;
-}
+	function onTouchMove(event) {
+		if (cuttingMode || cuttingOuterMode) {
+			event.preventDefault();
+			const { clientX, clientY } = getPosition(event);
+			const target = document.elementFromPoint(clientX, clientY);
+			if (cuttingTouchTarget != target) {
+				onMouseOut();
+				if (target) {
+					cuttingTouchTarget = target;
+					onMouseOver({ target });
+				}
+			}
+		}
+	}
 
 
-.domain {
-  font-size: 0.9em;
-  line-height: 1.48em;
-  padding-bottom: 4px;
-  font-family: Helvetica, Arial, sans-serif;
-  text-decoration: none;
-  border-bottom: 1px solid;
-  color: var(--link-foreground);
-}
+	function onKeyDown(event) {
+		if (cuttingMode || cuttingOuterMode) {
+			if (event.code == "Tab") {
+				if (cuttingPath) {
+					const delta = event.shiftKey ? -1 : 1;
+					let element = cuttingPath[cuttingPathIndex];
+					let nextElement = cuttingPath[cuttingPathIndex + delta];
+					if (nextElement) {
+						let pathIndex = cuttingPathIndex + delta;
+						while (
+							nextElement &&
+							(
+								(delta == 1 &&
+									element.getBoundingClientRect().width >= nextElement.getBoundingClientRect().width &&
+									element.getBoundingClientRect().height >= nextElement.getBoundingClientRect().height) ||
+								(delta == -1 &&
+									element.getBoundingClientRect().width <= nextElement.getBoundingClientRect().width &&
+									element.getBoundingClientRect().height <= nextElement.getBoundingClientRect().height))) {
+							pathIndex += delta;
+							nextElement = cuttingPath[pathIndex];
+						}
+						if (nextElement && nextElement.classList && nextElement != document.body && nextElement != document.documentElement) {
+							unhighlightCutElement();
+							cuttingPathIndex = pathIndex;
+							highlightCutElement();
+						}
+					}
+				}
+				event.preventDefault();
+			}
+			if (event.code == "Space") {
+				if (cuttingPath) {
+					if (event.ctrlKey) {
+						const element = cuttingPath[cuttingPathIndex];
+						element.classList.add(cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS);
+					} else {
+						validateCutElement(event.shiftKey);
+					}
+					event.preventDefault();
+				}
+			}
+			if (event.code == "Escape") {
+				resetSelectedElements();
+				event.preventDefault();
+			}
+			if (event.key.toLowerCase() == "z" && event.ctrlKey) {
+				if (event.shiftKey) {
+					redoCutPage();
+				} else {
+					undoCutPage();
+				}
+				event.preventDefault();
+			}
+		}
+		if (event.key.toLowerCase() == "s" && event.ctrlKey) {
+			window.parent.postMessage(JSON.stringify({ "method": "savePage" }), "*");
+			event.preventDefault();
+		}
+		if (event.key.toLowerCase() == "p" && event.ctrlKey) {
+			printPage();
+			event.preventDefault();
+		}
+	}
 
 
-.header > h1 {
-  font-size: 1.6em;
-  line-height: 1.25em;
-  width: 100%;
-  margin: 30px 0;
-  padding: 0;
-}
+	function printPage() {
+		unhighlightCutElement();
+		resetSelectedElements();
+		window.print();
+	}
 
 
-.header > .credits {
-  font-size: 0.9em;
-  line-height: 1.48em;
-  margin: 0 0 10px;
-  padding: 0;
-  font-style: italic;
-}
+	function highlightCutElement() {
+		const element = cuttingPath[cuttingPathIndex];
+		element.classList.add(cuttingMode ? CUT_HOVER_CLASS : CUT_OUTER_HOVER_CLASS);
+	}
 
 
-.header > .meta-data {
-  font-size: 0.65em;
-  margin: 0 0 15px;
-}
+	function unhighlightCutElement() {
+		if (cuttingPath) {
+			const element = cuttingPath[cuttingPathIndex];
+			element.classList.remove(CUT_HOVER_CLASS);
+			element.classList.remove(CUT_OUTER_HOVER_CLASS);
+		}
+	}
 
 
-.reader-estimated-time {
-  text-align: match-parent;
-}
+	function disableHighlight(doc = document) {
+		if (highlightColor) {
+			doc.documentElement.classList.remove(highlightColor + "-mode");
+		}
+	}
 
 
-/* Controls toolbar */
+	function undoCutPage() {
+		if (removedElementIndex) {
+			removedElements[removedElementIndex - 1].forEach(element => element.classList.remove(REMOVED_CONTENT_CLASS));
+			removedElementIndex--;
+		}
+	}
 
 
-.toolbar-container {
-  position: sticky;
-  z-index: 2;
-  top: 32px;
-  height: 0; /* take up no space, so body is at the top. */
-
-  /* As a stick container, we're positioned relative to the body. Move us to
-   * the edge of the viewport using margins, and take the width of
-   * the body padding into account for calculating our width.
-   */
-  margin-inline-start: calc(-1 * var(--body-padding));
-  width: max(var(--body-padding), calc((100% - var(--content-width)) / 2 + var(--body-padding)));
-  font-size: var(--font-size); /* Needed to ensure 'em' units match, is reset for .reader-controls */
-}
+	function redoCutPage() {
+		if (removedElementIndex < removedElements.length) {
+			removedElements[removedElementIndex].forEach(element => element.classList.add(REMOVED_CONTENT_CLASS));
+			removedElementIndex++;
+		}
+	}
 
 
-.toolbar {
-  padding-block: 16px;
-  border: 1px solid var(--toolbar-border);
-  border-radius: 6px;
-  box-shadow: 0 2px 8px var(--toolbar-box-shadow);
+	function validateCutElement(invert) {
+		const selectedElement = cuttingPath[cuttingPathIndex];
+		if ((cuttingMode && !invert) || (cuttingOuterMode && invert)) {
+			if (document.documentElement != selectedElement && selectedElement.tagName.toLowerCase() != NOTE_TAGNAME) {
+				const elementsRemoved = [selectedElement].concat(...document.querySelectorAll("." + CUT_SELECTED_CLASS + ",." + CUT_SELECTED_CLASS + " *,." + CUT_HOVER_CLASS + " *"));
+				resetSelectedElements();
+				if (elementsRemoved.length) {
+					elementsRemoved.forEach(element => {
+						unhighlightCutElement(element);
+						if (element.tagName.toLowerCase() == NOTE_TAGNAME) {
+							resetAnchorNote(element);
+						} else {
+							element.classList.add(REMOVED_CONTENT_CLASS);
+						}
+					});
+					removedElements[removedElementIndex] = elementsRemoved;
+					removedElementIndex++;
+					removedElements.length = removedElementIndex;
+					onUpdate(false);
+				}
+			}
+		} else {
+			if (document.documentElement != selectedElement && selectedElement.tagName.toLowerCase() != NOTE_TAGNAME) {
+				const elements = [];
+				const searchSelector = "*:not(style):not(meta):not(." + REMOVED_CONTENT_CLASS + ")";
+				const elementsKept = [selectedElement].concat(...document.querySelectorAll("." + CUT_OUTER_SELECTED_CLASS));
+				document.body.querySelectorAll(searchSelector).forEach(element => {
+					let removed = true;
+					elementsKept.forEach(elementKept => removed = removed && (elementKept != element && !isAncestor(elementKept, element) && !isAncestor(element, elementKept)));
+					if (removed) {
+						if (element.tagName.toLowerCase() == NOTE_TAGNAME) {
+							resetAnchorNote(element);
+						} else {
+							elements.push(element);
+						}
+					}
+				});
+				elementsKept.forEach(elementKept => {
+					unhighlightCutElement(elementKept);
+					const elementKeptRect = elementKept.getBoundingClientRect();
+					elementKept.querySelectorAll(searchSelector).forEach(descendant => {
+						const descendantRect = descendant.getBoundingClientRect();
+						if (descendantRect.width && descendantRect.height && (
+							descendantRect.left + descendantRect.width < elementKeptRect.left ||
+							descendantRect.right > elementKeptRect.right + elementKeptRect.width ||
+							descendantRect.top + descendantRect.height < elementKeptRect.top ||
+							descendantRect.bottom > elementKeptRect.bottom + elementKeptRect.height
+						)) {
+							elements.push(descendant);
+						}
+					});
+				});
+				resetSelectedElements();
+				if (elements.length) {
+					elements.forEach(element => element.classList.add(REMOVED_CONTENT_CLASS));
+					removedElements[removedElementIndex] = elements;
+					removedElementIndex++;
+					removedElements.length = removedElementIndex;
+					onUpdate(false);
+				}
+			}
+		}
+	}
 
 
-  width: 32px; /* basic width, without padding/border */
+	function resetSelectedElements(doc = document) {
+		doc.querySelectorAll("." + CUT_OUTER_SELECTED_CLASS + ",." + CUT_SELECTED_CLASS).forEach(element => {
+			element.classList.remove(CUT_OUTER_SELECTED_CLASS);
+			element.classList.remove(CUT_SELECTED_CLASS);
+		});
+	}
 
 
-  /* padding should be 16px, except if there's not enough space for that, in
-   * which case use half the available space for padding (=25% on each side).
-   * The 34px here is the width + borders. We use a variable because we need
-   * to know this size for the margin calculation.
-   */
-  --inline-padding: min(16px, calc(25% - 0.25 * 34px));
-  padding-inline: var(--inline-padding);
+	function anchorNote(event, noteElement, deltaX, deltaY) {
+		event.preventDefault();
+		const { clientX, clientY } = getPosition(event);
+		document.documentElement.style.removeProperty("user-select");
+		noteElement.classList.remove(NOTE_MOVING_CLASS);
+		maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS);
+		maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS);
+		maskNoteElement.classList.remove(noteElement.dataset.color);
+		const headerElement = noteElement.querySelector("header");
+		headerElement.ontouchmove = document.documentElement.onmousemove = null;
+		let currentElement = anchorElement;
+		let positionedElement;
+		while (currentElement.parentElement && !positionedElement) {
+			if (!FORBIDDEN_TAG_NAMES.includes(currentElement.tagName.toLowerCase())) {
+				const currentElementStyle = getComputedStyle(currentElement);
+				if (currentElementStyle.position != "static") {
+					positionedElement = currentElement;
+				}
+			}
+			currentElement = currentElement.parentElement;
+		}
+		if (!positionedElement) {
+			positionedElement = document.documentElement;
+		}
+		const containerElement = noteElement.getRootNode().host;
+		if (positionedElement == document.documentElement) {
+			const firstMaskElement = document.querySelector("." + MASK_CLASS);
+			firstMaskElement.parentElement.insertBefore(containerElement, firstMaskElement);
+		} else {
+			positionedElement.appendChild(containerElement);
+		}
+		const boundingRectPositionedElement = positionedElement.getBoundingClientRect();
+		const stylePositionedElement = window.getComputedStyle(positionedElement);
+		const borderX = parseInt(stylePositionedElement.getPropertyValue("border-left-width"));
+		const borderY = parseInt(stylePositionedElement.getPropertyValue("border-top-width"));
+		noteElement.style.setProperty("position", "absolute");
+		noteElement.style.setProperty("left", (clientX - boundingRectPositionedElement.x - deltaX - borderX) + "px");
+		noteElement.style.setProperty("top", (clientY - boundingRectPositionedElement.y - deltaY - borderY) + "px");
+	}
 
 
-  /* Keep a maximum of 96px distance to the body, but center once the margin
-   * gets too small. We need to set the start margin, however...
-   * To center, we'd want 50% of the container, but we'd subtract half our
-   * own width (16px) and half the border (1px) and the inline padding.
-   * When the other margin would be 96px, we want 100% - 96px - the complete
-   * width of the actual toolbar (34px + 2 * padding)
-   */
-  margin-inline-start: max(calc(50% - 17px - var(--inline-padding)), calc(100% - 96px - 34px - 2 * var(--inline-padding)));
+	function resetAnchorNote(containerElement) {
+		const noteId = containerElement.dataset.noteId;
+		const noteElement = containerElement.shadowRoot.childNodes[1];
+		noteElement.classList.remove(NOTE_ANCHORED_CLASS);
+		deleteNoteRef(containerElement, noteId);
+		addNoteRef(document.documentElement, noteId);
+		document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
+	}
 
 
-  font-family: Helvetica, Arial, sans-serif;
-  list-style: none;
-  user-select: none;
-}
+	function getPosition(event) {
+		if (event.touches && event.touches.length) {
+			const touch = event.touches[0];
+			return touch;
+		} else {
+			return event;
+		}
+	}
 
 
-@media (prefers-reduced-motion: no-preference) {
-  .toolbar {
-    transition-property: border-color, box-shadow;
-    transition-duration: 250ms;
-  }
+	function highlightSelection() {
+		let highlightId = 0;
+		document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(highlightedElement => highlightId = Math.max(highlightId, highlightedElement.dataset.singlefileHighlightId));
+		highlightId++;
+		const selection = window.getSelection();
+		const highlightedNodes = new Set();
+		for (let indexRange = 0; indexRange < selection.rangeCount; indexRange++) {
+			const range = selection.getRangeAt(indexRange);
+			if (!range.collapsed) {
+				if (range.commonAncestorContainer.nodeType == range.commonAncestorContainer.TEXT_NODE) {
+					let contentText = range.startContainer.splitText(range.startOffset);
+					contentText = contentText.splitText(range.endOffset);
+					addHighLightedNode(contentText.previousSibling);
+				} else {
+					const treeWalker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
+					let highlightNodes;
+					while (treeWalker.nextNode()) {
+						if (highlightNodes && !treeWalker.currentNode.contains(range.endContainer)) {
+							addHighLightedNode(treeWalker.currentNode);
+						}
+						if (treeWalker.currentNode == range.startContainer) {
+							if (range.startContainer.nodeType == range.startContainer.TEXT_NODE) {
+								const contentText = range.startContainer.splitText(range.startOffset);
+								treeWalker.nextNode();
+								addHighLightedNode(contentText);
+							} else {
+								addHighLightedNode(range.startContainer.childNodes[range.startOffset]);
+							}
+							highlightNodes = true;
+						}
+						if (treeWalker.currentNode == range.endContainer) {
+							if (range.endContainer.nodeType == range.endContainer.TEXT_NODE) {
+								const contentText = range.endContainer.splitText(range.endOffset);
+								treeWalker.nextNode();
+								addHighLightedNode(contentText.previousSibling);
+							} else {
+								addHighLightedNode(range.endContainer.childNodes[range.endOffset]);
+							}
+							highlightNodes = false;
+						}
+					}
+					range.collapse();
+				}
+			}
+		}
+		highlightedNodes.forEach(node => highlightNode(node));
 
 
-  .toolbar .toolbar-button {
-    transition-property: opacity;
-    transition-duration: 250ms;
-  }
+		function addHighLightedNode(node) {
+			if (node && node.textContent.trim()) {
+				if (node.nodeType == node.TEXT_NODE && node.parentElement.childNodes.length == 1 && node.parentElement.classList.contains(HIGHLIGHT_CLASS)) {
+					highlightedNodes.add(node.parentElement);
+				} else {
+					highlightedNodes.add(node);
+				}
+			}
+		}
 
 
-  .toolbar-container.scrolled .toolbar:not(:hover, :focus-within) {
-    border-color: var(--toolbar-transparent-border);
-    box-shadow: 0 2px 8px transparent;
-  }
+		function highlightNode(node) {
+			if (node.nodeType == node.ELEMENT_NODE) {
+				resetHighlightedElement(node);
+				node.classList.add(HIGHLIGHT_CLASS);
+				node.classList.add(highlightColor);
+				node.dataset.singlefileHighlightId = highlightId;
+			} else if (node.parentElement) {
+				highlightTextNode(node);
+			}
+		}
 
 
-  .toolbar-container.scrolled .toolbar:not(:hover, :focus-within) .toolbar-button {
-    opacity: 0.6;
-  }
+		function highlightTextNode(node) {
+			const spanElement = document.createElement("span");
+			spanElement.classList.add(HIGHLIGHT_CLASS);
+			spanElement.classList.add(highlightColor);
+			spanElement.textContent = node.textContent;
+			spanElement.dataset.singlefileHighlightId = highlightId;
+			node.parentNode.replaceChild(spanElement, node);
+			return spanElement;
+		}
+	}
 
 
-  .toolbar-container.transition-location {
-    transition-duration: 250ms;
-    transition-property: width;
-  }
-}
-
-.toolbar-container.overlaps .toolbar-button {
-  opacity: 0.1;
-}
-
-.dropdown-open .toolbar {
-  border-color: var(--toolbar-transparent-border);
-  box-shadow: 0 2px 8px transparent;
-}
+	function getParents(element) {
+		const path = [];
+		while (element) {
+			path.push(element);
+			element = element.parentElement;
+		}
+		return path;
+	}
 
 
-.reader-controls {
-  /* We use 'em's above this node to get it to the right size. However,
-   * the UI inside the toolbar should use a fixed, smaller size. */
-  font-size: 11px;
-}
+	function formatPage(applySystemTheme, contentWidth) {
+		if (pageCompressContent) {
+			serializeShadowRoots(document);
+			previousContent = document.documentElement.cloneNode(true);
+			deserializeShadowRoots(document);
+		} else {
+			previousContent = getContent(false, []);
+		}
+		const shadowRoots = {};
+		const classesToPreserve = ["single-file-highlight", "single-file-highlight-yellow", "single-file-highlight-green", "single-file-highlight-pink", "single-file-highlight-blue"];
+		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
+			shadowRoots[containerElement.dataset.noteId] = containerElement.shadowRoot;
+			const className = "singlefile-note-id-" + containerElement.dataset.noteId;
+			containerElement.classList.add(className);
+			classesToPreserve.push(className);
+		});
+		const optionsElement = document.querySelector("script[type=\"application/json\"][" + SCRIPT_OPTIONS + "]");
+		const article = new Readability(document, { classesToPreserve }).parse();
+		const articleMetadata = Object.assign({}, article);
+		delete articleMetadata.content;
+		delete articleMetadata.textContent;
+		for (const key in articleMetadata) {
+			if (articleMetadata[key] == null) {
+				delete articleMetadata[key];
+			}
+		}
+		removedElements = [];
+		removedElementIndex = 0;
+		document.body.innerHTML = "";
+		const domParser = new DOMParser();
+		const doc = domParser.parseFromString(article.content, "text/html");
+		const contentEditable = document.body.contentEditable;
+		document.documentElement.replaceChild(doc.body, document.body);
+		if (optionsElement) {
+			document.body.appendChild(optionsElement);
+		}
+		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
+			const noteId = (Array.from(containerElement.classList).find(className => /singlefile-note-id-\d+/.test(className))).split("singlefile-note-id-")[1];
+			containerElement.classList.remove("singlefile-note-id-" + noteId);
+			containerElement.dataset.noteId = noteId;
+			if (!containerElement.shadowRoot) {
+				containerElement.attachShadow({ mode: "open" });
+				containerElement.shadowRoot.appendChild(shadowRoots[noteId]);
+			}
+		});
+		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => shadowRoots[containerElement.dataset.noteId].childNodes.forEach(node => containerElement.shadowRoot.appendChild(node)));
+		document.body.contentEditable = contentEditable;
+		document.head.querySelectorAll("style").forEach(styleElement => styleElement.remove());
+		const styleElement = document.createElement("style");
+		styleElement.textContent = getStyleFormattedPage(contentWidth);
+		document.head.appendChild(styleElement);
+		document.body.classList.add("moz-reader-content");
+		document.body.classList.add("content-width6");
+		document.body.classList.add("reader-show-element");
+		document.body.classList.add("sans-serif");
+		document.body.classList.add("container");
+		document.body.classList.add("line-height4");
+		const prefersColorSchemeDark = matchMedia("(prefers-color-scheme: dark)");
+		if (applySystemTheme && prefersColorSchemeDark && prefersColorSchemeDark.matches) {
+			document.body.classList.add("dark");
+		}
+		document.body.style.setProperty("display", "block");
+		document.body.style.setProperty("padding", "24px");
+		const titleElement = document.createElement("h1");
+		titleElement.classList.add("reader-title");
+		titleElement.textContent = article.title;
+		document.body.insertBefore(titleElement, document.body.firstChild);
+		const existingMetaDataElement = document.querySelector("script[id=singlefile-readability-metadata]");
+		if (existingMetaDataElement) {
+			existingMetaDataElement.remove();
+		}
+		const metaDataElement = document.createElement("script");
+		metaDataElement.type = "application/json";
+		metaDataElement.id = "singlefile-readability-metadata";
+		metaDataElement.textContent = JSON.stringify(articleMetadata, null, 2);
+		document.head.appendChild(metaDataElement);
+		document.querySelectorAll("a[href]").forEach(element => {
+			const href = element.getAttribute("href").trim();
+			if (href.startsWith(document.baseURI + "#")) {
+				element.setAttribute("href", href.substring(document.baseURI.length));
+			}
+		});
+		insertHighlightStylesheet(document);
+		maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
+		maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
+		reflowNotes();
+		onUpdate(false);
+	}
 
 
-button {
-  -moz-context-properties: fill;
-  color: var(--font-color);
-  fill: var(--icon-fill);
-}
+	async function cancelFormatPage() {
+		if (previousContent) {
+			const contentEditable = document.body.contentEditable;
+			if (pageCompressContent) {
+				document.replaceChild(previousContent, document.documentElement);
+				deserializeShadowRoots(document);
+				await initPage();
+			} else {
+				await init({ content: previousContent }, { reset: true });
+			}
+			document.body.contentEditable = contentEditable;
+			onUpdate(false);
+			previousContent = null;
+		}
+	}
 
 
-button:disabled {
-  fill: var(--icon-disabled-fill);
-}
+	function insertHighlightStylesheet(doc) {
+		if (!doc.querySelector("." + HIGHLIGHTS_STYLESHEET_CLASS)) {
+			const styleheetHighlights = getStyleElement(HIGHLIGHTS_WEB_STYLESHEET);
+			styleheetHighlights.classList.add(HIGHLIGHTS_STYLESHEET_CLASS);
+			doc.documentElement.appendChild(styleheetHighlights);
+		}
+	}
 
 
-.toolbar button::-moz-focus-inner {
-  border: 0;
-}
+	function getContent(compressHTML, updatedResources) {
+		unhighlightCutElement();
+		serializeShadowRoots(document);
+		const doc = document.cloneNode(true);
+		disableHighlight(doc);
+		resetSelectedElements(doc);
+		deserializeShadowRoots(doc);
+		deserializeShadowRoots(document);
+		doc.documentElement.classList.remove(CUT_MODE_CLASS);
+		doc.querySelectorAll("[" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "]").forEach(element => {
+			element.textContent = element.getAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
+			element.removeAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
+		});
+		doc.querySelectorAll("." + MASK_CLASS + ", " + singlefile.helper.INFOBAR_TAGNAME + ", ." + REMOVED_CONTENT_CLASS).forEach(element => element.remove());
+		if (includeInfobar) {
+			const options = singlefile.helper.extractInfobarData(doc);
+			options.openInfobar = openInfobar;
+			options.infobarPositionAbsolute = infobarPositionAbsolute;
+			options.infobarPositionTop = infobarPositionTop;
+			options.infobarPositionRight = infobarPositionRight;
+			options.infobarPositionBottom = infobarPositionBottom;
+			options.infobarPositionLeft = infobarPositionLeft;
+			singlefile.helper.appendInfobar(doc, options);
+		}
+		doc.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.remove(HIGHLIGHT_HIDDEN_CLASS));
+		doc.querySelectorAll(`template[${SHADOWROOT_ATTRIBUTE_NAME}]`).forEach(templateElement => {
+			const noteElement = templateElement.querySelector("." + NOTE_CLASS);
+			if (noteElement) {
+				noteElement.classList.remove(NOTE_HIDDEN_CLASS);
+			}
+			const mainElement = templateElement.querySelector("textarea");
+			if (mainElement) {
+				mainElement.textContent = mainElement.value;
+			}
+		});
+		doc.querySelectorAll("iframe").forEach(element => {
+			const pointerEvents = "pointer-events";
+			element.style.setProperty(pointerEvents, element.style.getPropertyValue("-sf-" + pointerEvents), element.style.getPropertyPriority("-sf-" + pointerEvents));
+			element.style.removeProperty("-sf-" + pointerEvents);
+		});
+		doc.body.removeAttribute("contentEditable");
+		const newResources = Object.keys(updatedResources).filter(url => updatedResources[url].type == "stylesheet").map(url => updatedResources[url]);
+		newResources.forEach(resource => {
+			const element = doc.createElement("style");
+			doc.body.appendChild(element);
+			element.textContent = resource.content;
+		});
+		if (pageCompressContent) {
+			const pageFilename = pageResources
+				.filter(resource => resource.filename.endsWith("index.html"))
+				.sort((resourceLeft, resourceRight) => resourceLeft.filename.length - resourceRight.filename.length)[0].filename;
+			const resources = pageResources.filter(resource => resource.parentResources.includes(pageFilename));
+			doc.querySelectorAll("[src]").forEach(element => resources.forEach(resource => {
+				if (element.src == resource.content) {
+					element.src = resource.name;
+				}
+			}));
+			let content = singlefile.helper.serialize(doc, compressHTML);
+			resources.sort((resourceLeft, resourceRight) => resourceRight.content.length - resourceLeft.content.length);
+			resources.forEach(resource => content = content.replaceAll(resource.content, resource.name));
+			return content + "<script " + SCRIPT_TEMPLATE_SHADOW_ROOT + ">" + getEmbedScript() + "</script>";
+		} else {
+			return singlefile.helper.serialize(doc, compressHTML) + "<script " + SCRIPT_TEMPLATE_SHADOW_ROOT + ">" + getEmbedScript() + "</script>";
+		}
+	}
 
 
-.toolbar-button {
-  position: relative;
-  width: 32px;
-  height: 32px;
-  padding: 0;
-  border: 1px solid var(--toolbar-button-border);
-  border-radius: 4px;
-  margin: 4px 0;
-  background-color: var(--toolbar-button-background);
-  background-size: 16px 16px;
-  background-position: center;
-  background-repeat: no-repeat;
-}
+	function onUpdate(saved) {
+		window.parent.postMessage(JSON.stringify({ "method": "onUpdate", saved }), "*");
+	}
 
 
-.toolbar-button:hover,
-.toolbar-button:focus-visible {
-  background-color: var(--toolbar-button-background-hover);
-  border-color: var(--toolbar-button-border-hover);
-  fill: var(--toolbar-button-foreground-hover);
-}
+	function waitResourcesLoad() {
+		return new Promise(resolve => {
+			let counterMutations = 0;
+			const done = () => {
+				observer.disconnect();
+				resolve();
+			};
+			let timeoutInit = setTimeout(done, 100);
+			const observer = new MutationObserver(() => {
+				if (counterMutations < 20) {
+					counterMutations++;
+					clearTimeout(timeoutInit);
+					timeoutInit = setTimeout(done, 100);
+				} else {
+					done();
+				}
+			});
+			observer.observe(document, { subtree: true, childList: true, attributes: true });
+		});
+	}
 
 
-.open .toolbar-button,
-.toolbar-button:hover:active {
-  background-color: var(--toolbar-button-background-active);
-  border-color: var(--toolbar-button-border-active);
-  color: var(--toolbar-button-foreground-active);
-  fill: var(--toolbar-button-foreground-active);
-}
+	function reflowNotes() {
+		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
+			const noteElement = containerElement.shadowRoot.querySelector("." + NOTE_CLASS);
+			const noteBoundingRect = noteElement.getBoundingClientRect();
+			const anchorElement = getAnchorElement(containerElement);
+			const anchorBoundingRect = anchorElement.getBoundingClientRect();
+			const maxX = anchorBoundingRect.x + Math.max(0, anchorBoundingRect.width - noteBoundingRect.width);
+			const minX = anchorBoundingRect.x;
+			const maxY = anchorBoundingRect.y + Math.max(0, anchorBoundingRect.height - NOTE_HEADER_HEIGHT);
+			const minY = anchorBoundingRect.y;
+			let left = parseInt(noteElement.style.getPropertyValue("left"));
+			let top = parseInt(noteElement.style.getPropertyValue("top"));
+			if (noteBoundingRect.x > maxX) {
+				left -= noteBoundingRect.x - maxX;
+			}
+			if (noteBoundingRect.x < minX) {
+				left += minX - noteBoundingRect.x;
+			}
+			if (noteBoundingRect.y > maxY) {
+				top -= noteBoundingRect.y - maxY;
+			}
+			if (noteBoundingRect.y < minY) {
+				top += minY - noteBoundingRect.y;
+			}
+			noteElement.style.setProperty("position", "absolute");
+			noteElement.style.setProperty("left", left + "px");
+			noteElement.style.setProperty("top", top + "px");
+		});
+	}
 
 
-.hover-label {
-  position: absolute;
-  top: 4px;
-  inset-inline-start: 36px;
-  line-height: 16px;
-  white-space: pre; /* make sure we don't wrap */
-  background-color: var(--tooltip-background);
-  color: var(--tooltip-foreground);
-  padding: 4px 8px;
-  border: 1px solid var(--tooltip-border);
-  border-radius: 2px;
-  visibility: hidden;
-  pointer-events: none;
-  /* Put above .dropdown .dropdown-popup, which has z-index: 1000. */
-  z-index: 1001;
-}
+	function resetHighlightedElement(element) {
+		element.classList.remove(HIGHLIGHT_CLASS);
+		element.classList.remove("single-file-highlight-yellow");
+		element.classList.remove("single-file-highlight-pink");
+		element.classList.remove("single-file-highlight-blue");
+		element.classList.remove("single-file-highlight-green");
+		delete element.dataset.singlefileHighlightId;
+	}
 
 
-/* Show the hover tooltip on non-dropdown buttons. */
-.toolbar-button:not(.dropdown-toggle):hover > .hover-label,
-.toolbar-button:not(.dropdown-toggle):focus-visible > .hover-label,
-/* Show the hover tooltip for dropdown buttons unless its dropdown is open. */
-:not(.open) > li > .dropdown-toggle:hover > .hover-label,
-:not(.open) > li > .dropdown-toggle:focus-visible > .hover-label {
-  visibility: visible;
-}
+	function serializeShadowRoots(node) {
+		node.querySelectorAll("*").forEach(element => {
+			const shadowRoot = getShadowRoot(element);
+			if (shadowRoot) {
+				serializeShadowRoots(shadowRoot);
+				const templateElement = document.createElement("template");
+				templateElement.setAttribute(SHADOWROOT_ATTRIBUTE_NAME, "open");
+				Array.from(shadowRoot.childNodes).forEach(childNode => templateElement.appendChild(childNode));
+				element.appendChild(templateElement);
+			}
+		});
+	}
 
 
-.dropdown {
-  text-align: center;
-  list-style: none;
-  margin: 0;
-  padding: 0;
-  position: relative;
-}
+	function deserializeShadowRoots(node) {
+		node.querySelectorAll(`template[${SHADOWROOT_ATTRIBUTE_NAME}]`).forEach(element => {
+			if (element.parentElement) {
+				let shadowRoot = getShadowRoot(element.parentElement);
+				if (shadowRoot) {
+					Array.from(element.childNodes).forEach(node => shadowRoot.appendChild(node));
+					element.remove();
+				} else {
+					try {
+						shadowRoot = element.parentElement.attachShadow({ mode: "open" });
+						const contentDocument = (new DOMParser()).parseFromString(element.innerHTML, "text/html");
+						Array.from(contentDocument.head.childNodes).forEach(node => shadowRoot.appendChild(node));
+						Array.from(contentDocument.body.childNodes).forEach(node => shadowRoot.appendChild(node));
+						// eslint-disable-next-line no-unused-vars
+					} catch (error) {
+						// ignored
+					}
+				}
+				if (shadowRoot) {
+					deserializeShadowRoots(shadowRoot);
+				}
+			}
+		});
+	}
 
 
-.dropdown li {
-  margin: 0;
-  padding: 0;
-}
+	function getMaskElement(className, containerClassName) {
+		let maskElement = document.documentElement.querySelector("." + className);
+		if (!maskElement) {
+			maskElement = document.createElement("div");
+			const maskContainerElement = document.createElement("div");
+			if (containerClassName) {
+				maskContainerElement.classList.add(containerClassName);
+			}
+			maskContainerElement.classList.add(MASK_CLASS);
+			const firstNote = document.querySelector(NOTE_TAGNAME);
+			if (firstNote && firstNote.parentElement == document.documentElement) {
+				document.documentElement.insertBefore(maskContainerElement, firstNote);
+			} else {
+				document.documentElement.appendChild(maskContainerElement);
+			}
+			maskElement.classList.add(className);
+			const maskShadow = maskContainerElement.attachShadow({ mode: "open" });
+			maskShadow.appendChild(getStyleElement(MASK_WEB_STYLESHEET));
+			maskShadow.appendChild(maskElement);
+			return maskElement;
+		}
+	}
 
 
-/* Popup */
+	function getStyleFormattedPage(contentWidth) {
+		return `
+	/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
-.dropdown .dropdown-popup {
-  text-align: start;
-  position: absolute;
-  inset-inline-start: 40px;
-  z-index: 1000;
-  background-color: var(--popup-background);
-  visibility: hidden;
-  border-radius: 4px;
-  border: 1px solid var(--popup-border);
-  box-shadow: 0 0 10px 0 var(--popup-shadow);
-  top: 0;
-}
+/* Avoid adding ID selector rules in this style sheet, since they could
+ * inadvertently match elements in the article content. */
 
 
-.open > .dropdown-popup {
-  visibility: visible;
+:root {
+  --grey-90-a10: rgba(12, 12, 13, 0.1);
+  --grey-90-a20: rgba(12, 12, 13, 0.2);
+  --grey-90-a30: rgba(12, 12, 13, 0.3);
+  --grey-90-a80: rgba(12, 12, 13, 0.8);
+  --grey-30: #d7d7db;
+  --blue-40: #45a1ff;
+  --blue-40-a30: rgba(69, 161, 255, 0.3);
+  --blue-60: #0060df;
+  --body-padding: 64px;
+  --font-size: 12;
+  --content-width: ${contentWidth}em;
+  --line-height: 1.6em;
 }
 }
 
 
-.dropdown-arrow {
-  position: absolute;
-  height: 24px;
-  width: 16px;
-  inset-inline-start: -16px;
-  background-image: url("chrome://global/skin/reader/RM-Type-Controls-Arrow.svg");
-  display: block;
-  -moz-context-properties: fill, stroke;
-  fill: var(--popup-background);
-  stroke: var(--opaque-popup-border);
-  pointer-events: none;
+body {
+  --main-background: #fff;
+  --main-foreground: #333;
+  --font-color: #000000;
+  --primary-color: #0B83FF;
+  --toolbar-border: var(--grey-90-a20);
+  --toolbar-transparent-border: transparent;
+  --toolbar-box-shadow: var(--grey-90-a10);
+  --toolbar-button-background: transparent;
+  --toolbar-button-background-hover: var(--grey-90-a10);
+  --toolbar-button-foreground-hover: var(--font-color);
+  --toolbar-button-background-active: var(--grey-90-a20);
+  --toolbar-button-foreground-active: var(--primary-color);
+  --toolbar-button-border: transparent;
+  --toolbar-button-border-hover: transparent;
+  --toolbar-button-border-active: transparent;
+  --tooltip-background: var(--grey-90-a80);
+  --tooltip-foreground: white;
+  --tooltip-border: transparent;
+  --popup-background: white;
+  --popup-border: rgba(0, 0, 0, 0.12);
+  --opaque-popup-border: #e0e0e0;
+  --popup-line: var(--grey-30);
+  --popup-shadow: rgba(49, 49, 49, 0.3);
+  --popup-button-background: #edecf0;
+  --popup-button-background-hover: hsla(0,0%,70%,.4);
+  --popup-button-foreground-hover: var(--font-color);
+  --popup-button-background-active: hsla(240,5%,5%,.15);
+  --selected-background: var(--blue-40-a30);
+  --selected-border: var(--blue-40);
+  --font-value-border: var(--grey-30);
+  --icon-fill: #3b3b3c;
+  --icon-disabled-fill: #8080807F;
+  --link-foreground: var(--blue-60);
+  --link-selected-foreground: #333;
+  --visited-link-foreground: #b5007f;
+  /* light colours */
 }
 }
 
 
-.dropdown-arrow:dir(rtl) {
-  transform: scaleX(-1);
+body.sepia {
+  --main-background: #f4ecd8;
+  --main-foreground: #5b4636;
+  --toolbar-border: #5b4636;
 }
 }
 
 
-/* Align the style dropdown arrow (narrate does its own) */
-.style-dropdown .dropdown-arrow {
-  top: 7px;
+body.dark {
+  --main-background: rgb(28, 27, 34);
+  --main-foreground: #eee;
+  --font-color: #fff;
+  --toolbar-border: #4a4a4b;
+  --toolbar-box-shadow: black;
+  --toolbar-button-background-hover: var(--grey-90-a30);
+  --toolbar-button-background-active: var(--grey-90-a80);
+  --tooltip-background: black;
+  --tooltip-foreground: white;
+  --popup-background: rgb(66,65,77);
+  --opaque-popup-border: #434146;
+  --popup-line: rgb(82, 82, 94);
+  --popup-button-background: #5c5c61;
+  --popup-button-background-active: hsla(0,0%,70%,.6);
+  --selected-background: #3E6D9A;
+  --font-value-border: #656468;
+  --icon-fill: #fff;
+  --icon-disabled-fill: #ffffff66;
+  --link-foreground: #45a1ff;
+  --link-selected-foreground: #fff;
+  --visited-link-foreground: #e675fd;
+  /* dark colours */
 }
 }
 
 
-/* Font style popup */
-
-.radio-button {
-  /* We visually hide these, but we keep them around so they can be focused
-   * and changed by interacting with them via the label, or the keyboard, or
-   * assistive technology.
-   */
-  opacity: 0;
-  pointer-events: none;
-  position: absolute;
-}
-
-.radiorow,
-.buttonrow {
-  display: flex;
-  align-content: center;
-  justify-content: center;
+body.hcm {
+  --main-background: Canvas;
+  --main-foreground: CanvasText;
+  --font-color: CanvasText;
+  --primary-color: SelectedItem;
+  --toolbar-border: CanvasText;
+   /* We need a true transparent but in HCM this would compute to an actual color,
+      so select the page's background color instead: */
+  --toolbar-transparent-border: Canvas;
+  --toolbar-box-shadow: Canvas;
+  --toolbar-button-background: ButtonFace;
+  --toolbar-button-background-hover: ButtonText;
+  --toolbar-button-foreground-hover: ButtonFace;
+  --toolbar-button-background-active: SelectedItem;
+  --toolbar-button-foreground-active: SelectedItemText;
+  --toolbar-button-border: ButtonText;
+  --toolbar-button-border-hover: ButtonText;
+  --toolbar-button-border-active: ButtonText;
+  --tooltip-background: Canvas;
+  --tooltip-foreground: CanvasText;
+  --tooltip-border: CanvasText;
+  --popup-background: Canvas;
+  --popup-border: CanvasText;
+  --opaque-popup-border: CanvasText;
+  --popup-line: CanvasText;
+  --popup-button-background: ButtonFace;
+  --popup-button-background-hover: ButtonText;
+  --popup-button-foreground-hover: ButtonFace;
+  --popup-button-background-active: ButtonText;
+  --selected-background: Canvas;
+  --selected-border: SelectedItem;
+  --font-value-border: CanvasText;
+  --icon-fill: ButtonText;
+  --icon-disabled-fill: GrayText;
+  --link-foreground: LinkText;
+  --link-selected-foreground: ActiveText;
+  --visited-link-foreground: VisitedText;
 }
 }
 
 
-.content-width-value,
-.font-size-value,
-.line-height-value {
-  box-sizing: border-box;
-  width: 36px;
-  height: 20px;
-  line-height: 20px;
-  display: flex;
-  justify-content: center;
-  align-content: center;
-  margin: auto;
-  border-radius: 10px;
-  border: 1px solid var(--font-value-border);
-  background-color: var(--popup-button-background);
+body {
+  margin: 0;
+  padding: var(--body-padding);
+  background-color: var(--main-background);
+  color: var(--main-foreground);
 }
 }
 
 
-.buttonrow > button {
-  border: 0;
-  height: 60px;
-  width: 90px;
-  background-color: transparent;
-  background-repeat: no-repeat;
-  background-position: center;
+body.loaded {
+  transition: color 0.4s, background-color 0.4s;
 }
 }
 
 
-.buttonrow > button:enabled:hover,
-.buttonrow > button:enabled:focus-visible {
-  background-color: var(--popup-button-background-hover);
-  color: var(--popup-button-foreground-hover);
-  fill: var(--popup-button-foreground-hover);
+body.dark *::-moz-selection {
+  background-color: var(--selected-background);
 }
 }
 
 
-.buttonrow > button:enabled:hover:active {
-  background-color: var(--popup-button-background-active);
+a::-moz-selection {
+  color: var(--link-selected-foreground);
 }
 }
 
 
-.radiorow:not(:last-child),
-.buttonrow:not(:last-child) {
-  border-bottom: 1px solid var(--popup-line);
+body.sans-serif,
+body.sans-serif .remove-button {
+  font-family: Helvetica, Arial, sans-serif;
 }
 }
 
 
-body.hcm .buttonrow.line-height-buttons {
-  /* On HCM the .color-scheme-buttons row is hidden, so remove the border from the row above it */
-  border-bottom: none;
+body.serif,
+body.serif .remove-button {
+  font-family: Georgia, "Times New Roman", serif;
 }
 }
 
 
-.radiorow > label {
-  position: relative;
-  box-sizing: border-box;
-  border-radius: 2px;
-  border: 2px solid var(--popup-border);
-}
+/* Override some controls and content styles based on color scheme */
 
 
-.radiorow > label[checked] {
-  border-color: var(--selected-border);
+body.light > .container > .header > .domain {
+  border-bottom-color: #333333 !important;
 }
 }
 
 
-/* For the hover style, we draw a line under the item by means of a
- * pseudo-element. Because these items are variable height, and
- * because their contents are variable height, position it absolutely,
- * and give it a width of 100% (the content width) + 4px for the 2 * 2px
- * border width.
- */
-.radiorow > input[type=radio]:focus-visible + label::after,
-.radiorow > label:hover::after {
-  content: "";
-  display: block;
-  border-bottom: 2px solid var(--selected-border);
-  width: calc(100% + 4px);
-  position: absolute;
-  /* to skip the 2 * 2px border + 2px spacing. */
-  bottom: -6px;
-  /* Match the start of the 2px border of the element: */
-  inset-inline-start: -2px;
+body.sepia > .container > .header > .domain {
+  border-bottom-color: #5b4636 !important;
 }
 }
 
 
-.font-type-buttons > label {
-  height: 64px;
-  width: 105px;
-  /* Slightly more space between these items. */
-  margin: 10px;
-  /* Center the Sans-serif / Serif labels */
-  text-align: center;
-  background-size: 63px 39px;
-  background-repeat: no-repeat;
-  background-position: center 18px;
-  background-color: var(--popup-button-background);
-  fill: currentColor;
-  -moz-context-properties: fill;
-  /* This mostly matches baselines, but because of differences in font
-   * baselines between serif and sans-serif, this isn't always enough. */
-  line-height: 1;
-  padding-top: 2px;
+body.dark > .container > .header > .domain {
+  border-bottom-color: #eeeeee !important;
 }
 }
 
 
-.font-type-buttons > label[checked] {
-  background-color: var(--selected-background);
+body.light blockquote {
+  border-inline-start: 2px solid #333333 !important;
 }
 }
 
 
-.sans-serif-button {
-  font-family: Helvetica, Arial, sans-serif;
-  background-image: url("chrome://global/skin/reader/RM-Sans-Serif.svg");
+body.sepia blockquote {
+  border-inline-start: 2px solid #5b4636 !important;
 }
 }
 
 
-/* Tweak padding to match the baseline on mac */
-:root[platform=macosx] .sans-serif-button {
-  padding-top: 3px;
+body.dark blockquote {
+  border-inline-start: 2px solid #eeeeee !important;
 }
 }
 
 
-.serif-button {
-  font-family: Georgia, "Times New Roman", serif;
-  background-image: url("chrome://global/skin/reader/RM-Serif.svg");
+.light-button {
+  color: #333333;
+  background-color: #ffffff;
 }
 }
 
 
-body.hcm .color-scheme-buttons {
-  /* Disallow selecting themes when HCM is on. */
-  display: none;
+.dark-button {
+  color: #eeeeee;
+  background-color: #1c1b22;
 }
 }
 
 
-.color-scheme-buttons > label {
-  padding: 12px;
-  height: 34px;
-  font-size: 12px;
-  /* Center the labels horizontally as well as vertically */
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  /* We want 10px between items, but there's no margin collapsing in flexbox. */
-  margin: 10px 5px;
+.sepia-button {
+  color: #5b4636;
+  background-color: #f4ecd8;
 }
 }
 
 
-.color-scheme-buttons > input:first-child + label {
-  margin-inline-start: 10px;
+.auto-button {
+  text-align: center;
 }
 }
 
 
-.color-scheme-buttons > label:last-child {
-  margin-inline-end: 10px;
+@media (prefers-color-scheme: dark) {
+  .auto-button {
+    background-color: #1c1b22;
+    color: #eeeeee;
+  }
 }
 }
 
 
-/* Toolbar icons */
-
-.close-button {
-  background-image: url("chrome://global/skin/icons/close.svg");
+@media not (prefers-color-scheme: dark) {
+  .auto-button {
+    background-color: #ffffff;
+    color: #333333;
+  }
 }
 }
 
 
-.style-button {
-  background-image: url("chrome://global/skin/reader/RM-Type-Controls-24x24.svg");
-}
+/* Loading/error message */
 
 
-.minus-button {
-  background-size: 18px 18px;
-  background-image: url("chrome://global/skin/reader/RM-Minus-24x24.svg");
+.reader-message {
+  margin-top: 40px;
+  display: none;
+  text-align: center;
+  width: 100%;
+  font-size: 0.9em;
 }
 }
 
 
-.plus-button {
-  background-size: 18px 18px;
-  background-image: url("chrome://global/skin/reader/RM-Plus-24x24.svg");
+/* Detector element to see if we're at the top of the doc or not. */
+.top-anchor {
+  position: absolute;
+  top: 0;
+  width: 0;
+  height: 5px;
+  pointer-events: none;
 }
 }
 
 
-.content-width-minus-button {
-  background-size: 42px 16px;
-  background-image: url("chrome://global/skin/reader/RM-Content-Width-Minus-42x16.svg");
-}
+/* Header */
 
 
-.content-width-plus-button {
-  background-size: 44px 16px;
-  background-image: url("chrome://global/skin/reader/RM-Content-Width-Plus-44x16.svg");
+.header {
+  text-align: start;
+  display: none;
 }
 }
 
 
-.line-height-minus-button {
-  background-size: 34px 14px;
-  background-image: url("chrome://global/skin/reader/RM-Line-Height-Minus-38x14.svg");
+.domain {
+  font-size: 0.9em;
+  line-height: 1.48em;
+  padding-bottom: 4px;
+  font-family: Helvetica, Arial, sans-serif;
+  text-decoration: none;
+  border-bottom: 1px solid;
+  color: var(--link-foreground);
 }
 }
 
 
-.line-height-plus-button {
-  background-size: 34px 24px;
-  background-image: url("chrome://global/skin/reader/RM-Line-Height-Plus-38x24.svg");
+.header > h1 {
+  font-size: 1.6em;
+  line-height: 1.25em;
+  width: 100%;
+  margin: 30px 0;
+  padding: 0;
 }
 }
 
 
-/* Mirror the line height buttons if the article is RTL. */
-.reader-controls[articledir="rtl"] .line-height-minus-button,
-.reader-controls[articledir="rtl"] .line-height-plus-button {
-  transform: scaleX(-1);
+.header > .credits {
+  font-size: 0.9em;
+  line-height: 1.48em;
+  margin: 0 0 10px;
+  padding: 0;
+  font-style: italic;
 }
 }
 
 
-@media print {
-  .toolbar {
-    display: none !important;
-  }
+.header > .meta-data {
+  font-size: 0.65em;
+  margin: 0 0 15px;
 }
 }
 
 
-/* Article content */
+.reader-estimated-time {
+  text-align: match-parent;
+}
 
 
-/* Note that any class names from the original article that we want to match on
- * must be added to CLASSES_TO_PRESERVE in ReaderMode.jsm, so that
- * Readability.js doesn't strip them out */
+/* Controls toolbar */
 
 
-.container {
-  margin: 0 auto;
-  font-size: var(--font-size);
-  max-width: var(--content-width);
-  line-height: var(--line-height);
-}
+.toolbar-container {
+  position: sticky;
+  z-index: 2;
+  top: 32px;
+  height: 0; /* take up no space, so body is at the top. */
 
 
-pre {
-  font-family: inherit;
+  /* As a stick container, we're positioned relative to the body. Move us to
+   * the edge of the viewport using margins, and take the width of
+   * the body padding into account for calculating our width.
+   */
+  margin-inline-start: calc(-1 * var(--body-padding));
+  width: max(var(--body-padding), calc((100% - var(--content-width)) / 2 + var(--body-padding)));
+  font-size: var(--font-size); /* Needed to ensure 'em' units match, is reset for .reader-controls */
 }
 }
 
 
-.moz-reader-content {
-  display: none;
-  font-size: 1em;
+.toolbar {
+  padding-block: 16px;
+  border: 1px solid var(--toolbar-border);
+  border-radius: 6px;
+  box-shadow: 0 2px 8px var(--toolbar-box-shadow);
+
+  width: 32px; /* basic width, without padding/border */
+
+  /* padding should be 16px, except if there's not enough space for that, in
+   * which case use half the available space for padding (=25% on each side).
+   * The 34px here is the width + borders. We use a variable because we need
+   * to know this size for the margin calculation.
+   */
+  --inline-padding: min(16px, calc(25% - 0.25 * 34px));
+  padding-inline: var(--inline-padding);
+
+  /* Keep a maximum of 96px distance to the body, but center once the margin
+   * gets too small. We need to set the start margin, however...
+   * To center, we'd want 50% of the container, but we'd subtract half our
+   * own width (16px) and half the border (1px) and the inline padding.
+   * When the other margin would be 96px, we want 100% - 96px - the complete
+   * width of the actual toolbar (34px + 2 * padding)
+   */
+  margin-inline-start: max(calc(50% - 17px - var(--inline-padding)), calc(100% - 96px - 34px - 2 * var(--inline-padding)));
+
+  font-family: Helvetica, Arial, sans-serif;
+  list-style: none;
+  user-select: none;
 }
 }
 
 
-@media print {
-  .moz-reader-content p,
-  .moz-reader-content code,
-  .moz-reader-content pre,
-  .moz-reader-content blockquote,
-  .moz-reader-content ul,
-  .moz-reader-content ol,
-  .moz-reader-content li,
-  .moz-reader-content figure,
-  .moz-reader-content .wp-caption {
-    margin: 0 0 10px !important;
-    padding: 0 !important;
+@media (prefers-reduced-motion: no-preference) {
+  .toolbar {
+    transition-property: border-color, box-shadow;
+    transition-duration: 250ms;
   }
   }
-}
 
 
-.moz-reader-content h1,
-.moz-reader-content h2,
-.moz-reader-content h3 {
-  font-weight: bold;
-}
+  .toolbar .toolbar-button {
+    transition-property: opacity;
+    transition-duration: 250ms;
+  }
 
 
-.moz-reader-content h1 {
-  font-size: 1.6em;
-  line-height: 1.25em;
-}
+  .toolbar-container.scrolled .toolbar:not(:hover, :focus-within) {
+    border-color: var(--toolbar-transparent-border);
+    box-shadow: 0 2px 8px transparent;
+  }
 
 
-.moz-reader-content h2 {
-  font-size: 1.2em;
-  line-height: 1.51em;
-}
+  .toolbar-container.scrolled .toolbar:not(:hover, :focus-within) .toolbar-button {
+    opacity: 0.6;
+  }
 
 
-.moz-reader-content h3 {
-  font-size: 1em;
-  line-height: 1.66em;
+  .toolbar-container.transition-location {
+    transition-duration: 250ms;
+    transition-property: width;
+  }
 }
 }
 
 
-.moz-reader-content a:link {
-  text-decoration: underline;
-  font-weight: normal;
+.toolbar-container.overlaps .toolbar-button {
+  opacity: 0.1;
 }
 }
 
 
-.moz-reader-content a:link,
-.moz-reader-content a:link:hover,
-.moz-reader-content a:link:active {
-  color: var(--link-foreground);
+.dropdown-open .toolbar {
+  border-color: var(--toolbar-transparent-border);
+  box-shadow: 0 2px 8px transparent;
 }
 }
 
 
-.moz-reader-content a:visited {
-  color: var(--visited-link-foreground);
+.reader-controls {
+  /* We use 'em's above this node to get it to the right size. However,
+   * the UI inside the toolbar should use a fixed, smaller size. */
+  font-size: 11px;
 }
 }
 
 
-.moz-reader-content * {
-  max-width: 100%;
-  height: auto;
+button {
+  -moz-context-properties: fill;
+  color: var(--font-color);
+  fill: var(--icon-fill);
 }
 }
 
 
-.moz-reader-content p,
-.moz-reader-content p,
-.moz-reader-content code,
-.moz-reader-content pre,
-.moz-reader-content blockquote,
-.moz-reader-content ul,
-.moz-reader-content ol,
-.moz-reader-content li,
-.moz-reader-content figure,
-.moz-reader-content .wp-caption {
-  margin: -10px -10px 20px;
-  padding: 10px;
-  border-radius: 5px;
+button:disabled {
+  fill: var(--icon-disabled-fill);
 }
 }
 
 
-.moz-reader-content li {
-  margin-bottom: 0;
+.toolbar button::-moz-focus-inner {
+  border: 0;
 }
 }
 
 
-.moz-reader-content li > ul,
-.moz-reader-content li > ol {
-  margin-bottom: -10px;
+.toolbar-button {
+  position: relative;
+  width: 32px;
+  height: 32px;
+  padding: 0;
+  border: 1px solid var(--toolbar-button-border);
+  border-radius: 4px;
+  margin: 4px 0;
+  background-color: var(--toolbar-button-background);
+  background-size: 16px 16px;
+  background-position: center;
+  background-repeat: no-repeat;
 }
 }
 
 
-.moz-reader-content p > img:only-child,
-.moz-reader-content p > a:only-child > img:only-child,
-.moz-reader-content .wp-caption img,
-.moz-reader-content figure img {
-  display: block;
+.toolbar-button:hover,
+.toolbar-button:focus-visible {
+  background-color: var(--toolbar-button-background-hover);
+  border-color: var(--toolbar-button-border-hover);
+  fill: var(--toolbar-button-foreground-hover);
 }
 }
 
 
-.moz-reader-content img[moz-reader-center] {
-  margin-inline: auto;
+.open .toolbar-button,
+.toolbar-button:hover:active {
+  background-color: var(--toolbar-button-background-active);
+  border-color: var(--toolbar-button-border-active);
+  color: var(--toolbar-button-foreground-active);
+  fill: var(--toolbar-button-foreground-active);
 }
 }
 
 
-.moz-reader-content .caption,
-.moz-reader-content .wp-caption-text
-.moz-reader-content figcaption {
-  font-size: 0.9em;
-  line-height: 1.48em;
-  font-style: italic;
+.hover-label {
+  position: absolute;
+  top: 4px;
+  inset-inline-start: 36px;
+  line-height: 16px;
+  white-space: pre; /* make sure we don't wrap */
+  background-color: var(--tooltip-background);
+  color: var(--tooltip-foreground);
+  padding: 4px 8px;
+  border: 1px solid var(--tooltip-border);
+  border-radius: 2px;
+  visibility: hidden;
+  pointer-events: none;
+  /* Put above .dropdown .dropdown-popup, which has z-index: 1000. */
+  z-index: 1001;
 }
 }
 
 
-.moz-reader-content pre {
-  white-space: pre-wrap;
+/* Show the hover tooltip on non-dropdown buttons. */
+.toolbar-button:not(.dropdown-toggle):hover > .hover-label,
+.toolbar-button:not(.dropdown-toggle):focus-visible > .hover-label,
+/* Show the hover tooltip for dropdown buttons unless its dropdown is open. */
+:not(.open) > li > .dropdown-toggle:hover > .hover-label,
+:not(.open) > li > .dropdown-toggle:focus-visible > .hover-label {
+  visibility: visible;
 }
 }
 
 
-.moz-reader-content blockquote {
+.dropdown {
+  text-align: center;
+  list-style: none;
+  margin: 0;
   padding: 0;
   padding: 0;
-  padding-inline-start: 16px;
+  position: relative;
 }
 }
 
 
-.moz-reader-content ul,
-.moz-reader-content ol {
+.dropdown li {
+  margin: 0;
   padding: 0;
   padding: 0;
 }
 }
 
 
-.moz-reader-content ul {
-  padding-inline-start: 30px;
-  list-style: disc;
-}
-
-.moz-reader-content ol {
-  padding-inline-start: 30px;
-}
+/* Popup */
 
 
-table,
-th,
-td {
-  border: 1px solid currentColor;
-  border-collapse: collapse;
-  padding: 6px;
-  vertical-align: top;
+.dropdown .dropdown-popup {
+  text-align: start;
+  position: absolute;
+  inset-inline-start: 40px;
+  z-index: 1000;
+  background-color: var(--popup-background);
+  visibility: hidden;
+  border-radius: 4px;
+  border: 1px solid var(--popup-border);
+  box-shadow: 0 0 10px 0 var(--popup-shadow);
+  top: 0;
 }
 }
 
 
-table {
-  margin: 5px;
+.open > .dropdown-popup {
+  visibility: visible;
 }
 }
 
 
-/* Visually hide (but don't display: none) screen reader elements */
-.moz-reader-content .visually-hidden,
-.moz-reader-content .visuallyhidden,
-.moz-reader-content .sr-only {
-  display: inline-block;
-  width: 1px;
-  height: 1px;
-  margin: -1px;
-  overflow: hidden;
-  padding: 0;
-  border-width: 0;
+.dropdown-arrow {
+  position: absolute;
+  height: 24px;
+  width: 16px;
+  inset-inline-start: -16px;
+  background-image: url("chrome://global/skin/reader/RM-Type-Controls-Arrow.svg");
+  display: block;
+  -moz-context-properties: fill, stroke;
+  fill: var(--popup-background);
+  stroke: var(--opaque-popup-border);
+  pointer-events: none;
 }
 }
 
 
-/* Hide elements with common "hidden" class names */
-.moz-reader-content .hidden,
-.moz-reader-content .invisible {
-  display: none;
+.dropdown-arrow:dir(rtl) {
+  transform: scaleX(-1);
 }
 }
 
 
-/* Enforce wordpress and similar emoji/smileys aren't sized to be full-width,
- * see bug 1399616 for context. */
-.moz-reader-content img.wp-smiley,
-.moz-reader-content img.emoji {
-  display: inline-block;
-  border-width: 0;
-  /* height: auto is implied from '.moz-reader-content *' rule. */
-  width: 1em;
-  margin: 0 .07em;
-  padding: 0;
+/* Align the style dropdown arrow (narrate does its own) */
+.style-dropdown .dropdown-arrow {
+  top: 7px;
 }
 }
 
 
-.reader-show-element {
-  display: initial;
-}
+/* Font style popup */
 
 
-/* Provide extra spacing for images that may be aided with accompanying element such as <figcaption> */
-.moz-reader-block-img:not(:last-child) {
-  margin-block-end: 12px;
+.radio-button {
+  /* We visually hide these, but we keep them around so they can be focused
+   * and changed by interacting with them via the label, or the keyboard, or
+   * assistive technology.
+   */
+  opacity: 0;
+  pointer-events: none;
+  position: absolute;
 }
 }
 
 
-.moz-reader-wide-table {
-  overflow-x: auto;
-  display: block;
+.radiorow,
+.buttonrow {
+  display: flex;
+  align-content: center;
+  justify-content: center;
 }
 }
 
 
-pre code {
-  background-color: var(--main-background);
-  border: 1px solid var(--font-color);
-  display: block;
-  overflow: auto;
-}`;
-
-	let NOTES_WEB_STYLESHEET, MASK_WEB_STYLESHEET, HIGHLIGHTS_WEB_STYLESHEET;
-	let selectedNote, anchorElement, maskNoteElement, maskPageElement, highlightSelectionMode, removeHighlightMode, resizingNoteMode, movingNoteMode, highlightColor, collapseNoteTimeout, cuttingOuterMode, cuttingMode, cuttingTouchTarget, cuttingPath, cuttingPathIndex, previousContent;
-	let removedElements = [], removedElementIndex = 0, initScriptContent, pageResources, pageUrl, pageCompressContent, includeInfobar, openInfobar, infobarPositionAbsolute, infobarPositionTop, infobarPositionBottom, infobarPositionLeft, infobarPositionRight;
-
-	globalThis.zip = singlefile.helper.zip;
-	initEventListeners();
-	new MutationObserver(initEventListeners).observe(document, { childList: true });
+.content-width-value,
+.font-size-value,
+.line-height-value {
+  box-sizing: border-box;
+  width: 36px;
+  height: 20px;
+  line-height: 20px;
+  display: flex;
+  justify-content: center;
+  align-content: center;
+  margin: auto;
+  border-radius: 10px;
+  border: 1px solid var(--font-value-border);
+  background-color: var(--popup-button-background);
+}
 
 
-	function initEventListeners() {
-		window.onmessage = async event => {
-			const message = JSON.parse(event.data);
-			if (message.method == "init") {
-				await init(message);
-			}
-			if (message.method == "addNote") {
-				addNote(message);
-			}
-			if (message.method == "displayNotes") {
-				document.querySelectorAll(NOTE_TAGNAME).forEach(noteElement => noteElement.shadowRoot.querySelector("." + NOTE_CLASS).classList.remove(NOTE_HIDDEN_CLASS));
-			}
-			if (message.method == "hideNotes") {
-				document.querySelectorAll(NOTE_TAGNAME).forEach(noteElement => noteElement.shadowRoot.querySelector("." + NOTE_CLASS).classList.add(NOTE_HIDDEN_CLASS));
-			}
-			if (message.method == "enableHighlight") {
-				if (highlightColor) {
-					document.documentElement.classList.remove(highlightColor + "-mode");
-				}
-				highlightColor = message.color;
-				highlightSelectionMode = true;
-				document.documentElement.classList.add(message.color + "-mode");
-			}
-			if (message.method == "disableHighlight") {
-				disableHighlight();
-				highlightSelectionMode = false;
-			}
-			if (message.method == "displayHighlights") {
-				document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.remove(HIGHLIGHT_HIDDEN_CLASS));
-			}
-			if (message.method == "hideHighlights") {
-				document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.add(HIGHLIGHT_HIDDEN_CLASS));
-			}
-			if (message.method == "enableRemoveHighlights") {
-				removeHighlightMode = true;
-				document.documentElement.classList.add("single-file-remove-highlights-mode");
-			}
-			if (message.method == "disableRemoveHighlights") {
-				removeHighlightMode = false;
-				document.documentElement.classList.remove("single-file-remove-highlights-mode");
-			}
-			if (message.method == "enableEditPage") {
-				document.body.contentEditable = true;
-				onUpdate(false);
-			}
-			if (message.method == "formatPage") {
-				formatPage(!message.applySystemTheme);
-			}
-			if (message.method == "cancelFormatPage") {
-				cancelFormatPage();
-			}
-			if (message.method == "disableEditPage") {
-				document.body.contentEditable = false;
-			}
-			if (message.method == "enableCutInnerPage") {
-				cuttingMode = true;
-				document.documentElement.classList.add(CUT_MODE_CLASS);
-			}
-			if (message.method == "enableCutOuterPage") {
-				cuttingOuterMode = true;
-				document.documentElement.classList.add(CUT_MODE_CLASS);
-			}
-			if (message.method == "disableCutInnerPage" || message.method == "disableCutOuterPage") {
-				if (message.method == "disableCutInnerPage") {
-					cuttingMode = false;
-				} else {
-					cuttingOuterMode = false;
-				}
-				document.documentElement.classList.remove(CUT_MODE_CLASS);
-				resetSelectedElements();
-				if (cuttingPath) {
-					unhighlightCutElement();
-					cuttingPath = null;
-				}
-			}
-			if (message.method == "undoCutPage") {
-				undoCutPage();
-			}
-			if (message.method == "undoAllCutPage") {
-				while (removedElementIndex) {
-					removedElements[removedElementIndex - 1].forEach(element => element.classList.remove(REMOVED_CONTENT_CLASS));
-					removedElementIndex--;
-				}
-			}
-			if (message.method == "redoCutPage") {
-				redoCutPage();
-			}
-			if (message.method == "getContent") {
-				onUpdate(true);
-				includeInfobar = message.includeInfobar;
-				openInfobar = message.openInfobar;
-				infobarPositionAbsolute = message.infobarPositionAbsolute;
-				infobarPositionTop = message.infobarPositionTop;
-				infobarPositionBottom = message.infobarPositionBottom;
-				infobarPositionLeft = message.infobarPositionLeft;
-				infobarPositionRight = message.infobarPositionRight;
-				let content = getContent(message.compressHTML, message.updatedResources);
-				if (initScriptContent) {
-					content = content.replace(/<script data-template-shadow-root src.*?<\/script>/g, initScriptContent);
-				}
-				let filename;
-				const pageOptions = loadOptionsFromPage(document);
-				if (pageOptions) {
-					pageOptions.backgroundSave = message.backgroundSave;
-					pageOptions.saveDate = new Date(pageOptions.saveDate);
-					pageOptions.visitDate = new Date(pageOptions.visitDate);
-					filename = await singlefile.helper.formatFilename(content, document, pageOptions);
-				}
-				if (message.sharePage) {
-					setLabels(message.labels);
-				}
-				if (pageCompressContent) {
-					const viewport = document.head.querySelector("meta[name=viewport]");
-					window.parent.postMessage(JSON.stringify({
-						method: "setContent",
-						content,
-						filename,
-						title: document.title,
-						doctype: singlefile.helper.getDoctypeString(document),
-						url: pageUrl,
-						viewport: viewport ? viewport.content : null,
-						compressContent: true,
-						foregroundSave: message.foregroundSave,
-						sharePage: message.sharePage,
-						documentHeight: document.documentElement.offsetHeight
-					}), "*");
-				} else {
-					if (message.foregroundSave || message.sharePage) {
-						try {
-							await downloadPageForeground({
-								content,
-								filename: filename || message.filename,
-								mimeType: "text/html"
-							}, { sharePage: message.sharePage });
-						} catch (error) {
-							console.log(error); // eslint-disable-line no-console
-							window.parent.postMessage(JSON.stringify({ method: "onError", error: error.message }), "*");
-						}
-					} else {
-						window.parent.postMessage(JSON.stringify({
-							method: "setContent",
-							content,
-							filename
-						}), "*");
-					}
-				}
-			}
-			if (message.method == "printPage") {
-				printPage();
-			}
-			if (message.method == "displayInfobar") {
-				singlefile.helper.displayIcon(document, true, {
-					openInfobar: message.openInfobar,
-					infobarPositionAbsolute: message.infobarPositionAbsolute,
-					infobarPositionTop: message.infobarPositionTop,
-					infobarPositionBottom: message.infobarPositionBottom,
-					infobarPositionLeft: message.infobarPositionLeft,
-					infobarPositionRight: message.infobarPositionRight
-				});
-				const infobarDoc = document.implementation.createHTMLDocument();
-				infobarDoc.body.appendChild(document.querySelector(singlefile.helper.INFOBAR_TAGNAME));
-				serializeShadowRoots(infobarDoc.body);
-				const content = singlefile.helper.serialize(infobarDoc, true);
-				window.parent.postMessage(JSON.stringify({
-					method: "displayInfobar",
-					content
-				}), "*");
-			}
-			if (message.method == "download") {
-				try {
-					await downloadPageForeground({
-						content: message.content,
-						filename: message.filename,
-						mimeType: message.mimeType
-					}, { sharePage: message.sharePage });
-				} catch (error) {
-					console.log(error); // eslint-disable-line no-console
-					window.parent.postMessage(JSON.stringify({ method: "onError", error: error.message }), "*");
-				}
-			}
-		};
-		window.onresize = reflowNotes;
-		document.ondragover = event => event.preventDefault();
-		document.ondrop = async event => {
-			if (event.dataTransfer.files && event.dataTransfer.files[0]) {
-				const file = event.dataTransfer.files[0];
-				event.preventDefault();
-				const content = new TextDecoder().decode(await file.arrayBuffer());
-				const compressContent = /<html[^>]* data-sfz[^>]*>/i.test(content);
-				if (compressContent) {
-					await init({ content: file, compressContent }, { filename: file.name });
-				} else {
-					await init({ content }, { filename: file.name });
-				}
-			}
-		};
-	}
+.buttonrow > button {
+  border: 0;
+  height: 60px;
+  width: 90px;
+  background-color: transparent;
+  background-repeat: no-repeat;
+  background-position: center;
+}
 
 
-	async function init({ content, password, compressContent }, { filename, reset } = {}) {
-		await initConstants();
-		if (compressContent) {
-			const zipOptions = {
-				workerScripts: { inflate: ["/lib/single-file-z-worker.js"] }
-			};
-			try {
-				const worker = new Worker(zipOptions.workerScripts.inflate[0]);
-				worker.terminate();
-				// eslint-disable-next-line no-unused-vars
-			} catch (error) {
-				delete zipOptions.workerScripts;
-			}
-			zipOptions.useWebWorkers = IS_NOT_SAFARI;
-			const { docContent, origDocContent, resources, url } = await singlefile.helper.extract(content, {
-				password,
-				prompt,
-				shadowRootScriptURL: new URL("/lib/single-file-extension-editor-init.js", document.baseURI).href,
-				zipOptions
-			});
-			pageResources = resources;
-			pageUrl = url;
-			pageCompressContent = true;
-			const contentDocument = (new DOMParser()).parseFromString(docContent, "text/html");
-			if (detectSavedPage(contentDocument)) {
-				await singlefile.helper.display(document, docContent, { disableFramePointerEvents: true });
-				const infobarElement = document.querySelector(singlefile.helper.INFOBAR_TAGNAME);
-				if (infobarElement) {
-					infobarElement.remove();
-				}
-				await initPage();
-				let icon;
-				const origContentDocument = (new DOMParser()).parseFromString(origDocContent, "text/html");
-				const iconElement = origContentDocument.querySelector("link[rel*=icon]");
-				if (iconElement) {
-					const iconResource = resources.find(resource => resource.filename == iconElement.getAttribute("href"));
-					if (iconResource && iconResource.content) {
-						const reader = new FileReader();
-						reader.readAsDataURL(await (await fetch(iconResource.content)).blob());
-						icon = await new Promise((resolve, reject) => {
-							reader.addEventListener("load", () => resolve(reader.result), false);
-							reader.addEventListener("error", reject, false);
-						});
-					} else {
-						icon = iconElement.href;
-					}
-				}
-				window.parent.postMessage(JSON.stringify({
-					method: "onInit",
-					title: document.title,
-					icon,
-					filename,
-					reset,
-					formatPageEnabled: isProbablyReaderable(document)
-				}), "*");
-			}
-		} else {
-			const initScriptContentMatch = content.match(/<script data-template-shadow-root.*<\/script>/);
-			if (initScriptContentMatch && initScriptContentMatch[0]) {
-				initScriptContent = initScriptContentMatch[0];
-			}
-			content = content.replace(/<script data-template-shadow-root.*<\/script>/g, "<script data-template-shadow-root src=/lib/single-file-extension-editor-init.js></script>");
-			const contentDocument = (new DOMParser()).parseFromString(content, "text/html");
-			if (detectSavedPage(contentDocument)) {
-				if (contentDocument.doctype) {
-					if (document.doctype) {
-						document.replaceChild(contentDocument.doctype, document.doctype);
-					} else {
-						document.insertBefore(contentDocument.doctype, document.documentElement);
-					}
-				} else if (document.doctype) {
-					document.doctype.remove();
-				}
-				const infobarElement = contentDocument.querySelector(singlefile.helper.INFOBAR_TAGNAME);
-				if (infobarElement) {
-					infobarElement.remove();
-				}
-				contentDocument.querySelectorAll("noscript").forEach(element => {
-					element.setAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME, element.innerHTML);
-					element.textContent = "";
-				});
-				contentDocument.querySelectorAll("iframe").forEach(element => {
-					const pointerEvents = "pointer-events";
-					element.style.setProperty("-sf-" + pointerEvents, element.style.getPropertyValue(pointerEvents), element.style.getPropertyPriority(pointerEvents));
-					element.style.setProperty(pointerEvents, "none", "important");
-				});
-				document.replaceChild(contentDocument.documentElement, document.documentElement);
-				document.querySelectorAll("[data-single-file-note-refs]").forEach(noteRefElement => noteRefElement.dataset.singleFileNoteRefs = noteRefElement.dataset.singleFileNoteRefs.replace(/,/g, " "));
-				deserializeShadowRoots(document);
-				document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => attachNoteListeners(containerElement, true));
-				insertHighlightStylesheet(document);
-				maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
-				maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
-				document.documentElement.onmousedown = onMouseDown;
-				document.documentElement.onmouseup = document.documentElement.ontouchend = onMouseUp;
-				document.documentElement.onmouseover = onMouseOver;
-				document.documentElement.onmouseout = onMouseOut;
-				document.documentElement.onkeydown = onKeyDown;
-				document.documentElement.ontouchstart = document.documentElement.ontouchmove = onTouchMove;
-				window.onclick = event => event.preventDefault();
-				const iconElement = document.querySelector("link[rel*=icon]");
-				window.parent.postMessage(JSON.stringify({
-					method: "onInit",
-					title: document.title,
-					icon: iconElement && iconElement.href,
-					filename,
-					reset,
-					formatPageEnabled: isProbablyReaderable(document)
-				}), "*");
-			}
-		}
-	}
+.buttonrow > button:enabled:hover,
+.buttonrow > button:enabled:focus-visible {
+  background-color: var(--popup-button-background-hover);
+  color: var(--popup-button-foreground-hover);
+  fill: var(--popup-button-foreground-hover);
+}
 
 
-	function loadOptionsFromPage(doc) {
-		const optionsElement = doc.body.querySelector("script[type=\"application/json\"][" + SCRIPT_OPTIONS + "]");
-		if (optionsElement) {
-			return JSON.parse(optionsElement.textContent);
-		}
-	}
+.buttonrow > button:enabled:hover:active {
+  background-color: var(--popup-button-background-active);
+}
 
 
-	async function initPage() {
-		document.querySelectorAll("iframe").forEach(element => {
-			const pointerEvents = "pointer-events";
-			element.style.setProperty("-sf-" + pointerEvents, element.style.getPropertyValue(pointerEvents), element.style.getPropertyPriority(pointerEvents));
-			element.style.setProperty(pointerEvents, "none", "important");
-		});
-		document.querySelectorAll("[data-single-file-note-refs]").forEach(noteRefElement => noteRefElement.dataset.singleFileNoteRefs = noteRefElement.dataset.singleFileNoteRefs.replace(/,/g, " "));
-		deserializeShadowRoots(document);
-		reflowNotes();
-		await waitResourcesLoad();
-		reflowNotes();
-		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => attachNoteListeners(containerElement, true));
-		insertHighlightStylesheet(document);
-		maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
-		maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
-		document.documentElement.onmousedown = onMouseDown;
-		document.documentElement.onmouseup = document.documentElement.ontouchend = onMouseUp;
-		document.documentElement.onmouseover = onMouseOver;
-		document.documentElement.onmouseout = onMouseOut;
-		document.documentElement.onkeydown = onKeyDown;
-		document.documentElement.ontouchstart = document.documentElement.ontouchmove = onTouchMove;
-		window.onclick = event => event.preventDefault();
-	}
+.radiorow:not(:last-child),
+.buttonrow:not(:last-child) {
+  border-bottom: 1px solid var(--popup-line);
+}
 
 
-	async function initConstants() {
-		[NOTES_WEB_STYLESHEET, MASK_WEB_STYLESHEET, HIGHLIGHTS_WEB_STYLESHEET] = await Promise.all([
-			minifyText(await ((await fetch("../pages/editor-note-web.css")).text())),
-			minifyText(await ((await fetch("../pages/editor-mask-web.css")).text())),
-			minifyText(await ((await fetch("../pages/editor-frame-web.css")).text()))
-		]);
-	}
+body.hcm .buttonrow.line-height-buttons {
+  /* On HCM the .color-scheme-buttons row is hidden, so remove the border from the row above it */
+  border-bottom: none;
+}
 
 
-	function addNote({ color }) {
-		const containerElement = document.createElement(NOTE_TAGNAME);
-		const noteElement = document.createElement("div");
-		const headerElement = document.createElement("header");
-		const blockquoteElement = document.createElement("blockquote");
-		const mainElement = document.createElement("textarea");
-		const resizeElement = document.createElement("div");
-		const removeNoteElement = document.createElement("img");
-		const anchorIconElement = document.createElement("img");
-		const noteShadow = containerElement.attachShadow({ mode: "open" });
-		headerElement.appendChild(anchorIconElement);
-		headerElement.appendChild(removeNoteElement);
-		blockquoteElement.appendChild(mainElement);
-		noteElement.appendChild(headerElement);
-		noteElement.appendChild(blockquoteElement);
-		noteElement.appendChild(resizeElement);
-		noteShadow.appendChild(getStyleElement(NOTES_WEB_STYLESHEET));
-		noteShadow.appendChild(noteElement);
-		const notesElements = Array.from(document.querySelectorAll(NOTE_TAGNAME));
-		const noteId = Math.max.call(Math, 0, ...notesElements.map(noteElement => Number(noteElement.dataset.noteId))) + 1;
-		blockquoteElement.cite = "https://www.w3.org/TR/annotation-model/#selector(type=CssSelector,value=[data-single-file-note-refs~=\"" + noteId + "\"])";
-		noteElement.classList.add(NOTE_CLASS);
-		noteElement.classList.add(NOTE_ANCHORED_CLASS);
-		noteElement.classList.add(color);
-		noteElement.dataset.color = color;
-		mainElement.dir = "auto";
-		const boundingRectDocument = document.documentElement.getBoundingClientRect();
-		let positionX = NOTE_INITIAL_WIDTH + NOTE_INITIAL_POSITION_X - 1 - boundingRectDocument.x;
-		let positionY = NOTE_INITIAL_HEIGHT + NOTE_INITIAL_POSITION_Y - 1 - boundingRectDocument.y;
-		while (Array.from(document.elementsFromPoint(positionX - window.scrollX, positionY - window.scrollY)).find(element => element.tagName.toLowerCase() == NOTE_TAGNAME)) {
-			positionX += NOTE_INITIAL_POSITION_X;
-			positionY += NOTE_INITIAL_POSITION_Y;
-		}
-		noteElement.style.setProperty("left", (positionX - NOTE_INITIAL_WIDTH - 1) + "px");
-		noteElement.style.setProperty("top", (positionY - NOTE_INITIAL_HEIGHT - 1) + "px");
-		resizeElement.className = "note-resize";
-		resizeElement.ondragstart = event => event.preventDefault();
-		removeNoteElement.className = "note-remove";
-		removeNoteElement.src = BUTTON_CLOSE_URL;
-		removeNoteElement.ondragstart = event => event.preventDefault();
-		anchorIconElement.className = "note-anchor";
-		anchorIconElement.src = BUTTON_ANCHOR_URL;
-		anchorIconElement.ondragstart = event => event.preventDefault();
-		containerElement.dataset.noteId = noteId;
-		addNoteRef(document.documentElement, noteId);
-		attachNoteListeners(containerElement, true);
-		document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
-		noteElement.classList.add(NOTE_SELECTED_CLASS);
-		selectedNote = noteElement;
-		onUpdate(false);
-	}
+.radiorow > label {
+  position: relative;
+  box-sizing: border-box;
+  border-radius: 2px;
+  border: 2px solid var(--popup-border);
+}
 
 
-	function attachNoteListeners(containerElement, editable = false) {
-		const SELECT_PX_THRESHOLD = 4;
-		const COLLAPSING_NOTE_DELAY = 750;
-		const noteShadow = containerElement.shadowRoot;
-		const noteElement = noteShadow.childNodes[1];
-		const headerElement = noteShadow.querySelector("header");
-		const mainElement = noteShadow.querySelector("textarea");
-		const noteId = containerElement.dataset.noteId;
-		const resizeElement = noteShadow.querySelector(".note-resize");
-		const anchorIconElement = noteShadow.querySelector(".note-anchor");
-		const removeNoteElement = noteShadow.querySelector(".note-remove");
-		mainElement.readOnly = !editable;
-		if (!editable) {
-			anchorIconElement.style.setProperty("display", "none", "important");
-		} else {
-			anchorIconElement.style.removeProperty("display");
-		}
-		headerElement.ontouchstart = headerElement.onmousedown = event => {
-			if (event.target == headerElement) {
-				collapseNoteTimeout = setTimeout(() => {
-					noteElement.classList.toggle("note-collapsed");
-					hideMaskNote();
-				}, COLLAPSING_NOTE_DELAY);
-				event.preventDefault();
-				const position = getPosition(event);
-				const clientX = position.clientX;
-				const clientY = position.clientY;
-				const boundingRect = noteElement.getBoundingClientRect();
-				const deltaX = clientX - boundingRect.left;
-				const deltaY = clientY - boundingRect.top;
-				maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS);
-				document.documentElement.style.setProperty("user-select", "none", "important");
-				anchorElement = getAnchorElement(containerElement);
-				displayMaskNote();
-				selectNote(noteElement);
-				moveNote(event, deltaX, deltaY);
-				movingNoteMode = { event, deltaX, deltaY };
-				document.documentElement.ontouchmove = document.documentElement.onmousemove = event => {
-					clearTimeout(collapseNoteTimeout);
-					if (!movingNoteMode) {
-						movingNoteMode = { deltaX, deltaY };
-					}
-					movingNoteMode.event = event;
-					moveNote(event, deltaX, deltaY);
-				};
-			}
-		};
-		resizeElement.ontouchstart = resizeElement.onmousedown = event => {
-			event.preventDefault();
-			resizingNoteMode = true;
-			selectNote(noteElement);
-			maskPageElement.classList.add(PAGE_MASK_ACTIVE_CLASS);
-			document.documentElement.style.setProperty("user-select", "none", "important");
-			document.documentElement.ontouchmove = document.documentElement.onmousemove = event => {
-				event.preventDefault();
-				const { clientX, clientY } = getPosition(event);
-				const boundingRectNote = noteElement.getBoundingClientRect();
-				noteElement.style.width = clientX - boundingRectNote.left + "px";
-				noteElement.style.height = clientY - boundingRectNote.top + "px";
-			};
-		};
-		anchorIconElement.ontouchend = anchorIconElement.onclick = event => {
-			event.preventDefault();
-			noteElement.classList.toggle(NOTE_ANCHORED_CLASS);
-			if (!noteElement.classList.contains(NOTE_ANCHORED_CLASS)) {
-				deleteNoteRef(containerElement, noteId);
-				addNoteRef(document.documentElement, noteId);
-			}
-			onUpdate(false);
-		};
-		removeNoteElement.ontouchend = removeNoteElement.onclick = event => {
-			event.preventDefault();
-			deleteNoteRef(containerElement, noteId);
-			containerElement.remove();
-		};
-		noteElement.onmousedown = () => {
-			selectNote(noteElement);
-		};
+.radiorow > label[checked] {
+  border-color: var(--selected-border);
+}
+
+/* For the hover style, we draw a line under the item by means of a
+ * pseudo-element. Because these items are variable height, and
+ * because their contents are variable height, position it absolutely,
+ * and give it a width of 100% (the content width) + 4px for the 2 * 2px
+ * border width.
+ */
+.radiorow > input[type=radio]:focus-visible + label::after,
+.radiorow > label:hover::after {
+  content: "";
+  display: block;
+  border-bottom: 2px solid var(--selected-border);
+  width: calc(100% + 4px);
+  position: absolute;
+  /* to skip the 2 * 2px border + 2px spacing. */
+  bottom: -6px;
+  /* Match the start of the 2px border of the element: */
+  inset-inline-start: -2px;
+}
+
+.font-type-buttons > label {
+  height: 64px;
+  width: 105px;
+  /* Slightly more space between these items. */
+  margin: 10px;
+  /* Center the Sans-serif / Serif labels */
+  text-align: center;
+  background-size: 63px 39px;
+  background-repeat: no-repeat;
+  background-position: center 18px;
+  background-color: var(--popup-button-background);
+  fill: currentColor;
+  -moz-context-properties: fill;
+  /* This mostly matches baselines, but because of differences in font
+   * baselines between serif and sans-serif, this isn't always enough. */
+  line-height: 1;
+  padding-top: 2px;
+}
+
+.font-type-buttons > label[checked] {
+  background-color: var(--selected-background);
+}
+
+.sans-serif-button {
+  font-family: Helvetica, Arial, sans-serif;
+  background-image: url("chrome://global/skin/reader/RM-Sans-Serif.svg");
+}
+
+/* Tweak padding to match the baseline on mac */
+:root[platform=macosx] .sans-serif-button {
+  padding-top: 3px;
+}
+
+.serif-button {
+  font-family: Georgia, "Times New Roman", serif;
+  background-image: url("chrome://global/skin/reader/RM-Serif.svg");
+}
+
+body.hcm .color-scheme-buttons {
+  /* Disallow selecting themes when HCM is on. */
+  display: none;
+}
+
+.color-scheme-buttons > label {
+  padding: 12px;
+  height: 34px;
+  font-size: 12px;
+  /* Center the labels horizontally as well as vertically */
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  /* We want 10px between items, but there's no margin collapsing in flexbox. */
+  margin: 10px 5px;
+}
 
 
-		function moveNote(event, deltaX, deltaY) {
-			event.preventDefault();
-			const { clientX, clientY } = getPosition(event);
-			noteElement.classList.add(NOTE_MOVING_CLASS);
-			if (editable) {
-				if (noteElement.classList.contains(NOTE_ANCHORED_CLASS)) {
-					deleteNoteRef(containerElement, noteId);
-					anchorElement = getTarget(clientX, clientY) || document.documentElement;
-					addNoteRef(anchorElement, noteId);
-				} else {
-					anchorElement = document.documentElement;
-				}
-			}
-			document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
-			noteElement.style.setProperty("left", (clientX - deltaX) + "px");
-			noteElement.style.setProperty("top", (clientY - deltaY) + "px");
-			noteElement.style.setProperty("position", "fixed");
-			displayMaskNote();
-		}
+.color-scheme-buttons > input:first-child + label {
+  margin-inline-start: 10px;
+}
 
 
-		function displayMaskNote() {
-			if (anchorElement == document.documentElement || anchorElement == document.documentElement) {
-				hideMaskNote();
-			} else {
-				const boundingRectAnchor = anchorElement.getBoundingClientRect();
-				maskNoteElement.classList.add(NOTE_MASK_MOVING_CLASS);
-				if (selectedNote) {
-					maskNoteElement.classList.add(selectedNote.dataset.color);
-				}
-				maskNoteElement.style.setProperty("top", (boundingRectAnchor.y - 3) + "px");
-				maskNoteElement.style.setProperty("left", (boundingRectAnchor.x - 3) + "px");
-				maskNoteElement.style.setProperty("width", (boundingRectAnchor.width + 3) + "px");
-				maskNoteElement.style.setProperty("height", (boundingRectAnchor.height + 3) + "px");
-			}
-		}
+.color-scheme-buttons > label:last-child {
+  margin-inline-end: 10px;
+}
 
 
-		function hideMaskNote() {
-			maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS);
-			if (selectedNote) {
-				maskNoteElement.classList.remove(selectedNote.dataset.color);
-			}
-		}
+/* Toolbar icons */
 
 
-		function selectNote(noteElement) {
-			if (selectedNote) {
-				selectedNote.classList.remove(NOTE_SELECTED_CLASS);
-				maskNoteElement.classList.remove(selectedNote.dataset.color);
-			}
-			noteElement.classList.add(NOTE_SELECTED_CLASS);
-			noteElement.classList.add(noteElement.dataset.color);
-			selectedNote = noteElement;
-		}
+.close-button {
+  background-image: url("chrome://global/skin/icons/close.svg");
+}
 
 
-		function getTarget(clientX, clientY) {
-			const targets = Array.from(document.elementsFromPoint(clientX, clientY)).filter(element => element.matches("html *:not(" + NOTE_TAGNAME + "):not(." + MASK_CLASS + ")"));
-			if (!targets.includes(document.documentElement)) {
-				targets.push(document.documentElement);
-			}
-			let newTarget, target = targets[0], boundingRect = target.getBoundingClientRect();
-			newTarget = determineTargetElement("floor", target, clientX - boundingRect.left, getMatchedParents(target, "left"));
-			if (newTarget == target) {
-				newTarget = determineTargetElement("ceil", target, boundingRect.left + boundingRect.width - clientX, getMatchedParents(target, "right"));
-			}
-			if (newTarget == target) {
-				newTarget = determineTargetElement("floor", target, clientY - boundingRect.top, getMatchedParents(target, "top"));
-			}
-			if (newTarget == target) {
-				newTarget = determineTargetElement("ceil", target, boundingRect.top + boundingRect.height - clientY, getMatchedParents(target, "bottom"));
-			}
-			target = newTarget;
-			while (boundingRect = target && target.getBoundingClientRect(), boundingRect && boundingRect.width <= SELECT_PX_THRESHOLD && boundingRect.height <= SELECT_PX_THRESHOLD) {
-				target = target.parentElement;
-			}
-			return target;
-		}
+.style-button {
+  background-image: url("chrome://global/skin/reader/RM-Type-Controls-24x24.svg");
+}
 
 
-		function getMatchedParents(target, property) {
-			let element = target, matchedParent, parents = [];
-			do {
-				const boundingRect = element.getBoundingClientRect();
-				if (element.parentElement && !element.parentElement.tagName.toLowerCase() != NOTE_TAGNAME && !element.classList.contains(MASK_CLASS)) {
-					const parentBoundingRect = element.parentElement.getBoundingClientRect();
-					matchedParent = Math.abs(parentBoundingRect[property] - boundingRect[property]) <= SELECT_PX_THRESHOLD;
-					if (matchedParent) {
-						if (element.parentElement.clientWidth > SELECT_PX_THRESHOLD && element.parentElement.clientHeight > SELECT_PX_THRESHOLD &&
-							((element.parentElement.clientWidth - element.clientWidth > SELECT_PX_THRESHOLD) || (element.parentElement.clientHeight - element.clientHeight > SELECT_PX_THRESHOLD))) {
-							parents.push(element.parentElement);
-						}
-						element = element.parentElement;
-					}
-				} else {
-					matchedParent = false;
-				}
-			} while (matchedParent && element);
-			return parents;
-		}
+.minus-button {
+  background-size: 18px 18px;
+  background-image: url("chrome://global/skin/reader/RM-Minus-24x24.svg");
+}
 
 
-		function determineTargetElement(roundingMethod, target, widthDistance, parents) {
-			if (Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) <= parents.length) {
-				target = parents[parents.length - Math[roundingMethod](widthDistance / SELECT_PX_THRESHOLD) - 1];
-			}
-			return target;
-		}
-	}
+.plus-button {
+  background-size: 18px 18px;
+  background-image: url("chrome://global/skin/reader/RM-Plus-24x24.svg");
+}
 
 
-	function onMouseDown(event) {
-		if ((cuttingMode || cuttingOuterMode) && cuttingPath) {
-			event.preventDefault();
-		}
-	}
+.content-width-minus-button {
+  background-size: 42px 16px;
+  background-image: url("chrome://global/skin/reader/RM-Content-Width-Minus-42x16.svg");
+}
 
 
-	function onMouseUp(event) {
-		if (highlightSelectionMode) {
-			event.preventDefault();
-			highlightSelection();
-			onUpdate(false);
-		}
-		if (removeHighlightMode) {
-			event.preventDefault();
-			let element = event.target, done;
-			while (element && !done) {
-				if (element.classList.contains(HIGHLIGHT_CLASS)) {
-					document.querySelectorAll("." + HIGHLIGHT_CLASS + "[data-singlefile-highlight-id=" + JSON.stringify(element.dataset.singlefileHighlightId) + "]").forEach(highlightedElement => {
-						resetHighlightedElement(highlightedElement);
-						onUpdate(false);
-					});
-					done = true;
-				}
-				element = element.parentElement;
-			}
-		}
-		if (resizingNoteMode) {
-			event.preventDefault();
-			resizingNoteMode = false;
-			document.documentElement.style.removeProperty("user-select");
-			maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS);
-			document.documentElement.ontouchmove = onTouchMove;
-			document.documentElement.onmousemove = null;
-			onUpdate(false);
-		}
-		if (movingNoteMode) {
-			event.preventDefault();
-			anchorNote(movingNoteMode.event || event, selectedNote, movingNoteMode.deltaX, movingNoteMode.deltaY);
-			movingNoteMode = null;
-			document.documentElement.ontouchmove = onTouchMove;
-			document.documentElement.onmousemove = null;
-			onUpdate(false);
-		}
-		if ((cuttingMode || cuttingOuterMode) && cuttingPath) {
-			event.preventDefault();
-			if (event.ctrlKey) {
-				const element = cuttingPath[cuttingPathIndex];
-				element.classList.toggle(cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS);
-			} else {
-				validateCutElement(event.shiftKey);
-			}
-		}
-		if (collapseNoteTimeout) {
-			clearTimeout(collapseNoteTimeout);
-			collapseNoteTimeout = null;
-		}
-	}
+.content-width-plus-button {
+  background-size: 44px 16px;
+  background-image: url("chrome://global/skin/reader/RM-Content-Width-Plus-44x16.svg");
+}
 
 
-	function onMouseOver(event) {
-		if (cuttingMode || cuttingOuterMode) {
-			const target = event.target;
-			if (target.classList) {
-				let ancestorFound;
-				document.querySelectorAll("." + (cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS)).forEach(element => {
-					if (element == target || isAncestor(element, target) || isAncestor(target, element)) {
-						ancestorFound = element;
-					}
-				});
-				if (ancestorFound) {
-					cuttingPath = [ancestorFound];
-				} else {
-					cuttingPath = getParents(event.target);
-				}
-				cuttingPathIndex = 0;
-				highlightCutElement();
-			}
-		}
-	}
+.line-height-minus-button {
+  background-size: 34px 14px;
+  background-image: url("chrome://global/skin/reader/RM-Line-Height-Minus-38x14.svg");
+}
 
 
-	function onMouseOut() {
-		if (cuttingMode || cuttingOuterMode) {
-			if (cuttingPath) {
-				unhighlightCutElement();
-				cuttingPath = null;
-			}
-		}
-	}
+.line-height-plus-button {
+  background-size: 34px 24px;
+  background-image: url("chrome://global/skin/reader/RM-Line-Height-Plus-38x24.svg");
+}
 
 
-	function onTouchMove(event) {
-		if (cuttingMode || cuttingOuterMode) {
-			event.preventDefault();
-			const { clientX, clientY } = getPosition(event);
-			const target = document.elementFromPoint(clientX, clientY);
-			if (cuttingTouchTarget != target) {
-				onMouseOut();
-				if (target) {
-					cuttingTouchTarget = target;
-					onMouseOver({ target });
-				}
-			}
-		}
-	}
+/* Mirror the line height buttons if the article is RTL. */
+.reader-controls[articledir="rtl"] .line-height-minus-button,
+.reader-controls[articledir="rtl"] .line-height-plus-button {
+  transform: scaleX(-1);
+}
+
+@media print {
+  .toolbar {
+    display: none !important;
+  }
+}
+
+/* Article content */
+
+/* Note that any class names from the original article that we want to match on
+ * must be added to CLASSES_TO_PRESERVE in ReaderMode.jsm, so that
+ * Readability.js doesn't strip them out */
 
 
-	function onKeyDown(event) {
-		if (cuttingMode || cuttingOuterMode) {
-			if (event.code == "Tab") {
-				if (cuttingPath) {
-					const delta = event.shiftKey ? -1 : 1;
-					let element = cuttingPath[cuttingPathIndex];
-					let nextElement = cuttingPath[cuttingPathIndex + delta];
-					if (nextElement) {
-						let pathIndex = cuttingPathIndex + delta;
-						while (
-							nextElement &&
-							(
-								(delta == 1 &&
-									element.getBoundingClientRect().width >= nextElement.getBoundingClientRect().width &&
-									element.getBoundingClientRect().height >= nextElement.getBoundingClientRect().height) ||
-								(delta == -1 &&
-									element.getBoundingClientRect().width <= nextElement.getBoundingClientRect().width &&
-									element.getBoundingClientRect().height <= nextElement.getBoundingClientRect().height))) {
-							pathIndex += delta;
-							nextElement = cuttingPath[pathIndex];
-						}
-						if (nextElement && nextElement.classList && nextElement != document.body && nextElement != document.documentElement) {
-							unhighlightCutElement();
-							cuttingPathIndex = pathIndex;
-							highlightCutElement();
-						}
-					}
-				}
-				event.preventDefault();
-			}
-			if (event.code == "Space") {
-				if (cuttingPath) {
-					if (event.ctrlKey) {
-						const element = cuttingPath[cuttingPathIndex];
-						element.classList.add(cuttingMode ? CUT_SELECTED_CLASS : CUT_OUTER_SELECTED_CLASS);
-					} else {
-						validateCutElement(event.shiftKey);
-					}
-					event.preventDefault();
-				}
-			}
-			if (event.code == "Escape") {
-				resetSelectedElements();
-				event.preventDefault();
-			}
-			if (event.key.toLowerCase() == "z" && event.ctrlKey) {
-				if (event.shiftKey) {
-					redoCutPage();
-				} else {
-					undoCutPage();
-				}
-				event.preventDefault();
-			}
-		}
-		if (event.key.toLowerCase() == "s" && event.ctrlKey) {
-			window.parent.postMessage(JSON.stringify({ "method": "savePage" }), "*");
-			event.preventDefault();
-		}
-		if (event.key.toLowerCase() == "p" && event.ctrlKey) {
-			printPage();
-			event.preventDefault();
-		}
-	}
+.container {
+  margin: 0 auto;
+  font-size: var(--font-size);
+  max-width: var(--content-width);
+  line-height: var(--line-height);
+}
 
 
-	function printPage() {
-		unhighlightCutElement();
-		resetSelectedElements();
-		window.print();
-	}
+pre {
+  font-family: inherit;
+}
 
 
-	function highlightCutElement() {
-		const element = cuttingPath[cuttingPathIndex];
-		element.classList.add(cuttingMode ? CUT_HOVER_CLASS : CUT_OUTER_HOVER_CLASS);
-	}
+.moz-reader-content {
+  display: none;
+  font-size: 1em;
+}
 
 
-	function unhighlightCutElement() {
-		if (cuttingPath) {
-			const element = cuttingPath[cuttingPathIndex];
-			element.classList.remove(CUT_HOVER_CLASS);
-			element.classList.remove(CUT_OUTER_HOVER_CLASS);
-		}
-	}
+@media print {
+  .moz-reader-content p,
+  .moz-reader-content code,
+  .moz-reader-content pre,
+  .moz-reader-content blockquote,
+  .moz-reader-content ul,
+  .moz-reader-content ol,
+  .moz-reader-content li,
+  .moz-reader-content figure,
+  .moz-reader-content .wp-caption {
+    margin: 0 0 10px !important;
+    padding: 0 !important;
+  }
+}
 
 
-	function disableHighlight(doc = document) {
-		if (highlightColor) {
-			doc.documentElement.classList.remove(highlightColor + "-mode");
-		}
-	}
+.moz-reader-content h1,
+.moz-reader-content h2,
+.moz-reader-content h3 {
+  font-weight: bold;
+}
 
 
-	function undoCutPage() {
-		if (removedElementIndex) {
-			removedElements[removedElementIndex - 1].forEach(element => element.classList.remove(REMOVED_CONTENT_CLASS));
-			removedElementIndex--;
-		}
-	}
+.moz-reader-content h1 {
+  font-size: 1.6em;
+  line-height: 1.25em;
+}
 
 
-	function redoCutPage() {
-		if (removedElementIndex < removedElements.length) {
-			removedElements[removedElementIndex].forEach(element => element.classList.add(REMOVED_CONTENT_CLASS));
-			removedElementIndex++;
-		}
-	}
+.moz-reader-content h2 {
+  font-size: 1.2em;
+  line-height: 1.51em;
+}
 
 
-	function validateCutElement(invert) {
-		const selectedElement = cuttingPath[cuttingPathIndex];
-		if ((cuttingMode && !invert) || (cuttingOuterMode && invert)) {
-			if (document.documentElement != selectedElement && selectedElement.tagName.toLowerCase() != NOTE_TAGNAME) {
-				const elementsRemoved = [selectedElement].concat(...document.querySelectorAll("." + CUT_SELECTED_CLASS + ",." + CUT_SELECTED_CLASS + " *,." + CUT_HOVER_CLASS + " *"));
-				resetSelectedElements();
-				if (elementsRemoved.length) {
-					elementsRemoved.forEach(element => {
-						unhighlightCutElement(element);
-						if (element.tagName.toLowerCase() == NOTE_TAGNAME) {
-							resetAnchorNote(element);
-						} else {
-							element.classList.add(REMOVED_CONTENT_CLASS);
-						}
-					});
-					removedElements[removedElementIndex] = elementsRemoved;
-					removedElementIndex++;
-					removedElements.length = removedElementIndex;
-					onUpdate(false);
-				}
-			}
-		} else {
-			if (document.documentElement != selectedElement && selectedElement.tagName.toLowerCase() != NOTE_TAGNAME) {
-				const elements = [];
-				const searchSelector = "*:not(style):not(meta):not(." + REMOVED_CONTENT_CLASS + ")";
-				const elementsKept = [selectedElement].concat(...document.querySelectorAll("." + CUT_OUTER_SELECTED_CLASS));
-				document.body.querySelectorAll(searchSelector).forEach(element => {
-					let removed = true;
-					elementsKept.forEach(elementKept => removed = removed && (elementKept != element && !isAncestor(elementKept, element) && !isAncestor(element, elementKept)));
-					if (removed) {
-						if (element.tagName.toLowerCase() == NOTE_TAGNAME) {
-							resetAnchorNote(element);
-						} else {
-							elements.push(element);
-						}
-					}
-				});
-				elementsKept.forEach(elementKept => {
-					unhighlightCutElement(elementKept);
-					const elementKeptRect = elementKept.getBoundingClientRect();
-					elementKept.querySelectorAll(searchSelector).forEach(descendant => {
-						const descendantRect = descendant.getBoundingClientRect();
-						if (descendantRect.width && descendantRect.height && (
-							descendantRect.left + descendantRect.width < elementKeptRect.left ||
-							descendantRect.right > elementKeptRect.right + elementKeptRect.width ||
-							descendantRect.top + descendantRect.height < elementKeptRect.top ||
-							descendantRect.bottom > elementKeptRect.bottom + elementKeptRect.height
-						)) {
-							elements.push(descendant);
-						}
-					});
-				});
-				resetSelectedElements();
-				if (elements.length) {
-					elements.forEach(element => element.classList.add(REMOVED_CONTENT_CLASS));
-					removedElements[removedElementIndex] = elements;
-					removedElementIndex++;
-					removedElements.length = removedElementIndex;
-					onUpdate(false);
-				}
-			}
-		}
-	}
+.moz-reader-content h3 {
+  font-size: 1em;
+  line-height: 1.66em;
+}
 
 
-	function resetSelectedElements(doc = document) {
-		doc.querySelectorAll("." + CUT_OUTER_SELECTED_CLASS + ",." + CUT_SELECTED_CLASS).forEach(element => {
-			element.classList.remove(CUT_OUTER_SELECTED_CLASS);
-			element.classList.remove(CUT_SELECTED_CLASS);
-		});
-	}
+.moz-reader-content a:link {
+  text-decoration: underline;
+  font-weight: normal;
+}
 
 
-	function anchorNote(event, noteElement, deltaX, deltaY) {
-		event.preventDefault();
-		const { clientX, clientY } = getPosition(event);
-		document.documentElement.style.removeProperty("user-select");
-		noteElement.classList.remove(NOTE_MOVING_CLASS);
-		maskNoteElement.classList.remove(NOTE_MASK_MOVING_CLASS);
-		maskPageElement.classList.remove(PAGE_MASK_ACTIVE_CLASS);
-		maskNoteElement.classList.remove(noteElement.dataset.color);
-		const headerElement = noteElement.querySelector("header");
-		headerElement.ontouchmove = document.documentElement.onmousemove = null;
-		let currentElement = anchorElement;
-		let positionedElement;
-		while (currentElement.parentElement && !positionedElement) {
-			if (!FORBIDDEN_TAG_NAMES.includes(currentElement.tagName.toLowerCase())) {
-				const currentElementStyle = getComputedStyle(currentElement);
-				if (currentElementStyle.position != "static") {
-					positionedElement = currentElement;
-				}
-			}
-			currentElement = currentElement.parentElement;
-		}
-		if (!positionedElement) {
-			positionedElement = document.documentElement;
-		}
-		const containerElement = noteElement.getRootNode().host;
-		if (positionedElement == document.documentElement) {
-			const firstMaskElement = document.querySelector("." + MASK_CLASS);
-			firstMaskElement.parentElement.insertBefore(containerElement, firstMaskElement);
-		} else {
-			positionedElement.appendChild(containerElement);
-		}
-		const boundingRectPositionedElement = positionedElement.getBoundingClientRect();
-		const stylePositionedElement = window.getComputedStyle(positionedElement);
-		const borderX = parseInt(stylePositionedElement.getPropertyValue("border-left-width"));
-		const borderY = parseInt(stylePositionedElement.getPropertyValue("border-top-width"));
-		noteElement.style.setProperty("position", "absolute");
-		noteElement.style.setProperty("left", (clientX - boundingRectPositionedElement.x - deltaX - borderX) + "px");
-		noteElement.style.setProperty("top", (clientY - boundingRectPositionedElement.y - deltaY - borderY) + "px");
-	}
+.moz-reader-content a:link,
+.moz-reader-content a:link:hover,
+.moz-reader-content a:link:active {
+  color: var(--link-foreground);
+}
 
 
-	function resetAnchorNote(containerElement) {
-		const noteId = containerElement.dataset.noteId;
-		const noteElement = containerElement.shadowRoot.childNodes[1];
-		noteElement.classList.remove(NOTE_ANCHORED_CLASS);
-		deleteNoteRef(containerElement, noteId);
-		addNoteRef(document.documentElement, noteId);
-		document.documentElement.insertBefore(containerElement, maskPageElement.getRootNode().host);
-	}
+.moz-reader-content a:visited {
+  color: var(--visited-link-foreground);
+}
 
 
-	function getPosition(event) {
-		if (event.touches && event.touches.length) {
-			const touch = event.touches[0];
-			return touch;
-		} else {
-			return event;
-		}
-	}
+.moz-reader-content * {
+  max-width: 100%;
+  height: auto;
+}
 
 
-	function highlightSelection() {
-		let highlightId = 0;
-		document.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(highlightedElement => highlightId = Math.max(highlightId, highlightedElement.dataset.singlefileHighlightId));
-		highlightId++;
-		const selection = window.getSelection();
-		const highlightedNodes = new Set();
-		for (let indexRange = 0; indexRange < selection.rangeCount; indexRange++) {
-			const range = selection.getRangeAt(indexRange);
-			if (!range.collapsed) {
-				if (range.commonAncestorContainer.nodeType == range.commonAncestorContainer.TEXT_NODE) {
-					let contentText = range.startContainer.splitText(range.startOffset);
-					contentText = contentText.splitText(range.endOffset);
-					addHighLightedNode(contentText.previousSibling);
-				} else {
-					const treeWalker = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
-					let highlightNodes;
-					while (treeWalker.nextNode()) {
-						if (highlightNodes && !treeWalker.currentNode.contains(range.endContainer)) {
-							addHighLightedNode(treeWalker.currentNode);
-						}
-						if (treeWalker.currentNode == range.startContainer) {
-							if (range.startContainer.nodeType == range.startContainer.TEXT_NODE) {
-								const contentText = range.startContainer.splitText(range.startOffset);
-								treeWalker.nextNode();
-								addHighLightedNode(contentText);
-							} else {
-								addHighLightedNode(range.startContainer.childNodes[range.startOffset]);
-							}
-							highlightNodes = true;
-						}
-						if (treeWalker.currentNode == range.endContainer) {
-							if (range.endContainer.nodeType == range.endContainer.TEXT_NODE) {
-								const contentText = range.endContainer.splitText(range.endOffset);
-								treeWalker.nextNode();
-								addHighLightedNode(contentText.previousSibling);
-							} else {
-								addHighLightedNode(range.endContainer.childNodes[range.endOffset]);
-							}
-							highlightNodes = false;
-						}
-					}
-					range.collapse();
-				}
-			}
-		}
-		highlightedNodes.forEach(node => highlightNode(node));
+.moz-reader-content p,
+.moz-reader-content p,
+.moz-reader-content code,
+.moz-reader-content pre,
+.moz-reader-content blockquote,
+.moz-reader-content ul,
+.moz-reader-content ol,
+.moz-reader-content li,
+.moz-reader-content figure,
+.moz-reader-content .wp-caption {
+  margin: -10px -10px 20px;
+  padding: 10px;
+  border-radius: 5px;
+}
 
 
-		function addHighLightedNode(node) {
-			if (node && node.textContent.trim()) {
-				if (node.nodeType == node.TEXT_NODE && node.parentElement.childNodes.length == 1 && node.parentElement.classList.contains(HIGHLIGHT_CLASS)) {
-					highlightedNodes.add(node.parentElement);
-				} else {
-					highlightedNodes.add(node);
-				}
-			}
-		}
+.moz-reader-content li {
+  margin-bottom: 0;
+}
 
 
-		function highlightNode(node) {
-			if (node.nodeType == node.ELEMENT_NODE) {
-				resetHighlightedElement(node);
-				node.classList.add(HIGHLIGHT_CLASS);
-				node.classList.add(highlightColor);
-				node.dataset.singlefileHighlightId = highlightId;
-			} else if (node.parentElement) {
-				highlightTextNode(node);
-			}
-		}
+.moz-reader-content li > ul,
+.moz-reader-content li > ol {
+  margin-bottom: -10px;
+}
 
 
-		function highlightTextNode(node) {
-			const spanElement = document.createElement("span");
-			spanElement.classList.add(HIGHLIGHT_CLASS);
-			spanElement.classList.add(highlightColor);
-			spanElement.textContent = node.textContent;
-			spanElement.dataset.singlefileHighlightId = highlightId;
-			node.parentNode.replaceChild(spanElement, node);
-			return spanElement;
-		}
-	}
+.moz-reader-content p > img:only-child,
+.moz-reader-content p > a:only-child > img:only-child,
+.moz-reader-content .wp-caption img,
+.moz-reader-content figure img {
+  display: block;
+}
 
 
-	function getParents(element) {
-		const path = [];
-		while (element) {
-			path.push(element);
-			element = element.parentElement;
-		}
-		return path;
-	}
+.moz-reader-content img[moz-reader-center] {
+  margin-inline: auto;
+}
 
 
-	function formatPage(applySystemTheme) {
-		if (pageCompressContent) {
-			serializeShadowRoots(document);
-			previousContent = document.documentElement.cloneNode(true);
-			deserializeShadowRoots(document);
-		} else {
-			previousContent = getContent(false, []);
-		}
-		const shadowRoots = {};
-		const classesToPreserve = ["single-file-highlight", "single-file-highlight-yellow", "single-file-highlight-green", "single-file-highlight-pink", "single-file-highlight-blue"];
-		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
-			shadowRoots[containerElement.dataset.noteId] = containerElement.shadowRoot;
-			const className = "singlefile-note-id-" + containerElement.dataset.noteId;
-			containerElement.classList.add(className);
-			classesToPreserve.push(className);
-		});
-		const optionsElement = document.querySelector("script[type=\"application/json\"][" + SCRIPT_OPTIONS + "]");
-		const article = new Readability(document, { classesToPreserve }).parse();
-		const articleMetadata = Object.assign({}, article);
-		delete articleMetadata.content;
-		delete articleMetadata.textContent;
-		for (const key in articleMetadata) {
-			if (articleMetadata[key] == null) {
-				delete articleMetadata[key];
-			}
-		}
-		removedElements = [];
-		removedElementIndex = 0;
-		document.body.innerHTML = "";
-		const domParser = new DOMParser();
-		const doc = domParser.parseFromString(article.content, "text/html");
-		const contentEditable = document.body.contentEditable;
-		document.documentElement.replaceChild(doc.body, document.body);
-		if (optionsElement) {
-			document.body.appendChild(optionsElement);
-		}
-		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
-			const noteId = (Array.from(containerElement.classList).find(className => /singlefile-note-id-\d+/.test(className))).split("singlefile-note-id-")[1];
-			containerElement.classList.remove("singlefile-note-id-" + noteId);
-			containerElement.dataset.noteId = noteId;
-			if (!containerElement.shadowRoot) {
-				containerElement.attachShadow({ mode: "open" });
-				containerElement.shadowRoot.appendChild(shadowRoots[noteId]);
-			}
-		});
-		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => shadowRoots[containerElement.dataset.noteId].childNodes.forEach(node => containerElement.shadowRoot.appendChild(node)));
-		document.body.contentEditable = contentEditable;
-		document.head.querySelectorAll("style").forEach(styleElement => styleElement.remove());
-		const styleElement = document.createElement("style");
-		styleElement.textContent = STYLE_FORMATTED_PAGE;
-		document.head.appendChild(styleElement);
-		document.body.classList.add("moz-reader-content");
-		document.body.classList.add("content-width6");
-		document.body.classList.add("reader-show-element");
-		document.body.classList.add("sans-serif");
-		document.body.classList.add("container");
-		document.body.classList.add("line-height4");
-		const prefersColorSchemeDark = matchMedia("(prefers-color-scheme: dark)");
-		if (applySystemTheme && prefersColorSchemeDark && prefersColorSchemeDark.matches) {
-			document.body.classList.add("dark");
-		}
-		document.body.style.setProperty("display", "block");
-		document.body.style.setProperty("padding", "24px");
-		const titleElement = document.createElement("h1");
-		titleElement.classList.add("reader-title");
-		titleElement.textContent = article.title;
-		document.body.insertBefore(titleElement, document.body.firstChild);
-		const existingMetaDataElement = document.querySelector("script[id=singlefile-readability-metadata]");
-		if (existingMetaDataElement) {
-			existingMetaDataElement.remove();
-		}
-		const metaDataElement = document.createElement("script");
-		metaDataElement.type = "application/json";
-		metaDataElement.id = "singlefile-readability-metadata";
-		metaDataElement.textContent = JSON.stringify(articleMetadata, null, 2);
-		document.head.appendChild(metaDataElement);
-		document.querySelectorAll("a[href]").forEach(element => {
-			const href = element.getAttribute("href").trim();
-			if (href.startsWith(document.baseURI + "#")) {
-				element.setAttribute("href", href.substring(document.baseURI.length));
-			}
-		});
-		insertHighlightStylesheet(document);
-		maskPageElement = getMaskElement(PAGE_MASK_CLASS, PAGE_MASK_CONTAINER_CLASS);
-		maskNoteElement = getMaskElement(NOTE_MASK_CLASS);
-		reflowNotes();
-		onUpdate(false);
-	}
+.moz-reader-content .caption,
+.moz-reader-content .wp-caption-text
+.moz-reader-content figcaption {
+  font-size: 0.9em;
+  line-height: 1.48em;
+  font-style: italic;
+}
 
 
-	async function cancelFormatPage() {
-		if (previousContent) {
-			const contentEditable = document.body.contentEditable;
-			if (pageCompressContent) {
-				document.replaceChild(previousContent, document.documentElement);
-				deserializeShadowRoots(document);
-				await initPage();
-			} else {
-				await init({ content: previousContent }, { reset: true });
-			}
-			document.body.contentEditable = contentEditable;
-			onUpdate(false);
-			previousContent = null;
-		}
-	}
+.moz-reader-content pre {
+  white-space: pre-wrap;
+}
+
+.moz-reader-content blockquote {
+  padding: 0;
+  padding-inline-start: 16px;
+}
+
+.moz-reader-content ul,
+.moz-reader-content ol {
+  padding: 0;
+}
 
 
-	function insertHighlightStylesheet(doc) {
-		if (!doc.querySelector("." + HIGHLIGHTS_STYLESHEET_CLASS)) {
-			const styleheetHighlights = getStyleElement(HIGHLIGHTS_WEB_STYLESHEET);
-			styleheetHighlights.classList.add(HIGHLIGHTS_STYLESHEET_CLASS);
-			doc.documentElement.appendChild(styleheetHighlights);
-		}
-	}
+.moz-reader-content ul {
+  padding-inline-start: 30px;
+  list-style: disc;
+}
 
 
-	function getContent(compressHTML, updatedResources) {
-		unhighlightCutElement();
-		serializeShadowRoots(document);
-		const doc = document.cloneNode(true);
-		disableHighlight(doc);
-		resetSelectedElements(doc);
-		deserializeShadowRoots(doc);
-		deserializeShadowRoots(document);
-		doc.documentElement.classList.remove(CUT_MODE_CLASS);
-		doc.querySelectorAll("[" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "]").forEach(element => {
-			element.textContent = element.getAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
-			element.removeAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
-		});
-		doc.querySelectorAll("." + MASK_CLASS + ", " + singlefile.helper.INFOBAR_TAGNAME + ", ." + REMOVED_CONTENT_CLASS).forEach(element => element.remove());
-		if (includeInfobar) {
-			const options = singlefile.helper.extractInfobarData(doc);
-			options.openInfobar = openInfobar;
-			options.infobarPositionAbsolute = infobarPositionAbsolute;
-			options.infobarPositionTop = infobarPositionTop;
-			options.infobarPositionRight = infobarPositionRight;
-			options.infobarPositionBottom = infobarPositionBottom;
-			options.infobarPositionLeft = infobarPositionLeft;
-			singlefile.helper.appendInfobar(doc, options);
-		}
-		doc.querySelectorAll("." + HIGHLIGHT_CLASS).forEach(noteElement => noteElement.classList.remove(HIGHLIGHT_HIDDEN_CLASS));
-		doc.querySelectorAll(`template[${SHADOWROOT_ATTRIBUTE_NAME}]`).forEach(templateElement => {
-			const noteElement = templateElement.querySelector("." + NOTE_CLASS);
-			if (noteElement) {
-				noteElement.classList.remove(NOTE_HIDDEN_CLASS);
-			}
-			const mainElement = templateElement.querySelector("textarea");
-			if (mainElement) {
-				mainElement.textContent = mainElement.value;
-			}
-		});
-		doc.querySelectorAll("iframe").forEach(element => {
-			const pointerEvents = "pointer-events";
-			element.style.setProperty(pointerEvents, element.style.getPropertyValue("-sf-" + pointerEvents), element.style.getPropertyPriority("-sf-" + pointerEvents));
-			element.style.removeProperty("-sf-" + pointerEvents);
-		});
-		doc.body.removeAttribute("contentEditable");
-		const newResources = Object.keys(updatedResources).filter(url => updatedResources[url].type == "stylesheet").map(url => updatedResources[url]);
-		newResources.forEach(resource => {
-			const element = doc.createElement("style");
-			doc.body.appendChild(element);
-			element.textContent = resource.content;
-		});
-		if (pageCompressContent) {
-			const pageFilename = pageResources
-				.filter(resource => resource.filename.endsWith("index.html"))
-				.sort((resourceLeft, resourceRight) => resourceLeft.filename.length - resourceRight.filename.length)[0].filename;
-			const resources = pageResources.filter(resource => resource.parentResources.includes(pageFilename));
-			doc.querySelectorAll("[src]").forEach(element => resources.forEach(resource => {
-				if (element.src == resource.content) {
-					element.src = resource.name;
-				}
-			}));
-			let content = singlefile.helper.serialize(doc, compressHTML);
-			resources.sort((resourceLeft, resourceRight) => resourceRight.content.length - resourceLeft.content.length);
-			resources.forEach(resource => content = content.replaceAll(resource.content, resource.name));
-			return content + "<script " + SCRIPT_TEMPLATE_SHADOW_ROOT + ">" + getEmbedScript() + "</script>";
-		} else {
-			return singlefile.helper.serialize(doc, compressHTML) + "<script " + SCRIPT_TEMPLATE_SHADOW_ROOT + ">" + getEmbedScript() + "</script>";
-		}
-	}
+.moz-reader-content ol {
+  padding-inline-start: 30px;
+}
 
 
-	function onUpdate(saved) {
-		window.parent.postMessage(JSON.stringify({ "method": "onUpdate", saved }), "*");
-	}
+table,
+th,
+td {
+  border: 1px solid currentColor;
+  border-collapse: collapse;
+  padding: 6px;
+  vertical-align: top;
+}
 
 
-	function waitResourcesLoad() {
-		return new Promise(resolve => {
-			let counterMutations = 0;
-			const done = () => {
-				observer.disconnect();
-				resolve();
-			};
-			let timeoutInit = setTimeout(done, 100);
-			const observer = new MutationObserver(() => {
-				if (counterMutations < 20) {
-					counterMutations++;
-					clearTimeout(timeoutInit);
-					timeoutInit = setTimeout(done, 100);
-				} else {
-					done();
-				}
-			});
-			observer.observe(document, { subtree: true, childList: true, attributes: true });
-		});
-	}
+table {
+  margin: 5px;
+}
 
 
-	function reflowNotes() {
-		document.querySelectorAll(NOTE_TAGNAME).forEach(containerElement => {
-			const noteElement = containerElement.shadowRoot.querySelector("." + NOTE_CLASS);
-			const noteBoundingRect = noteElement.getBoundingClientRect();
-			const anchorElement = getAnchorElement(containerElement);
-			const anchorBoundingRect = anchorElement.getBoundingClientRect();
-			const maxX = anchorBoundingRect.x + Math.max(0, anchorBoundingRect.width - noteBoundingRect.width);
-			const minX = anchorBoundingRect.x;
-			const maxY = anchorBoundingRect.y + Math.max(0, anchorBoundingRect.height - NOTE_HEADER_HEIGHT);
-			const minY = anchorBoundingRect.y;
-			let left = parseInt(noteElement.style.getPropertyValue("left"));
-			let top = parseInt(noteElement.style.getPropertyValue("top"));
-			if (noteBoundingRect.x > maxX) {
-				left -= noteBoundingRect.x - maxX;
-			}
-			if (noteBoundingRect.x < minX) {
-				left += minX - noteBoundingRect.x;
-			}
-			if (noteBoundingRect.y > maxY) {
-				top -= noteBoundingRect.y - maxY;
-			}
-			if (noteBoundingRect.y < minY) {
-				top += minY - noteBoundingRect.y;
-			}
-			noteElement.style.setProperty("position", "absolute");
-			noteElement.style.setProperty("left", left + "px");
-			noteElement.style.setProperty("top", top + "px");
-		});
-	}
+/* Visually hide (but don't display: none) screen reader elements */
+.moz-reader-content .visually-hidden,
+.moz-reader-content .visuallyhidden,
+.moz-reader-content .sr-only {
+  display: inline-block;
+  width: 1px;
+  height: 1px;
+  margin: -1px;
+  overflow: hidden;
+  padding: 0;
+  border-width: 0;
+}
 
 
-	function resetHighlightedElement(element) {
-		element.classList.remove(HIGHLIGHT_CLASS);
-		element.classList.remove("single-file-highlight-yellow");
-		element.classList.remove("single-file-highlight-pink");
-		element.classList.remove("single-file-highlight-blue");
-		element.classList.remove("single-file-highlight-green");
-		delete element.dataset.singlefileHighlightId;
-	}
+/* Hide elements with common "hidden" class names */
+.moz-reader-content .hidden,
+.moz-reader-content .invisible {
+  display: none;
+}
 
 
-	function serializeShadowRoots(node) {
-		node.querySelectorAll("*").forEach(element => {
-			const shadowRoot = getShadowRoot(element);
-			if (shadowRoot) {
-				serializeShadowRoots(shadowRoot);
-				const templateElement = document.createElement("template");
-				templateElement.setAttribute(SHADOWROOT_ATTRIBUTE_NAME, "open");
-				Array.from(shadowRoot.childNodes).forEach(childNode => templateElement.appendChild(childNode));
-				element.appendChild(templateElement);
-			}
-		});
-	}
+/* Enforce wordpress and similar emoji/smileys aren't sized to be full-width,
+ * see bug 1399616 for context. */
+.moz-reader-content img.wp-smiley,
+.moz-reader-content img.emoji {
+  display: inline-block;
+  border-width: 0;
+  /* height: auto is implied from '.moz-reader-content *' rule. */
+  width: 1em;
+  margin: 0 .07em;
+  padding: 0;
+}
 
 
-	function deserializeShadowRoots(node) {
-		node.querySelectorAll(`template[${SHADOWROOT_ATTRIBUTE_NAME}]`).forEach(element => {
-			if (element.parentElement) {
-				let shadowRoot = getShadowRoot(element.parentElement);
-				if (shadowRoot) {
-					Array.from(element.childNodes).forEach(node => shadowRoot.appendChild(node));
-					element.remove();
-				} else {
-					try {
-						shadowRoot = element.parentElement.attachShadow({ mode: "open" });
-						const contentDocument = (new DOMParser()).parseFromString(element.innerHTML, "text/html");
-						Array.from(contentDocument.head.childNodes).forEach(node => shadowRoot.appendChild(node));
-						Array.from(contentDocument.body.childNodes).forEach(node => shadowRoot.appendChild(node));
-						// eslint-disable-next-line no-unused-vars
-					} catch (error) {
-						// ignored
-					}
-				}
-				if (shadowRoot) {
-					deserializeShadowRoots(shadowRoot);
-				}
-			}
-		});
-	}
+.reader-show-element {
+  display: initial;
+}
 
 
-	function getMaskElement(className, containerClassName) {
-		let maskElement = document.documentElement.querySelector("." + className);
-		if (!maskElement) {
-			maskElement = document.createElement("div");
-			const maskContainerElement = document.createElement("div");
-			if (containerClassName) {
-				maskContainerElement.classList.add(containerClassName);
-			}
-			maskContainerElement.classList.add(MASK_CLASS);
-			const firstNote = document.querySelector(NOTE_TAGNAME);
-			if (firstNote && firstNote.parentElement == document.documentElement) {
-				document.documentElement.insertBefore(maskContainerElement, firstNote);
-			} else {
-				document.documentElement.appendChild(maskContainerElement);
-			}
-			maskElement.classList.add(className);
-			const maskShadow = maskContainerElement.attachShadow({ mode: "open" });
-			maskShadow.appendChild(getStyleElement(MASK_WEB_STYLESHEET));
-			maskShadow.appendChild(maskElement);
-			return maskElement;
-		}
+/* Provide extra spacing for images that may be aided with accompanying element such as <figcaption> */
+.moz-reader-block-img:not(:last-child) {
+  margin-block-end: 12px;
+}
+
+.moz-reader-wide-table {
+  overflow-x: auto;
+  display: block;
+}
+
+pre code {
+  background-color: var(--main-background);
+  border: 1px solid var(--font-color);
+  display: block;
+  overflow: auto;
+}`;
 	}
 	}
 
 
 	function getEmbedScript() {
 	function getEmbedScript() {

+ 5 - 0
src/ui/pages/help.html

@@ -659,6 +659,11 @@
 							browser (e.g. dark or light) when you click on the button "Format the page for better
 							browser (e.g. dark or light) when you click on the button "Format the page for better
 							readability" in the annotation editor.</p>
 							readability" in the annotation editor.</p>
 					</li>
 					</li>
+					<li data-options-label="contentWidthLabel"> <span class="option">Option: content width when
+							formatting a page (em)</span>
+						<p>Enter the content width in em to use when you click on the button "Format the page for better
+							readability" in the annotation editor.</p>
+					</li>
 					<li data-options-label="warnUnsavedPageLabel"> <span class="option">Option: warn if leaving page
 					<li data-options-label="warnUnsavedPageLabel"> <span class="option">Option: warn if leaving page
 							with unsaved changes</span>
 							with unsaved changes</span>
 						<p>Check this option to display a blocking popup when you leave the annotation editor (e.g.
 						<p>Check this option to display a blocking popup when you leave the annotation editor (e.g.

+ 4 - 0
src/ui/pages/options.html

@@ -460,6 +460,10 @@
 				<label for="applySystemThemeInput" id="applySystemThemeLabel"></label>
 				<label for="applySystemThemeInput" id="applySystemThemeLabel"></label>
 				<input type="checkbox" id="applySystemThemeInput">
 				<input type="checkbox" id="applySystemThemeInput">
 			</div>
 			</div>
+			<div class="option">
+				<label for="contentWidthInput" id="contentWidthLabel"></label>
+				<input type="number" id="contentWidthInput" min="0">
+			</div>
 			<div class="option">
 			<div class="option">
 				<label for="warnUnsavedPageInput" id="warnUnsavedPageLabel"></label>
 				<label for="warnUnsavedPageInput" id="warnUnsavedPageLabel"></label>
 				<input type="checkbox" id="warnUnsavedPageInput">
 				<input type="checkbox" id="warnUnsavedPageInput">

Некоторые файлы не были показаны из-за большого количества измененных файлов