aboutsummaryrefslogtreecommitdiff
path: root/ext/js/dom
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js/dom')
-rw-r--r--ext/js/dom/document-util.js798
-rw-r--r--ext/js/dom/dom-data-binder.js4
-rw-r--r--ext/js/dom/text-source-element.js4
-rw-r--r--ext/js/dom/text-source-generator.js8
-rw-r--r--ext/js/dom/text-source-range.js16
5 files changed, 415 insertions, 415 deletions
diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js
index a98bfe86..bf11f421 100644
--- a/ext/js/dom/document-util.js
+++ b/ext/js/dom/document-util.js
@@ -15,466 +15,466 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-
/**
- * This class contains utility functions related to the HTML document.
- * TODO : This class should be made non-static
+ * This variable is stateful, but it is only used to do feature detection,
+ * and its value should be constant for the lifetime of the extension.
+ * @type {?boolean}
*/
-// eslint-disable-next-line unicorn/no-static-only-class
-export class DocumentUtil {
- /** @type {?boolean} */
- static _cssZoomSupported = null;
+let cssZoomSupported = null;
- /**
- * Computes the scaling adjustment that is necessary for client space coordinates based on the
- * CSS zoom level.
- * @param {?Node} node A node in the document.
- * @returns {number} The scaling factor.
- */
- static computeZoomScale(node) {
- if (this._cssZoomSupported === null) {
- this._cssZoomSupported = this._computeCssZoomSupported();
- }
- if (!this._cssZoomSupported) { return 1; }
- // documentElement must be excluded because the computer style of its zoom property is inconsistent.
- // * If CSS `:root{zoom:X;}` is specified, the computed zoom will always report `X`.
- // * If CSS `:root{zoom:X;}` is not specified, the computed zoom report the browser's zoom level.
- // Therefor, if CSS root zoom is specified as a value other than 1, the adjusted {x, y} values
- // would be incorrect, which is not new behaviour.
- let scale = 1;
- const {ELEMENT_NODE, DOCUMENT_FRAGMENT_NODE} = Node;
- const {documentElement} = document;
- for (; node !== null && node !== documentElement; node = node.parentNode) {
- const {nodeType} = node;
- if (nodeType === DOCUMENT_FRAGMENT_NODE) {
- const {host} = /** @type {ShadowRoot} */ (node);
- if (typeof host !== 'undefined') {
- node = host;
- }
- continue;
- } else if (nodeType !== ELEMENT_NODE) {
- continue;
+/**
+ * Computes the scaling adjustment that is necessary for client space coordinates based on the
+ * CSS zoom level.
+ * @param {?Node} node A node in the document.
+ * @returns {number} The scaling factor.
+ */
+export function computeZoomScale(node) {
+ if (cssZoomSupported === null) {
+ cssZoomSupported = computeCssZoomSupported();
+ }
+ if (!cssZoomSupported) { return 1; }
+ // documentElement must be excluded because the computer style of its zoom property is inconsistent.
+ // * If CSS `:root{zoom:X;}` is specified, the computed zoom will always report `X`.
+ // * If CSS `:root{zoom:X;}` is not specified, the computed zoom report the browser's zoom level.
+ // Therefor, if CSS root zoom is specified as a value other than 1, the adjusted {x, y} values
+ // would be incorrect, which is not new behaviour.
+ let scale = 1;
+ const {ELEMENT_NODE, DOCUMENT_FRAGMENT_NODE} = Node;
+ const {documentElement} = document;
+ for (; node !== null && node !== documentElement; node = node.parentNode) {
+ const {nodeType} = node;
+ if (nodeType === DOCUMENT_FRAGMENT_NODE) {
+ const {host} = /** @type {ShadowRoot} */ (node);
+ if (typeof host !== 'undefined') {
+ node = host;
}
- const zoomString = getComputedStyle(/** @type {HTMLElement} */ (node)).getPropertyValue('zoom');
- if (typeof zoomString !== 'string' || zoomString.length === 0) { continue; }
- const zoom = Number.parseFloat(zoomString);
- if (!Number.isFinite(zoom) || zoom === 0) { continue; }
- scale *= zoom;
+ continue;
+ } else if (nodeType !== ELEMENT_NODE) {
+ continue;
}
- return scale;
+ const zoomString = getComputedStyle(/** @type {HTMLElement} */ (node)).getPropertyValue('zoom');
+ if (typeof zoomString !== 'string' || zoomString.length === 0) { continue; }
+ const zoom = Number.parseFloat(zoomString);
+ if (!Number.isFinite(zoom) || zoom === 0) { continue; }
+ scale *= zoom;
}
+ return scale;
+}
- /**
- * Converts a rect based on the CSS zoom scaling for a given node.
- * @param {DOMRect} rect The rect to convert.
- * @param {Node} node The node to compute the zoom from.
- * @returns {DOMRect} The updated rect, or the same rect if no change is needed.
- */
- static convertRectZoomCoordinates(rect, node) {
- const scale = this.computeZoomScale(node);
- return (scale === 1 ? rect : new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale));
- }
+/**
+ * Converts a rect based on the CSS zoom scaling for a given node.
+ * @param {DOMRect} rect The rect to convert.
+ * @param {Node} node The node to compute the zoom from.
+ * @returns {DOMRect} The updated rect, or the same rect if no change is needed.
+ */
+export function convertRectZoomCoordinates(rect, node) {
+ const scale = computeZoomScale(node);
+ return (scale === 1 ? rect : new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale));
+}
- /**
- * Converts multiple rects based on the CSS zoom scaling for a given node.
- * @param {DOMRect[]|DOMRectList} rects The rects to convert.
- * @param {Node} node The node to compute the zoom from.
- * @returns {DOMRect[]} The updated rects, or the same rects array if no change is needed.
- */
- static convertMultipleRectZoomCoordinates(rects, node) {
- const scale = this.computeZoomScale(node);
- if (scale === 1) { return [...rects]; }
- const results = [];
- for (const rect of rects) {
- results.push(new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale));
- }
- return results;
+/**
+ * Converts multiple rects based on the CSS zoom scaling for a given node.
+ * @param {DOMRect[]|DOMRectList} rects The rects to convert.
+ * @param {Node} node The node to compute the zoom from.
+ * @returns {DOMRect[]} The updated rects, or the same rects array if no change is needed.
+ */
+export function convertMultipleRectZoomCoordinates(rects, node) {
+ const scale = computeZoomScale(node);
+ if (scale === 1) { return [...rects]; }
+ const results = [];
+ for (const rect of rects) {
+ results.push(new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale));
}
+ return results;
+}
- /**
- * Checks whether a given point is contained within a rect.
- * @param {number} x The horizontal coordinate.
- * @param {number} y The vertical coordinate.
- * @param {DOMRect} rect The rect to check.
- * @returns {boolean} `true` if the point is inside the rect, `false` otherwise.
- */
- static isPointInRect(x, y, rect) {
- return (
- x >= rect.left && x < rect.right &&
- y >= rect.top && y < rect.bottom
- );
- }
+/**
+ * Checks whether a given point is contained within a rect.
+ * @param {number} x The horizontal coordinate.
+ * @param {number} y The vertical coordinate.
+ * @param {DOMRect} rect The rect to check.
+ * @returns {boolean} `true` if the point is inside the rect, `false` otherwise.
+ */
+export function isPointInRect(x, y, rect) {
+ return (
+ x >= rect.left && x < rect.right &&
+ y >= rect.top && y < rect.bottom
+ );
+}
- /**
- * Checks whether a given point is contained within any rect in a list.
- * @param {number} x The horizontal coordinate.
- * @param {number} y The vertical coordinate.
- * @param {DOMRect[]|DOMRectList} rects The rect to check.
- * @returns {boolean} `true` if the point is inside any of the rects, `false` otherwise.
- */
- static isPointInAnyRect(x, y, rects) {
- for (const rect of rects) {
- if (this.isPointInRect(x, y, rect)) {
- return true;
- }
+/**
+ * Checks whether a given point is contained within any rect in a list.
+ * @param {number} x The horizontal coordinate.
+ * @param {number} y The vertical coordinate.
+ * @param {DOMRect[]|DOMRectList} rects The rect to check.
+ * @returns {boolean} `true` if the point is inside any of the rects, `false` otherwise.
+ */
+export function isPointInAnyRect(x, y, rects) {
+ for (const rect of rects) {
+ if (isPointInRect(x, y, rect)) {
+ return true;
}
- return false;
}
+ return false;
+}
- /**
- * Checks whether a given point is contained within a selection range.
- * @param {number} x The horizontal coordinate.
- * @param {number} y The vertical coordinate.
- * @param {Selection} selection The selection to check.
- * @returns {boolean} `true` if the point is inside the selection, `false` otherwise.
- */
- static isPointInSelection(x, y, selection) {
- for (let i = 0; i < selection.rangeCount; ++i) {
- const range = selection.getRangeAt(i);
- if (this.isPointInAnyRect(x, y, range.getClientRects())) {
- return true;
- }
+/**
+ * Checks whether a given point is contained within a selection range.
+ * @param {number} x The horizontal coordinate.
+ * @param {number} y The vertical coordinate.
+ * @param {Selection} selection The selection to check.
+ * @returns {boolean} `true` if the point is inside the selection, `false` otherwise.
+ */
+export function isPointInSelection(x, y, selection) {
+ for (let i = 0; i < selection.rangeCount; ++i) {
+ const range = selection.getRangeAt(i);
+ if (isPointInAnyRect(x, y, range.getClientRects())) {
+ return true;
}
- return false;
}
+ return false;
+}
- /**
- * Gets an array of the active modifier keys.
- * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check.
- * @returns {import('input').ModifierKey[]} An array of modifiers.
- */
- static getActiveModifiers(event) {
- /** @type {import('input').ModifierKey[]} */
- const modifiers = [];
- if (event.altKey) { modifiers.push('alt'); }
- if (event.ctrlKey) { modifiers.push('ctrl'); }
- if (event.metaKey) { modifiers.push('meta'); }
- if (event.shiftKey) { modifiers.push('shift'); }
- return modifiers;
- }
+/**
+ * Gets an array of the active modifier keys.
+ * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check.
+ * @returns {import('input').ModifierKey[]} An array of modifiers.
+ */
+export function getActiveModifiers(event) {
+ /** @type {import('input').ModifierKey[]} */
+ const modifiers = [];
+ if (event.altKey) { modifiers.push('alt'); }
+ if (event.ctrlKey) { modifiers.push('ctrl'); }
+ if (event.metaKey) { modifiers.push('meta'); }
+ if (event.shiftKey) { modifiers.push('shift'); }
+ return modifiers;
+}
- /**
- * Gets an array of the active modifier keys and buttons.
- * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check.
- * @returns {import('input').Modifier[]} An array of modifiers and buttons.
- */
- static getActiveModifiersAndButtons(event) {
- /** @type {import('input').Modifier[]} */
- const modifiers = this.getActiveModifiers(event);
- if (event instanceof MouseEvent) {
- this._getActiveButtons(event, modifiers);
- }
- return modifiers;
+/**
+ * Gets an array of the active modifier keys and buttons.
+ * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check.
+ * @returns {import('input').Modifier[]} An array of modifiers and buttons.
+ */
+export function getActiveModifiersAndButtons(event) {
+ /** @type {import('input').Modifier[]} */
+ const modifiers = getActiveModifiers(event);
+ if (event instanceof MouseEvent) {
+ getActiveButtonsInternal(event, modifiers);
}
+ return modifiers;
+}
- /**
- * Gets an array of the active buttons.
- * @param {MouseEvent} event The event to check.
- * @returns {import('input').ModifierMouseButton[]} An array of modifiers and buttons.
- */
- static getActiveButtons(event) {
- /** @type {import('input').ModifierMouseButton[]} */
- const buttons = [];
- this._getActiveButtons(event, buttons);
- return buttons;
- }
+/**
+ * Gets an array of the active buttons.
+ * @param {MouseEvent} event The event to check.
+ * @returns {import('input').ModifierMouseButton[]} An array of modifiers and buttons.
+ */
+export function getActiveButtons(event) {
+ /** @type {import('input').ModifierMouseButton[]} */
+ const buttons = [];
+ getActiveButtonsInternal(event, buttons);
+ return buttons;
+}
- /**
- * Adds a fullscreen change event listener. This function handles all of the browser-specific variants.
- * @param {EventListener} onFullscreenChanged The event callback.
- * @param {?import('../core/event-listener-collection.js').EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to.
- */
- static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection = null) {
- const target = document;
- const options = false;
- const fullscreenEventNames = [
- 'fullscreenchange',
- 'MSFullscreenChange',
- 'mozfullscreenchange',
- 'webkitfullscreenchange'
- ];
- for (const eventName of fullscreenEventNames) {
- if (eventListenerCollection === null) {
- target.addEventListener(eventName, onFullscreenChanged, options);
- } else {
- eventListenerCollection.addEventListener(target, eventName, onFullscreenChanged, options);
- }
+/**
+ * Adds a fullscreen change event listener. This function handles all of the browser-specific variants.
+ * @param {EventListener} onFullscreenChanged The event callback.
+ * @param {?import('../core/event-listener-collection.js').EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to.
+ */
+export function addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection = null) {
+ const target = document;
+ const options = false;
+ const fullscreenEventNames = [
+ 'fullscreenchange',
+ 'MSFullscreenChange',
+ 'mozfullscreenchange',
+ 'webkitfullscreenchange'
+ ];
+ for (const eventName of fullscreenEventNames) {
+ if (eventListenerCollection === null) {
+ target.addEventListener(eventName, onFullscreenChanged, options);
+ } else {
+ eventListenerCollection.addEventListener(target, eventName, onFullscreenChanged, options);
}
}
+}
- /**
- * Returns the current fullscreen element. This function handles all of the browser-specific variants.
- * @returns {?Element} The current fullscreen element, or `null` if the window is not fullscreen.
- */
- static getFullscreenElement() {
- return (
- document.fullscreenElement ||
- // @ts-expect-error - vendor prefix
- document.msFullscreenElement ||
- // @ts-expect-error - vendor prefix
- document.mozFullScreenElement ||
- // @ts-expect-error - vendor prefix
- document.webkitFullscreenElement ||
- null
- );
- }
+/**
+ * Returns the current fullscreen element. This function handles all of the browser-specific variants.
+ * @returns {?Element} The current fullscreen element, or `null` if the window is not fullscreen.
+ */
+export function getFullscreenElement() {
+ return (
+ document.fullscreenElement ||
+ // @ts-expect-error - vendor prefix
+ document.msFullscreenElement ||
+ // @ts-expect-error - vendor prefix
+ document.mozFullScreenElement ||
+ // @ts-expect-error - vendor prefix
+ document.webkitFullscreenElement ||
+ null
+ );
+}
- /**
- * Gets all of the nodes within a `Range`.
- * @param {Range} range The range to check.
- * @returns {Node[]} The list of nodes.
- */
- static getNodesInRange(range) {
- const end = range.endContainer;
- const nodes = [];
- for (let node = /** @type {?Node} */ (range.startContainer); node !== null; node = this.getNextNode(node)) {
- nodes.push(node);
- if (node === end) { break; }
- }
- return nodes;
+/**
+ * Gets all of the nodes within a `Range`.
+ * @param {Range} range The range to check.
+ * @returns {Node[]} The list of nodes.
+ */
+export function getNodesInRange(range) {
+ const end = range.endContainer;
+ const nodes = [];
+ for (let node = /** @type {?Node} */ (range.startContainer); node !== null; node = getNextNode(node)) {
+ nodes.push(node);
+ if (node === end) { break; }
}
+ return nodes;
+}
- /**
- * Gets the next node after a specified node. This traverses the DOM in its logical order.
- * @param {Node} node The node to start at.
- * @returns {?Node} The next node, or `null` if there is no next node.
- */
- static getNextNode(node) {
- let next = /** @type {?Node} */ (node.firstChild);
- if (next === null) {
- while (true) {
- next = node.nextSibling;
- if (next !== null) { break; }
+/**
+ * Gets the next node after a specified node. This traverses the DOM in its logical order.
+ * @param {Node} node The node to start at.
+ * @returns {?Node} The next node, or `null` if there is no next node.
+ */
+export function getNextNode(node) {
+ let next = /** @type {?Node} */ (node.firstChild);
+ if (next === null) {
+ while (true) {
+ next = node.nextSibling;
+ if (next !== null) { break; }
- next = node.parentNode;
- if (next === null) { break; }
+ next = node.parentNode;
+ if (next === null) { break; }
- node = next;
- }
+ node = next;
}
- return next;
}
+ return next;
+}
- /**
- * Checks whether any node in a list of nodes matches a selector.
- * @param {Node[]} nodes The list of ndoes to check.
- * @param {string} selector The selector to test.
- * @returns {boolean} `true` if any element node matches the selector, `false` otherwise.
- */
- static anyNodeMatchesSelector(nodes, selector) {
- const ELEMENT_NODE = Node.ELEMENT_NODE;
- // This is a rather ugly way of getting the "node" variable to be a nullable
- for (let node of /** @type {(?Node)[]} */ (nodes)) {
- while (node !== null) {
- if (node.nodeType !== ELEMENT_NODE) {
- node = node.parentNode;
- continue;
- }
- if (/** @type {HTMLElement} */ (node).matches(selector)) { return true; }
- break;
+/**
+ * Checks whether any node in a list of nodes matches a selector.
+ * @param {Node[]} nodes The list of ndoes to check.
+ * @param {string} selector The selector to test.
+ * @returns {boolean} `true` if any element node matches the selector, `false` otherwise.
+ */
+export function anyNodeMatchesSelector(nodes, selector) {
+ const ELEMENT_NODE = Node.ELEMENT_NODE;
+ // This is a rather ugly way of getting the "node" variable to be a nullable
+ for (let node of /** @type {(?Node)[]} */ (nodes)) {
+ while (node !== null) {
+ if (node.nodeType !== ELEMENT_NODE) {
+ node = node.parentNode;
+ continue;
}
+ if (/** @type {HTMLElement} */ (node).matches(selector)) { return true; }
+ break;
}
- return false;
}
+ return false;
+}
- /**
- * Checks whether every node in a list of nodes matches a selector.
- * @param {Node[]} nodes The list of ndoes to check.
- * @param {string} selector The selector to test.
- * @returns {boolean} `true` if every element node matches the selector, `false` otherwise.
- */
- static everyNodeMatchesSelector(nodes, selector) {
- const ELEMENT_NODE = Node.ELEMENT_NODE;
- // This is a rather ugly way of getting the "node" variable to be a nullable
- for (let node of /** @type {(?Node)[]} */ (nodes)) {
- while (true) {
- if (node === null) { return false; }
- if (node.nodeType === ELEMENT_NODE && /** @type {HTMLElement} */ (node).matches(selector)) { break; }
- node = node.parentNode;
- }
+/**
+ * Checks whether every node in a list of nodes matches a selector.
+ * @param {Node[]} nodes The list of ndoes to check.
+ * @param {string} selector The selector to test.
+ * @returns {boolean} `true` if every element node matches the selector, `false` otherwise.
+ */
+export function everyNodeMatchesSelector(nodes, selector) {
+ const ELEMENT_NODE = Node.ELEMENT_NODE;
+ // This is a rather ugly way of getting the "node" variable to be a nullable
+ for (let node of /** @type {(?Node)[]} */ (nodes)) {
+ while (true) {
+ if (node === null) { return false; }
+ if (node.nodeType === ELEMENT_NODE && /** @type {HTMLElement} */ (node).matches(selector)) { break; }
+ node = node.parentNode;
}
- return true;
}
+ return true;
+}
- /**
- * Checks whether the meta key is supported in the browser on the specified operating system.
- * @param {string} os The operating system to check.
- * @param {string} browser The browser to check.
- * @returns {boolean} `true` if supported, `false` otherwise.
- */
- static isMetaKeySupported(os, browser) {
- return !(browser === 'firefox' || browser === 'firefox-mobile') || os === 'mac';
- }
+/**
+ * Checks whether the meta key is supported in the browser on the specified operating system.
+ * @param {string} os The operating system to check.
+ * @param {string} browser The browser to check.
+ * @returns {boolean} `true` if supported, `false` otherwise.
+ */
+export function isMetaKeySupported(os, browser) {
+ return !(browser === 'firefox' || browser === 'firefox-mobile') || os === 'mac';
+}
- /**
- * Checks whether an element on the page that can accept input is focused.
- * @returns {boolean} `true` if an input element is focused, `false` otherwise.
- */
- static isInputElementFocused() {
- const element = document.activeElement;
- if (element === null) { return false; }
- const type = element.nodeName.toUpperCase();
- switch (type) {
- case 'INPUT':
- case 'TEXTAREA':
- case 'SELECT':
- return true;
- default:
- return element instanceof HTMLElement && element.isContentEditable;
- }
+/**
+ * Checks whether an element on the page that can accept input is focused.
+ * @returns {boolean} `true` if an input element is focused, `false` otherwise.
+ */
+export function isInputElementFocused() {
+ const element = document.activeElement;
+ if (element === null) { return false; }
+ const type = element.nodeName.toUpperCase();
+ switch (type) {
+ case 'INPUT':
+ case 'TEXTAREA':
+ case 'SELECT':
+ return true;
+ default:
+ return element instanceof HTMLElement && element.isContentEditable;
}
+}
- /**
- * Offsets an array of DOMRects by a given amount.
- * @param {DOMRect[]} rects The DOMRects to offset.
- * @param {number} x The horizontal offset amount.
- * @param {number} y The vertical offset amount.
- * @returns {DOMRect[]} The DOMRects with the offset applied.
- */
- static offsetDOMRects(rects, x, y) {
- const results = [];
- for (const rect of rects) {
- results.push(new DOMRect(rect.left + x, rect.top + y, rect.width, rect.height));
- }
- return results;
+/**
+ * Offsets an array of DOMRects by a given amount.
+ * @param {DOMRect[]} rects The DOMRects to offset.
+ * @param {number} x The horizontal offset amount.
+ * @param {number} y The vertical offset amount.
+ * @returns {DOMRect[]} The DOMRects with the offset applied.
+ */
+export function offsetDOMRects(rects, x, y) {
+ const results = [];
+ for (const rect of rects) {
+ results.push(new DOMRect(rect.left + x, rect.top + y, rect.width, rect.height));
}
+ return results;
+}
- /**
- * Gets the parent writing mode of an element.
- * See: https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode.
- * @param {?Element} element The HTML element to check.
- * @returns {import('document-util').NormalizedWritingMode} The writing mode.
- */
- static getElementWritingMode(element) {
- if (element !== null) {
- const {writingMode} = getComputedStyle(element);
- if (typeof writingMode === 'string') {
- return this.normalizeWritingMode(writingMode);
- }
+/**
+ * Gets the parent writing mode of an element.
+ * See: https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode.
+ * @param {?Element} element The HTML element to check.
+ * @returns {import('document-util').NormalizedWritingMode} The writing mode.
+ */
+export function getElementWritingMode(element) {
+ if (element !== null) {
+ const {writingMode} = getComputedStyle(element);
+ if (typeof writingMode === 'string') {
+ return normalizeWritingMode(writingMode);
}
- return 'horizontal-tb';
}
+ return 'horizontal-tb';
+}
- /**
- * Normalizes a CSS writing mode value by converting non-standard and deprecated values
- * into their corresponding standard vaules.
- * @param {string} writingMode The writing mode to normalize.
- * @returns {import('document-util').NormalizedWritingMode} The normalized writing mode.
- */
- static normalizeWritingMode(writingMode) {
- switch (writingMode) {
- case 'tb':
- return 'vertical-lr';
- case 'tb-rl':
- return 'vertical-rl';
- case 'horizontal-tb':
- case 'vertical-rl':
- case 'vertical-lr':
- case 'sideways-rl':
- case 'sideways-lr':
- return writingMode;
- default: // 'lr', 'lr-tb', 'rl'
- return 'horizontal-tb';
- }
+/**
+ * Normalizes a CSS writing mode value by converting non-standard and deprecated values
+ * into their corresponding standard vaules.
+ * @param {string} writingMode The writing mode to normalize.
+ * @returns {import('document-util').NormalizedWritingMode} The normalized writing mode.
+ */
+export function normalizeWritingMode(writingMode) {
+ switch (writingMode) {
+ case 'tb':
+ return 'vertical-lr';
+ case 'tb-rl':
+ return 'vertical-rl';
+ case 'horizontal-tb':
+ case 'vertical-rl':
+ case 'vertical-lr':
+ case 'sideways-rl':
+ case 'sideways-lr':
+ return writingMode;
+ default: // 'lr', 'lr-tb', 'rl'
+ return 'horizontal-tb';
}
+}
- /**
- * Converts a value from an element to a number.
- * @param {string} valueString A string representation of a number.
- * @param {import('document-util').ToNumberConstraints} constraints An object which might contain `min`, `max`, and `step` fields which are used to constrain the value.
- * @returns {number} The parsed and constrained number.
- */
- static convertElementValueToNumber(valueString, constraints) {
- let value = Number.parseFloat(valueString);
- if (!Number.isFinite(value)) { value = 0; }
+/**
+ * Converts a value from an element to a number.
+ * @param {string} valueString A string representation of a number.
+ * @param {import('document-util').ToNumberConstraints} constraints An object which might contain `min`, `max`, and `step` fields which are used to constrain the value.
+ * @returns {number} The parsed and constrained number.
+ */
+export function convertElementValueToNumber(valueString, constraints) {
+ let value = Number.parseFloat(valueString);
+ if (!Number.isFinite(value)) { value = 0; }
- const min = this._convertToNumberOrNull(constraints.min);
- const max = this._convertToNumberOrNull(constraints.max);
- const step = this._convertToNumberOrNull(constraints.step);
- if (typeof min === 'number') { value = Math.max(value, min); }
- if (typeof max === 'number') { value = Math.min(value, max); }
- if (typeof step === 'number' && step !== 0) { value = Math.round(value / step) * step; }
- return value;
- }
+ const min = convertToNumberOrNull(constraints.min);
+ const max = convertToNumberOrNull(constraints.max);
+ const step = convertToNumberOrNull(constraints.step);
+ if (typeof min === 'number') { value = Math.max(value, min); }
+ if (typeof max === 'number') { value = Math.min(value, max); }
+ if (typeof step === 'number' && step !== 0) { value = Math.round(value / step) * step; }
+ return value;
+}
- /**
- * @param {string} value
- * @returns {?import('input').Modifier}
- */
- static normalizeModifier(value) {
- switch (value) {
- case 'alt':
- case 'ctrl':
- case 'meta':
- case 'shift':
- case 'mouse0':
- case 'mouse1':
- case 'mouse2':
- case 'mouse3':
- case 'mouse4':
- case 'mouse5':
- return value;
- default:
- return null;
- }
+/**
+ * @param {string} value
+ * @returns {?import('input').Modifier}
+ */
+export function normalizeModifier(value) {
+ switch (value) {
+ case 'alt':
+ case 'ctrl':
+ case 'meta':
+ case 'shift':
+ case 'mouse0':
+ case 'mouse1':
+ case 'mouse2':
+ case 'mouse3':
+ case 'mouse4':
+ case 'mouse5':
+ return value;
+ default:
+ return null;
}
+}
- /**
- * @param {string} value
- * @returns {?import('input').ModifierKey}
- */
- static normalizeModifierKey(value) {
- switch (value) {
- case 'alt':
- case 'ctrl':
- case 'meta':
- case 'shift':
- return value;
- default:
- return null;
- }
+/**
+ * @param {string} value
+ * @returns {?import('input').ModifierKey}
+ */
+export function normalizeModifierKey(value) {
+ switch (value) {
+ case 'alt':
+ case 'ctrl':
+ case 'meta':
+ case 'shift':
+ return value;
+ default:
+ return null;
}
+}
- /**
- * @param {MouseEvent} event The event to check.
- * @param {import('input').ModifierMouseButton[]|import('input').Modifier[]} array
- */
- static _getActiveButtons(event, array) {
- let {buttons} = event;
- if (typeof buttons === 'number' && buttons > 0) {
- for (let i = 0; i < 6; ++i) {
- const buttonFlag = (1 << i);
- if ((buttons & buttonFlag) !== 0) {
- array.push(/** @type {import('input').ModifierMouseButton} */ (`mouse${i}`));
- buttons &= ~buttonFlag;
- if (buttons === 0) { break; }
- }
+/**
+ * @param {MouseEvent} event The event to check.
+ * @param {import('input').ModifierMouseButton[]|import('input').Modifier[]} array
+ */
+function getActiveButtonsInternal(event, array) {
+ let {buttons} = event;
+ if (typeof buttons === 'number' && buttons > 0) {
+ for (let i = 0; i < 6; ++i) {
+ const buttonFlag = (1 << i);
+ if ((buttons & buttonFlag) !== 0) {
+ array.push(/** @type {import('input').ModifierMouseButton} */ (`mouse${i}`));
+ buttons &= ~buttonFlag;
+ if (buttons === 0) { break; }
}
}
}
+}
- /**
- * @param {string|number|undefined} value
- * @returns {?number}
- */
- static _convertToNumberOrNull(value) {
- if (typeof value !== 'number') {
- if (typeof value !== 'string' || value.length === 0) {
- return null;
- }
- value = Number.parseFloat(value);
+/**
+ * @param {string|number|undefined} value
+ * @returns {?number}
+ */
+function convertToNumberOrNull(value) {
+ if (typeof value !== 'number') {
+ if (typeof value !== 'string' || value.length === 0) {
+ return null;
}
- return !Number.isNaN(value) ? value : null;
+ value = Number.parseFloat(value);
}
+ return !Number.isNaN(value) ? value : null;
+}
- /**
- * Computes whether or not this browser and document supports CSS zoom, which is primarily a legacy Chromium feature.
- * @returns {boolean}
- */
- static _computeCssZoomSupported() {
- // 'style' can be undefined in certain contexts, such as when document is an SVG document.
- const {style} = document.createElement('div');
+/**
+ * Computes whether or not this browser and document supports CSS zoom, which is primarily a legacy Chromium feature.
+ * @returns {boolean}
+ */
+function computeCssZoomSupported() {
+ // 'style' can be undefined in certain contexts, such as when document is an SVG document.
+ const {style} = document.createElement('div');
+ return (
+ typeof style === 'object' &&
+ style !== null &&
// @ts-expect-error - zoom is a non-standard property.
- return (typeof style === 'object' && style !== null && typeof style.zoom === 'string');
- }
+ typeof style.zoom === 'string'
+ );
}
diff --git a/ext/js/dom/dom-data-binder.js b/ext/js/dom/dom-data-binder.js
index 7523e434..be5633d7 100644
--- a/ext/js/dom/dom-data-binder.js
+++ b/ext/js/dom/dom-data-binder.js
@@ -17,7 +17,7 @@
*/
import {TaskAccumulator} from '../general/task-accumulator.js';
-import {DocumentUtil} from './document-util.js';
+import {convertElementValueToNumber} from './document-util.js';
import {SelectorObserver} from './selector-observer.js';
/**
@@ -264,7 +264,7 @@ export class DOMDataBinder {
case 'text':
return `${/** @type {HTMLInputElement} */ (element).value}`;
case 'number':
- return DocumentUtil.convertElementValueToNumber(/** @type {HTMLInputElement} */ (element).value, /** @type {HTMLInputElement} */ (element));
+ return convertElementValueToNumber(/** @type {HTMLInputElement} */ (element).value, /** @type {HTMLInputElement} */ (element));
case 'textarea':
return /** @type {HTMLTextAreaElement} */ (element).value;
case 'select':
diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js
index 927783d1..b2829e75 100644
--- a/ext/js/dom/text-source-element.js
+++ b/ext/js/dom/text-source-element.js
@@ -17,7 +17,7 @@
*/
import {readCodePointsBackward, readCodePointsForward} from '../data/sandbox/string-util.js';
-import {DocumentUtil} from './document-util.js';
+import {convertMultipleRectZoomCoordinates} from './document-util.js';
/**
* This class represents a text source that is attached to a HTML element, such as an <img>
@@ -145,7 +145,7 @@ export class TextSourceElement {
* @returns {DOMRect[]} The rects.
*/
getRects() {
- return DocumentUtil.convertMultipleRectZoomCoordinates(this._element.getClientRects(), this._element);
+ return convertMultipleRectZoomCoordinates(this._element.getClientRects(), this._element);
}
/**
diff --git a/ext/js/dom/text-source-generator.js b/ext/js/dom/text-source-generator.js
index 83c7271c..68bf036a 100644
--- a/ext/js/dom/text-source-generator.js
+++ b/ext/js/dom/text-source-generator.js
@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {DocumentUtil} from './document-util.js';
+import {computeZoomScale, isPointInAnyRect} from './document-util.js';
import {DOMTextScanner} from './dom-text-scanner.js';
import {TextSourceElement} from './text-source-element.js';
import {TextSourceRange} from './text-source-range.js';
@@ -358,7 +358,7 @@ export class TextSourceGenerator {
// Convert CSS zoom coordinates
if (normalizeCssZoom) {
- const scale = DocumentUtil.computeZoomScale(startContainer);
+ const scale = computeZoomScale(startContainer);
x /= scale;
y /= scale;
}
@@ -370,7 +370,7 @@ export class TextSourceGenerator {
const {node, offset, content} = new DOMTextScanner(nodePre, offsetPre, true, false).seek(1);
range.setEnd(node, offset);
- if (!this._isWhitespace(content) && DocumentUtil.isPointInAnyRect(x, y, range.getClientRects())) {
+ if (!this._isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects())) {
return true;
}
} finally {
@@ -381,7 +381,7 @@ export class TextSourceGenerator {
const {node, offset, content} = new DOMTextScanner(startContainer, range.startOffset, true, false).seek(-1);
range.setStart(node, offset);
- if (!this._isWhitespace(content) && DocumentUtil.isPointInAnyRect(x, y, range.getClientRects())) {
+ if (!this._isWhitespace(content) && isPointInAnyRect(x, y, range.getClientRects())) {
// This purposefully leaves the starting offset as modified and sets the range length to 0.
range.setEnd(node, offset);
return true;
diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js
index 05e7b6fb..2012af7a 100644
--- a/ext/js/dom/text-source-range.js
+++ b/ext/js/dom/text-source-range.js
@@ -17,7 +17,7 @@
*/
import {toError} from '../core/to-error.js';
-import {DocumentUtil} from './document-util.js';
+import {convertMultipleRectZoomCoordinates, convertRectZoomCoordinates, getElementWritingMode, getNodesInRange, offsetDOMRects} from './document-util.js';
import {DOMTextScanner} from './dom-text-scanner.js';
/**
@@ -170,7 +170,7 @@ export class TextSourceRange {
*/
getRects() {
if (this._isImposterDisconnected()) { return this._getCachedRects(); }
- return DocumentUtil.convertMultipleRectZoomCoordinates(this._range.getClientRects(), this._range.startContainer);
+ return convertMultipleRectZoomCoordinates(this._range.getClientRects(), this._range.startContainer);
}
/**
@@ -181,7 +181,7 @@ export class TextSourceRange {
getWritingMode() {
let node = this._isImposterDisconnected() ? this._imposterSourceElement : this._range.startContainer;
if (node !== null && node.nodeType !== Node.ELEMENT_NODE) { node = node.parentElement; }
- return DocumentUtil.getElementWritingMode(/** @type {?Element} */ (node));
+ return getElementWritingMode(/** @type {?Element} */ (node));
}
/**
@@ -243,7 +243,7 @@ export class TextSourceRange {
* @returns {Node[]} The nodes in the range.
*/
getNodesInRange() {
- return DocumentUtil.getNodesInRange(this._range);
+ return getNodesInRange(this._range);
}
/**
@@ -263,8 +263,8 @@ export class TextSourceRange {
* @returns {TextSourceRange} A new instance of the class corresponding to the range.
*/
static createFromImposter(range, imposterElement, imposterSourceElement) {
- const cachedRects = DocumentUtil.convertMultipleRectZoomCoordinates(range.getClientRects(), range.startContainer);
- const cachedSourceRect = DocumentUtil.convertRectZoomCoordinates(imposterSourceElement.getBoundingClientRect(), imposterSourceElement);
+ const cachedRects = convertMultipleRectZoomCoordinates(range.getClientRects(), range.startContainer);
+ const cachedSourceRect = convertRectZoomCoordinates(imposterSourceElement.getBoundingClientRect(), imposterSourceElement);
return new TextSourceRange(range, range.startOffset, range.toString(), imposterElement, imposterSourceElement, cachedRects, cachedSourceRect);
}
@@ -289,8 +289,8 @@ export class TextSourceRange {
) {
throw new Error('Cached rects not valid for this instance');
}
- const sourceRect = DocumentUtil.convertRectZoomCoordinates(this._imposterSourceElement.getBoundingClientRect(), this._imposterSourceElement);
- return DocumentUtil.offsetDOMRects(
+ const sourceRect = convertRectZoomCoordinates(this._imposterSourceElement.getBoundingClientRect(), this._imposterSourceElement);
+ return offsetDOMRects(
this._cachedRects,
sourceRect.left - this._cachedSourceRect.left,
sourceRect.top - this._cachedSourceRect.top