tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

selections.js (2734B)


      1 /**
      2 * Replaces the current selection (if any) with a new range, after
      3 * it’s configured by the given function.
      4 *
      5 * See also: selectNodeContents
      6 * Example:
      7 *
      8 * selectRangeWith(range => {
      9 *     range.selectNodeContents(foo);
     10 *     range.setStart(foo.childNodes[0], 3);
     11 *     range.setEnd(foo.childNodes[0], 5);
     12 * });
     13 */
     14 function selectRangeWith(fun) {
     15    const selection = getSelection();
     16 
     17    // Deselect any ranges that happen to be selected, to prevent the
     18    // Selection#addRange call from ignoring our new range (see
     19    // <https://www.chromestatus.com/feature/6680566019653632> for
     20    // more details).
     21    selection.removeAllRanges();
     22 
     23    // Create and configure a range.
     24    const range = document.createRange();
     25    fun(range);
     26 
     27    // Select our new range.
     28    selection.addRange(range);
     29 }
     30 
     31 /**
     32 * Replaces the current selection (if any) with a new range, spanning
     33 * the contents of the given node.
     34 */
     35 function selectNodeContents(node) {
     36    const previousActive = document.activeElement;
     37 
     38    selectRangeWith(range => range.selectNodeContents(node));
     39 
     40    // If the selection update causes the node or an ancestor to be
     41    // focused (Chromium 80+), unfocus it, to avoid any focus-related
     42    // styling such as outlines.
     43    if (document.activeElement != previousActive) {
     44        document.activeElement.blur();
     45    }
     46 }
     47 
     48 /**
     49 * Tries to convince a UA with lazy spellcheck to check and highlight
     50 * the contents of the given nodes (form fields or @contenteditables).
     51 *
     52 * Each node is focused then immediately unfocused. Both focus and
     53 * selection can be used for this purpose, but only focus works for
     54 * @contenteditables.
     55 */
     56 function trySpellcheck(...nodes) {
     57    // This is inherently a flaky test risk, but Chromium (as of 87)
     58    // seems to cancel spellcheck on a node if it wasn’t the last one
     59    // focused for “long enough” (though immediate unfocus is ok).
     60    // Using requestAnimationFrame or setInterval(0) are usually not
     61    // long enough (see <https://bucket.daz.cat/work/igalia/0/0.html>
     62    // under “trySpellcheck strategy” for an example).
     63    const interval = setInterval(() => {
     64        if (nodes.length > 0) {
     65            const node = nodes.shift();
     66            node.focus();
     67            node.blur();
     68        } else {
     69            clearInterval(interval);
     70        }
     71    }, 250);
     72 }
     73 
     74 function createRangeForTextOnly(element, start, end) {
     75    const textNode = element.firstChild;
     76    if (element.childNodes.length != 1 || textNode.nodeName != '#text') {
     77        throw new Error('element must contain a single #text node only');
     78    }
     79    const range = document.createRange();
     80    range.setStart(textNode, start);
     81    range.setEnd(textNode, end);
     82    return range;
     83 }