tor-browser

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

utils.js (6470B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 loader.lazyRequireGetter(
      8  this,
      9  "KeyCodes",
     10  "resource://devtools/client/shared/keycodes.js",
     11  true
     12 );
     13 loader.lazyRequireGetter(
     14  this,
     15  "InspectorCSSParserWrapper",
     16  "resource://devtools/shared/css/lexer.js",
     17  true
     18 );
     19 loader.lazyRequireGetter(
     20  this,
     21  "parseDeclarations",
     22  "resource://devtools/shared/css/parsing-utils.js",
     23  true
     24 );
     25 
     26 const HTML_NS = "http://www.w3.org/1999/xhtml";
     27 
     28 /**
     29 * Called when a character is typed in a value editor.  This decides
     30 * whether to advance or not, first by checking to see if ";" was
     31 * typed, and then by lexing the input and seeing whether the ";"
     32 * would be a terminator at this point.
     33 *
     34 * @param {number} keyCode
     35 *        Key code to be checked.
     36 * @param {string} aValue
     37 *        Current text editor value.
     38 * @param {number} insertionPoint
     39 *        The index of the insertion point.
     40 * @return {boolean} True if the focus should advance; false if
     41 *        the character should be inserted.
     42 */
     43 function advanceValidate(keyCode, value, insertionPoint) {
     44  // Only ";" has special handling here.
     45  if (keyCode !== KeyCodes.DOM_VK_SEMICOLON) {
     46    return false;
     47  }
     48 
     49  // Insert the character provisionally and see what happens.  If we
     50  // end up with a ";" symbol token, then the semicolon terminates the
     51  // value.  Otherwise it's been inserted in some spot where it has a
     52  // valid meaning, like a comment or string.
     53  value = value.slice(0, insertionPoint) + ";" + value.slice(insertionPoint);
     54  const lexer = new InspectorCSSParserWrapper(value);
     55  while (true) {
     56    const token = lexer.nextToken();
     57    if (token.endOffset > insertionPoint) {
     58      if (token.tokenType === "Semicolon") {
     59        // The ";" is a terminator.
     60        return true;
     61      }
     62      // The ";" is not a terminator in this context.
     63      break;
     64    }
     65  }
     66  return false;
     67 }
     68 
     69 /**
     70 * Append a text node to an element.
     71 *
     72 * @param {Element} parent
     73 *        The parent node.
     74 * @param {string} text
     75 *        The text content for the text node.
     76 */
     77 function appendText(parent, text) {
     78  parent.appendChild(parent.ownerDocument.createTextNode(text));
     79 }
     80 
     81 /**
     82 * Event handler that causes a blur on the target if the input has
     83 * multiple CSS properties as the value.
     84 */
     85 function blurOnMultipleProperties(cssProperties) {
     86  return e => {
     87    setTimeout(() => {
     88      const props = parseDeclarations(cssProperties.isKnown, e.target.value);
     89      if (props.length > 1) {
     90        e.target.blur();
     91      }
     92    }, 0);
     93  };
     94 }
     95 
     96 /**
     97 * Create a child element with a set of attributes.
     98 *
     99 * @param {Element} parent
    100 *        The parent node.
    101 * @param {string} tagName
    102 *        The tag name.
    103 * @param {object} attributes
    104 *        A set of attributes to set on the node.
    105 */
    106 function createChild(parent, tagName, attributes = {}) {
    107  const elt = parent.ownerDocument.createElementNS(HTML_NS, tagName);
    108  for (const attr in attributes) {
    109    if (attributes.hasOwnProperty(attr)) {
    110      if (attr === "textContent") {
    111        elt.textContent = attributes[attr];
    112      } else if (attr === "child") {
    113        elt.appendChild(attributes[attr]);
    114      } else {
    115        elt.setAttribute(attr, attributes[attr]);
    116      }
    117    }
    118  }
    119  parent.appendChild(elt);
    120  return elt;
    121 }
    122 
    123 /**
    124 * Retrieve the content of a longString (via a promise resolving a LongStringActor).
    125 *
    126 * @param  {Promise} longStringActorPromise
    127 *         promise expected to resolve a LongStringActor instance
    128 * @return {Promise} promise resolving with the retrieved string as argument
    129 */
    130 async function getLongString(longStringActorPromise) {
    131  try {
    132    const longStringActor = await longStringActorPromise;
    133    const string = await longStringActor.string();
    134    longStringActor.release().catch(console.error);
    135    return string;
    136  } catch (e) {
    137    console.error(e);
    138    return undefined;
    139  }
    140 }
    141 
    142 /**
    143 * Returns a selector of the Element Rep from the grip. This is based on the
    144 * getElements() function in our devtools-reps component for a ElementNode.
    145 *
    146 * @param  {object} grip
    147 *         Grip-like object that can be used with Reps.
    148 * @return {string} selector of the element node.
    149 */
    150 function getSelectorFromGrip(grip) {
    151  const { attributes, displayName } = grip.preview;
    152  let selector = displayName;
    153 
    154  if (attributes.id) {
    155    selector += `#${attributes.id}`;
    156  }
    157 
    158  if (attributes.class) {
    159    selector += attributes.class
    160      .trim()
    161      .split(/\s+/)
    162      .map(cls => `.${cls}`)
    163      .join("");
    164  }
    165 
    166  return selector;
    167 }
    168 
    169 /**
    170 * Log the provided error to the console and return a rejected Promise for
    171 * this error.
    172 *
    173 * @param {Error} error
    174 *         The error to log
    175 * @return {Promise} A rejected promise
    176 */
    177 function promiseWarn(error) {
    178  console.error(error);
    179  return Promise.reject(error);
    180 }
    181 
    182 /**
    183 * While waiting for a reps fix in https://github.com/firefox-devtools/reps/issues/92,
    184 * translate nodeFront to a grip-like object that can be used with an ElementNode rep.
    185 *
    186 * @param {NodeFront} nodeFront
    187 *          The NodeFront for which we want to create a grip-like object.
    188 * @returns {object} a grip-like object that can be used with Reps.
    189 */
    190 function translateNodeFrontToGrip(nodeFront) {
    191  const { attributes } = nodeFront;
    192 
    193  // The main difference between NodeFront and grips is that attributes are treated as
    194  // a map in grips and as an array in NodeFronts.
    195  const attributesMap = {};
    196  for (const { name, value } of attributes) {
    197    attributesMap[name] = value;
    198  }
    199 
    200  return {
    201    actor: nodeFront.actorID,
    202    preview: {
    203      attributes: attributesMap,
    204      attributesLength: attributes.length,
    205      isPseudoElement: nodeFront.isPseudoElement,
    206      // All the grid containers are assumed to be in the DOM tree.
    207      isConnected: true,
    208      displayName: nodeFront.displayName,
    209      // nodeName is already lowerCased in Node grips
    210      nodeName: nodeFront.nodeName.toLowerCase(),
    211      nodeType: nodeFront.nodeType,
    212    },
    213  };
    214 }
    215 
    216 exports.advanceValidate = advanceValidate;
    217 exports.appendText = appendText;
    218 exports.blurOnMultipleProperties = blurOnMultipleProperties;
    219 exports.createChild = createChild;
    220 exports.getLongString = getLongString;
    221 exports.getSelectorFromGrip = getSelectorFromGrip;
    222 exports.promiseWarn = promiseWarn;
    223 exports.translateNodeFrontToGrip = translateNodeFrontToGrip;