From 4da4827bcbcdd1ef163f635d9b29416ff272b0bb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 12:48:14 -0500 Subject: Add JSDoc type annotations to project (rebased) --- ext/js/dom/document-focus-controller.js | 22 ++- ext/js/dom/document-util.js | 290 +++++++++++++++++++++++--------- ext/js/dom/dom-data-binder.js | 144 +++++++++++++--- ext/js/dom/dom-text-scanner.js | 55 ++++-- ext/js/dom/html-template-collection.js | 39 ++++- ext/js/dom/native-simple-dom-parser.js | 74 ++++++-- ext/js/dom/panel-element.js | 34 ++++ ext/js/dom/popup-menu.js | 71 +++++++- ext/js/dom/sandbox/css-style-applier.js | 43 ++--- ext/js/dom/scroll-element.js | 50 ++++++ ext/js/dom/selector-observer.js | 113 +++++++------ ext/js/dom/simple-dom-parser.js | 122 ++++++++++---- ext/js/dom/text-source-element.js | 41 +++-- ext/js/dom/text-source-range.js | 33 +++- 14 files changed, 854 insertions(+), 277 deletions(-) (limited to 'ext/js/dom') diff --git a/ext/js/dom/document-focus-controller.js b/ext/js/dom/document-focus-controller.js index 501d1fad..32ea2ce8 100644 --- a/ext/js/dom/document-focus-controller.js +++ b/ext/js/dom/document-focus-controller.js @@ -29,7 +29,9 @@ export class DocumentFocusController { * should be automatically focused on prepare. */ constructor(autofocusElementSelector=null) { + /** @type {?HTMLElement} */ this._autofocusElement = (autofocusElementSelector !== null ? document.querySelector(autofocusElementSelector) : null); + /** @type {?HTMLElement} */ this._contentScrollFocusElement = document.querySelector('#content-scroll-focus'); } @@ -46,7 +48,7 @@ export class DocumentFocusController { /** * Removes focus from a given element. - * @param {Element} element The element to remove focus from. + * @param {HTMLElement} element The element to remove focus from. */ blurElement(element) { if (document.activeElement !== element) { return; } @@ -56,10 +58,14 @@ export class DocumentFocusController { // Private + /** */ _onWindowFocus() { this._updateFocusedElement(false); } + /** + * @param {boolean} force + */ _updateFocusedElement(force) { const target = this._contentScrollFocusElement; if (target === null) { return; } @@ -73,6 +79,7 @@ export class DocumentFocusController { ) { // Get selection const selection = window.getSelection(); + if (selection === null) { return; } const selectionRanges1 = this._getSelectionRanges(selection); // Note: This function will cause any selected text to be deselected on Firefox. @@ -86,6 +93,10 @@ export class DocumentFocusController { } } + /** + * @param {Selection} selection + * @returns {Range[]} + */ _getSelectionRanges(selection) { const ranges = []; for (let i = 0, ii = selection.rangeCount; i < ii; ++i) { @@ -94,6 +105,10 @@ export class DocumentFocusController { return ranges; } + /** + * @param {Selection} selection + * @param {Range[]} ranges + */ _setSelectionRanges(selection, ranges) { selection.removeAllRanges(); for (const range of ranges) { @@ -101,6 +116,11 @@ export class DocumentFocusController { } } + /** + * @param {Range[]} ranges1 + * @param {Range[]} ranges2 + * @returns {boolean} + */ _areRangesSame(ranges1, ranges2) { const ii = ranges1.length; if (ii !== ranges2.length) { diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index d379192c..f53d55fd 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -25,30 +25,12 @@ import {TextSourceRange} from './text-source-range.js'; * This class contains utility functions related to the HTML document. */ export class DocumentUtil { - /** - * Options to configure how element detection is performed. - * @typedef {object} GetRangeFromPointOptions - * @property {boolean} deepContentScan Whether or deep content scanning should be performed. When deep content scanning is enabled, - * some transparent overlay elements will be ignored when looking for the element at the input position. - * @property {boolean} normalizeCssZoom Whether or not zoom coordinates should be normalized. - */ - - /** - * Scans the document for text or elements with text information at the given coordinate. - * Coordinates are provided in [client space](https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems). - * @callback GetRangeFromPointHandler - * @param {number} x The x coordinate to search at. - * @param {number} y The y coordinate to search at. - * @param {GetRangeFromPointOptions} options Options to configure how element detection is performed. - * @returns {?TextSourceRange|TextSourceElement} A range for the hovered text or element, or `null` if no applicable content was found. - */ - /** * Scans the document for text or elements with text information at the given coordinate. * Coordinates are provided in [client space](https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems). * @param {number} x The x coordinate to search at. * @param {number} y The y coordinate to search at. - * @param {GetRangeFromPointOptions} options Options to configure how element detection is performed. + * @param {import('document-util').GetRangeFromPointOptions} options Options to configure how element detection is performed. * @returns {?TextSourceRange|TextSourceElement} A range for the hovered text or element, or `null` if no applicable content was found. */ static getRangeFromPoint(x, y, options) { @@ -60,8 +42,11 @@ export class DocumentUtil { const {deepContentScan, normalizeCssZoom} = options; const elements = this._getElementsFromPoint(x, y, deepContentScan); + /** @type {?HTMLDivElement} */ let imposter = null; + /** @type {?HTMLDivElement} */ let imposterContainer = null; + /** @type {?Element} */ let imposterSourceElement = null; if (elements.length > 0) { const element = elements[0]; @@ -71,14 +56,14 @@ export class DocumentUtil { case 'SELECT': return TextSourceElement.create(element); case 'INPUT': - if (element.type === 'text') { + if (/** @type {HTMLInputElement} */ (element).type === 'text') { imposterSourceElement = element; - [imposter, imposterContainer] = this._createImposter(element, false); + [imposter, imposterContainer] = this._createImposter(/** @type {HTMLInputElement} */ (element), false); } break; case 'TEXTAREA': imposterSourceElement = element; - [imposter, imposterContainer] = this._createImposter(element, true); + [imposter, imposterContainer] = this._createImposter(/** @type {HTMLTextAreaElement} */ (element), true); break; } } @@ -86,14 +71,17 @@ export class DocumentUtil { const range = this._caretRangeFromPointExt(x, y, deepContentScan ? elements : [], normalizeCssZoom); if (range !== null) { if (imposter !== null) { - this._setImposterStyle(imposterContainer.style, 'z-index', '-2147483646'); + this._setImposterStyle(/** @type {HTMLDivElement} */ (imposterContainer).style, 'z-index', '-2147483646'); this._setImposterStyle(imposter.style, 'pointer-events', 'none'); - return TextSourceRange.createFromImposter(range, imposterContainer, imposterSourceElement); + return TextSourceRange.createFromImposter(range, /** @type {HTMLDivElement} */ (imposterContainer), /** @type {HTMLElement} */ (imposterSourceElement)); } return TextSourceRange.create(range); } else { if (imposterContainer !== null) { - imposterContainer.parentNode.removeChild(imposterContainer); + const {parentNode} = imposterContainer; + if (parentNode !== null) { + parentNode.removeChild(imposterContainer); + } } return null; } @@ -101,7 +89,7 @@ export class DocumentUtil { /** * Registers a custom handler for scanning for text or elements at the input position. - * @param {GetRangeFromPointHandler} handler The handler callback which will be invoked when calling `getRangeFromPoint`. + * @param {import('document-util').GetRangeFromPointHandler} handler The handler callback which will be invoked when calling `getRangeFromPoint`. */ static registerGetRangeFromPointHandler(handler) { this._getRangeFromPointHandlers.push(handler); @@ -129,7 +117,7 @@ export class DocumentUtil { * ```js * new Map([ [character: string, [otherCharacter: string, includeCharacterAtEnd: boolean]], ... ]) * ``` - * @returns {{sentence: string, offset: number}} The sentence and the offset to the original source. + * @returns {{text: string, offset: number}} The sentence and the offset to the original source. */ static extractSentence(source, layoutAwareScan, extent, terminateAtNewlines, terminatorMap, forwardQuoteMap, backwardQuoteMap) { // Scan text @@ -218,11 +206,12 @@ export class DocumentUtil { /** * 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. + * @param {?Node} node A node in the document. * @returns {number} The scaling factor. */ static computeZoomScale(node) { if (this._cssZoomSupported === null) { + // @ts-ignore - zoom is a non-standard property that exists in Chromium-based browsers this._cssZoomSupported = (typeof document.createElement('div').style.zoom === 'string'); } if (!this._cssZoomSupported) { return 1; } @@ -237,7 +226,7 @@ export class DocumentUtil { for (; node !== null && node !== documentElement; node = node.parentNode) { const {nodeType} = node; if (nodeType === DOCUMENT_FRAGMENT_NODE) { - const {host} = node; + const {host} = /** @type {ShadowRoot} */ (node); if (typeof host !== 'undefined') { node = host; } @@ -245,9 +234,9 @@ export class DocumentUtil { } else if (nodeType !== ELEMENT_NODE) { continue; } - let {zoom} = getComputedStyle(node); - if (typeof zoom !== 'string') { continue; } - zoom = Number.parseFloat(zoom); + 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; } @@ -267,13 +256,13 @@ export class DocumentUtil { /** * Converts multiple rects based on the CSS zoom scaling for a given node. - * @param {DOMRect[]} rects The rects to convert. + * @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; } + 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)); @@ -299,7 +288,7 @@ export class DocumentUtil { * 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[]} rects The rect to check. + * @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) { @@ -331,9 +320,10 @@ export class DocumentUtil { /** * Gets an array of the active modifier keys. * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check. - * @returns {string[]} An array of modifiers. + * @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'); } @@ -345,20 +335,24 @@ export class DocumentUtil { /** * Gets an array of the active modifier keys and buttons. * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check. - * @returns {string[]} An array of modifiers and buttons. + * @returns {import('input').Modifier[]} An array of modifiers and buttons. */ static getActiveModifiersAndButtons(event) { + /** @type {import('input').Modifier[]} */ const modifiers = this.getActiveModifiers(event); - this._getActiveButtons(event, modifiers); + if (event instanceof MouseEvent) { + this._getActiveButtons(event, modifiers); + } return modifiers; } /** * Gets an array of the active buttons. - * @param {KeyboardEvent|MouseEvent|TouchEvent} event The event to check. - * @returns {string[]} An array of modifiers and 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; @@ -366,7 +360,7 @@ export class DocumentUtil { /** * Adds a fullscreen change event listener. This function handles all of the browser-specific variants. - * @param {Function} onFullscreenChanged The event callback. + * @param {EventListener} onFullscreenChanged The event callback. * @param {?EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. */ static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection=null) { @@ -394,8 +388,11 @@ export class DocumentUtil { static getFullscreenElement() { return ( document.fullscreenElement || + // @ts-ignore - vendor prefix document.msFullscreenElement || + // @ts-ignore - vendor prefix document.mozFullScreenElement || + // @ts-ignore - vendor prefix document.webkitFullscreenElement || null ); @@ -409,7 +406,7 @@ export class DocumentUtil { static getNodesInRange(range) { const end = range.endContainer; const nodes = []; - for (let node = range.startContainer; node !== null; node = this.getNextNode(node)) { + for (let node = /** @type {?Node} */ (range.startContainer); node !== null; node = this.getNextNode(node)) { nodes.push(node); if (node === end) { break; } } @@ -422,7 +419,7 @@ export class DocumentUtil { * @returns {?Node} The next node, or `null` if there is no next node. */ static getNextNode(node) { - let next = node.firstChild; + let next = /** @type {?Node} */ (node.firstChild); if (next === null) { while (true) { next = node.nextSibling; @@ -445,10 +442,14 @@ export class DocumentUtil { */ static anyNodeMatchesSelector(nodes, selector) { const ELEMENT_NODE = Node.ELEMENT_NODE; - for (let node of nodes) { - for (; node !== null; node = node.parentNode) { - if (node.nodeType !== ELEMENT_NODE) { continue; } - if (node.matches(selector)) { return true; } + // 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; } } @@ -463,10 +464,11 @@ export class DocumentUtil { */ static everyNodeMatchesSelector(nodes, selector) { const ELEMENT_NODE = Node.ELEMENT_NODE; - for (let node of nodes) { + // 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 && node.matches(selector)) { break; } + if (node.nodeType === ELEMENT_NODE && /** @type {HTMLElement} */ (node).matches(selector)) { break; } node = node.parentNode; } } @@ -497,7 +499,7 @@ export class DocumentUtil { case 'SELECT': return true; default: - return element.isContentEditable; + return element instanceof HTMLElement && element.isContentEditable; } } @@ -506,7 +508,7 @@ export class DocumentUtil { * @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. + * @returns {DOMRect[]} The DOMRects with the offset applied. */ static offsetDOMRects(rects, x, y) { const results = []; @@ -519,8 +521,8 @@ export class DocumentUtil { /** * 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 {string} The writing mode. + * @param {?Element} element The HTML element to check. + * @returns {import('document-util').NormalizedWritingMode} The writing mode. */ static getElementWritingMode(element) { if (element !== null) { @@ -536,52 +538,95 @@ export class DocumentUtil { * 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 {string} The normalized writing mode. + * @returns {import('document-util').NormalizedWritingMode} The normalized writing mode. */ static normalizeWritingMode(writingMode) { switch (writingMode) { - case 'lr': - case 'lr-tb': - case 'rl': - return 'horizontal-tb'; case 'tb': return 'vertical-lr'; case 'tb-rl': return 'vertical-rl'; - default: + 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} value A string representation of a number. - * @param {object} constraints An object which might contain `min`, `max`, and `step` fields which are used to constrain the value. + * @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(value, constraints) { - value = parseFloat(value); + static convertElementValueToNumber(valueString, constraints) { + let value = Number.parseFloat(valueString); if (!Number.isFinite(value)) { value = 0; } - let {min, max, step} = constraints; - min = this._convertToNumberOrNull(min); - max = this._convertToNumberOrNull(max); - step = this._convertToNumberOrNull(step); + 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; } + /** + * @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').ModifierKey} + */ + static normalizeModifierKey(value) { + switch (value) { + case 'alt': + case 'ctrl': + case 'meta': + case 'shift': + return value; + default: + return null; + } + } + // Private + /** + * @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(`mouse${i}`); + array.push(/** @type {import('input').ModifierMouseButton} */ (`mouse${i}`)); buttons &= ~buttonFlag; if (buttons === 0) { break; } } @@ -589,10 +634,20 @@ export class DocumentUtil { } } + /** + * @param {CSSStyleDeclaration} style + * @param {string} propertyName + * @param {string} value + */ static _setImposterStyle(style, propertyName, value) { style.setProperty(propertyName, value, 'important'); } + /** + * @param {HTMLInputElement|HTMLTextAreaElement} element + * @param {boolean} isTextarea + * @returns {[imposter: ?HTMLDivElement, container: ?HTMLDivElement]} + */ static _createImposter(element, isTextarea) { const body = document.body; if (body === null) { return [null, null]; } @@ -669,6 +724,12 @@ export class DocumentUtil { return [imposter, container]; } + /** + * @param {number} x + * @param {number} y + * @param {boolean} all + * @returns {Element[]} + */ static _getElementsFromPoint(x, y, all) { if (all) { // document.elementsFromPoint can return duplicates which must be removed. @@ -680,6 +741,13 @@ export class DocumentUtil { return e !== null ? [e] : []; } + /** + * @param {number} x + * @param {number} y + * @param {Range} range + * @param {boolean} normalizeCssZoom + * @returns {boolean} + */ static _isPointInRange(x, y, range, normalizeCssZoom) { // Require a text node to start const {startContainer} = range; @@ -722,16 +790,26 @@ export class DocumentUtil { return false; } + /** + * @param {string} string + * @returns {boolean} + */ static _isWhitespace(string) { return string.trim().length === 0; } + /** + * @param {number} x + * @param {number} y + * @returns {?Range} + */ static _caretRangeFromPoint(x, y) { if (typeof document.caretRangeFromPoint === 'function') { // Chrome, Edge return document.caretRangeFromPoint(x, y); } + // @ts-ignore - caretPositionFromPoint is non-standard if (typeof document.caretPositionFromPoint === 'function') { // Firefox return this._caretPositionFromPoint(x, y); @@ -741,8 +819,14 @@ export class DocumentUtil { return null; } + /** + * @param {number} x + * @param {number} y + * @returns {?Range} + */ static _caretPositionFromPoint(x, y) { - const position = document.caretPositionFromPoint(x, y); + // @ts-ignore - caretPositionFromPoint is non-standard + const position = /** @type {(x: number, y: number) => ?{offsetNode: Node, offset: number}} */ (document.caretPositionFromPoint)(x, y); if (position === null) { return null; } @@ -760,8 +844,8 @@ export class DocumentUtil { case Node.ELEMENT_NODE: // Elements with user-select: all will return the element // instead of a text point inside the element. - if (this._isElementUserSelectAll(node)) { - return this._caretPositionFromPointNormalizeStyles(x, y, node); + if (this._isElementUserSelectAll(/** @type {Element} */ (node))) { + return this._caretPositionFromPointNormalizeStyles(x, y, /** @type {Element} */ (node)); } break; } @@ -778,14 +862,23 @@ export class DocumentUtil { } } + /** + * @param {number} x + * @param {number} y + * @param {Element} nextElement + * @returns {?Range} + */ static _caretPositionFromPointNormalizeStyles(x, y, nextElement) { const previousStyles = new Map(); try { while (true) { - this._recordPreviousStyle(previousStyles, nextElement); - nextElement.style.setProperty('user-select', 'text', 'important'); + if (nextElement instanceof HTMLElement) { + this._recordPreviousStyle(previousStyles, nextElement); + nextElement.style.setProperty('user-select', 'text', 'important'); + } - const position = document.caretPositionFromPoint(x, y); + // @ts-ignore - caretPositionFromPoint is non-standard + const position = /** @type {(x: number, y: number) => ?{offsetNode: Node, offset: number}} */ (document.caretPositionFromPoint)(x, y); if (position === null) { return null; } @@ -803,12 +896,12 @@ export class DocumentUtil { case Node.ELEMENT_NODE: // Elements with user-select: all will return the element // instead of a text point inside the element. - if (this._isElementUserSelectAll(node)) { + if (this._isElementUserSelectAll(/** @type {Element} */ (node))) { if (previousStyles.has(node)) { // Recursive return null; } - nextElement = node; + nextElement = /** @type {Element} */ (node); continue; } break; @@ -830,6 +923,13 @@ export class DocumentUtil { } } + /** + * @param {number} x + * @param {number} y + * @param {Element[]} elements + * @param {boolean} normalizeCssZoom + * @returns {?Range} + */ static _caretRangeFromPointExt(x, y, elements, normalizeCssZoom) { let previousStyles = null; try { @@ -862,6 +962,12 @@ export class DocumentUtil { } } + /** + * @param {Element[]} elements + * @param {number} i + * @param {Map} previousStyles + * @returns {number} + */ static _disableTransparentElement(elements, i, previousStyles) { while (true) { if (i >= elements.length) { @@ -870,19 +976,28 @@ export class DocumentUtil { const element = elements[i++]; if (this._isElementTransparent(element)) { - this._recordPreviousStyle(previousStyles, element); - element.style.setProperty('pointer-events', 'none', 'important'); + if (element instanceof HTMLElement) { + this._recordPreviousStyle(previousStyles, element); + element.style.setProperty('pointer-events', 'none', 'important'); + } return i; } } } + /** + * @param {Map} previousStyles + * @param {Element} element + */ static _recordPreviousStyle(previousStyles, element) { if (previousStyles.has(element)) { return; } const style = element.hasAttribute('style') ? element.getAttribute('style') : null; previousStyles.set(element, style); } + /** + * @param {Map} previousStyles + */ static _revertStyles(previousStyles) { for (const [element, style] of previousStyles.entries()) { if (style === null) { @@ -893,6 +1008,10 @@ export class DocumentUtil { } } + /** + * @param {Element} element + * @returns {boolean} + */ static _isElementTransparent(element) { if ( element === document.body || @@ -908,14 +1027,26 @@ export class DocumentUtil { ); } + /** + * @param {string} cssColor + * @returns {boolean} + */ static _isColorTransparent(cssColor) { return this._transparentColorPattern.test(cssColor); } + /** + * @param {Element} element + * @returns {boolean} + */ static _isElementUserSelectAll(element) { return getComputedStyle(element).userSelect === 'all'; } + /** + * @param {string|number|undefined} value + * @returns {?number} + */ static _convertToNumberOrNull(value) { if (typeof value !== 'number') { if (typeof value !== 'string' || value.length === 0) { @@ -926,9 +1057,12 @@ export class DocumentUtil { return !Number.isNaN(value) ? value : null; } } +/** @type {RegExp} */ // eslint-disable-next-line no-underscore-dangle DocumentUtil._transparentColorPattern = /rgba\s*\([^)]*,\s*0(?:\.0+)?\s*\)/; +/** @type {?boolean} */ // eslint-disable-next-line no-underscore-dangle DocumentUtil._cssZoomSupported = null; +/** @type {import('document-util').GetRangeFromPointHandler[]} */ // eslint-disable-next-line no-underscore-dangle DocumentUtil._getRangeFromPointHandlers = []; diff --git a/ext/js/dom/dom-data-binder.js b/ext/js/dom/dom-data-binder.js index 4da5b0c2..cf98a243 100644 --- a/ext/js/dom/dom-data-binder.js +++ b/ext/js/dom/dom-data-binder.js @@ -20,42 +20,66 @@ import {TaskAccumulator} from '../general/task-accumulator.js'; import {DocumentUtil} from './document-util.js'; import {SelectorObserver} from './selector-observer.js'; +/** + * @template [T=unknown] + */ export class DOMDataBinder { + /** + * @param {import('dom-data-binder').ConstructorDetails} details + */ constructor({selector, createElementMetadata, compareElementMetadata, getValues, setValues, onError=null}) { + /** @type {string} */ this._selector = selector; + /** @type {import('dom-data-binder').CreateElementMetadataCallback} */ this._createElementMetadata = createElementMetadata; + /** @type {import('dom-data-binder').CompareElementMetadataCallback} */ this._compareElementMetadata = compareElementMetadata; + /** @type {import('dom-data-binder').GetValuesCallback} */ this._getValues = getValues; + /** @type {import('dom-data-binder').SetValuesCallback} */ this._setValues = setValues; + /** @type {?import('dom-data-binder').OnErrorCallback} */ this._onError = onError; + /** @type {TaskAccumulator, import('dom-data-binder').UpdateTaskValue>} */ this._updateTasks = new TaskAccumulator(this._onBulkUpdate.bind(this)); + /** @type {TaskAccumulator, import('dom-data-binder').AssignTaskValue>} */ this._assignTasks = new TaskAccumulator(this._onBulkAssign.bind(this)); - this._selectorObserver = new SelectorObserver({ + /** @type {SelectorObserver>} */ + this._selectorObserver = /** @type {SelectorObserver>} */ (new SelectorObserver({ selector, ignoreSelector: null, onAdded: this._createObserver.bind(this), onRemoved: this._removeObserver.bind(this), onChildrenUpdated: this._onObserverChildrenUpdated.bind(this), isStale: this._isObserverStale.bind(this) - }); + })); } + /** + * @param {Element} element + */ observe(element) { this._selectorObserver.observe(element, true); } + /** */ disconnect() { this._selectorObserver.disconnect(); } + /** */ async refresh() { await this._updateTasks.enqueue(null, {all: true}); } // Private + /** + * @param {import('dom-data-binder').UpdateTask[]} tasks + */ async _onBulkUpdate(tasks) { let all = false; + /** @type {import('dom-data-binder').ApplyTarget[]} */ const targets = []; for (const [observer, task] of tasks) { if (observer === null) { @@ -82,17 +106,29 @@ export class DOMDataBinder { this._applyValues(targets, responses, true); } + /** + * @param {import('dom-data-binder').AssignTask[]} tasks + */ async _onBulkAssign(tasks) { - const targets = tasks; - const args = targets.map(([observer, task]) => ({ - element: observer.element, - metadata: observer.metadata, - value: task.data.value - })); + /** @type {import('dom-data-binder').ApplyTarget[]} */ + const targets = []; + const args = []; + for (const [observer, task] of tasks) { + if (observer === null) { continue; } + args.push({ + element: observer.element, + metadata: observer.metadata, + value: task.data.value + }); + targets.push([observer, task]); + } const responses = await this._setValues(args); this._applyValues(targets, responses, false); } + /** + * @param {import('dom-data-binder').ElementObserver} observer + */ _onElementChange(observer) { const value = this._getElementValue(observer.element); observer.value = value; @@ -100,9 +136,12 @@ export class DOMDataBinder { this._assignTasks.enqueue(observer, {value}); } + /** + * @param {import('dom-data-binder').ApplyTarget[]} targets + * @param {import('dom-data-binder').TaskResult[]} response + * @param {boolean} ignoreStale + */ _applyValues(targets, response, ignoreStale) { - if (!Array.isArray(response)) { return; } - for (let i = 0, ii = targets.length; i < ii; ++i) { const [observer, task] = targets[i]; const {error, result} = response[i]; @@ -123,8 +162,14 @@ export class DOMDataBinder { } } + /** + * @param {Element} element + * @returns {import('dom-data-binder').ElementObserver|undefined} + */ _createObserver(element) { const metadata = this._createElementMetadata(element); + if (typeof metadata === 'undefined') { return void 0; } + /** @type {import('dom-data-binder').ElementObserver} */ const observer = { element, type: this._getNormalizedElementType(element), @@ -137,76 +182,121 @@ export class DOMDataBinder { element.addEventListener('change', observer.onChange, false); - this._updateTasks.enqueue(observer); + this._updateTasks.enqueue(observer, {all: false}); return observer; } + /** + * @param {Element} element + * @param {import('dom-data-binder').ElementObserver} observer + */ _removeObserver(element, observer) { + if (observer.onChange === null) { return; } element.removeEventListener('change', observer.onChange, false); observer.onChange = null; } + /** + * @param {Element} element + * @param {import('dom-data-binder').ElementObserver} observer + */ _onObserverChildrenUpdated(element, observer) { if (observer.hasValue) { this._setElementValue(element, observer.value); } } + /** + * @param {Element} element + * @param {import('dom-data-binder').ElementObserver} observer + * @returns {boolean} + */ _isObserverStale(element, observer) { const {type, metadata} = observer; - return !( - type === this._getNormalizedElementType(element) && - this._compareElementMetadata(metadata, this._createElementMetadata(element)) - ); + if (type !== this._getNormalizedElementType(element)) { return false; } + const newMetadata = this._createElementMetadata(element); + return typeof newMetadata === 'undefined' || !this._compareElementMetadata(metadata, newMetadata); } + /** + * @param {Element} element + * @param {unknown} value + */ _setElementValue(element, value) { switch (this._getNormalizedElementType(element)) { case 'checkbox': - element.checked = value; + /** @type {HTMLInputElement} */ (element).checked = typeof value === 'boolean' && value; break; case 'text': case 'number': case 'textarea': case 'select': - element.value = value; + /** @type {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement} */ (element).value = typeof value === 'string' ? value : `${value}`; break; } - const event = new CustomEvent('settingChanged', {detail: {value}}); + /** @type {number|string|boolean} */ + let safeValue; + switch (typeof value) { + case 'number': + case 'string': + case 'boolean': + safeValue = value; + break; + default: + safeValue = `${value}`; + break; + } + /** @type {import('dom-data-binder').SettingChangedEvent} */ + const event = new CustomEvent('settingChanged', {detail: {value: safeValue}}); element.dispatchEvent(event); } + /** + * @param {Element} element + * @returns {boolean|string|number|null} + */ _getElementValue(element) { switch (this._getNormalizedElementType(element)) { case 'checkbox': - return !!element.checked; + return !!(/** @type {HTMLInputElement} */ (element).checked); case 'text': - return `${element.value}`; + return `${/** @type {HTMLInputElement} */ (element).value}`; case 'number': - return DocumentUtil.convertElementValueToNumber(element.value, element); + return DocumentUtil.convertElementValueToNumber(/** @type {HTMLInputElement} */ (element).value, /** @type {HTMLInputElement} */ (element)); case 'textarea': + return /** @type {HTMLTextAreaElement} */ (element).value; case 'select': - return element.value; + return /** @type {HTMLSelectElement} */ (element).value; } return null; } + /** + * @param {Element} element + * @returns {import('dom-data-binder').NormalizedElementType} + */ _getNormalizedElementType(element) { switch (element.nodeName.toUpperCase()) { case 'INPUT': { - let {type} = element; - if (type === 'password') { type = 'text'; } - return type; + const {type} = /** @type {HTMLInputElement} */ (element); + switch (type) { + case 'text': + case 'password': + return 'text'; + case 'number': + case 'checkbox': + return type; + } + break; } case 'TEXTAREA': return 'textarea'; case 'SELECT': return 'select'; - default: - return null; } + return null; } } diff --git a/ext/js/dom/dom-text-scanner.js b/ext/js/dom/dom-text-scanner.js index ccd1c90b..3a785680 100644 --- a/ext/js/dom/dom-text-scanner.js +++ b/ext/js/dom/dom-text-scanner.js @@ -36,15 +36,25 @@ export class DOMTextScanner { const resetOffset = (ruby !== null); if (resetOffset) { node = ruby; } + /** @type {Node} */ this._node = node; + /** @type {number} */ this._offset = offset; + /** @type {string} */ this._content = ''; + /** @type {number} */ this._remainder = 0; + /** @type {boolean} */ this._resetOffset = resetOffset; + /** @type {number} */ this._newlines = 0; + /** @type {boolean} */ this._lineHasWhitespace = false; + /** @type {boolean} */ this._lineHasContent = false; + /** @type {boolean} */ this._forcePreserveWhitespace = forcePreserveWhitespace; + /** @type {boolean} */ this._generateLayoutContent = generateLayoutContent; } @@ -99,8 +109,8 @@ export class DOMTextScanner { const ELEMENT_NODE = Node.ELEMENT_NODE; const generateLayoutContent = this._generateLayoutContent; - let node = this._node; - let lastNode = node; + let node = /** @type {?Node} */ (this._node); + let lastNode = /** @type {Node} */ (node); let resetOffset = this._resetOffset; let newlines = 0; while (node !== null) { @@ -111,8 +121,8 @@ export class DOMTextScanner { lastNode = node; if (!( forward ? - this._seekTextNodeForward(node, resetOffset) : - this._seekTextNodeBackward(node, resetOffset) + this._seekTextNodeForward(/** @type {Text} */ (node), resetOffset) : + this._seekTextNodeBackward(/** @type {Text} */ (node), resetOffset) )) { // Length reached break; @@ -120,18 +130,19 @@ export class DOMTextScanner { } else if (nodeType === ELEMENT_NODE) { lastNode = node; this._offset = 0; - ({enterable, newlines} = DOMTextScanner.getElementSeekInfo(node)); + ({enterable, newlines} = DOMTextScanner.getElementSeekInfo(/** @type {Element} */ (node))); if (newlines > this._newlines && generateLayoutContent) { this._newlines = newlines; } } + /** @type {Node[]} */ const exitedNodes = []; node = DOMTextScanner.getNextNode(node, forward, enterable, exitedNodes); for (const exitedNode of exitedNodes) { if (exitedNode.nodeType !== ELEMENT_NODE) { continue; } - ({newlines} = DOMTextScanner.getElementSeekInfo(exitedNode)); + ({newlines} = DOMTextScanner.getElementSeekInfo(/** @type {Element} */ (exitedNode))); if (newlines > this._newlines && generateLayoutContent) { this._newlines = newlines; } @@ -155,7 +166,7 @@ export class DOMTextScanner { * @returns {boolean} `true` if scanning should continue, or `false` if the scan length has been reached. */ _seekTextNodeForward(textNode, resetOffset) { - const nodeValue = textNode.nodeValue; + const nodeValue = /** @type {string} */ (textNode.nodeValue); const nodeValueLength = nodeValue.length; const {preserveNewlines, preserveWhitespace} = this._getWhitespaceSettings(textNode); @@ -241,7 +252,7 @@ export class DOMTextScanner { * @returns {boolean} `true` if scanning should continue, or `false` if the scan length has been reached. */ _seekTextNodeBackward(textNode, resetOffset) { - const nodeValue = textNode.nodeValue; + const nodeValue = /** @type {string} */ (textNode.nodeValue); const nodeValueLength = nodeValue.length; const {preserveNewlines, preserveWhitespace} = this._getWhitespaceSettings(textNode); @@ -350,6 +361,7 @@ export class DOMTextScanner { * @returns {?Node} The next node in the document, or `null` if there is no next node. */ static getNextNode(node, forward, visitChildren, exitedNodes) { + /** @type {?Node} */ let next = visitChildren ? (forward ? node.firstChild : node.lastChild) : null; if (next === null) { while (true) { @@ -369,14 +381,17 @@ export class DOMTextScanner { /** * Gets the parent element of a given Node. - * @param {Node} node The node to check. - * @returns {?Node} The parent element if one exists, otherwise `null`. + * @param {?Node} node The node to check. + * @returns {?Element} The parent element if one exists, otherwise `null`. */ static getParentElement(node) { - while (node !== null && node.nodeType !== Node.ELEMENT_NODE) { + while (node !== null) { + if (node.nodeType === Node.ELEMENT_NODE) { + return /** @type {Element} */ (node); + } node = node.parentNode; } - return node; + return null; } /** @@ -387,11 +402,12 @@ export class DOMTextScanner { * @returns {?HTMLElement} A node if the input node is contained in one, otherwise `null`. */ static getParentRubyElement(node) { - node = DOMTextScanner.getParentElement(node); - if (node !== null && node.nodeName.toUpperCase() === 'RT') { - node = node.parentNode; - if (node !== null && node.nodeName.toUpperCase() === 'RUBY') { - return node; + /** @type {?Node} */ + let node2 = DOMTextScanner.getParentElement(node); + if (node2 !== null && node2.nodeName.toUpperCase() === 'RT') { + node2 = node2.parentNode; + if (node2 !== null && node2.nodeName.toUpperCase() === 'RUBY') { + return /** @type {HTMLElement} */ (node2); } } return null; @@ -504,8 +520,11 @@ export class DOMTextScanner { static isStyleSelectable(style) { return !( style.userSelect === 'none' || + // @ts-ignore - vendor prefix style.webkitUserSelect === 'none' || + // @ts-ignore - vendor prefix style.MozUserSelect === 'none' || + // @ts-ignore - vendor prefix style.msUserSelect === 'none' ); } @@ -513,7 +532,7 @@ export class DOMTextScanner { /** * Checks whether a CSS color is transparent or not. * @param {string} cssColor A CSS color string, expected to be encoded in rgb(a) form. - * @returns {false} `true` if the color is transparent, otherwise `false`. + * @returns {boolean} `true` if the color is transparent, otherwise `false`. */ static isCSSColorTransparent(cssColor) { return ( diff --git a/ext/js/dom/html-template-collection.js b/ext/js/dom/html-template-collection.js index 100ba55c..62d18224 100644 --- a/ext/js/dom/html-template-collection.js +++ b/ext/js/dom/html-template-collection.js @@ -17,9 +17,15 @@ */ export class HtmlTemplateCollection { - constructor(source) { + constructor() { + /** @type {Map} */ this._templates = new Map(); + } + /** + * @param {string|Document} source + */ + load(source) { const sourceNode = ( typeof source === 'string' ? new DOMParser().parseFromString(source, 'text/html') : @@ -35,28 +41,53 @@ export class HtmlTemplateCollection { } } + /** + * @template {Element} T + * @param {string} name + * @returns {T} + * @throws {Error} + */ instantiate(name) { const template = this._templates.get(name); - return document.importNode(template.content.firstChild, true); + if (typeof template === 'undefined') { throw new Error(`Failed to find template: ${name}`); } + const {firstElementChild} = template.content; + if (firstElementChild === null) { throw new Error(`Failed to find template content element: ${name}`); } + return /** @type {T} */ (document.importNode(firstElementChild, true)); } + /** + * @param {string} name + * @returns {DocumentFragment} + * @throws {Error} + */ instantiateFragment(name) { const template = this._templates.get(name); - return document.importNode(template.content, true); + if (typeof template === 'undefined') { throw new Error(`Failed to find template: ${name}`); } + const {content} = template; + return document.importNode(content, true); } + /** + * @returns {IterableIterator} + */ getAllTemplates() { return this._templates.values(); } // Private + /** + * @param {HTMLTemplateElement} template + */ _prepareTemplate(template) { if (template.dataset.removeWhitespaceText === 'true') { this._removeWhitespaceText(template); } } + /** + * @param {HTMLTemplateElement} template + */ _removeWhitespaceText(template) { const {content} = template; const {TEXT_NODE} = Node; @@ -65,7 +96,7 @@ export class HtmlTemplateCollection { while (true) { const node = iterator.nextNode(); if (node === null) { break; } - if (node.nodeType === TEXT_NODE && node.nodeValue.trim().length === 0) { + if (node.nodeType === TEXT_NODE && /** @type {string} */ (node.nodeValue).trim().length === 0) { removeNodes.push(node); } } diff --git a/ext/js/dom/native-simple-dom-parser.js b/ext/js/dom/native-simple-dom-parser.js index 882469a0..418a4e3c 100644 --- a/ext/js/dom/native-simple-dom-parser.js +++ b/ext/js/dom/native-simple-dom-parser.js @@ -17,35 +17,89 @@ */ export class NativeSimpleDOMParser { + /** + * @param {string} content + */ constructor(content) { + /** @type {Document} */ this._document = new DOMParser().parseFromString(content, 'text/html'); } - getElementById(id, root=null) { - return (root || this._document).querySelector(`[id='${id}']`); + /** + * @param {string} id + * @param {import('simple-dom-parser').Element} [root] + * @returns {?import('simple-dom-parser').Element} + */ + getElementById(id, root) { + return this._convertElementOrDocument(root).querySelector(`[id='${id}']`); } - getElementByTagName(tagName, root=null) { - return (root || this._document).querySelector(tagName); + /** + * @param {string} tagName + * @param {import('simple-dom-parser').Element} [root] + * @returns {?import('simple-dom-parser').Element} + */ + getElementByTagName(tagName, root) { + return this._convertElementOrDocument(root).querySelector(tagName); } - getElementsByTagName(tagName, root=null) { - return [...(root || this._document).querySelectorAll(tagName)]; + /** + * @param {string} tagName + * @param {import('simple-dom-parser').Element} [root] + * @returns {import('simple-dom-parser').Element[]} + */ + getElementsByTagName(tagName, root) { + return [...this._convertElementOrDocument(root).querySelectorAll(tagName)]; } - getElementsByClassName(className, root=null) { - return [...(root || this._document).querySelectorAll(`.${className}`)]; + /** + * @param {string} className + * @param {import('simple-dom-parser').Element} [root] + * @returns {import('simple-dom-parser').Element[]} + */ + getElementsByClassName(className, root) { + return [...this._convertElementOrDocument(root).querySelectorAll(`.${className}`)]; } + /** + * @param {import('simple-dom-parser').Element} element + * @param {string} attribute + * @returns {?string} + */ getAttribute(element, attribute) { - return element.hasAttribute(attribute) ? element.getAttribute(attribute) : null; + const element2 = this._convertElement(element); + return element2.hasAttribute(attribute) ? element2.getAttribute(attribute) : null; } + /** + * @param {import('simple-dom-parser').Element} element + * @returns {string} + */ getTextContent(element) { - return element.textContent; + const {textContent} = this._convertElement(element); + return typeof textContent === 'string' ? textContent : ''; } + /** + * @returns {boolean} + */ static isSupported() { return typeof DOMParser !== 'undefined'; } + + /** + * @param {import('simple-dom-parser').Element} element + * @returns {Element} + */ + _convertElement(element) { + return /** @type {Element} */ (element); + } + + /** + * @param {import('simple-dom-parser').Element|undefined} element + * @returns {Element|Document} + */ + _convertElementOrDocument(element) { + return typeof element !== 'undefined' ? /** @type {Element} */ (element) : this._document; + } } diff --git a/ext/js/dom/panel-element.js b/ext/js/dom/panel-element.js index 9b056920..314eb2fd 100644 --- a/ext/js/dom/panel-element.js +++ b/ext/js/dom/panel-element.js @@ -18,25 +18,45 @@ import {EventDispatcher} from '../core.js'; +/** + * @augments EventDispatcher + */ export class PanelElement extends EventDispatcher { + /** + * @param {import('panel-element').ConstructorDetails} details + */ constructor({node, closingAnimationDuration}) { super(); + /** @type {HTMLElement} */ this._node = node; + /** @type {number} */ this._closingAnimationDuration = closingAnimationDuration; + /** @type {string} */ this._hiddenAnimatingClass = 'hidden-animating'; + /** @type {?MutationObserver} */ this._mutationObserver = null; + /** @type {boolean} */ this._visible = false; + /** @type {?number} */ this._closeTimer = null; } + /** @type {HTMLElement} */ get node() { return this._node; } + /** + * @returns {boolean} + */ isVisible() { return !this._node.hidden; } + /** + * @param {boolean} value + * @param {boolean} [animate] + */ setVisible(value, animate=true) { value = !!value; if (this.isVisible() === value) { return; } @@ -63,6 +83,11 @@ export class PanelElement extends EventDispatcher { } } + /** + * @param {import('panel-element').EventType} eventName + * @param {(details: import('core').SafeAny) => void} callback + * @returns {void} + */ on(eventName, callback) { if (eventName === 'visibilityChanged') { if (this._mutationObserver === null) { @@ -78,6 +103,11 @@ export class PanelElement extends EventDispatcher { return super.on(eventName, callback); } + /** + * @param {import('panel-element').EventType} eventName + * @param {(details: import('core').SafeAny) => void} callback + * @returns {boolean} + */ off(eventName, callback) { const result = super.off(eventName, callback); if (eventName === 'visibilityChanged' && !this.hasListeners(eventName)) { @@ -91,6 +121,7 @@ export class PanelElement extends EventDispatcher { // Private + /** */ _onMutation() { const visible = this.isVisible(); if (this._visible === visible) { return; } @@ -98,6 +129,9 @@ export class PanelElement extends EventDispatcher { this.trigger('visibilityChanged', {visible}); } + /** + * @param {boolean} reopening + */ _completeClose(reopening) { this._closeTimer = null; this._node.classList.remove(this._hiddenAnimatingClass); diff --git a/ext/js/dom/popup-menu.js b/ext/js/dom/popup-menu.js index 7cae1dff..78394c93 100644 --- a/ext/js/dom/popup-menu.js +++ b/ext/js/dom/popup-menu.js @@ -18,38 +18,58 @@ import {EventDispatcher, EventListenerCollection} from '../core.js'; +/** + * @augments EventDispatcher + */ export class PopupMenu extends EventDispatcher { + /** + * @param {HTMLElement} sourceElement + * @param {HTMLElement} containerNode + */ constructor(sourceElement, containerNode) { super(); + /** @type {HTMLElement} */ this._sourceElement = sourceElement; + /** @type {HTMLElement} */ this._containerNode = containerNode; - this._node = containerNode.querySelector('.popup-menu'); - this._bodyNode = containerNode.querySelector('.popup-menu-body'); + /** @type {HTMLElement} */ + this._node = /** @type {HTMLElement} */ (containerNode.querySelector('.popup-menu')); + /** @type {HTMLElement} */ + this._bodyNode = /** @type {HTMLElement} */ (containerNode.querySelector('.popup-menu-body')); + /** @type {boolean} */ this._isClosed = false; + /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); + /** @type {EventListenerCollection} */ this._itemEventListeners = new EventListenerCollection(); } + /** @type {HTMLElement} */ get sourceElement() { return this._sourceElement; } + /** @type {HTMLElement} */ get containerNode() { return this._containerNode; } + /** @type {HTMLElement} */ get node() { return this._node; } + /** @type {HTMLElement} */ get bodyNode() { return this._bodyNode; } + /** @type {boolean} */ get isClosed() { return this._isClosed; } + /** */ prepare() { this._setPosition(); this._containerNode.focus(); @@ -61,17 +81,25 @@ export class PopupMenu extends EventDispatcher { PopupMenu.openMenus.add(this); + /** @type {import('popup-menu').MenuOpenEventDetails} */ + const detail = {menu: this}; + this._sourceElement.dispatchEvent(new CustomEvent('menuOpen', { bubbles: false, cancelable: false, - detail: {menu: this} + detail })); } + /** + * @param {boolean} [cancelable] + * @returns {boolean} + */ close(cancelable=true) { - return this._close(null, 'close', cancelable, {}); + return this._close(null, 'close', cancelable, null); } + /** */ updateMenuItems() { this._itemEventListeners.removeAllEventListeners(); const items = this._bodyNode.querySelectorAll('.popup-menu-item'); @@ -81,12 +109,16 @@ export class PopupMenu extends EventDispatcher { } } + /** */ updatePosition() { this._setPosition(); } // Private + /** + * @param {MouseEvent} e + */ _onMenuContainerClick(e) { if (e.currentTarget !== e.target) { return; } if (this._close(null, 'outside', true, e)) { @@ -95,8 +127,11 @@ export class PopupMenu extends EventDispatcher { } } + /** + * @param {MouseEvent} e + */ _onMenuItemClick(e) { - const item = e.currentTarget; + const item = /** @type {HTMLButtonElement} */ (e.currentTarget); if (item.disabled) { return; } if (this._close(item, 'item', true, e)) { e.stopPropagation(); @@ -104,10 +139,12 @@ export class PopupMenu extends EventDispatcher { } } + /** */ _onWindowResize() { - this._close(null, 'resize', true, {}); + this._close(null, 'resize', true, null); } + /** */ _setPosition() { // Get flags let horizontal = 1; @@ -187,11 +224,29 @@ export class PopupMenu extends EventDispatcher { menu.style.top = `${y}px`; } + /** + * @param {?HTMLElement} item + * @param {import('popup-menu').CloseReason} cause + * @param {boolean} cancelable + * @param {?MouseEvent} originalEvent + * @returns {boolean} + */ _close(item, cause, cancelable, originalEvent) { if (this._isClosed) { return true; } - const action = (item !== null ? item.dataset.menuAction : null); + /** @type {?string} */ + let action = null; + if (item !== null) { + const {menuAction} = item.dataset; + if (typeof menuAction === 'string') { action = menuAction; } + } + + const {altKey, ctrlKey, metaKey, shiftKey} = ( + originalEvent !== null ? + originalEvent : + {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false} + ); - const {altKey=false, ctrlKey=false, metaKey=false, shiftKey=false} = originalEvent; + /** @type {import('popup-menu').MenuCloseEventDetails} */ const detail = { menu: this, item, diff --git a/ext/js/dom/sandbox/css-style-applier.js b/ext/js/dom/sandbox/css-style-applier.js index 4f570bb7..332ca4f2 100644 --- a/ext/js/dom/sandbox/css-style-applier.js +++ b/ext/js/dom/sandbox/css-style-applier.js @@ -21,41 +21,21 @@ * that is the same across different browsers. */ export class CssStyleApplier { - /** - * A CSS rule. - * @typedef {object} CssRule - * @property {string} selectors A CSS selector string representing one or more selectors. - * @property {CssDeclaration[]} styles A list of CSS property and value pairs. - */ - - /** - * A single CSS property declaration. - * @typedef {object} CssDeclaration - * @property {string} property A CSS property's name, using kebab-case. - * @property {string} value The property's value. - */ - - /* eslint-disable jsdoc/check-line-alignment */ /** * Creates a new instance of the class. - * @param {string} styleDataUrl The local URL to the JSON file containing the style rules. - * The style rules should be of the format: - * ``` - * [ - * { - * selectors: [(selector:string)...], - * styles: [ - * [(property:string), (value:string)]... - * ] - * }... - * ] - * ``` + * @param {string} styleDataUrl The local URL to the JSON file continaing the style rules. + * The style rules should follow the format of {@link CssStyleApplierRawStyleData}. */ constructor(styleDataUrl) { + /** @type {string} */ this._styleDataUrl = styleDataUrl; + /** @type {import('css-style-applier').CssRule[]} */ this._styleData = []; + /** @type {Map} */ this._cachedRules = new Map(); + /** @type {RegExp} */ this._patternHtmlWhitespace = /[\t\r\n\f ]+/g; + /** @type {RegExp} */ this._patternClassNameCharacter = /[0-9a-zA-Z-_]/; } /* eslint-enable jsdoc/check-line-alignment */ @@ -64,7 +44,8 @@ export class CssStyleApplier { * Loads the data file for use. */ async prepare() { - let rawData; + /** @type {import('css-style-applier').RawStyleData} */ + let rawData = []; try { rawData = await this._fetchJsonAsset(this._styleDataUrl); } catch (e) { @@ -94,7 +75,7 @@ export class CssStyleApplier { const elementStyles = []; for (const element of elements) { const className = element.getAttribute('class'); - if (className.length === 0) { continue; } + if (className === null || className.length === 0) { continue; } let cssTextNew = ''; for (const {selectors, styles} of this._getCandidateCssRulesForClass(className)) { if (!element.matches(selectors)) { continue; } @@ -139,7 +120,7 @@ export class CssStyleApplier { /** * Gets an array of candidate CSS rules which might match a specific class. * @param {string} className A whitespace-separated list of classes. - * @returns {CssRule[]} An array of candidate CSS rules. + * @returns {import('css-style-applier').CssRule[]} An array of candidate CSS rules. */ _getCandidateCssRulesForClass(className) { let rules = this._cachedRules.get(className); @@ -159,7 +140,7 @@ export class CssStyleApplier { /** * Converts an array of CSS rules to a CSS string. - * @param {CssRule[]} styles An array of CSS rules. + * @param {import('css-style-applier').CssDeclaration[]} styles An array of CSS rules. * @returns {string} The CSS string. */ _getCssText(styles) { diff --git a/ext/js/dom/scroll-element.js b/ext/js/dom/scroll-element.js index 4b2ac70d..95f5ce5b 100644 --- a/ext/js/dom/scroll-element.js +++ b/ext/js/dom/scroll-element.js @@ -17,39 +17,68 @@ */ export class ScrollElement { + /** + * @param {Element} node + */ constructor(node) { + /** @type {Element} */ this._node = node; + /** @type {?number} */ this._animationRequestId = null; + /** @type {number} */ this._animationStartTime = 0; + /** @type {number} */ this._animationStartX = 0; + /** @type {number} */ this._animationStartY = 0; + /** @type {number} */ this._animationEndTime = 0; + /** @type {number} */ this._animationEndX = 0; + /** @type {number} */ this._animationEndY = 0; + /** @type {(time: number) => void} */ this._requestAnimationFrameCallback = this._onAnimationFrame.bind(this); } + /** @type {number} */ get x() { return this._node !== null ? this._node.scrollLeft : window.scrollX || window.pageXOffset; } + /** @type {number} */ get y() { return this._node !== null ? this._node.scrollTop : window.scrollY || window.pageYOffset; } + /** + * @param {number} y + */ toY(y) { this.to(this.x, y); } + /** + * @param {number} x + */ toX(x) { this.to(x, this.y); } + /** + * @param {number} x + * @param {number} y + */ to(x, y) { this.stop(); this._scroll(x, y); } + /** + * @param {number} x + * @param {number} y + * @param {number} time + */ animate(x, y, time) { this._animationStartX = this.x; this._animationStartY = this.y; @@ -60,6 +89,7 @@ export class ScrollElement { this._animationRequestId = window.requestAnimationFrame(this._requestAnimationFrameCallback); } + /** */ stop() { if (this._animationRequestId === null) { return; @@ -69,12 +99,18 @@ export class ScrollElement { this._animationRequestId = null; } + /** + * @returns {DOMRect} + */ getRect() { return this._node.getBoundingClientRect(); } // Private + /** + * @param {number} time + */ _onAnimationFrame(time) { if (time >= this._animationEndTime) { this._scroll(this._animationEndX, this._animationEndY); @@ -91,6 +127,10 @@ export class ScrollElement { this._animationRequestId = window.requestAnimationFrame(this._requestAnimationFrameCallback); } + /** + * @param {number} t + * @returns {number} + */ _easeInOutCubic(t) { if (t < 0.5) { return (4.0 * t * t * t); @@ -100,10 +140,20 @@ export class ScrollElement { } } + /** + * @param {number} start + * @param {number} end + * @param {number} percent + * @returns {number} + */ _lerp(start, end, percent) { return (end - start) * percent + start; } + /** + * @param {number} x + * @param {number} y + */ _scroll(x, y) { if (this._node !== null) { this._node.scrollLeft = x; diff --git a/ext/js/dom/selector-observer.js b/ext/js/dom/selector-observer.js index e0e3d4ff..2cf46543 100644 --- a/ext/js/dom/selector-observer.js +++ b/ext/js/dom/selector-observer.js @@ -18,55 +18,35 @@ /** * Class which is used to observe elements matching a selector in specific element. + * @template T */ export class SelectorObserver { - /** - * @function OnAddedCallback - * @param {Element} element The element which was added. - * @returns {*} Custom data which is assigned to element and passed to callbacks. - */ - - /** - * @function OnRemovedCallback - * @param {Element} element The element which was removed. - * @param {*} data The custom data corresponding to the element. - */ - - /** - * @function OnChildrenUpdatedCallback - * @param {Element} element The element which had its children updated. - * @param {*} data The custom data corresponding to the element. - */ - - /** - * @function IsStaleCallback - * @param {Element} element The element which had its children updated. - * @param {*} data The custom data corresponding to the element. - * @returns {boolean} Whether or not the data is stale for the element. - */ - /** * Creates a new instance. - * @param {object} details The configuration for the object. - * @param {string} details.selector A string CSS selector used to find elements. - * @param {?string} details.ignoreSelector A string CSS selector used to filter elements, or `null` for no filtering. - * @param {OnAddedCallback} details.onAdded A function which is invoked for each element that is added that matches the selector. - * @param {?OnRemovedCallback} details.onRemoved A function which is invoked for each element that is removed, or `null`. - * @param {?OnChildrenUpdatedCallback} details.onChildrenUpdated A function which is invoked for each element which has its children updated, or `null`. - * @param {?IsStaleCallback} details.isStale A function which checks if the data is stale for a given element, or `null`. - * If the element is stale, it will be removed and potentially re-added. + * @param {import('selector-observer').ConstructorDetails} details The configuration for the object. */ constructor({selector, ignoreSelector=null, onAdded=null, onRemoved=null, onChildrenUpdated=null, isStale=null}) { + /** @type {string} */ this._selector = selector; + /** @type {?string} */ this._ignoreSelector = ignoreSelector; + /** @type {?import('selector-observer').OnAddedCallback} */ this._onAdded = onAdded; + /** @type {?import('selector-observer').OnRemovedCallback} */ this._onRemoved = onRemoved; + /** @type {?import('selector-observer').OnChildrenUpdatedCallback} */ this._onChildrenUpdated = onChildrenUpdated; + /** @type {?import('selector-observer').IsStaleCallback} */ this._isStale = isStale; + /** @type {?Element} */ this._observingElement = null; + /** @type {MutationObserver} */ this._mutationObserver = new MutationObserver(this._onMutation.bind(this)); + /** @type {Map>} */ this._elementMap = new Map(); // Map([element => observer]...) + /** @type {Map>>} */ this._elementAncestorMap = new Map(); // Map([element => Set([observer]...)]...) + /** @type {boolean} */ this._isObserving = false; } @@ -100,9 +80,10 @@ export class SelectorObserver { subtree: true }); + const {parentNode} = element; this._onMutation([{ type: 'childList', - target: element.parentNode, + target: parentNode !== null ? parentNode : element, addedNodes: [element], removedNodes: [] }]); @@ -124,17 +105,19 @@ export class SelectorObserver { /** * Returns an iterable list of [element, data] pairs. - * @yields A sequence of [element, data] pairs. + * @yields {[element: Element, data: T]} A sequence of [element, data] pairs. + * @returns {Generator<[element: Element, data: T], void, unknown>} */ *entries() { - for (const [element, {data}] of this._elementMap) { + for (const {element, data} of this._elementMap.values()) { yield [element, data]; } } /** * Returns an iterable list of data for every element. - * @yields A sequence of data values. + * @yields {T} A sequence of data values. + * @returns {Generator} */ *datas() { for (const {data} of this._elementMap.values()) { @@ -144,6 +127,9 @@ export class SelectorObserver { // Private + /** + * @param {(MutationRecord|import('selector-observer').MutationRecordLike)[]} mutationList + */ _onMutation(mutationList) { for (const mutation of mutationList) { switch (mutation.type) { @@ -157,6 +143,9 @@ export class SelectorObserver { } } + /** + * @param {MutationRecord|import('selector-observer').MutationRecordLike} record + */ _onChildListMutation({addedNodes, removedNodes, target}) { const selector = this._selector; const ELEMENT_NODE = Node.ELEMENT_NODE; @@ -171,10 +160,10 @@ export class SelectorObserver { for (const node of addedNodes) { if (node.nodeType !== ELEMENT_NODE) { continue; } - if (node.matches(selector)) { - this._createObserver(node); + if (/** @type {Element} */ (node).matches(selector)) { + this._createObserver(/** @type {Element} */ (node)); } - for (const childNode of node.querySelectorAll(selector)) { + for (const childNode of /** @type {Element} */ (node).querySelectorAll(selector)) { this._createObserver(childNode); } } @@ -183,7 +172,7 @@ export class SelectorObserver { this._onChildrenUpdated !== null && (addedNodes.length !== 0 || addedNodes.length !== 0) ) { - for (let node = target; node !== null; node = node.parentNode) { + for (let node = /** @type {?Node} */ (target); node !== null; node = node.parentNode) { const observer = this._elementMap.get(node); if (typeof observer !== 'undefined') { this._onObserverChildrenUpdated(observer); @@ -192,9 +181,12 @@ export class SelectorObserver { } } + /** + * @param {MutationRecord|import('selector-observer').MutationRecordLike} record + */ _onAttributeMutation({target}) { const selector = this._selector; - const observers = this._elementAncestorMap.get(target); + const observers = this._elementAncestorMap.get(/** @type {Element} */ (target)); if (typeof observers !== 'undefined') { for (const observer of observers) { const element = observer.element; @@ -208,15 +200,19 @@ export class SelectorObserver { } } - if (target.matches(selector)) { - this._createObserver(target); + if (/** @type {Element} */ (target).matches(selector)) { + this._createObserver(/** @type {Element} */ (target)); } } + /** + * @param {Element} element + */ _createObserver(element) { if (this._elementMap.has(element) || this._shouldIgnoreElement(element) || this._onAdded === null) { return; } const data = this._onAdded(element); + if (typeof data === 'undefined') { return; } const ancestors = this._getAncestors(element); const observer = {element, ancestors, data}; @@ -232,6 +228,9 @@ export class SelectorObserver { } } + /** + * @param {import('selector-observer').Observer} observer + */ _removeObserver(observer) { const {element, ancestors, data} = observer; @@ -252,26 +251,42 @@ export class SelectorObserver { } } + /** + * @param {import('selector-observer').Observer} observer + */ _onObserverChildrenUpdated(observer) { + if (this._onChildrenUpdated === null) { return; } this._onChildrenUpdated(observer.element, observer.data); } + /** + * @param {import('selector-observer').Observer} observer + * @returns {boolean} + */ _isObserverStale(observer) { return (this._isStale !== null && this._isStale(observer.element, observer.data)); } + /** + * @param {Element} element + * @returns {boolean} + */ _shouldIgnoreElement(element) { return (this._ignoreSelector !== null && element.matches(this._ignoreSelector)); } + /** + * @param {Node} node + * @returns {Node[]} + */ _getAncestors(node) { const root = this._observingElement; const results = []; - while (true) { - results.push(node); - if (node === root) { break; } - node = node.parentNode; - if (node === null) { break; } + let n = /** @type {?Node} */ (node); + while (n !== null) { + results.push(n); + if (n === root) { break; } + n = n.parentNode; } return results; } diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index 3e84b783..a1f63890 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -18,55 +18,91 @@ import * as parse5 from '../../lib/parse5.js'; +/** + * @augments import('simple-dom-parser').ISimpleDomParser + */ export class SimpleDOMParser { + /** + * @param {string} content + */ constructor(content) { - this._document = parse5.parse(content); + /** @type {import('parse5')} */ + // @ts-ignore - parse5 global is not defined in typescript declaration + this._parse5Lib = /** @type {import('parse5')} */ (parse5); + /** @type {import('parse5').TreeAdapter} */ + this._treeAdapter = this._parse5Lib.defaultTreeAdapter; + /** @type {import('simple-dom-parser').Parse5Document} */ + this._document = this._parse5Lib.parse(content, { + treeAdapter: this._treeAdapter + }); + /** @type {RegExp} */ this._patternHtmlWhitespace = /[\t\r\n\f ]+/g; } - getElementById(id, root=null) { + /** + * @param {string} id + * @param {import('simple-dom-parser').Element} [root] + * @returns {?import('simple-dom-parser').Element} + */ + getElementById(id, root) { for (const node of this._allNodes(root)) { - if (typeof node.tagName === 'string' && this.getAttribute(node, 'id') === id) { - return node; - } + if (!this._treeAdapter.isElementNode(node) || this.getAttribute(node, 'id') !== id) { continue; } + return node; } return null; } - getElementByTagName(tagName, root=null) { + /** + * @param {string} tagName + * @param {import('simple-dom-parser').Element} [root] + * @returns {?import('simple-dom-parser').Element} + */ + getElementByTagName(tagName, root) { for (const node of this._allNodes(root)) { - if (node.tagName === tagName) { - return node; - } + if (!this._treeAdapter.isElementNode(node) || node.tagName !== tagName) { continue; } + return node; } return null; } - getElementsByTagName(tagName, root=null) { + /** + * @param {string} tagName + * @param {import('simple-dom-parser').Element} [root] + * @returns {import('simple-dom-parser').Element[]} + */ + getElementsByTagName(tagName, root) { const results = []; for (const node of this._allNodes(root)) { - if (node.tagName === tagName) { - results.push(node); - } + if (!this._treeAdapter.isElementNode(node) || node.tagName !== tagName) { continue; } + results.push(node); } return results; } - getElementsByClassName(className, root=null) { + /** + * @param {string} className + * @param {import('simple-dom-parser').Element} [root] + * @returns {import('simple-dom-parser').Element[]} + */ + getElementsByClassName(className, root) { const results = []; for (const node of this._allNodes(root)) { - if (typeof node.tagName === 'string') { - const nodeClassName = this.getAttribute(node, 'class'); - if (nodeClassName !== null && this._hasToken(nodeClassName, className)) { - results.push(node); - } + if (!this._treeAdapter.isElementNode(node)) { continue; } + const nodeClassName = this.getAttribute(node, 'class'); + if (nodeClassName !== null && this._hasToken(nodeClassName, className)) { + results.push(node); } } return results; } + /** + * @param {import('simple-dom-parser').Element} element + * @param {string} attribute + * @returns {?string} + */ getAttribute(element, attribute) { - for (const attr of element.attrs) { + for (const attr of /** @type {import('simple-dom-parser').Parse5Element} */ (element).attrs) { if ( attr.name === attribute && typeof attr.namespace === 'undefined' @@ -77,43 +113,63 @@ export class SimpleDOMParser { return null; } + /** + * @param {import('simple-dom-parser').Element} element + * @returns {string} + */ getTextContent(element) { let source = ''; for (const node of this._allNodes(element)) { - if (node.nodeName === '#text') { + if (this._treeAdapter.isTextNode(node)) { source += node.value; } } return source; } + /** + * @returns {boolean} + */ static isSupported() { + // @ts-ignore - parse5 global is not defined in typescript declaration return typeof parse5 !== 'undefined'; } // Private + /** + * @param {import('simple-dom-parser').Element|undefined} root + * @returns {Generator} + * @yields {import('simple-dom-parser').Parse5ChildNode} + */ *_allNodes(root) { - if (root === null) { - root = this._document; - } - // Depth-first pre-order traversal - const nodeQueue = [root]; + /** @type {import('simple-dom-parser').Parse5ChildNode[]} */ + const nodeQueue = []; + if (typeof root !== 'undefined') { + nodeQueue.push(/** @type {import('simple-dom-parser').Parse5Element} */ (root)); + } else { + nodeQueue.push(...this._document.childNodes); + } while (nodeQueue.length > 0) { - const node = nodeQueue.pop(); - + const node = /** @type {import('simple-dom-parser').Parse5ChildNode} */ (nodeQueue.pop()); yield node; - - const childNodes = node.childNodes; - if (typeof childNodes !== 'undefined') { - for (let i = childNodes.length - 1; i >= 0; --i) { - nodeQueue.push(childNodes[i]); + if (this._treeAdapter.isElementNode(node)) { + const {childNodes} = node; + if (typeof childNodes !== 'undefined') { + for (let i = childNodes.length - 1; i >= 0; --i) { + nodeQueue.push(childNodes[i]); + } } } } } + /** + * @param {string} tokenListString + * @param {string} token + * @returns {boolean} + */ _hasToken(tokenListString, token) { let start = 0; const pattern = this._patternHtmlWhitespace; diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index 95534975..fbe89a61 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -33,16 +33,21 @@ export class TextSourceElement { * @param {number} endOffset The text end offset position within the full content. */ constructor(element, fullContent, startOffset, endOffset) { + /** @type {Element} */ this._element = element; + /** @type {string} */ this._fullContent = fullContent; + /** @type {number} */ this._startOffset = startOffset; + /** @type {number} */ this._endOffset = endOffset; + /** @type {string} */ this._content = this._fullContent.substring(this._startOffset, this._endOffset); } /** * Gets the type name of this instance. - * @type {string} + * @type {'element'} */ get type() { return 'element'; @@ -147,7 +152,7 @@ export class TextSourceElement { /** * Gets writing mode that is used for this element. * See: https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode. - * @returns {string} The rects. + * @returns {import('document-util').NormalizedWritingMode} The writing mode. */ getWritingMode() { return 'horizontal-tb'; @@ -206,23 +211,39 @@ export class TextSourceElement { * @returns {string} The content string. */ static _getElementContent(element) { - let content; + let content = ''; switch (element.nodeName.toUpperCase()) { case 'BUTTON': - content = element.textContent; + { + const {textContent} = /** @type {HTMLButtonElement} */ (element); + if (textContent !== null) { + content = textContent; + } + } break; case 'IMG': - content = element.getAttribute('alt') || ''; + { + const alt = /** @type {HTMLImageElement} */ (element).getAttribute('alt'); + if (typeof alt === 'string') { + content = alt; + } + } break; case 'SELECT': { - const {selectedIndex, options} = element; - const option = (selectedIndex >= 0 && selectedIndex < options.length ? options[selectedIndex] : null); - content = (option !== null ? option.textContent : ''); + const {selectedIndex, options} = /** @type {HTMLSelectElement} */ (element); + if (selectedIndex >= 0 && selectedIndex < options.length) { + const {textContent} = options[selectedIndex]; + if (textContent !== null) { + content = textContent; + } + } } break; - default: - content = `${element.value}`; + case 'INPUT': + { + content = /** @type {HTMLInputElement} */ (element).value; + } break; } diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index d5e70052..5c3d4184 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -36,26 +36,33 @@ export class TextSourceRange { * @param {?Element} imposterElement The temporary imposter element. * @param {?Element} imposterSourceElement The source element which the imposter is imitating. * Must not be `null` if imposterElement is specified. - * @param {?Rect[]} cachedRects A set of cached `DOMRect`s representing the rects of the text source, + * @param {?DOMRect[]} cachedRects A set of cached `DOMRect`s representing the rects of the text source, * which can be used after the imposter element is removed from the page. * Must not be `null` if imposterElement is specified. - * @param {?Rect} cachedSourceRect A cached `DOMRect` representing the rect of the `imposterSourceElement`, + * @param {?DOMRect} cachedSourceRect A cached `DOMRect` representing the rect of the `imposterSourceElement`, * which can be used after the imposter element is removed from the page. * Must not be `null` if imposterElement is specified. */ constructor(range, rangeStartOffset, content, imposterElement, imposterSourceElement, cachedRects, cachedSourceRect) { + /** @type {Range} */ this._range = range; + /** @type {number} */ this._rangeStartOffset = rangeStartOffset; + /** @type {string} */ this._content = content; + /** @type {?Element} */ this._imposterElement = imposterElement; + /** @type {?Element} */ this._imposterSourceElement = imposterSourceElement; + /** @type {?DOMRect[]} */ this._cachedRects = cachedRects; + /** @type {?DOMRect} */ this._cachedSourceRect = cachedSourceRect; } /** * Gets the type name of this instance. - * @type {string} + * @type {'range'} */ get type() { return 'range'; @@ -71,7 +78,7 @@ export class TextSourceRange { /** * The starting offset for the range. - * @type {Range} + * @type {number} */ get rangeStartOffset() { return this._rangeStartOffset; @@ -169,12 +176,12 @@ export class TextSourceRange { /** * Gets writing mode that is used for this element. * See: https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode. - * @returns {string} The rects. + * @returns {import('document-util').NormalizedWritingMode} The writing mode. */ getWritingMode() { let node = this._isImposterDisconnected() ? this._imposterSourceElement : this._range.startContainer; if (node !== null && node.nodeType !== Node.ELEMENT_NODE) { node = node.parentElement; } - return DocumentUtil.getElementWritingMode(node); + return DocumentUtil.getElementWritingMode(/** @type {?Element} */ (node)); } /** @@ -183,6 +190,7 @@ export class TextSourceRange { select() { if (this._imposterElement !== null) { return; } const selection = window.getSelection(); + if (selection === null) { return; } selection.removeAllRanges(); selection.addRange(this._range); } @@ -193,6 +201,7 @@ export class TextSourceRange { deselect() { if (this._imposterElement !== null) { return; } const selection = window.getSelection(); + if (selection === null) { return; } selection.removeAllRanges(); } @@ -220,7 +229,7 @@ export class TextSourceRange { try { return this._range.compareBoundaryPoints(Range.START_TO_START, other.range) === 0; } catch (e) { - if (e.name === 'WrongDocumentError') { + if (e instanceof Error && e.name === 'WrongDocumentError') { // This can happen with shadow DOMs if the ranges are in different documents. return false; } @@ -269,9 +278,17 @@ export class TextSourceRange { /** * Gets the cached rects for a disconnected imposter element. - * @returns {Rect[]} The rects for the element. + * @returns {DOMRect[]} The rects for the element. + * @throws {Error} */ _getCachedRects() { + if ( + this._cachedRects === null || + this._cachedSourceRect === null || + this._imposterSourceElement === null + ) { + throw new Error('Cached rects not valid for this instance'); + } const sourceRect = DocumentUtil.convertRectZoomCoordinates(this._imposterSourceElement.getBoundingClientRect(), this._imposterSourceElement); return DocumentUtil.offsetDOMRects( this._cachedRects, -- cgit v1.2.3 From aabd761ee9064f6a46703f234e016f31f6441fa0 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 13:36:04 -0500 Subject: Remove unneeded references --- ext/js/accessibility/accessibility-controller.js | 5 ++--- ext/js/accessibility/google-docs-util.js | 1 - ext/js/app/frontend.js | 6 ++---- ext/js/app/popup-proxy.js | 14 ++++++-------- ext/js/app/popup-window.js | 9 ++++----- ext/js/background/backend.js | 1 - ext/js/comm/anki-connect.js | 1 - ext/js/dom/document-util.js | 1 - ext/js/dom/text-source-element.js | 1 - ext/js/dom/text-source-range.js | 1 - ext/js/language/dictionary-worker-handler.js | 1 - ext/js/language/dictionary-worker-media-loader.js | 2 +- ext/js/language/translator.js | 1 - ext/js/templates/template-renderer-proxy.js | 2 +- 14 files changed, 16 insertions(+), 30 deletions(-) (limited to 'ext/js/dom') diff --git a/ext/js/accessibility/accessibility-controller.js b/ext/js/accessibility/accessibility-controller.js index a4239947..8250b369 100644 --- a/ext/js/accessibility/accessibility-controller.js +++ b/ext/js/accessibility/accessibility-controller.js @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import {ScriptManager} from '../background/script-manager.js'; import {log} from '../core.js'; /** @@ -25,10 +24,10 @@ import {log} from '../core.js'; export class AccessibilityController { /** * Creates a new instance. - * @param {ScriptManager} scriptManager An instance of the `ScriptManager` class. + * @param {import('../background/script-manager.js').ScriptManager} scriptManager An instance of the `ScriptManager` class. */ constructor(scriptManager) { - /** @type {ScriptManager} */ + /** @type {import('../background/script-manager.js').ScriptManager} */ this._scriptManager = scriptManager; /** @type {?import('core').TokenObject} */ this._updateGoogleDocsAccessibilityToken = null; diff --git a/ext/js/accessibility/google-docs-util.js b/ext/js/accessibility/google-docs-util.js index 9db45cc1..4321f082 100644 --- a/ext/js/accessibility/google-docs-util.js +++ b/ext/js/accessibility/google-docs-util.js @@ -17,7 +17,6 @@ */ import {DocumentUtil} from '../dom/document-util.js'; -import {TextSourceElement} from '../dom/text-source-element.js'; import {TextSourceRange} from '../dom/text-source-range.js'; /** diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index fec933f8..628c504e 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -21,10 +21,8 @@ import {EventListenerCollection, invokeMessageHandler, log, promiseAnimationFram import {DocumentUtil} from '../dom/document-util.js'; import {TextSourceElement} from '../dom/text-source-element.js'; import {TextSourceRange} from '../dom/text-source-range.js'; -import {HotkeyHandler} from '../input/hotkey-handler.js'; import {TextScanner} from '../language/text-scanner.js'; import {yomitan} from '../yomitan.js'; -import {PopupFactory} from './popup-factory.js'; /** * This is the main class responsible for scanning and handling webpage content. @@ -50,7 +48,7 @@ export class Frontend { }) { /** @type {import('frontend').PageType} */ this._pageType = pageType; - /** @type {PopupFactory} */ + /** @type {import('./popup-factory.js').PopupFactory} */ this._popupFactory = popupFactory; /** @type {number} */ this._depth = depth; @@ -70,7 +68,7 @@ export class Frontend { this._allowRootFramePopupProxy = allowRootFramePopupProxy; /** @type {boolean} */ this._childrenSupported = childrenSupported; - /** @type {HotkeyHandler} */ + /** @type {import('../input/hotkey-handler.js').HotkeyHandler} */ this._hotkeyHandler = hotkeyHandler; /** @type {?import('popup').PopupAny} */ this._popup = null; diff --git a/ext/js/app/popup-proxy.js b/ext/js/app/popup-proxy.js index 9b5b0214..924175e2 100644 --- a/ext/js/app/popup-proxy.js +++ b/ext/js/app/popup-proxy.js @@ -16,10 +16,8 @@ * along with this program. If not, see . */ -import {FrameOffsetForwarder} from '../comm/frame-offset-forwarder.js'; import {EventDispatcher, log} from '../core.js'; import {yomitan} from '../yomitan.js'; -import {Popup} from './popup.js'; /** * This class is a proxy for a Popup that is hosted in a different frame. @@ -44,7 +42,7 @@ export class PopupProxy extends EventDispatcher { this._depth = depth; /** @type {number} */ this._frameId = frameId; - /** @type {?FrameOffsetForwarder} */ + /** @type {?import('../comm/frame-offset-forwarder.js').FrameOffsetForwarder} */ this._frameOffsetForwarder = frameOffsetForwarder; /** @type {number} */ @@ -70,7 +68,7 @@ export class PopupProxy extends EventDispatcher { /** * The parent of the popup, which is always `null` for `PopupProxy` instances, * since any potential parent popups are in a different frame. - * @type {?Popup} + * @type {?import('./popup.js').Popup} */ get parent() { return null; @@ -78,7 +76,7 @@ export class PopupProxy extends EventDispatcher { /** * Attempts to set the parent popup. - * @param {Popup} _value The parent to assign. + * @param {import('./popup.js').Popup} _value The parent to assign. * @throws {Error} Throws an error, since this class doesn't support a direct parent. */ set parent(_value) { @@ -88,7 +86,7 @@ export class PopupProxy extends EventDispatcher { /** * The popup child popup, which is always null for `PopupProxy` instances, * since any potential child popups are in a different frame. - * @type {?Popup} + * @type {?import('./popup.js').Popup} */ get child() { return null; @@ -96,7 +94,7 @@ export class PopupProxy extends EventDispatcher { /** * Attempts to set the child popup. - * @param {Popup} _child The child to assign. + * @param {import('./popup.js').Popup} _child The child to assign. * @throws {Error} Throws an error, since this class doesn't support children. */ set child(_child) { @@ -354,7 +352,7 @@ export class PopupProxy extends EventDispatcher { * @param {number} now */ async _updateFrameOffsetInner(now) { - this._frameOffsetPromise = /** @type {FrameOffsetForwarder} */ (this._frameOffsetForwarder).getOffset(); + this._frameOffsetPromise = /** @type {import('../comm/frame-offset-forwarder.js').FrameOffsetForwarder} */ (this._frameOffsetForwarder).getOffset(); try { const offset = await this._frameOffsetPromise; if (offset !== null) { diff --git a/ext/js/app/popup-window.js b/ext/js/app/popup-window.js index af1ac1e4..9a0f8011 100644 --- a/ext/js/app/popup-window.js +++ b/ext/js/app/popup-window.js @@ -18,7 +18,6 @@ import {EventDispatcher} from '../core.js'; import {yomitan} from '../yomitan.js'; -import {Popup} from './popup.js'; /** * This class represents a popup that is hosted in a new native window. @@ -54,7 +53,7 @@ export class PopupWindow extends EventDispatcher { } /** - * @type {?Popup} + * @type {?import('./popup.js').Popup} */ get parent() { return null; @@ -63,7 +62,7 @@ export class PopupWindow extends EventDispatcher { /** * The parent of the popup, which is always `null` for `PopupWindow` instances, * since any potential parent popups are in a different frame. - * @param {Popup} _value The parent to assign. + * @param {import('./popup.js').Popup} _value The parent to assign. * @throws {Error} Throws an error, since this class doesn't support children. */ set parent(_value) { @@ -73,7 +72,7 @@ export class PopupWindow extends EventDispatcher { /** * The popup child popup, which is always null for `PopupWindow` instances, * since any potential child popups are in a different frame. - * @type {?Popup} + * @type {?import('./popup.js').Popup} */ get child() { return null; @@ -81,7 +80,7 @@ export class PopupWindow extends EventDispatcher { /** * Attempts to set the child popup. - * @param {Popup} _value The child to assign. + * @param {import('./popup.js').Popup} _value The child to assign. * @throws Throws an error, since this class doesn't support children. */ set child(_value) { diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index a8683b3f..14877cf1 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -25,7 +25,6 @@ import {Mecab} from '../comm/mecab.js'; import {clone, deferPromise, generateId, invokeMessageHandler, isObject, log, promiseTimeout} from '../core.js'; import {ExtensionError} from '../core/extension-error.js'; import {AnkiUtil} from '../data/anki-util.js'; -import {JsonSchema} from '../data/json-schema.js'; import {OptionsUtil} from '../data/options-util.js'; import {PermissionsUtil} from '../data/permissions-util.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/comm/anki-connect.js b/ext/js/comm/anki-connect.js index 3262af41..b876703f 100644 --- a/ext/js/comm/anki-connect.js +++ b/ext/js/comm/anki-connect.js @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import {isObject} from '../core.js'; import {AnkiUtil} from '../data/anki-util.js'; /** diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index f53d55fd..cf58d39f 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../core.js'; import {DOMTextScanner} from './dom-text-scanner.js'; import {TextSourceElement} from './text-source-element.js'; import {TextSourceRange} from './text-source-range.js'; diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index fbe89a61..47c18e30 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -18,7 +18,6 @@ import {StringUtil} from '../data/sandbox/string-util.js'; import {DocumentUtil} from './document-util.js'; -import {TextSourceRange} from './text-source-range.js'; /** * This class represents a text source that is attached to a HTML element, such as an diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 5c3d4184..5dbbd636 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -18,7 +18,6 @@ import {DocumentUtil} from './document-util.js'; import {DOMTextScanner} from './dom-text-scanner.js'; -import {TextSourceElement} from './text-source-element.js'; /** * This class represents a text source that comes from text nodes in the document. diff --git a/ext/js/language/dictionary-worker-handler.js b/ext/js/language/dictionary-worker-handler.js index 8ac342b2..3a85cb71 100644 --- a/ext/js/language/dictionary-worker-handler.js +++ b/ext/js/language/dictionary-worker-handler.js @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import {serializeError} from '../core.js'; import {DictionaryDatabase} from './dictionary-database.js'; import {DictionaryImporter} from './dictionary-importer.js'; import {DictionaryWorkerMediaLoader} from './dictionary-worker-media-loader.js'; diff --git a/ext/js/language/dictionary-worker-media-loader.js b/ext/js/language/dictionary-worker-media-loader.js index 9e3fd67e..2701389e 100644 --- a/ext/js/language/dictionary-worker-media-loader.js +++ b/ext/js/language/dictionary-worker-media-loader.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {deserializeError, generateId} from '../core.js'; +import {generateId} from '../core.js'; /** * Class used for loading and validating media from a worker thread diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 9b01c1ff..67cc53c6 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -19,7 +19,6 @@ import {RegexUtil} from '../general/regex-util.js'; import {TextSourceMap} from '../general/text-source-map.js'; import {Deinflector} from './deinflector.js'; -import {DictionaryDatabase} from './dictionary-database.js'; /** * Class which finds term and kanji dictionary entries for text. diff --git a/ext/js/templates/template-renderer-proxy.js b/ext/js/templates/template-renderer-proxy.js index 6d019d14..e67b715a 100644 --- a/ext/js/templates/template-renderer-proxy.js +++ b/ext/js/templates/template-renderer-proxy.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {deserializeError, generateId} from '../core.js'; +import {generateId} from '../core.js'; export class TemplateRendererProxy { constructor() { -- cgit v1.2.3 From 7aed9a371b0d74c0d75179a08068e8935b76d780 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 14:55:27 -0500 Subject: Update types --- ext/js/background/backend.js | 22 ++++----- ext/js/background/offscreen-proxy.js | 27 +++++++++-- ext/js/background/offscreen.js | 9 +++- ext/js/background/request-builder.js | 13 ++---- ext/js/comm/api.js | 6 +-- ext/js/comm/clipboard-reader.js | 4 +- ext/js/display/search-action-popup-controller.js | 4 +- ext/js/dom/sandbox/css-style-applier.js | 2 +- ext/js/dom/text-source-element.js | 2 +- ext/js/dom/text-source-range.js | 2 +- ext/js/general/regex-util.js | 2 +- .../__mocks__/dictionary-importer-media-loader.js | 1 + ext/js/language/dictionary-importer.js | 2 +- ext/js/language/dictionary-worker.js | 2 + ext/js/language/sandbox/japanese-util.js | 8 ++-- ext/js/language/text-scanner.js | 1 + ext/js/language/translator.js | 4 +- ext/js/media/audio-downloader.js | 6 +-- ext/js/pages/settings/backup-controller.js | 54 +++++++++------------- .../settings/recommended-permissions-controller.js | 36 +++++++++++++-- types/ext/api.d.ts | 12 +++++ types/ext/request-builder.d.ts | 2 + 22 files changed, 139 insertions(+), 82 deletions(-) (limited to 'ext/js/dom') diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 14877cf1..be68ecf4 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -96,7 +96,7 @@ export class Backend { }); /** @type {?import('settings').Options} */ this._options = null; - /** @type {JsonSchema[]} */ + /** @type {import('../data/json-schema.js').JsonSchema[]} */ this._profileConditionsSchemaCache = []; /** @type {ProfileConditionsUtil} */ this._profileConditionsUtil = new ProfileConditionsUtil(); @@ -665,7 +665,7 @@ export class Backend { async _onApiInjectStylesheet({type, value}, sender) { const {frameId, tab} = sender; if (typeof tab !== 'object' || tab === null || typeof tab.id !== 'number') { throw new Error('Invalid tab'); } - return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false, true, 'document_start'); + return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false); } /** @type {import('api').Handler} */ @@ -895,13 +895,7 @@ export class Backend { } } - /** - * - * @param root0 - * @param root0.targetTabId - * @param root0.targetFrameId - * @param sender - */ + /** @type {import('api').Handler} */ _onApiOpenCrossFramePort({targetTabId, targetFrameId}, sender) { const sourceTabId = (sender && sender.tab ? sender.tab.id : null); if (typeof sourceTabId !== 'number') { @@ -922,7 +916,9 @@ export class Backend { otherTabId: sourceTabId, otherFrameId: sourceFrameId }; + /** @type {?chrome.runtime.Port} */ let sourcePort = chrome.tabs.connect(sourceTabId, {frameId: sourceFrameId, name: JSON.stringify(sourceDetails)}); + /** @type {?chrome.runtime.Port} */ let targetPort = chrome.tabs.connect(targetTabId, {frameId: targetFrameId, name: JSON.stringify(targetDetails)}); const cleanup = () => { @@ -937,8 +933,12 @@ export class Backend { } }; - sourcePort.onMessage.addListener((message) => { targetPort.postMessage(message); }); - targetPort.onMessage.addListener((message) => { sourcePort.postMessage(message); }); + sourcePort.onMessage.addListener((message) => { + if (targetPort !== null) { targetPort.postMessage(message); } + }); + targetPort.onMessage.addListener((message) => { + if (sourcePort !== null) { sourcePort.postMessage(message); } + }); sourcePort.onDisconnect.addListener(cleanup); targetPort.onDisconnect.addListener(cleanup); diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index c01f523d..0fb2f269 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {deserializeError, isObject} from '../core.js'; +import {isObject} from '../core.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; export class OffscreenProxy { @@ -158,15 +158,36 @@ export class TranslatorProxy { } export class ClipboardReaderProxy { + /** + * @param {OffscreenProxy} offscreen + */ constructor(offscreen) { + /** @type {?import('environment').Browser} */ + this._browser = null; + /** @type {OffscreenProxy} */ this._offscreen = offscreen; } + /** @type {?import('environment').Browser} */ + get browser() { return this._browser; } + set browser(value) { + if (this._browser === value) { return; } + this._browser = value; + this._offscreen.sendMessagePromise({action: 'clipboardSetBrowserOffsecreen', params: {value}}); + } + + /** + * @param {boolean} useRichText + * @returns {Promise} + */ async getText(useRichText) { - return this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}}); + return await this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}}); } + /** + * @returns {Promise} + */ async getImage() { - return this._offscreen.sendMessagePromise({action: 'clipboardGetImageOffscreen'}); + return await this._offscreen.sendMessagePromise({action: 'clipboardGetImageOffscreen'}); } } diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 27cee8c4..6302aa84 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -50,6 +50,7 @@ export class Offscreen { this._messageHandlers = new Map([ ['clipboardGetTextOffscreen', {async: true, contentScript: true, handler: this._getTextHandler.bind(this)}], ['clipboardGetImageOffscreen', {async: true, contentScript: true, handler: this._getImageHandler.bind(this)}], + ['clipboardSetBrowserOffsecreen', {async: false, contentScript: true, handler: this._setClipboardBrowser.bind(this)}], ['databasePrepareOffscreen', {async: true, contentScript: true, handler: this._prepareDatabaseHandler.bind(this)}], ['getDictionaryInfoOffscreen', {async: true, contentScript: true, handler: this._getDictionaryInfoHandler.bind(this)}], ['databasePurgeOffscreen', {async: true, contentScript: true, handler: this._purgeDatabaseHandler.bind(this)}], @@ -59,7 +60,6 @@ export class Offscreen { ['findTermsOffscreen', {async: true, contentScript: true, handler: this._findTermsHandler.bind(this)}], ['getTermFrequenciesOffscreen', {async: true, contentScript: true, handler: this._getTermFrequenciesHandler.bind(this)}], ['clearDatabaseCachesOffscreen', {async: false, contentScript: true, handler: this._clearDatabaseCachesHandler.bind(this)}] - ]); const onMessage = this._onMessage.bind(this); @@ -76,6 +76,13 @@ export class Offscreen { return this._clipboardReader.getImage(); } + /** + * @param {{value: import('environment').Browser}} details + */ + _setClipboardBrowser({value}) { + this._clipboardReader.browser = value; + } + _prepareDatabaseHandler() { if (this._prepareDatabasePromise !== null) { return this._prepareDatabasePromise; diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js index 48fe2dd9..5ae7fbf5 100644 --- a/ext/js/background/request-builder.js +++ b/ext/js/background/request-builder.js @@ -21,12 +21,6 @@ * with additional controls over anonymity and error handling. */ export class RequestBuilder { - /** - * A progress callback for a fetch read. - * @callback ProgressCallback - * @param {boolean} complete Whether or not the data has been completely read. - */ - /** * Creates a new instance. */ @@ -109,14 +103,17 @@ export class RequestBuilder { /** * Reads the array buffer body of a fetch response, with an optional `onProgress` callback. * @param {Response} response The response of a `fetch` call. - * @param {ProgressCallback} onProgress The progress callback + * @param {?import('request-builder.js').ProgressCallback} onProgress The progress callback * @returns {Promise} The resulting binary data. */ static async readFetchResponseArrayBuffer(response, onProgress) { let reader; try { if (typeof onProgress === 'function') { - reader = response.body.getReader(); + const {body} = response; + if (body !== null) { + reader = body.getReader(); + } } } catch (e) { // Not supported diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 62dc98b1..0cfdba59 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -415,9 +415,9 @@ export class API { } /** - * - * @param targetTabId - * @param targetFrameId + * @param {import('api').OpenCrossFramePortDetails['targetTabId']} targetTabId + * @param {import('api').OpenCrossFramePortDetails['targetFrameId']} targetFrameId + * @returns {Promise} */ openCrossFramePort(targetTabId, targetFrameId) { return this._invoke('openCrossFramePort', {targetTabId, targetFrameId}); diff --git a/ext/js/comm/clipboard-reader.js b/ext/js/comm/clipboard-reader.js index c7b45a7c..364e31a3 100644 --- a/ext/js/comm/clipboard-reader.js +++ b/ext/js/comm/clipboard-reader.js @@ -29,7 +29,7 @@ export class ClipboardReader { constructor({document=null, pasteTargetSelector=null, richContentPasteTargetSelector=null}) { /** @type {?Document} */ this._document = document; - /** @type {?string} */ + /** @type {?import('environment').Browser} */ this._browser = null; /** @type {?HTMLTextAreaElement} */ this._pasteTarget = null; @@ -43,7 +43,7 @@ export class ClipboardReader { /** * Gets the browser being used. - * @type {?string} + * @type {?import('environment').Browser} */ get browser() { return this._browser; diff --git a/ext/js/display/search-action-popup-controller.js b/ext/js/display/search-action-popup-controller.js index 733fd70a..3a2057a1 100644 --- a/ext/js/display/search-action-popup-controller.js +++ b/ext/js/display/search-action-popup-controller.js @@ -18,10 +18,10 @@ export class SearchActionPopupController { /** - * @param {SearchPersistentStateController} searchPersistentStateController + * @param {import('./search-persistent-state-controller.js').SearchPersistentStateController} searchPersistentStateController */ constructor(searchPersistentStateController) { - /** @type {SearchPersistentStateController} */ + /** @type {import('./search-persistent-state-controller.js').SearchPersistentStateController} */ this._searchPersistentStateController = searchPersistentStateController; } diff --git a/ext/js/dom/sandbox/css-style-applier.js b/ext/js/dom/sandbox/css-style-applier.js index 332ca4f2..ea36a02d 100644 --- a/ext/js/dom/sandbox/css-style-applier.js +++ b/ext/js/dom/sandbox/css-style-applier.js @@ -24,7 +24,7 @@ export class CssStyleApplier { /** * Creates a new instance of the class. * @param {string} styleDataUrl The local URL to the JSON file continaing the style rules. - * The style rules should follow the format of {@link CssStyleApplierRawStyleData}. + * The style rules should follow the format of `CssStyleApplierRawStyleData`. */ constructor(styleDataUrl) { /** @type {string} */ diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index 47c18e30..40ff5cc9 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -173,7 +173,7 @@ export class TextSourceElement { /** * Checks whether another text source has the same starting point. - * @param {TextSourceElement|TextSourceRange} other The other source to test. + * @param {import('text-source').TextSource} other The other source to test. * @returns {boolean} `true` if the starting points are equivalent, `false` otherwise. */ hasSameStart(other) { diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 5dbbd636..fd09fdda 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -206,7 +206,7 @@ export class TextSourceRange { /** * Checks whether another text source has the same starting point. - * @param {TextSourceElement|TextSourceRange} other The other source to test. + * @param {import('text-source').TextSource} other The other source to test. * @returns {boolean} `true` if the starting points are equivalent, `false` otherwise. * @throws {Error} An exception can be thrown if `Range.compareBoundaryPoints` fails, * which shouldn't happen, but the handler is kept in case of unexpected errors. diff --git a/ext/js/general/regex-util.js b/ext/js/general/regex-util.js index 726ce9f2..62248968 100644 --- a/ext/js/general/regex-util.js +++ b/ext/js/general/regex-util.js @@ -25,7 +25,7 @@ export class RegexUtil { * Applies string.replace using a regular expression and replacement string as arguments. * A source map of the changes is also maintained. * @param {string} text A string of the text to replace. - * @param {TextSourceMap} sourceMap An instance of `TextSourceMap` which corresponds to `text`. + * @param {import('./text-source-map.js').TextSourceMap} sourceMap An instance of `TextSourceMap` which corresponds to `text`. * @param {RegExp} pattern A regular expression to use as the replacement. * @param {string} replacement A replacement string that follows the format of the standard * JavaScript regular expression replacement string. diff --git a/ext/js/language/__mocks__/dictionary-importer-media-loader.js b/ext/js/language/__mocks__/dictionary-importer-media-loader.js index 96f0f6dd..ffda29b3 100644 --- a/ext/js/language/__mocks__/dictionary-importer-media-loader.js +++ b/ext/js/language/__mocks__/dictionary-importer-media-loader.js @@ -17,6 +17,7 @@ */ export class DictionaryImporterMediaLoader { + /** @type {import('dictionary-importer-media-loader').GetImageDetailsFunction} */ async getImageDetails(content) { // Placeholder values return {content, width: 100, height: 100}; diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index aa6d7ae6..2a2f4063 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -36,7 +36,7 @@ export class DictionaryImporter { } /** - * @param {DictionaryDatabase} dictionaryDatabase + * @param {import('./dictionary-database.js').DictionaryDatabase} dictionaryDatabase * @param {ArrayBuffer} archiveContent * @param {import('dictionary-importer').ImportDetails} details * @returns {Promise} diff --git a/ext/js/language/dictionary-worker.js b/ext/js/language/dictionary-worker.js index 3e78a6ff..3119dd7b 100644 --- a/ext/js/language/dictionary-worker.js +++ b/ext/js/language/dictionary-worker.js @@ -157,6 +157,8 @@ export class DictionaryWorker { resolve(result2); } else { // If formatResult is not provided, the response is assumed to be the same type + // For some reason, eslint thinks the TResponse type is undefined + // eslint-disable-next-line jsdoc/no-undefined-types resolve(/** @type {TResponse} */ (/** @type {unknown} */ (result))); } } diff --git a/ext/js/language/sandbox/japanese-util.js b/ext/js/language/sandbox/japanese-util.js index f7f20b3b..4c9c46bd 100644 --- a/ext/js/language/sandbox/japanese-util.js +++ b/ext/js/language/sandbox/japanese-util.js @@ -466,7 +466,7 @@ export class JapaneseUtil { /** * @param {string} text - * @param {?TextSourceMap} [sourceMap] + * @param {?import('../../general/text-source-map.js').TextSourceMap} [sourceMap] * @returns {string} */ convertHalfWidthKanaToFullWidth(text, sourceMap=null) { @@ -513,7 +513,7 @@ export class JapaneseUtil { /** * @param {string} text - * @param {?TextSourceMap} sourceMap + * @param {?import('../../general/text-source-map.js').TextSourceMap} sourceMap * @returns {string} */ convertAlphabeticToKana(text, sourceMap=null) { @@ -676,7 +676,7 @@ export class JapaneseUtil { /** * @param {string} text * @param {boolean} fullCollapse - * @param {?TextSourceMap} [sourceMap] + * @param {?import('../../general/text-source-map.js').TextSourceMap} [sourceMap] * @returns {string} */ collapseEmphaticSequences(text, fullCollapse, sourceMap=null) { @@ -816,7 +816,7 @@ export class JapaneseUtil { /** * @param {string} text - * @param {?TextSourceMap} sourceMap + * @param {?import('../../general/text-source-map.js').TextSourceMap} sourceMap * @param {number} sourceMapStart * @returns {string} */ diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index b4d9a642..f6bcde8d 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -18,6 +18,7 @@ import {EventDispatcher, EventListenerCollection, clone, log} from '../core.js'; import {DocumentUtil} from '../dom/document-util.js'; +import {TextSourceElement} from '../dom/text-source-element.js'; import {yomitan} from '../yomitan.js'; /** diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 67cc53c6..c21b16b1 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -29,9 +29,9 @@ export class Translator { * @param {import('translator').ConstructorDetails} details The details for the class. */ constructor({japaneseUtil, database}) { - /** @type {JapaneseUtil} */ + /** @type {import('./sandbox/japanese-util.js').JapaneseUtil} */ this._japaneseUtil = japaneseUtil; - /** @type {DictionaryDatabase} */ + /** @type {import('./dictionary-database.js').DictionaryDatabase} */ this._database = database; /** @type {?Deinflector} */ this._deinflector = null; diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 7b236790..0847d479 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -25,10 +25,10 @@ import {SimpleDOMParser} from '../dom/simple-dom-parser.js'; export class AudioDownloader { /** - * @param {{japaneseUtil: JapaneseUtil, requestBuilder: RequestBuilder}} details + * @param {{japaneseUtil: import('../language/sandbox/japanese-util.js').JapaneseUtil, requestBuilder: RequestBuilder}} details */ constructor({japaneseUtil, requestBuilder}) { - /** @type {JapaneseUtil} */ + /** @type {import('../language/sandbox/japanese-util.js').JapaneseUtil} */ this._japaneseUtil = japaneseUtil; /** @type {RequestBuilder} */ this._requestBuilder = requestBuilder; @@ -314,7 +314,7 @@ export class AudioDownloader { */ async _downloadAudioFromUrl(url, sourceType, idleTimeout) { let signal; - /** @type {?(done: boolean) => void} */ + /** @type {?import('request-builder.js').ProgressCallback} */ let onProgress = null; /** @type {?number} */ let idleTimer = null; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 50a50b1a..52c5f418 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -534,12 +534,11 @@ export class BackupController { // Exporting Dictionaries Database /** - * - * @param message - * @param isWarning + * @param {string} message + * @param {boolean} [isWarning] */ _databaseExportImportErrorMessage(message, isWarning=false) { - const errorMessageContainer = document.querySelector('#db-ops-error-report'); + const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report')); errorMessageContainer.style.display = 'block'; errorMessageContainer.textContent = message; @@ -553,15 +552,11 @@ export class BackupController { } /** - * - * @param root0 - * @param root0.totalRows - * @param root0.completedRows - * @param root0.done + * @param {{totalRows: number, completedRows: number, done: boolean}} details */ _databaseExportProgressCallback({totalRows, completedRows, done}) { console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); - const messageContainer = document.querySelector('#db-ops-progress-report'); + const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report')); messageContainer.style.display = 'block'; messageContainer.textContent = `Export Progress: ${completedRows} of ${totalRows} rows completed`; @@ -572,8 +567,8 @@ export class BackupController { } /** - * - * @param databaseName + * @param {string} databaseName + * @returns {Promise} */ async _exportDatabase(databaseName) { const db = await new Dexie(databaseName).open(); @@ -592,7 +587,7 @@ export class BackupController { return; } - const errorMessageContainer = document.querySelector('#db-ops-error-report'); + const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report')); errorMessageContainer.style.display = 'none'; const date = new Date(Date.now()); @@ -616,15 +611,11 @@ export class BackupController { // Importing Dictionaries Database /** - * - * @param root0 - * @param root0.totalRows - * @param root0.completedRows - * @param root0.done + * @param {{totalRows: number, completedRows: number, done: boolean}} details */ _databaseImportProgressCallback({totalRows, completedRows, done}) { console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); - const messageContainer = document.querySelector('#db-ops-progress-report'); + const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report')); messageContainer.style.display = 'block'; messageContainer.style.color = '#4169e1'; messageContainer.textContent = `Import Progress: ${completedRows} of ${totalRows} rows completed`; @@ -637,9 +628,8 @@ export class BackupController { } /** - * - * @param databaseName - * @param file + * @param {string} databaseName + * @param {File} file */ async _importDatabase(databaseName, file) { await yomitan.api.purgeDatabase(); @@ -648,16 +638,13 @@ export class BackupController { yomitan.trigger('storageChanged'); } - /** - * - */ + /** */ _onSettingsImportDatabaseClick() { - document.querySelector('#settings-import-db').click(); + /** @type {HTMLElement} */ (document.querySelector('#settings-import-db')).click(); } /** - * - * @param e + * @param {Event} e */ async _onSettingsImportDatabaseChange(e) { if (this._settingsExportDatabaseToken !== null) { @@ -666,22 +653,23 @@ export class BackupController { return; } - const errorMessageContainer = document.querySelector('#db-ops-error-report'); + const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report')); errorMessageContainer.style.display = 'none'; - const files = e.target.files; - if (files.length === 0) { return; } + const element = /** @type {HTMLInputElement} */ (e.currentTarget); + const files = element.files; + if (files === null || files.length === 0) { return; } const pageExitPrevention = this._settingsController.preventPageExit(); const file = files[0]; - e.target.value = null; + element.value = ''; try { const token = {}; this._settingsExportDatabaseToken = token; await this._importDatabase(this._dictionariesDatabaseName, file); } catch (error) { console.log(error); - const messageContainer = document.querySelector('#db-ops-progress-report'); + const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report')); messageContainer.style.color = 'red'; this._databaseExportImportErrorMessage('Encountered errors when importing. Please restart the browser and try again. If it continues to fail, reinstall Yomitan and import dictionaries one-by-one.'); } finally { diff --git a/ext/js/pages/settings/recommended-permissions-controller.js b/ext/js/pages/settings/recommended-permissions-controller.js index e04dbdf7..b19311aa 100644 --- a/ext/js/pages/settings/recommended-permissions-controller.js +++ b/ext/js/pages/settings/recommended-permissions-controller.js @@ -19,13 +19,21 @@ import {EventListenerCollection} from '../../core.js'; export class RecommendedPermissionsController { + /** + * @param {import('./settings-controller.js').SettingsController} settingsController + */ constructor(settingsController) { + /** @type {import('./settings-controller.js').SettingsController} */ this._settingsController = settingsController; + /** @type {?NodeListOf} */ this._originToggleNodes = null; + /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); + /** @type {?HTMLElement} */ this._errorContainer = null; } + /** */ async prepare() { this._originToggleNodes = document.querySelectorAll('.recommended-permissions-toggle'); this._errorContainer = document.querySelector('#recommended-permissions-error'); @@ -39,35 +47,53 @@ export class RecommendedPermissionsController { // Private + /** + * @param {import('settings-controller').PermissionsChangedEvent} details + */ _onPermissionsChanged({permissions}) { this._eventListeners.removeAllEventListeners(); const originsSet = new Set(permissions.origins); - for (const node of this._originToggleNodes) { - node.checked = originsSet.has(node.dataset.origin); + if (this._originToggleNodes !== null) { + for (const node of this._originToggleNodes) { + const {origin} = node.dataset; + node.checked = typeof origin === 'string' && originsSet.has(origin); + } } } + /** + * @param {Event} e + */ _onOriginToggleChange(e) { - const node = e.currentTarget; + const node = /** @type {HTMLInputElement} */ (e.currentTarget); const value = node.checked; node.checked = !value; const {origin} = node.dataset; + if (typeof origin !== 'string') { return; } this._setOriginPermissionEnabled(origin, value); } + /** */ async _updatePermissions() { const permissions = await this._settingsController.permissionsUtil.getAllPermissions(); this._onPermissionsChanged({permissions}); } + /** + * @param {string} origin + * @param {boolean} enabled + * @returns {Promise} + */ async _setOriginPermissionEnabled(origin, enabled) { let added = false; try { added = await this._settingsController.permissionsUtil.setPermissionsGranted({origins: [origin]}, enabled); } catch (e) { - this._errorContainer.hidden = false; - this._errorContainer.textContent = e.message; + if (this._errorContainer !== null) { + this._errorContainer.hidden = false; + this._errorContainer.textContent = e instanceof Error ? e.message : `${e}`; + } } if (!added) { return false; } await this._updatePermissions(); diff --git a/types/ext/api.d.ts b/types/ext/api.d.ts index 19a62c1c..6b7b4b19 100644 --- a/types/ext/api.d.ts +++ b/types/ext/api.d.ts @@ -444,6 +444,18 @@ export type LoadExtensionScriptsDetails = { export type LoadExtensionScriptsResult = void; +// openCrossFramePort + +export type OpenCrossFramePortDetails = { + targetTabId: number; + targetFrameId: number; +}; + +export type OpenCrossFramePortResult = { + targetTabId: number; + targetFrameId: number; +}; + // requestBackendReadySignal export type RequestBackendReadySignalDetails = Record; diff --git a/types/ext/request-builder.d.ts b/types/ext/request-builder.d.ts index 0acf5ede..8f375754 100644 --- a/types/ext/request-builder.d.ts +++ b/types/ext/request-builder.d.ts @@ -19,3 +19,5 @@ export type FetchEventListeners = { onBeforeSendHeaders: ((details: chrome.webRequest.WebRequestHeadersDetails) => (chrome.webRequest.BlockingResponse | void)) | null; onErrorOccurred: ((details: chrome.webRequest.WebResponseErrorDetails) => void) | null; }; + +export type ProgressCallback = (complete: boolean) => void; -- cgit v1.2.3 From d5b1217df3fe7480fc5f58fe194f5bbf73281094 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 19:23:17 -0500 Subject: Use import map --- dev/jsconfig.json | 1 + ext/action-popup.html | 8 ++++++++ ext/background.html | 8 ++++++++ ext/info.html | 8 ++++++++ ext/issues.html | 8 ++++++++ ext/js/background/backend.js | 2 +- ext/js/background/offscreen.js | 2 +- ext/js/display/search-display-controller.js | 2 +- ext/js/display/search-main.js | 2 +- ext/js/dom/simple-dom-parser.js | 2 +- ext/js/language/dictionary-importer.js | 4 ++-- ext/js/pages/settings/backup-controller.js | 2 +- ext/js/pages/settings/popup-preview-frame.js | 2 +- ext/js/templates/sandbox/anki-template-renderer.js | 2 +- ext/js/templates/sandbox/template-renderer-media-provider.js | 2 +- ext/js/templates/sandbox/template-renderer.js | 2 +- ext/legal.html | 8 ++++++++ ext/offscreen.html | 8 ++++++++ ext/permissions.html | 8 ++++++++ ext/popup-preview.html | 8 ++++++++ ext/popup.html | 8 ++++++++ ext/search.html | 8 ++++++++ ext/settings.html | 8 ++++++++ ext/template-renderer.html | 8 ++++++++ ext/welcome.html | 8 ++++++++ jsconfig.json | 3 ++- test/japanese-util.test.js | 2 +- test/jsconfig.json | 1 + 28 files changed, 121 insertions(+), 14 deletions(-) (limited to 'ext/js/dom') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index a012f32f..518f97ad 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { + "zip.js": ["@zip.js/zip.js"], "anki-templates": ["../types/ext/anki-templates"], "anki-templates-internal": ["../types/ext/anki-templates-internal"], "cache-map": ["../types/ext/cache-map"], diff --git a/ext/action-popup.html b/ext/action-popup.html index b60e7055..5c6bfce4 100644 --- a/ext/action-popup.html +++ b/ext/action-popup.html @@ -12,6 +12,14 @@ + diff --git a/ext/background.html b/ext/background.html index dc88f397..6f9ee5f6 100644 --- a/ext/background.html +++ b/ext/background.html @@ -12,6 +12,14 @@ + diff --git a/ext/info.html b/ext/info.html index cb80053d..9e71ffd4 100644 --- a/ext/info.html +++ b/ext/info.html @@ -13,6 +13,14 @@ + diff --git a/ext/issues.html b/ext/issues.html index 904fbf16..c75683dd 100644 --- a/ext/issues.html +++ b/ext/issues.html @@ -13,6 +13,14 @@ + diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index be68ecf4..44f5a42d 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {AccessibilityController} from '../accessibility/accessibility-controller.js'; import {AnkiConnect} from '../comm/anki-connect.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 1b68887b..45345c01 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {ClipboardReader} from '../comm/clipboard-reader.js'; import {invokeMessageHandler} from '../core.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index a9bf2217..b93757c2 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; import {EventListenerCollection, invokeMessageHandler} from '../core.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index c20cc135..c1445e37 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {log} from '../core.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index a1f63890..7ee28d51 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as parse5 from '../../lib/parse5.js'; +import * as parse5 from 'parse5'; /** * @augments import('simple-dom-parser').ISimpleDomParser diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 115e0726..5a4d7257 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -16,14 +16,14 @@ * along with this program. If not, see . */ -import * as ajvSchemas0 from '../../lib/validate-schemas.js'; +import * as ajvSchemas0 from 'validate-schemas'; import { BlobWriter as BlobWriter0, TextWriter as TextWriter0, Uint8ArrayReader as Uint8ArrayReader0, ZipReader as ZipReader0, configure -} from '../../lib/zip.js'; +} from 'zip.js'; import {stringReverse} from '../core.js'; import {MediaUtil} from '../media/media-util.js'; import {ExtensionError} from '../core/extension-error.js'; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 52c5f418..aeff2a97 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Dexie} from '../../../lib/dexie.js'; +import {Dexie} from 'dexie'; import {isObject, log} from '../../core.js'; import {OptionsUtil} from '../../data/options-util.js'; import {ArrayBufferUtil} from '../../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index c1a0d706..bb00829f 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {Frontend} from '../../app/frontend.js'; import {TextSourceRange} from '../../dom/text-source-range.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index 9f4bf6ff..b0dc8223 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from '../../../lib/handlebars.js'; +import {Handlebars} from 'handlebars'; import {AnkiNoteDataCreator} from '../../data/sandbox/anki-note-data-creator.js'; import {PronunciationGenerator} from '../../display/sandbox/pronunciation-generator.js'; import {StructuredContentGenerator} from '../../display/sandbox/structured-content-generator.js'; diff --git a/ext/js/templates/sandbox/template-renderer-media-provider.js b/ext/js/templates/sandbox/template-renderer-media-provider.js index d8a0a16d..23f334e1 100644 --- a/ext/js/templates/sandbox/template-renderer-media-provider.js +++ b/ext/js/templates/sandbox/template-renderer-media-provider.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from '../../../lib/handlebars.js'; +import {Handlebars} from 'handlebars'; export class TemplateRendererMediaProvider { constructor() { diff --git a/ext/js/templates/sandbox/template-renderer.js b/ext/js/templates/sandbox/template-renderer.js index fe240b5f..d4aebd64 100644 --- a/ext/js/templates/sandbox/template-renderer.js +++ b/ext/js/templates/sandbox/template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from '../../../lib/handlebars.js'; +import {Handlebars} from 'handlebars'; import {ExtensionError} from '../../core/extension-error.js'; export class TemplateRenderer { diff --git a/ext/legal.html b/ext/legal.html index 94912c7e..b853f3e8 100644 --- a/ext/legal.html +++ b/ext/legal.html @@ -14,6 +14,14 @@ + diff --git a/ext/offscreen.html b/ext/offscreen.html index afb7eb44..cfab53ee 100644 --- a/ext/offscreen.html +++ b/ext/offscreen.html @@ -12,6 +12,14 @@ + diff --git a/ext/permissions.html b/ext/permissions.html index 61b0d363..7baff14e 100644 --- a/ext/permissions.html +++ b/ext/permissions.html @@ -14,6 +14,14 @@ + diff --git a/ext/popup-preview.html b/ext/popup-preview.html index 15810242..7bd54470 100644 --- a/ext/popup-preview.html +++ b/ext/popup-preview.html @@ -13,6 +13,14 @@ + diff --git a/ext/popup.html b/ext/popup.html index 30e8a8c0..6bc8d690 100644 --- a/ext/popup.html +++ b/ext/popup.html @@ -15,6 +15,14 @@ + diff --git a/ext/search.html b/ext/search.html index 8c595cc4..377a966a 100644 --- a/ext/search.html +++ b/ext/search.html @@ -16,6 +16,14 @@ + diff --git a/ext/settings.html b/ext/settings.html index 346cc1d7..276a7222 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -14,6 +14,14 @@ + diff --git a/ext/template-renderer.html b/ext/template-renderer.html index 116f1a0c..d1747e99 100644 --- a/ext/template-renderer.html +++ b/ext/template-renderer.html @@ -11,6 +11,14 @@ + diff --git a/ext/welcome.html b/ext/welcome.html index 40639881..355bbc5f 100644 --- a/ext/welcome.html +++ b/ext/welcome.html @@ -13,6 +13,14 @@ + diff --git a/jsconfig.json b/jsconfig.json index ace0c2aa..4f316174 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { + "zip.js": ["@zip.js/zip.js"], "*": ["./types/ext/*"] }, "types": [ @@ -29,7 +30,7 @@ "include": [ "ext/**/*.js", "types/ext/**/*.ts", - "types/other/web-set-timeout.d.ts" + "types/other/globals.d.ts" ], "exclude": [ "node_modules", diff --git a/test/japanese-util.test.js b/test/japanese-util.test.js index 47da4ccb..ae5b1a16 100644 --- a/test/japanese-util.test.js +++ b/test/japanese-util.test.js @@ -16,10 +16,10 @@ * along with this program. If not, see . */ +import * as wanakana from 'wanakana'; import {expect, test} from 'vitest'; import {TextSourceMap} from '../ext/js/general/text-source-map.js'; import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; -import * as wanakana from '../ext/lib/wanakana.js'; const jp = new JapaneseUtil(wanakana); diff --git a/test/jsconfig.json b/test/jsconfig.json index b025918c..261ec345 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { + "zip.js": ["@zip.js/zip.js"], "*": ["../types/ext/*"], "dev/*": ["../types/dev/*"], "test/*": ["../types/test/*"] -- cgit v1.2.3 From 14d12f6ba20b837a04c638b935773f3120e194ff Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 19:33:01 -0500 Subject: Update timer types and such --- dev/jsconfig.json | 2 +- ext/js/app/frontend.js | 11 +++++++++-- ext/js/background/backend.js | 4 ++-- ext/js/background/offscreen.js | 4 +++- ext/js/comm/api.js | 2 +- ext/js/comm/clipboard-monitor.js | 2 +- ext/js/comm/cross-frame-api.js | 21 +++++++++++++-------- ext/js/comm/frame-ancestry-handler.js | 2 +- ext/js/comm/frame-client.js | 2 +- ext/js/comm/mecab.js | 2 +- ext/js/core.js | 2 +- ext/js/display/display-audio.js | 2 +- ext/js/display/display-notification.js | 2 +- ext/js/display/display.js | 2 +- ext/js/display/element-overflow-controller.js | 8 ++++---- ext/js/display/option-toggle-hotkey-handler.js | 2 +- ext/js/display/search-display-controller.js | 2 +- ext/js/dom/document-util.js | 2 +- ext/js/dom/panel-element.js | 2 +- ext/js/language/text-scanner.js | 6 +++--- ext/js/media/audio-downloader.js | 2 +- ext/js/pages/settings/popup-preview-frame.js | 2 +- test/jsconfig.json | 2 +- types/ext/core.d.ts | 2 ++ types/ext/cross-frame-api.d.ts | 2 +- types/ext/offscreen.d.ts | 2 +- types/other/globals.d.ts | 22 ++++++++++++++++++++++ types/other/web-set-timeout.d.ts | 24 ------------------------ 28 files changed, 77 insertions(+), 63 deletions(-) create mode 100644 types/other/globals.d.ts delete mode 100644 types/other/web-set-timeout.d.ts (limited to 'ext/js/dom') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index 518f97ad..d4efe694 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -71,7 +71,7 @@ "../ext/js/language/translator.js", "../ext/js/media/media-util.js", "../types/dev/**/*.ts", - "../types/other/web-set-timeout.d.ts" + "../types/other/globals.d.ts" ], "exclude": [ "../node_modules" diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index 628c504e..e1f8d8c9 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -99,7 +99,7 @@ export class Frontend { this._popupEventListeners = new EventListenerCollection(); /** @type {?import('core').TokenObject} */ this._updatePopupToken = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._clearSelectionTimer = null; /** @type {boolean} */ this._isPointerOverPopup = false; @@ -840,7 +840,7 @@ export class Frontend { */ async _waitForFrontendReady(frameId, timeout) { return new Promise((resolve, reject) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timeoutId = null; const cleanup = () => { @@ -973,6 +973,13 @@ export class Frontend { await yomitan.api.loadExtensionScripts([ '/js/accessibility/google-docs-util.js' ]); + this._prepareGoogleDocs2(); + } + + /** + * @returns {Promise} + */ + async _prepareGoogleDocs2() { if (typeof GoogleDocsUtil === 'undefined') { return; } DocumentUtil.registerGetRangeFromPointHandler(GoogleDocsUtil.getRangeFromPoint.bind(GoogleDocsUtil)); } diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 44f5a42d..f1a47e7f 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -138,7 +138,7 @@ export class Backend { /** @type {?string} */ this._defaultBrowserActionTitle = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._badgePrepareDelayTimer = null; /** @type {?import('log').LogLevel} */ this._logErrorLevel = null; @@ -1981,7 +1981,7 @@ export class Backend { */ _waitUntilTabFrameIsReady(tabId, frameId, timeout=null) { return new Promise((resolve, reject) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */ let onMessage = (message, sender) => { diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 45345c01..8da661ad 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -51,7 +51,7 @@ export class Offscreen { }); /** @type {import('offscreen').MessageHandlerMap} */ - this._messageHandlers = new Map([ + const messageHandlers = new Map([ ['clipboardGetTextOffscreen', {async: true, handler: this._getTextHandler.bind(this)}], ['clipboardGetImageOffscreen', {async: true, handler: this._getImageHandler.bind(this)}], ['clipboardSetBrowserOffscreen', {async: false, handler: this._setClipboardBrowser.bind(this)}], @@ -65,6 +65,8 @@ export class Offscreen { ['getTermFrequenciesOffscreen', {async: true, handler: this._getTermFrequenciesHandler.bind(this)}], ['clearDatabaseCachesOffscreen', {async: false, handler: this._clearDatabaseCachesHandler.bind(this)}] ]); + /** @type {import('offscreen').MessageHandlerMap} */ + this._messageHandlers = messageHandlers; const onMessage = this._onMessage.bind(this); chrome.runtime.onMessage.addListener(onMessage); diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 0cfdba59..26218595 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -431,7 +431,7 @@ export class API { */ _createActionPort(timeout) { return new Promise((resolve, reject) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; const portDetails = deferPromise(); diff --git a/ext/js/comm/clipboard-monitor.js b/ext/js/comm/clipboard-monitor.js index 06e95438..3b3a56a9 100644 --- a/ext/js/comm/clipboard-monitor.js +++ b/ext/js/comm/clipboard-monitor.js @@ -31,7 +31,7 @@ export class ClipboardMonitor extends EventDispatcher { this._japaneseUtil = japaneseUtil; /** @type {import('clipboard-monitor').ClipboardReaderLike} */ this._clipboardReader = clipboardReader; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._timerId = null; /** @type {?import('core').TokenObject} */ this._timerToken = null; diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js index 34f3f36a..3ac38cf2 100644 --- a/ext/js/comm/cross-frame-api.js +++ b/ext/js/comm/cross-frame-api.js @@ -25,14 +25,14 @@ import {yomitan} from '../yomitan.js'; */ class CrossFrameAPIPort extends EventDispatcher { /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @param {chrome.runtime.Port} port * @param {import('core').MessageHandlerMap} messageHandlers */ constructor(otherTabId, otherFrameId, port, messageHandlers) { super(); - /** @type {?number} */ + /** @type {number} */ this._otherTabId = otherTabId; /** @type {number} */ this._otherFrameId = otherFrameId; @@ -48,7 +48,7 @@ class CrossFrameAPIPort extends EventDispatcher { this._eventListeners = new EventListenerCollection(); } - /** @type {?number} */ + /** @type {number} */ get otherTabId() { return this._otherTabId; } @@ -299,7 +299,7 @@ export class CrossFrameAPI { this._ackTimeout = 3000; // 3 seconds /** @type {number} */ this._responseTimeout = 10000; // 10 seconds - /** @type {Map>} */ + /** @type {Map>} */ this._commPorts = new Map(); /** @type {import('core').MessageHandlerMap} */ this._messageHandlers = new Map(); @@ -339,7 +339,12 @@ export class CrossFrameAPI { * @returns {Promise} */ async invokeTab(targetTabId, targetFrameId, action, params) { - if (typeof targetTabId !== 'number') { targetTabId = this._tabId; } + if (typeof targetTabId !== 'number') { + targetTabId = this._tabId; + if (typeof targetTabId !== 'number') { + throw new Error('Unknown target tab id for invocation'); + } + } const commPort = await this._getOrCreateCommPort(targetTabId, targetFrameId); return await commPort.invoke(action, params, this._ackTimeout, this._responseTimeout); } @@ -405,7 +410,7 @@ export class CrossFrameAPI { } /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @returns {Promise} */ @@ -420,7 +425,7 @@ export class CrossFrameAPI { return await this._createCommPort(otherTabId, otherFrameId); } /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @returns {Promise} */ @@ -438,7 +443,7 @@ export class CrossFrameAPI { } /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @param {chrome.runtime.Port} port * @returns {CrossFrameAPIPort} diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index 49c96c22..687ec368 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -122,7 +122,7 @@ export class FrameAncestryHandler { const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`; /** @type {number[]} */ const results = []; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; const cleanup = () => { diff --git a/ext/js/comm/frame-client.js b/ext/js/comm/frame-client.js index 1519cf7f..8aa8c6d6 100644 --- a/ext/js/comm/frame-client.js +++ b/ext/js/comm/frame-client.js @@ -83,7 +83,7 @@ export class FrameClient { _connectInternal(frame, targetOrigin, hostFrameId, setupFrame, timeout) { return new Promise((resolve, reject) => { const tokenMap = new Map(); - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; const deferPromiseDetails = /** @type {import('core').DeferredPromiseDetails} */ (deferPromise()); const frameLoadedPromise = deferPromiseDetails.promise; diff --git a/ext/js/comm/mecab.js b/ext/js/comm/mecab.js index 072b42f3..0a87463b 100644 --- a/ext/js/comm/mecab.js +++ b/ext/js/comm/mecab.js @@ -31,7 +31,7 @@ export class Mecab { this._port = null; /** @type {number} */ this._sequence = 0; - /** @type {Map void, reject: (reason?: unknown) => void, timer: number}>} */ + /** @type {Map void, reject: (reason?: unknown) => void, timer: import('core').Timeout}>} */ this._invocations = new Map(); /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); diff --git a/ext/js/core.js b/ext/js/core.js index cdac2020..a53d5572 100644 --- a/ext/js/core.js +++ b/ext/js/core.js @@ -307,7 +307,7 @@ export function promiseAnimationFrame(timeout) { return; } - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; /** @type {?number} */ let frameRequest = null; diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index 3fbfc3c8..3576decb 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -36,7 +36,7 @@ export class DisplayAudio { this._playbackVolume = 1.0; /** @type {boolean} */ this._autoPlay = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._autoPlayAudioTimer = null; /** @type {number} */ this._autoPlayAudioDelay = 400; diff --git a/ext/js/display/display-notification.js b/ext/js/display/display-notification.js index b3f20700..d1cfa537 100644 --- a/ext/js/display/display-notification.js +++ b/ext/js/display/display-notification.js @@ -34,7 +34,7 @@ export class DisplayNotification { this._closeButton = /** @type {HTMLElement} */ (node.querySelector('.footer-notification-close-button')); /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._closeTimer = null; } diff --git a/ext/js/display/display.js b/ext/js/display/display.js index f14291d1..6e1450c3 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -117,7 +117,7 @@ export class Display extends EventDispatcher { this._queryOffset = 0; /** @type {HTMLElement} */ this._progressIndicator = /** @type {HTMLElement} */ (document.querySelector('#progress-indicator')); - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._progressIndicatorTimer = null; /** @type {DynamicProperty} */ this._progressIndicatorVisible = new DynamicProperty(false); diff --git a/ext/js/display/element-overflow-controller.js b/ext/js/display/element-overflow-controller.js index 1d2c808f..35277fa5 100644 --- a/ext/js/display/element-overflow-controller.js +++ b/ext/js/display/element-overflow-controller.js @@ -22,7 +22,7 @@ export class ElementOverflowController { constructor() { /** @type {Element[]} */ this._elements = []; - /** @type {?number} */ + /** @type {?(number|import('core').Timeout)} */ this._checkTimer = null; /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); @@ -154,7 +154,7 @@ export class ElementOverflowController { /** * @param {() => void} callback * @param {number} timeout - * @returns {number} + * @returns {number|import('core').Timeout} */ _requestIdleCallback(callback, timeout) { if (typeof requestIdleCallback === 'function') { @@ -165,11 +165,11 @@ export class ElementOverflowController { } /** - * @param {number} handle + * @param {number|import('core').Timeout} handle */ _cancelIdleCallback(handle) { if (typeof cancelIdleCallback === 'function') { - cancelIdleCallback(handle); + cancelIdleCallback(/** @type {number} */ (handle)); } else { clearTimeout(handle); } diff --git a/ext/js/display/option-toggle-hotkey-handler.js b/ext/js/display/option-toggle-hotkey-handler.js index 531e208d..edd7de5b 100644 --- a/ext/js/display/option-toggle-hotkey-handler.js +++ b/ext/js/display/option-toggle-hotkey-handler.js @@ -29,7 +29,7 @@ export class OptionToggleHotkeyHandler { this._display = display; /** @type {?import('./display-notification.js').DisplayNotification} */ this._notification = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._notificationHideTimer = null; /** @type {number} */ this._notificationHideTimeout = 5000; diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index b93757c2..98a4666b 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -63,7 +63,7 @@ export class SearchDisplayController { this._wanakanaBound = false; /** @type {boolean} */ this._introVisible = true; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._introAnimationTimer = null; /** @type {boolean} */ this._clipboardMonitorEnabled = false; diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index cf58d39f..549a8195 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -360,7 +360,7 @@ export class DocumentUtil { /** * Adds a fullscreen change event listener. This function handles all of the browser-specific variants. * @param {EventListener} onFullscreenChanged The event callback. - * @param {?EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. + * @param {?import('../core.js').EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. */ static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection=null) { const target = document; diff --git a/ext/js/dom/panel-element.js b/ext/js/dom/panel-element.js index 314eb2fd..748c3a36 100644 --- a/ext/js/dom/panel-element.js +++ b/ext/js/dom/panel-element.js @@ -37,7 +37,7 @@ export class PanelElement extends EventDispatcher { this._mutationObserver = null; /** @type {boolean} */ this._visible = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._closeTimer = null; } diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index f6bcde8d..d1b033e6 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -120,7 +120,7 @@ export class TextScanner extends EventDispatcher { /** @type {boolean} */ this._preventNextClickScan = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._preventNextClickScanTimer = null; /** @type {number} */ this._preventNextClickScanTimerDuration = 50; @@ -145,7 +145,7 @@ export class TextScanner extends EventDispatcher { /** @type {boolean} */ this._canClearSelection = true; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._textSelectionTimer = null; /** @type {boolean} */ this._yomitanIsChangingTextSelectionNow = false; @@ -995,7 +995,7 @@ export class TextScanner extends EventDispatcher { async _scanTimerWait() { const delay = this._delay; const promise = /** @type {Promise} */ (new Promise((resolve) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timeout = setTimeout(() => { timeout = null; resolve(true); diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 0847d479..e041cc67 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -316,7 +316,7 @@ export class AudioDownloader { let signal; /** @type {?import('request-builder.js').ProgressCallback} */ let onProgress = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let idleTimer = null; if (typeof idleTimeout === 'number') { const abortController = new AbortController(); diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index bb00829f..dab21f4d 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -43,7 +43,7 @@ export class PopupPreviewFrame { this._apiOptionsGetOld = null; /** @type {boolean} */ this._popupShown = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._themeChangeTimeout = null; /** @type {?import('text-source').TextSource} */ this._textSource = null; diff --git a/test/jsconfig.json b/test/jsconfig.json index 261ec345..934aab81 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -31,7 +31,7 @@ "../ext/**/*.js", "../types/ext/**/*.ts", "../types/dev/**/*.ts", - "../types/other/web-set-timeout.d.ts" + "../types/other/globals.d.ts" ], "exclude": [ "../node_modules", diff --git a/types/ext/core.d.ts b/types/ext/core.d.ts index ce3a09f0..b83e6a74 100644 --- a/types/ext/core.d.ts +++ b/types/ext/core.d.ts @@ -100,3 +100,5 @@ export type MessageHandlerDetails = { export type MessageHandlerMap = Map; export type MessageHandlerArray = [key: string, handlerDetails: MessageHandlerDetails][]; + +export type Timeout = number | NodeJS.Timeout; diff --git a/types/ext/cross-frame-api.d.ts b/types/ext/cross-frame-api.d.ts index 5cedbec9..88ce59a7 100644 --- a/types/ext/cross-frame-api.d.ts +++ b/types/ext/cross-frame-api.d.ts @@ -50,5 +50,5 @@ export type Invocation = { responseTimeout: number; action: string; ack: boolean; - timer: number | null; + timer: Core.Timeout | null; }; diff --git a/types/ext/offscreen.d.ts b/types/ext/offscreen.d.ts index 7dd64d1e..ddf7eadc 100644 --- a/types/ext/offscreen.d.ts +++ b/types/ext/offscreen.d.ts @@ -112,4 +112,4 @@ export type MessageHandler< details: MessageDetailsMap[TMessage], ) => (TIsAsync extends true ? Promise> : MessageReturn); -export type MessageHandlerMap = Map; +export type MessageHandlerMap = Map; diff --git a/types/other/globals.d.ts b/types/other/globals.d.ts new file mode 100644 index 00000000..330f16c2 --- /dev/null +++ b/types/other/globals.d.ts @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +declare global { + function clearTimeout(timeoutId: NodeJS.Timeout | string | number | undefined): void; +} + +export {}; diff --git a/types/other/web-set-timeout.d.ts b/types/other/web-set-timeout.d.ts deleted file mode 100644 index 98e40dab..00000000 --- a/types/other/web-set-timeout.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -declare module 'web-set-timeout' { - global { - // These types are used to ensure that setTimeout returns a number - function setTimeout(callback: (...args: TArgs) => void, ms?: number, ...args: TArgs): number; - function setTimeout(callback: (args: void) => void, ms?: number): number; - } -} -- cgit v1.2.3 From 58ae2ab871591eea82895b1ab2a18753521eab1f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 22:07:28 -0500 Subject: Revert "Use import map" --- dev/jsconfig.json | 1 - ext/action-popup.html | 8 -------- ext/background.html | 8 -------- ext/info.html | 8 -------- ext/issues.html | 8 -------- ext/js/background/backend.js | 2 +- ext/js/background/offscreen.js | 2 +- ext/js/display/search-display-controller.js | 2 +- ext/js/display/search-main.js | 2 +- ext/js/dom/simple-dom-parser.js | 2 +- ext/js/language/dictionary-importer.js | 4 ++-- ext/js/pages/settings/backup-controller.js | 2 +- ext/js/pages/settings/popup-preview-frame.js | 2 +- ext/js/templates/sandbox/anki-template-renderer.js | 2 +- ext/js/templates/sandbox/template-renderer-media-provider.js | 2 +- ext/js/templates/sandbox/template-renderer.js | 2 +- ext/legal.html | 8 -------- ext/offscreen.html | 8 -------- ext/permissions.html | 8 -------- ext/popup-preview.html | 8 -------- ext/popup.html | 8 -------- ext/search.html | 8 -------- ext/settings.html | 8 -------- ext/template-renderer.html | 8 -------- ext/welcome.html | 8 -------- jsconfig.json | 1 - test/japanese-util.test.js | 2 +- test/jsconfig.json | 1 - 28 files changed, 13 insertions(+), 120 deletions(-) (limited to 'ext/js/dom') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index d4efe694..5b1c450c 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -12,7 +12,6 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { - "zip.js": ["@zip.js/zip.js"], "anki-templates": ["../types/ext/anki-templates"], "anki-templates-internal": ["../types/ext/anki-templates-internal"], "cache-map": ["../types/ext/cache-map"], diff --git a/ext/action-popup.html b/ext/action-popup.html index 5c6bfce4..b60e7055 100644 --- a/ext/action-popup.html +++ b/ext/action-popup.html @@ -12,14 +12,6 @@ - diff --git a/ext/background.html b/ext/background.html index 6f9ee5f6..dc88f397 100644 --- a/ext/background.html +++ b/ext/background.html @@ -12,14 +12,6 @@ - diff --git a/ext/info.html b/ext/info.html index 9e71ffd4..cb80053d 100644 --- a/ext/info.html +++ b/ext/info.html @@ -13,14 +13,6 @@ - diff --git a/ext/issues.html b/ext/issues.html index c75683dd..904fbf16 100644 --- a/ext/issues.html +++ b/ext/issues.html @@ -13,14 +13,6 @@ - diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index f1a47e7f..3eefed53 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {AccessibilityController} from '../accessibility/accessibility-controller.js'; import {AnkiConnect} from '../comm/anki-connect.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 8da661ad..4b57514d 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {ClipboardReader} from '../comm/clipboard-reader.js'; import {invokeMessageHandler} from '../core.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index 98a4666b..2778c4cd 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; import {EventListenerCollection, invokeMessageHandler} from '../core.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index c1445e37..c20cc135 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {log} from '../core.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index 7ee28d51..a1f63890 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as parse5 from 'parse5'; +import * as parse5 from '../../lib/parse5.js'; /** * @augments import('simple-dom-parser').ISimpleDomParser diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 5a4d7257..115e0726 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -16,14 +16,14 @@ * along with this program. If not, see . */ -import * as ajvSchemas0 from 'validate-schemas'; +import * as ajvSchemas0 from '../../lib/validate-schemas.js'; import { BlobWriter as BlobWriter0, TextWriter as TextWriter0, Uint8ArrayReader as Uint8ArrayReader0, ZipReader as ZipReader0, configure -} from 'zip.js'; +} from '../../lib/zip.js'; import {stringReverse} from '../core.js'; import {MediaUtil} from '../media/media-util.js'; import {ExtensionError} from '../core/extension-error.js'; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index aeff2a97..52c5f418 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Dexie} from 'dexie'; +import {Dexie} from '../../../lib/dexie.js'; import {isObject, log} from '../../core.js'; import {OptionsUtil} from '../../data/options-util.js'; import {ArrayBufferUtil} from '../../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index dab21f4d..60d264fa 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../../lib/wanakana.js'; import {Frontend} from '../../app/frontend.js'; import {TextSourceRange} from '../../dom/text-source-range.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index b0dc8223..9f4bf6ff 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from 'handlebars'; +import {Handlebars} from '../../../lib/handlebars.js'; import {AnkiNoteDataCreator} from '../../data/sandbox/anki-note-data-creator.js'; import {PronunciationGenerator} from '../../display/sandbox/pronunciation-generator.js'; import {StructuredContentGenerator} from '../../display/sandbox/structured-content-generator.js'; diff --git a/ext/js/templates/sandbox/template-renderer-media-provider.js b/ext/js/templates/sandbox/template-renderer-media-provider.js index 23f334e1..d8a0a16d 100644 --- a/ext/js/templates/sandbox/template-renderer-media-provider.js +++ b/ext/js/templates/sandbox/template-renderer-media-provider.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from 'handlebars'; +import {Handlebars} from '../../../lib/handlebars.js'; export class TemplateRendererMediaProvider { constructor() { diff --git a/ext/js/templates/sandbox/template-renderer.js b/ext/js/templates/sandbox/template-renderer.js index d4aebd64..fe240b5f 100644 --- a/ext/js/templates/sandbox/template-renderer.js +++ b/ext/js/templates/sandbox/template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from 'handlebars'; +import {Handlebars} from '../../../lib/handlebars.js'; import {ExtensionError} from '../../core/extension-error.js'; export class TemplateRenderer { diff --git a/ext/legal.html b/ext/legal.html index b853f3e8..94912c7e 100644 --- a/ext/legal.html +++ b/ext/legal.html @@ -14,14 +14,6 @@ - diff --git a/ext/offscreen.html b/ext/offscreen.html index cfab53ee..afb7eb44 100644 --- a/ext/offscreen.html +++ b/ext/offscreen.html @@ -12,14 +12,6 @@ - diff --git a/ext/permissions.html b/ext/permissions.html index 7baff14e..61b0d363 100644 --- a/ext/permissions.html +++ b/ext/permissions.html @@ -14,14 +14,6 @@ - diff --git a/ext/popup-preview.html b/ext/popup-preview.html index 7bd54470..15810242 100644 --- a/ext/popup-preview.html +++ b/ext/popup-preview.html @@ -13,14 +13,6 @@ - diff --git a/ext/popup.html b/ext/popup.html index 6bc8d690..30e8a8c0 100644 --- a/ext/popup.html +++ b/ext/popup.html @@ -15,14 +15,6 @@ - diff --git a/ext/search.html b/ext/search.html index 377a966a..8c595cc4 100644 --- a/ext/search.html +++ b/ext/search.html @@ -16,14 +16,6 @@ - diff --git a/ext/settings.html b/ext/settings.html index 276a7222..346cc1d7 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -14,14 +14,6 @@ - diff --git a/ext/template-renderer.html b/ext/template-renderer.html index d1747e99..116f1a0c 100644 --- a/ext/template-renderer.html +++ b/ext/template-renderer.html @@ -11,14 +11,6 @@ - diff --git a/ext/welcome.html b/ext/welcome.html index 355bbc5f..40639881 100644 --- a/ext/welcome.html +++ b/ext/welcome.html @@ -13,14 +13,6 @@ - diff --git a/jsconfig.json b/jsconfig.json index 4f316174..0f780ead 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -12,7 +12,6 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { - "zip.js": ["@zip.js/zip.js"], "*": ["./types/ext/*"] }, "types": [ diff --git a/test/japanese-util.test.js b/test/japanese-util.test.js index ae5b1a16..47da4ccb 100644 --- a/test/japanese-util.test.js +++ b/test/japanese-util.test.js @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; import {expect, test} from 'vitest'; import {TextSourceMap} from '../ext/js/general/text-source-map.js'; import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; +import * as wanakana from '../ext/lib/wanakana.js'; const jp = new JapaneseUtil(wanakana); diff --git a/test/jsconfig.json b/test/jsconfig.json index 934aab81..2461fda9 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -12,7 +12,6 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { - "zip.js": ["@zip.js/zip.js"], "*": ["../types/ext/*"], "dev/*": ["../types/dev/*"], "test/*": ["../types/test/*"] -- cgit v1.2.3 From dcddbee07e20163ae167dd67fe58f0776f9acb64 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 4 Dec 2023 18:20:05 -0500 Subject: Update how ts comments are handled --- .eslintrc.json | 9 ++++++++- dev/translator-vm.js | 2 +- ext/js/accessibility/google-docs.js | 6 +++--- ext/js/app/popup.js | 2 +- ext/js/background/offscreen-proxy.js | 8 ++++---- ext/js/comm/frame-ancestry-handler.js | 4 ++-- ext/js/dom/document-util.js | 14 ++++++------- ext/js/dom/dom-text-scanner.js | 5 ++--- ext/js/dom/simple-dom-parser.js | 3 +-- ext/js/pages/settings/backup-controller.js | 4 ++-- ext/js/yomitan.js | 4 ++-- test/cache-map.test.js | 2 +- test/data/html/test-document2-script.js | 30 ++++++++++++++-------------- test/object-property-accessor.test.js | 6 +++--- test/profile-conditions-util.test.js | 32 +++++++++++++++--------------- vitest.config.js | 2 +- 16 files changed, 69 insertions(+), 64 deletions(-) (limited to 'ext/js/dom') diff --git a/.eslintrc.json b/.eslintrc.json index a46aff4b..83e7ffe6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -350,7 +350,14 @@ "allowAllPropertiesOnSameLine": true } ], - "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-comment": [ + "error", + { + "ts-expect-error": { + "descriptionFormat": "^ - .+$" + } + } + ], "@typescript-eslint/ban-types": [ "error", { diff --git a/dev/translator-vm.js b/dev/translator-vm.js index 4022d465..7fdda879 100644 --- a/dev/translator-vm.js +++ b/dev/translator-vm.js @@ -42,7 +42,7 @@ export class TranslatorVM { } } }; - // @ts-ignore - Overwriting a global + // @ts-expect-error - Overwriting a global global.chrome = chrome; /** @type {?JapaneseUtil} */ diff --git a/ext/js/accessibility/google-docs.js b/ext/js/accessibility/google-docs.js index da6ab994..27841b6d 100644 --- a/ext/js/accessibility/google-docs.js +++ b/ext/js/accessibility/google-docs.js @@ -18,9 +18,9 @@ (async () => { // Reentrant check - // @ts-ignore : Checking a property to the global object + // @ts-expect-error - Checking a property to the global object if (self.googleDocsAccessibilitySetup) { return; } - // @ts-ignore : Adding a property to the global object + // @ts-expect-error - Adding a property to the global object self.googleDocsAccessibilitySetup = true; /** @@ -57,7 +57,7 @@ // The extension ID below is on an allow-list that is used on the Google Docs webpage. /* eslint-disable */ - // @ts-ignore : Adding a property to the global object + // @ts-expect-error : Adding a property to the global object const inject = () => { window._docs_annotate_canvas_by_ext = 'ogmnaimimemjmbakcfefmnahgdfhfami'; }; /* eslint-enable */ diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index 4f201fc3..7419785b 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -745,7 +745,7 @@ export class Popup extends EventDispatcher { if ( fullscreenElement === null || fullscreenElement.shadowRoot || - // @ts-ignore - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions + // @ts-expect-error - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions fullscreenElement.openOrClosedShadowRoot ) { return defaultParent; diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index 7b504855..63f619fa 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -54,16 +54,16 @@ export class OffscreenProxy { */ async _hasOffscreenDocument() { const offscreenUrl = chrome.runtime.getURL('offscreen.html'); - // @ts-ignore - API not defined yet + // @ts-expect-error - API not defined yet if (!chrome.runtime.getContexts) { // chrome version below 116 // Clients: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/clients - // @ts-ignore - Types not set up for service workers yet + // @ts-expect-error - Types not set up for service workers yet const matchedClients = await clients.matchAll(); - // @ts-ignore - Types not set up for service workers yet + // @ts-expect-error - Types not set up for service workers yet return await matchedClients.some((client) => client.url === offscreenUrl); } - // @ts-ignore - API not defined yet + // @ts-expect-error - API not defined yet const contexts = await chrome.runtime.getContexts({ contextTypes: ['OFFSCREEN_DOCUMENT'], documentUrls: [offscreenUrl] diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index 687ec368..e4d08f28 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -295,7 +295,7 @@ export class FrameAncestryHandler { while (walker.nextNode()) { const element = /** @type {Element} */ (walker.currentNode); - // @ts-ignore - this is more simple to elide any type checks or casting + // @ts-expect-error - this is more simple to elide any type checks or casting if (element.contentWindow === contentWindow) { return element; } @@ -303,7 +303,7 @@ export class FrameAncestryHandler { /** @type {?ShadowRoot|undefined} */ const shadowRoot = ( element.shadowRoot || - // @ts-ignore - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions + // @ts-expect-error - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions element.openOrClosedShadowRoot ); if (shadowRoot) { diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index 549a8195..b2aa8f81 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -210,7 +210,7 @@ export class DocumentUtil { */ static computeZoomScale(node) { if (this._cssZoomSupported === null) { - // @ts-ignore - zoom is a non-standard property that exists in Chromium-based browsers + // @ts-expect-error - zoom is a non-standard property that exists in Chromium-based browsers this._cssZoomSupported = (typeof document.createElement('div').style.zoom === 'string'); } if (!this._cssZoomSupported) { return 1; } @@ -387,11 +387,11 @@ export class DocumentUtil { static getFullscreenElement() { return ( document.fullscreenElement || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix document.msFullscreenElement || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix document.mozFullScreenElement || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix document.webkitFullscreenElement || null ); @@ -808,7 +808,7 @@ export class DocumentUtil { return document.caretRangeFromPoint(x, y); } - // @ts-ignore - caretPositionFromPoint is non-standard + // @ts-expect-error - caretPositionFromPoint is non-standard if (typeof document.caretPositionFromPoint === 'function') { // Firefox return this._caretPositionFromPoint(x, y); @@ -824,7 +824,7 @@ export class DocumentUtil { * @returns {?Range} */ static _caretPositionFromPoint(x, y) { - // @ts-ignore - caretPositionFromPoint is non-standard + // @ts-expect-error - caretPositionFromPoint is non-standard const position = /** @type {(x: number, y: number) => ?{offsetNode: Node, offset: number}} */ (document.caretPositionFromPoint)(x, y); if (position === null) { return null; @@ -876,7 +876,7 @@ export class DocumentUtil { nextElement.style.setProperty('user-select', 'text', 'important'); } - // @ts-ignore - caretPositionFromPoint is non-standard + // @ts-expect-error - caretPositionFromPoint is non-standard const position = /** @type {(x: number, y: number) => ?{offsetNode: Node, offset: number}} */ (document.caretPositionFromPoint)(x, y); if (position === null) { return null; diff --git a/ext/js/dom/dom-text-scanner.js b/ext/js/dom/dom-text-scanner.js index 3a785680..42e0acc9 100644 --- a/ext/js/dom/dom-text-scanner.js +++ b/ext/js/dom/dom-text-scanner.js @@ -520,11 +520,10 @@ export class DOMTextScanner { static isStyleSelectable(style) { return !( style.userSelect === 'none' || - // @ts-ignore - vendor prefix style.webkitUserSelect === 'none' || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix style.MozUserSelect === 'none' || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix style.msUserSelect === 'none' ); } diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index a1f63890..bca1cd88 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -27,7 +27,7 @@ export class SimpleDOMParser { */ constructor(content) { /** @type {import('parse5')} */ - // @ts-ignore - parse5 global is not defined in typescript declaration + // @ts-expect-error - parse5 global is not defined in typescript declaration this._parse5Lib = /** @type {import('parse5')} */ (parse5); /** @type {import('parse5').TreeAdapter} */ this._treeAdapter = this._parse5Lib.defaultTreeAdapter; @@ -131,7 +131,6 @@ export class SimpleDOMParser { * @returns {boolean} */ static isSupported() { - // @ts-ignore - parse5 global is not defined in typescript declaration return typeof parse5 !== 'undefined'; } diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 52c5f418..bf44bb90 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -165,9 +165,9 @@ export class BackupController { _saveBlob(blob, fileName) { if ( typeof navigator === 'object' && navigator !== null && - // @ts-ignore - call for legacy Edge + // @ts-expect-error - call for legacy Edge typeof navigator.msSaveBlob === 'function' && - // @ts-ignore - call for legacy Edge + // @ts-expect-error - call for legacy Edge navigator.msSaveBlob(blob) ) { return; diff --git a/ext/js/yomitan.js b/ext/js/yomitan.js index 3c0f7cb9..7cf67aec 100644 --- a/ext/js/yomitan.js +++ b/ext/js/yomitan.js @@ -37,7 +37,7 @@ if ((() => { } return (hasBrowser && !hasChrome); })()) { - // @ts-ignore - objects should have roughly the same interface + // @ts-expect-error - objects should have roughly the same interface chrome = browser; } @@ -182,7 +182,7 @@ export class Yomitan extends EventDispatcher { */ sendMessage(...args) { try { - // @ts-ignore - issue with type conversion, somewhat difficult to resolve in pure JS + // @ts-expect-error - issue with type conversion, somewhat difficult to resolve in pure JS chrome.runtime.sendMessage(...args); } catch (e) { this.triggerExtensionUnloaded(); diff --git a/test/cache-map.test.js b/test/cache-map.test.js index 3e7def1f..df891188 100644 --- a/test/cache-map.test.js +++ b/test/cache-map.test.js @@ -30,7 +30,7 @@ function testConstructor() { [true, () => new CacheMap(1.5)], [true, () => new CacheMap(Number.NaN)], [true, () => new CacheMap(Number.POSITIVE_INFINITY)], - // @ts-ignore - Ignore because it should throw an error + // @ts-expect-error - Ignore because it should throw an error [true, () => new CacheMap('a')] ]; diff --git a/test/data/html/test-document2-script.js b/test/data/html/test-document2-script.js index 5a6ad4d1..f6082802 100644 --- a/test/data/html/test-document2-script.js +++ b/test/data/html/test-document2-script.js @@ -22,17 +22,17 @@ function requestFullscreen(element) { if (element.requestFullscreen) { element.requestFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (element.mozRequestFullScreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility element.mozRequestFullScreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (element.webkitRequestFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility element.webkitRequestFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (element.msRequestFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility element.msRequestFullscreen(); } } @@ -41,17 +41,17 @@ function requestFullscreen(element) { function exitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (document.mozCancelFullScreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.mozCancelFullScreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (document.webkitExitFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.webkitExitFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (document.msExitFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.msExitFullscreen(); } } @@ -62,11 +62,11 @@ function exitFullscreen() { function getFullscreenElement() { return ( document.fullscreenElement || - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.msFullscreenElement || - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.mozFullScreenElement || - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.webkitFullscreenElement || null ); diff --git a/test/object-property-accessor.test.js b/test/object-property-accessor.test.js index 4f1fa87b..4d50b1e9 100644 --- a/test/object-property-accessor.test.js +++ b/test/object-property-accessor.test.js @@ -334,7 +334,7 @@ function testGetPathString2() { ]; for (const [pathArray, message] of data) { - // @ts-ignore - Throwing is expected + // @ts-expect-error - Throwing is expected expect(() => ObjectPropertyAccessor.getPathString(pathArray)).toThrow(message); } }); @@ -424,7 +424,7 @@ function testHasProperty() { ]; for (const [object, property, expected] of data) { - // @ts-ignore - Ignore potentially property types + // @ts-expect-error - Ignore potentially property types expect(ObjectPropertyAccessor.hasProperty(object, property)).toStrictEqual(expected); } }); @@ -449,7 +449,7 @@ function testIsValidPropertyType() { ]; for (const [object, property, expected] of data) { - // @ts-ignore - Ignore potentially property types + // @ts-expect-error - Ignore potentially property types expect(ObjectPropertyAccessor.isValidPropertyType(object, property)).toStrictEqual(expected); } }); diff --git a/test/profile-conditions-util.test.js b/test/profile-conditions-util.test.js index 30052b34..62b21555 100644 --- a/test/profile-conditions-util.test.js +++ b/test/profile-conditions-util.test.js @@ -637,9 +637,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -673,9 +673,9 @@ function testSchemas() { {expected: false, context: {depth: 0, url: ''}}, {expected: false, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -711,9 +711,9 @@ function testSchemas() { {expected: false, context: {depth: 0, url: ''}}, {expected: false, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -753,9 +753,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -784,9 +784,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -819,9 +819,9 @@ function testSchemas() { {expected: false, context: {depth: 0, url: ''}}, {expected: false, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -849,9 +849,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -885,9 +885,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, diff --git a/vitest.config.js b/vitest.config.js index 3b6cdde0..025eec17 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -24,7 +24,7 @@ export default defineConfig({ 'test/playwright/**' ], environment: 'jsdom', - // @ts-ignore - Appears to not be defined in the type definitions (https://vitest.dev/advanced/pool) + // @ts-expect-error - Appears to not be defined in the type definitions (https://vitest.dev/advanced/pool) poolOptions: { threads: { useAtomics: true -- cgit v1.2.3