tor-browser

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

accessibleTextarea.js (5743B)


      1 /*
      2 * Copyright (C) 2012 Google Inc. All rights reserved.
      3 *
      4 * Redistribution and use in source and binary forms, with or without
      5 * modification, are permitted provided that the following conditions are
      6 * met:
      7 *
      8 *     * Redistributions of source code must retain the above copyright
      9 * notice, this list of conditions and the following disclaimer.
     10 *     * Redistributions in binary form must reproduce the above
     11 * copyright notice, this list of conditions and the following disclaimer
     12 * in the documentation and/or other materials provided with the
     13 * distribution.
     14 *     * Neither the name of Google Inc. nor the names of its
     15 * contributors may be used to endorse or promote products derived from
     16 * this software without specific prior written permission.
     17 *
     18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 */
     30 (function(mod) {
     31  mod(require("resource://devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js"));
     32 })(function(CodeMirror) {
     33  // CodeMirror uses an offscreen <textarea> to detect input.
     34  // Due to inconsistencies in the many browsers it supports, it simplifies things by
     35  // regularly checking if something is in the textarea, adding those characters to the
     36  // document, and then clearing the textarea.
     37  // This breaks assistive technology that wants to read from CodeMirror, because the
     38  // <textarea> that they interact with is constantly empty.
     39  // Because we target up-to-date Firefox, we can guarantee consistent input events.
     40  // This lets us leave the current line from the editor in our <textarea>.
     41  // CodeMirror still expects a mostly empty <textarea>, so we pass CodeMirror a fake
     42  // <textarea> that only contains the users input.
     43  CodeMirror.inputStyles.accessibleTextArea = class extends CodeMirror.inputStyles.textarea {
     44    /**
     45     * @override
     46     * @param {!Object} display
     47     */
     48    init(display) {
     49      super.init(display);
     50      this.textarea.addEventListener("compositionstart",
     51        this._onCompositionStart.bind(this));
     52    }
     53 
     54    _onCompositionStart() {
     55      if (this.textarea.selectionEnd === this.textarea.value.length) {
     56        return;
     57      }
     58 
     59      // CodeMirror always expects the caret to be at the end of the textarea
     60      // When in IME composition mode, clip the textarea to how CodeMirror expects it,
     61      // and then let CodeMirror do its thing.
     62      this.textarea.value = this.textarea.value.substring(0, this.textarea.selectionEnd);
     63      const length = this.textarea.value.length;
     64      this.textarea.setSelectionRange(length, length);
     65      this.prevInput = this.textarea.value;
     66    }
     67 
     68    /**
     69     * @override
     70     * @param {Boolean} typing
     71     */
     72    reset(typing) {
     73      if (
     74        typing ||
     75        this.contextMenuPending ||
     76        this.composing ||
     77        this.cm.somethingSelected()
     78      ) {
     79        super.reset(typing);
     80        return;
     81      }
     82 
     83      // When navigating around the document, keep the current visual line in the textarea.
     84      const cursor = this.cm.getCursor();
     85      let start, end;
     86      if (this.cm.options.lineWrapping) {
     87        // To get the visual line, compute the leftmost and rightmost character positions.
     88        const top = this.cm.charCoords(cursor, "page").top;
     89        start = this.cm.coordsChar({left: -Infinity, top});
     90        end = this.cm.coordsChar({left: Infinity, top});
     91      } else {
     92        // Limit the line to 1000 characters to prevent lag.
     93        const offset = Math.floor(cursor.ch / 1000) * 1000;
     94        start = {ch: offset, line: cursor.line};
     95        end = {ch: offset + 1000, line: cursor.line};
     96      }
     97 
     98      this.textarea.value = this.cm.getRange(start, end);
     99      const caretPosition = cursor.ch - start.ch;
    100      this.textarea.setSelectionRange(caretPosition, caretPosition);
    101      this.prevInput = this.textarea.value;
    102    }
    103 
    104    /**
    105     * @override
    106     * @return {boolean}
    107     */
    108    poll() {
    109      if (this.contextMenuPending || this.composing) {
    110        return super.poll();
    111      }
    112 
    113      const text = this.textarea.value;
    114      let start = 0;
    115      const length = Math.min(this.prevInput.length, text.length);
    116 
    117      while (start < length && this.prevInput[start] === text[start]) {
    118        ++start;
    119      }
    120 
    121      let end = 0;
    122 
    123      while (
    124        end < length - start &&
    125        this.prevInput[this.prevInput.length - end - 1] === text[text.length - end - 1]
    126      ) {
    127        ++end;
    128      }
    129 
    130      // CodeMirror expects the user to be typing into a blank <textarea>.
    131      // Pass a fake textarea into super.poll that only contains the users input.
    132      const placeholder = this.textarea;
    133      this.textarea = document.createElement("textarea");
    134      this.textarea.value = text.substring(start, text.length - end);
    135      this.textarea.setSelectionRange(
    136        placeholder.selectionStart - start,
    137        placeholder.selectionEnd - start
    138      );
    139      this.prevInput = "";
    140      const result = super.poll();
    141      this.prevInput = text;
    142      this.textarea = placeholder;
    143      return result;
    144    }
    145  };
    146 });