diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2022-09-26 19:37:14 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-09-26 19:37:14 -0400 | 
| commit | be7855bad2e3b452ca0700246a376f107a75e79e (patch) | |
| tree | df73805af15c5bd98a370ccbded679125d98e46d /ext/js | |
| parent | 38c3d1b98c269739b7be3b3c8b33a58a65af5e52 (diff) | |
More API documentation (#2238)
* Document TextSourceRange
* Document TextSourceElement
* Document DocumentUtil
* Document DocumentFocusController
Diffstat (limited to 'ext/js')
| -rw-r--r-- | ext/js/dom/document-focus-controller.js | 12 | ||||
| -rw-r--r-- | ext/js/dom/document-util.js | 92 | ||||
| -rw-r--r-- | ext/js/dom/text-source-element.js | 88 | ||||
| -rw-r--r-- | ext/js/dom/text-source-range.js | 110 | 
4 files changed, 302 insertions, 0 deletions
| diff --git a/ext/js/dom/document-focus-controller.js b/ext/js/dom/document-focus-controller.js index bc6c61b9..d6c24646 100644 --- a/ext/js/dom/document-focus-controller.js +++ b/ext/js/dom/document-focus-controller.js @@ -22,11 +22,19 @@   * focus a dummy element inside the main content, which gives keyboard scroll focus to that element.   */  class DocumentFocusController { +    /** +     * Creates a new instance of the class. +     * @param {?string} autofocusElementSelector A selector string which can be used to specify an element which +     *   should be automatically focused on prepare. +     */      constructor(autofocusElementSelector=null) {          this._autofocusElement = (autofocusElementSelector !== null ? document.querySelector(autofocusElementSelector) : null);          this._contentScrollFocusElement = document.querySelector('#content-scroll-focus');      } +    /** +     * Initializes the instance. +     */      prepare() {          window.addEventListener('focus', this._onWindowFocus.bind(this), false);          this._updateFocusedElement(false); @@ -35,6 +43,10 @@ class DocumentFocusController {          }      } +    /** +     * Removes focus from a given element. +     * @param {Element} element The element to remove focus from. +     */      blurElement(element) {          if (document.activeElement !== element) { return; }          element.blur(); diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index 73525ff5..ed0a98e7 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -21,6 +21,9 @@   * TextSourceRange   */ +/** + * This class contains utility functions related to the HTML document. + */  class DocumentUtil {      /**       * Options to configure how element detection is performed. @@ -251,11 +254,23 @@ class DocumentUtil {          return scale;      } +    /** +     * Converts a rect based on the CSS zoom scaling for a given node. +     * @param {DOMRect} rect The rect to convert. +     * @param {Node} node The node to compute the zoom from. +     * @returns {DOMRect} The updated rect, or the same rect if no change is needed. +     */      static convertRectZoomCoordinates(rect, node) {          const scale = this.computeZoomScale(node);          return (scale === 1 ? rect : new DOMRect(rect.left * scale, rect.top * scale, rect.width * scale, rect.height * scale));      } +    /** +     * Converts multiple rects based on the CSS zoom scaling for a given node. +     * @param {DOMRect[]} 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; } @@ -266,6 +281,13 @@ class DocumentUtil {          return results;      } +    /** +     * Checks whether a given point is contained within a rect. +     * @param {number} x The horizontal coordinate. +     * @param {number} y The vertical coordinate. +     * @param {DOMRect} rect The rect to check. +     * @returns {boolean} `true` if the point is inside the rect, `false` otherwise. +     */      static isPointInRect(x, y, rect) {          return (              x >= rect.left && x < rect.right && @@ -273,6 +295,13 @@ 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. +     * @returns {boolean} `true` if the point is inside any of the rects, `false` otherwise. +     */      static isPointInAnyRect(x, y, rects) {          for (const rect of rects) {              if (this.isPointInRect(x, y, rect)) { @@ -282,6 +311,13 @@ class DocumentUtil {          return false;      } +    /** +     * Checks whether a given point is contained within a selection range. +     * @param {number} x The horizontal coordinate. +     * @param {number} y The vertical coordinate. +     * @param {Selection} selection The selection to check. +     * @returns {boolean} `true` if the point is inside the selection, `false` otherwise. +     */      static isPointInSelection(x, y, selection) {          for (let i = 0; i < selection.rangeCount; ++i) {              const range = selection.getRangeAt(i); @@ -302,6 +338,11 @@ 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. +     */      static getActiveModifiers(event) {          const modifiers = [];          if (event.altKey) { modifiers.push('alt'); } @@ -311,18 +352,33 @@ class DocumentUtil {          return modifiers;      } +    /** +     * 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. +     */      static getActiveModifiersAndButtons(event) {          const modifiers = this.getActiveModifiers(event);          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. +     */      static getActiveButtons(event) {          const buttons = [];          this._getActiveButtons(event, buttons);          return buttons;      } +    /** +     * Adds a fullscreen change event listener. This function handles all of the browser-specific variants. +     * @param {Function} onFullscreenChanged The event callback. +     * @param {?EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. +     */      static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection=null) {          const target = document;          const options = false; @@ -341,6 +397,10 @@ class DocumentUtil {          }      } +    /** +     * Returns the current fullscreen element. This function handles all of the browser-specific variants. +     * @returns {?Element} The current fullscreen element, or `null` if the window is not fullscreen. +     */      static getFullscreenElement() {          return (              document.fullscreenElement || @@ -351,6 +411,11 @@ class DocumentUtil {          );      } +    /** +     * Gets all of the nodes within a `Range`. +     * @param {Range} range The range to check. +     * @returns {Node[]} The list of nodes. +     */      static getNodesInRange(range) {          const end = range.endContainer;          const nodes = []; @@ -361,6 +426,11 @@ class DocumentUtil {          return nodes;      } +    /** +     * Gets the next node after a specified node. This traverses the DOM in its logical order. +     * @param {Node} node The node to start at. +     * @returns {?Node} The next node, or `null` if there is no next node. +     */      static getNextNode(node) {          let next = node.firstChild;          if (next === null) { @@ -377,6 +447,12 @@ class DocumentUtil {          return next;      } +    /** +     * Checks whether any node in a list of nodes matches a selector. +     * @param {Node[]} nodes The list of ndoes to check. +     * @param {string} selector The selector to test. +     * @returns {boolean} `true` if any element node matches the selector, `false` otherwise. +     */      static anyNodeMatchesSelector(nodes, selector) {          const ELEMENT_NODE = Node.ELEMENT_NODE;          for (let node of nodes) { @@ -389,6 +465,12 @@ class DocumentUtil {          return false;      } +    /** +     * Checks whether every node in a list of nodes matches a selector. +     * @param {Node[]} nodes The list of ndoes to check. +     * @param {string} selector The selector to test. +     * @returns {boolean} `true` if every element node matches the selector, `false` otherwise. +     */      static everyNodeMatchesSelector(nodes, selector) {          const ELEMENT_NODE = Node.ELEMENT_NODE;          for (let node of nodes) { @@ -401,10 +483,20 @@ class DocumentUtil {          return true;      } +    /** +     * Checks whether the meta key is supported in the browser on the specified operating system. +     * @param {string} os The operating system to check. +     * @param {string} browser The browser to check. +     * @returns {boolean} `true` if supported, `false` otherwise. +     */      static isMetaKeySupported(os, browser) {          return !(browser === 'firefox' || browser === 'firefox-mobile') || os === 'mac';      } +    /** +     * Checks whether an element on the page that can accept input is focused. +     * @returns {boolean} `true` if an input element is focused, `false` otherwise. +     */      static isInputElementFocused() {          const element = document.activeElement;          if (element === null) { return false; } diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index 13c5cd86..1c60a5b8 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -20,7 +20,18 @@   * StringUtil   */ +/** + * This class represents a text source that is attached to a HTML element, such as an <img> + * with alt text or a <button>. + */  class TextSourceElement { +    /** +     * Creates a new instance of the class. +     * @param {Element} element The source element. +     * @param {string} fullContent The string representing the element's full text value. +     * @param {number} startOffset The text start offset position within the full content. +     * @param {number} endOffset The text end offset position within the full content. +     */      constructor(element, fullContent, startOffset, endOffset) {          this._element = element;          this._fullContent = fullContent; @@ -29,38 +40,76 @@ class TextSourceElement {          this._content = this._fullContent.substring(this._startOffset, this._endOffset);      } +    /** +     * Gets the type name of this instance. +     * @type {string} +     */      get type() {          return 'element';      } +    /** +     * The source element. +     * @type {Element} +     */      get element() {          return this._element;      } +    /** +     * The string representing the element's full text value. +     * @type {string} +     */      get fullContent() {          return this._fullContent;      } +    /** +     * The text start offset position within the full content. +     * @type {number} +     */      get startOffset() {          return this._startOffset;      } +    /** +     * The text end offset position within the full content. +     * @type {number} +     */      get endOffset() {          return this._endOffset;      } +    /** +     * Creates a clone of the instance. +     * @returns {TextSourceElement} The new clone. +     */      clone() {          return new TextSourceElement(this._element, this._fullContent, this._startOffset, this._endOffset);      } +    /** +     * Performs any cleanup that is necessary after the element has been used. +     */      cleanup() {          // NOP      } +    /** +     * Gets the selected text of element, which is a substring of the full content +     * starting at `startOffset` and ending at `endOffset`. +     * @returns {string} The text content. +     */      text() {          return this._content;      } +    /** +     * Moves the end offset of the text by a set amount of unicode codepoints. +     * @param {number} length The maximum number of codepoints to move by. +     * @param {boolean} fromEnd Whether to move the offset from the current end position (if `true`) or the start position (if `false`). +     * @returns {number} The actual number of characters (not codepoints) that were read. +     */      setEndOffset(length, fromEnd) {          const offset = fromEnd ? this._endOffset : this._startOffset;          length = Math.min(this._fullContent.length - offset, length); @@ -72,6 +121,11 @@ class TextSourceElement {          return length;      } +    /** +     * Moves the start offset of the text by a set amount of unicode codepoints. +     * @param {number} length The maximum number of codepoints to move by. +     * @returns {number} The actual number of characters (not codepoints) that were read. +     */      setStartOffset(length) {          length = Math.min(this._startOffset, length);          if (length > 0) { @@ -82,22 +136,42 @@ class TextSourceElement {          return length;      } +    /** +     * Gets the rects that represent the position and bounds of the text source. +     * @returns {DOMRect[]} The rects. +     */      getRects() {          return DocumentUtil.convertMultipleRectZoomCoordinates(this._element.getClientRects(), this._element);      } +    /** +     * 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. +     */      getWritingMode() {          return 'horizontal-tb';      } +    /** +     * Selects the text source in the document. +     */      select() {          // NOP      } +    /** +     * Deselects the text source in the document. +     */      deselect() {          // NOP      } +    /** +     * Checks whether another text source has the same starting point. +     * @param {TextSourceElement|TextSourceRange} other The other source to test. +     * @returns {boolean} `true` if the starting points are equivalent, `false` otherwise. +     */      hasSameStart(other) {          return (              typeof other === 'object' && @@ -109,14 +183,28 @@ class TextSourceElement {          );      } +    /** +     * Gets a list of the nodes in this text source's range. +     * @returns {Node[]} The nodes in the range. +     */      getNodesInRange() {          return [this._element];      } +    /** +     * Creates a new instance for a given element. +     * @param {Element} element The source element. +     * @returns {TextSourceElement} A new instance of the class corresponding to the element. +     */      static create(element) {          return new TextSourceElement(element, this._getElementContent(element), 0, 0);      } +    /** +     * Gets the full content string for a given element. +     * @param {Element} element The element to get the full content of. +     * @returns {string} The content string. +     */      static _getElementContent(element) {          let content;          switch (element.nodeName.toUpperCase()) { diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index e03783a5..a0225748 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -20,7 +20,29 @@   * DocumentUtil   */ +/** + * This class represents a text source that comes from text nodes in the document. + * Sometimes a temporary "imposter" element is created and used to store the text. + * This element is typically hidden from the page and removed after scanning has completed. + */  class TextSourceRange { +    /** +     * Creates a new instance of the class. +     * @param {Range} range The selection range. +     * @param {number} rangeStartOffset The `startOffset` of the range. This is somewhat redundant +     *   with the `range` parameter, but it is used when for when imposter elements are removed. +     * @param {string} content The `toString()` value of the range. This is somewhat redundant +     *   with the `range` parameter, but it is used when for when imposter elements are removed. +     * @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, +     *   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`, +     *   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) {          this._range = range;          this._rangeStartOffset = rangeStartOffset; @@ -31,22 +53,42 @@ class TextSourceRange {          this._cachedSourceRect = cachedSourceRect;      } +    /** +     * Gets the type name of this instance. +     * @type {string} +     */      get type() {          return 'range';      } +    /** +     * The internal range object. +     * @type {Range} +     */      get range() {          return this._range;      } +    /** +     * The starting offset for the range. +     * @type {Range} +     */      get rangeStartOffset() {          return this._rangeStartOffset;      } +    /** +     * The source element that the imposter element is imitating, if present. +     * @type {?Element} +     */      get imposterSourceElement() {          return this._imposterSourceElement;      } +    /** +     * Creates a clone of the instance. +     * @returns {TextSourceRange} The new clone. +     */      clone() {          return new TextSourceRange(              this._range.cloneRange(), @@ -59,16 +101,31 @@ class TextSourceRange {          );      } +    /** +     * Performs any cleanup that is necessary after the element has been used. +     */      cleanup() {          if (this._imposterElement !== null && this._imposterElement.parentNode !== null) {              this._imposterElement.parentNode.removeChild(this._imposterElement);          }      } +    /** +     * Gets the selected text of element, which is the `toString()` version of the range. +     * @returns {string} The text content. +     */      text() {          return this._content;      } +    /** +     * Moves the end offset of the text by a set amount of unicode codepoints. +     * @param {number} length The maximum number of codepoints to move by. +     * @param {boolean} fromEnd Whether to move the offset from the current end position (if `true`) or the start position (if `false`). +     * @param {boolean} layoutAwareScan Whether or not HTML layout information should be used to generate +     *   the string content when scanning. +     * @returns {number} The actual number of characters (not codepoints) that were read. +     */      setEndOffset(length, fromEnd, layoutAwareScan) {          let node;          let offset; @@ -85,6 +142,13 @@ class TextSourceRange {          return length - state.remainder;      } +    /** +     * Moves the start offset of the text by a set amount of unicode codepoints. +     * @param {number} length The maximum number of codepoints to move by. +     * @param {boolean} layoutAwareScan Whether or not HTML layout information should be used to generate +     *   the string content when scanning. +     * @returns {number} The actual number of characters (not codepoints) that were read. +     */      setStartOffset(length, layoutAwareScan) {          const state = new DOMTextScanner(this._range.startContainer, this._range.startOffset, !layoutAwareScan, layoutAwareScan).seek(-length);          this._range.setStart(state.node, state.offset); @@ -93,16 +157,28 @@ class TextSourceRange {          return length - state.remainder;      } +    /** +     * Gets the rects that represent the position and bounds of the text source. +     * @returns {DOMRect[]} The rects. +     */      getRects() {          if (this._isImposterDisconnected()) { return this._getCachedRects(); }          return DocumentUtil.convertMultipleRectZoomCoordinates(this._range.getClientRects(), this._range.startContainer);      } +    /** +     * 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. +     */      getWritingMode() {          const node = this._isImposterDisconnected() ? this._imposterSourceElement : this._range.startContainer;          return DocumentUtil.getElementWritingMode(node !== null ? node.parentElement : null);      } +    /** +     * Selects the text source in the document. +     */      select() {          if (this._imposterElement !== null) { return; }          const selection = window.getSelection(); @@ -110,12 +186,22 @@ class TextSourceRange {          selection.addRange(this._range);      } +    /** +     * Deselects the text source in the document. +     */      deselect() {          if (this._imposterElement !== null) { return; }          const selection = window.getSelection();          selection.removeAllRanges();      } +    /** +     * Checks whether another text source has the same starting point. +     * @param {TextSourceElement|TextSourceRange} 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. +     */      hasSameStart(other) {          if (!(              typeof other === 'object' && @@ -142,24 +228,48 @@ class TextSourceRange {          }      } +    /** +     * Gets a list of the nodes in this text source's range. +     * @returns {Node[]} The nodes in the range. +     */      getNodesInRange() {          return DocumentUtil.getNodesInRange(this._range);      } +    /** +     * Creates a new instance for a given range. +     * @param {Range} range The source range. +     * @returns {TextSourceRange} A new instance of the class corresponding to the range. +     */      static create(range) {          return new TextSourceRange(range, range.startOffset, range.toString(), null, null, null, null);      } +    /** +     * Creates a new instance for a given range using an imposter element. +     * @param {Range} range The source range. +     * @param {Element} imposterElement The temporary imposter element. +     * @param {Element} imposterSourceElement The source element which the imposter is imitating. +     * @returns {TextSourceRange} A new instance of the class corresponding to the range. +     */      static createFromImposter(range, imposterElement, imposterSourceElement) {          const cachedRects = DocumentUtil.convertMultipleRectZoomCoordinates(range.getClientRects(), range.startContainer);          const cachedSourceRect = DocumentUtil.convertRectZoomCoordinates(imposterSourceElement.getBoundingClientRect(), imposterSourceElement);          return new TextSourceRange(range, range.startOffset, range.toString(), imposterElement, imposterSourceElement, cachedRects, cachedSourceRect);      } +    /** +     * Checks whether the imposter element has been removed, if the instance is using one. +     * @returns {boolean} `true` if the instance has an imposter and it's no longer connected to the document, `false` otherwise. +     */      _isImposterDisconnected() {          return this._imposterElement !== null && !this._imposterElement.isConnected;      } +    /** +     * Gets the cached rects for a disconnected imposter element. +     * @returns {Rect[]} The rects for the element. +     */      _getCachedRects() {          const sourceRect = DocumentUtil.convertRectZoomCoordinates(this._imposterSourceElement.getBoundingClientRect(), this._imposterSourceElement);          return DocumentUtil.offsetDOMRects( |