tor-browser

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

attributes.js (13344B)


      1 /* import-globals-from common.js */
      2 
      3 // //////////////////////////////////////////////////////////////////////////////
      4 // Object attributes.
      5 
      6 /**
      7 * Test object attributes.
      8 *
      9 * @param aAccOrElmOrID         [in] the accessible identifier
     10 * @param aAttrs                [in] the map of expected object attributes
     11 *                              (name/value pairs)
     12 * @param aSkipUnexpectedAttrs  [in] points this function doesn't fail if
     13 *                              unexpected attribute is encountered
     14 * @param aTodo                 [in] true if this is a 'todo'
     15 */
     16 function testAttrs(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, aTodo) {
     17  testAttrsInternal(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, null, aTodo);
     18 }
     19 
     20 /**
     21 * Test object attributes that must not be present.
     22 *
     23 * @param aAccOrElmOrID         [in] the accessible identifier
     24 * @param aAbsentAttrs          [in] map of attributes that should not be
     25 *                              present (name/value pairs)
     26 * @param aTodo                 [in] true if this is a 'todo'
     27 */
     28 function testAbsentAttrs(aAccOrElmOrID, aAbsentAttrs, aTodo) {
     29  testAttrsInternal(aAccOrElmOrID, {}, true, aAbsentAttrs, aTodo);
     30 }
     31 
     32 /**
     33 * Test object attributes that aren't right, but should be (todo)
     34 *
     35 * @param aAccOrElmOrID         [in] the accessible identifier
     36 * @param aKey                  [in] attribute name
     37 * @param aExpectedValue        [in] expected attribute value
     38 */
     39 function todoAttr(aAccOrElmOrID, aKey, aExpectedValue) {
     40  testAttrs(
     41    aAccOrElmOrID,
     42    Object.fromEntries([[aKey, aExpectedValue]]),
     43    true,
     44    true
     45  );
     46 }
     47 
     48 /**
     49 * Test CSS based object attributes.
     50 */
     51 function testCSSAttrs(aID) {
     52  var node = document.getElementById(aID);
     53  var computedStyle = document.defaultView.getComputedStyle(node);
     54 
     55  var attrs = {
     56    display: computedStyle.display,
     57    "text-align": computedStyle.textAlign,
     58    "text-indent": computedStyle.textIndent,
     59    "margin-left": computedStyle.marginLeft,
     60    "margin-right": computedStyle.marginRight,
     61    "margin-top": computedStyle.marginTop,
     62    "margin-bottom": computedStyle.marginBottom,
     63  };
     64  testAttrs(aID, attrs, true);
     65 }
     66 
     67 /**
     68 * Test the accessible that it doesn't have CSS-based object attributes.
     69 */
     70 function testAbsentCSSAttrs(aID) {
     71  var attrs = {
     72    display: "",
     73    "text-align": "",
     74    "text-indent": "",
     75    "margin-left": "",
     76    "margin-right": "",
     77    "margin-top": "",
     78    "margin-bottom": "",
     79  };
     80  testAbsentAttrs(aID, attrs);
     81 }
     82 
     83 /**
     84 * Test group object attributes (posinset, setsize and level) and
     85 * nsIAccessible::groupPosition() method.
     86 *
     87 * @param aAccOrElmOrID  [in] the ID, DOM node or accessible
     88 * @param aPosInSet      [in] the value of 'posinset' attribute
     89 * @param aSetSize       [in] the value of 'setsize' attribute
     90 * @param aLevel         [in, optional] the value of 'level' attribute
     91 */
     92 function testGroupAttrs(aAccOrElmOrID, aPosInSet, aSetSize, aLevel, aTodo) {
     93  var acc = getAccessible(aAccOrElmOrID);
     94  var levelObj = {},
     95    posInSetObj = {},
     96    setSizeObj = {};
     97  acc.groupPosition(levelObj, setSizeObj, posInSetObj);
     98 
     99  let groupPos = {},
    100    expectedGroupPos = {};
    101 
    102  if (aPosInSet && aSetSize) {
    103    groupPos.setsize = String(setSizeObj.value);
    104    groupPos.posinset = String(posInSetObj.value);
    105 
    106    expectedGroupPos.setsize = String(aSetSize);
    107    expectedGroupPos.posinset = String(aPosInSet);
    108  }
    109 
    110  if (aLevel) {
    111    groupPos.level = String(levelObj.value);
    112 
    113    expectedGroupPos.level = String(aLevel);
    114  }
    115 
    116  compareSimpleObjects(
    117    groupPos,
    118    expectedGroupPos,
    119    false,
    120    "wrong groupPos",
    121    aTodo
    122  );
    123 
    124  testAttrs(aAccOrElmOrID, expectedGroupPos, true, aTodo);
    125 }
    126 
    127 function testGroupParentAttrs(
    128  aAccOrElmOrID,
    129  aChildItemCount,
    130  aIsHierarchical,
    131  aTodo
    132 ) {
    133  testAttrs(
    134    aAccOrElmOrID,
    135    { "child-item-count": String(aChildItemCount) },
    136    true,
    137    aTodo
    138  );
    139 
    140  if (aIsHierarchical) {
    141    testAttrs(aAccOrElmOrID, { tree: "true" }, true, aTodo);
    142  } else {
    143    testAbsentAttrs(aAccOrElmOrID, { tree: "true" });
    144  }
    145 }
    146 
    147 // //////////////////////////////////////////////////////////////////////////////
    148 // Text attributes.
    149 
    150 /**
    151 * Test text attributes.
    152 *
    153 * @param aID                   [in] the ID of DOM element having text
    154 *                              accessible
    155 * @param aOffset               [in] the offset inside text accessible to fetch
    156 *                              text attributes
    157 * @param aAttrs                [in] the map of expected text attributes
    158 *                              (name/value pairs) exposed at the offset
    159 * @param aDefAttrs             [in] the map of expected text attributes
    160 *                              (name/value pairs) exposed on hyper text
    161 *                              accessible
    162 * @param aStartOffset          [in] expected start offset where text attributes
    163 *                              are applied
    164 * @param aEndOffset            [in] expected end offset where text attribute
    165 *                              are applied
    166 * @param aSkipUnexpectedAttrs  [in] points the function doesn't fail if
    167 *                              unexpected attribute is encountered
    168 */
    169 function testTextAttrs(
    170  aID,
    171  aOffset,
    172  aAttrs,
    173  aDefAttrs,
    174  aStartOffset,
    175  aEndOffset,
    176  aSkipUnexpectedAttrs
    177 ) {
    178  var accessible = getAccessible(aID, [nsIAccessibleText]);
    179  if (!accessible) {
    180    return;
    181  }
    182 
    183  var startOffset = { value: -1 };
    184  var endOffset = { value: -1 };
    185 
    186  // do not include attributes exposed on hyper text accessible
    187  var attrs = getTextAttributes(
    188    aID,
    189    accessible,
    190    false,
    191    aOffset,
    192    startOffset,
    193    endOffset
    194  );
    195 
    196  if (!attrs) {
    197    return;
    198  }
    199 
    200  var errorMsg = " for " + aID + " at offset " + aOffset;
    201 
    202  is(startOffset.value, aStartOffset, "Wrong start offset" + errorMsg);
    203  is(endOffset.value, aEndOffset, "Wrong end offset" + errorMsg);
    204 
    205  compareAttrs(errorMsg, attrs, aAttrs, aSkipUnexpectedAttrs);
    206 
    207  // include attributes exposed on hyper text accessible
    208  var expectedAttrs = {};
    209  for (let name in aAttrs) {
    210    expectedAttrs[name] = aAttrs[name];
    211  }
    212 
    213  for (let name in aDefAttrs) {
    214    if (!(name in expectedAttrs)) {
    215      expectedAttrs[name] = aDefAttrs[name];
    216    }
    217  }
    218 
    219  attrs = getTextAttributes(
    220    aID,
    221    accessible,
    222    true,
    223    aOffset,
    224    startOffset,
    225    endOffset
    226  );
    227 
    228  if (!attrs) {
    229    return;
    230  }
    231 
    232  compareAttrs(errorMsg, attrs, expectedAttrs, aSkipUnexpectedAttrs);
    233 }
    234 
    235 /**
    236 * Test default text attributes.
    237 *
    238 * @param aID                   [in] the ID of DOM element having text
    239 *                              accessible
    240 * @param aDefAttrs             [in] the map of expected text attributes
    241 *                              (name/value pairs)
    242 * @param aSkipUnexpectedAttrs  [in] points the function doesn't fail if
    243 *                              unexpected attribute is encountered
    244 */
    245 function testDefaultTextAttrs(aID, aDefAttrs, aSkipUnexpectedAttrs) {
    246  var accessible = getAccessible(aID, [nsIAccessibleText]);
    247  if (!accessible) {
    248    return;
    249  }
    250 
    251  var defAttrs = null;
    252  try {
    253    defAttrs = accessible.defaultTextAttributes;
    254  } catch (e) {}
    255 
    256  if (!defAttrs) {
    257    ok(false, "Can't get default text attributes for " + aID);
    258    return;
    259  }
    260 
    261  var errorMsg = ". Getting default text attributes for " + aID;
    262  compareAttrs(errorMsg, defAttrs, aDefAttrs, aSkipUnexpectedAttrs);
    263 }
    264 
    265 /**
    266 * Test text attributes for wrong offset.
    267 */
    268 function testTextAttrsWrongOffset(aID, aOffset) {
    269  var res = false;
    270  try {
    271    var s = {},
    272      e = {};
    273    // Bug 1602031
    274    // eslint-disable-next-line no-undef
    275    var acc = getAccessible(ID, [nsIAccessibleText]);
    276    acc.getTextAttributes(false, 157, s, e);
    277  } catch (ex) {
    278    res = true;
    279  }
    280 
    281  ok(
    282    res,
    283    "text attributes are calculated successfully at wrong offset " +
    284      aOffset +
    285      " for " +
    286      prettyName(aID)
    287  );
    288 }
    289 
    290 const kNormalFontWeight = function equalsToNormal(aWeight) {
    291  return aWeight <= 400;
    292 };
    293 
    294 const kBoldFontWeight = function equalsToBold(aWeight) {
    295  return aWeight > 400;
    296 };
    297 
    298 // The pt font size of the input element can vary by Linux distro.
    299 const kInputFontSize =
    300  WIN || MAC
    301    ? "10pt"
    302    : function () {
    303        return true;
    304      };
    305 
    306 const kAbsentFontFamily = function (aFontFamily) {
    307  return aFontFamily != "sans-serif";
    308 };
    309 const kInputFontFamily = function (aFontFamily) {
    310  return aFontFamily != "sans-serif";
    311 };
    312 
    313 const kMonospaceFontFamily = function (aFontFamily) {
    314  return aFontFamily != "monospace";
    315 };
    316 const kSansSerifFontFamily = function (aFontFamily) {
    317  return aFontFamily != "sans-serif";
    318 };
    319 const kSerifFontFamily = function (aFontFamily) {
    320  return aFontFamily != "serif";
    321 };
    322 const kMathFontFamily = function (aFontFamily) {
    323  return aFontFamily != "math";
    324 };
    325 
    326 const kCursiveFontFamily = LINUX ? "DejaVu Serif" : "Comic Sans MS";
    327 
    328 /**
    329 * Return used font from the given computed style.
    330 */
    331 function fontFamily(aComputedStyle) {
    332  var name = aComputedStyle.fontFamily;
    333  switch (name) {
    334    case "monospace":
    335      return kMonospaceFontFamily;
    336    case "sans-serif":
    337      return kSansSerifFontFamily;
    338    case "serif":
    339      return kSerifFontFamily;
    340    case "math":
    341      return kMathFontFamily;
    342    default:
    343      return name;
    344  }
    345 }
    346 
    347 /**
    348 * Returns a computed system color for this document.
    349 */
    350 function getSystemColor(aColor) {
    351  let { r, g, b, a } = InspectorUtils.colorToRGBA(aColor);
    352  return a == 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a})`;
    353 }
    354 
    355 /**
    356 * Build an object of default text attributes expected for the given accessible.
    357 *
    358 * @param aID          [in] identifier of accessible
    359 * @param aFontSize    [in] font size
    360 * @param aFontWeight  [in, optional] kBoldFontWeight or kNormalFontWeight,
    361 *                      default value is kNormalFontWeight
    362 */
    363 function buildDefaultTextAttrs(aID, aFontSize, aFontWeight, aFontFamily) {
    364  var elm = getNode(aID);
    365  var computedStyle = document.defaultView.getComputedStyle(elm);
    366  var bgColor =
    367    computedStyle.backgroundColor == "rgba(0, 0, 0, 0)"
    368      ? getSystemColor("Canvas")
    369      : computedStyle.backgroundColor;
    370 
    371  var defAttrs = {
    372    "font-style": computedStyle.fontStyle,
    373    "font-size": aFontSize,
    374    "background-color": bgColor,
    375    "font-weight": aFontWeight ? aFontWeight : kNormalFontWeight,
    376    color: computedStyle.color,
    377    "font-family": aFontFamily ? aFontFamily : fontFamily(computedStyle),
    378    "text-position": computedStyle.verticalAlign,
    379  };
    380 
    381  return defAttrs;
    382 }
    383 
    384 // //////////////////////////////////////////////////////////////////////////////
    385 // Private.
    386 
    387 function getTextAttributes(
    388  aID,
    389  aAccessible,
    390  aIncludeDefAttrs,
    391  aOffset,
    392  aStartOffset,
    393  aEndOffset
    394 ) {
    395  // This function expects the passed in accessible to already be queried for
    396  // nsIAccessibleText.
    397  var attrs = null;
    398  try {
    399    attrs = aAccessible.getTextAttributes(
    400      aIncludeDefAttrs,
    401      aOffset,
    402      aStartOffset,
    403      aEndOffset
    404    );
    405  } catch (e) {}
    406 
    407  if (attrs) {
    408    return attrs;
    409  }
    410 
    411  ok(false, "Can't get text attributes for " + aID);
    412  return null;
    413 }
    414 
    415 function testAttrsInternal(
    416  aAccOrElmOrID,
    417  aAttrs,
    418  aSkipUnexpectedAttrs,
    419  aAbsentAttrs,
    420  aTodo
    421 ) {
    422  var accessible = getAccessible(aAccOrElmOrID);
    423  if (!accessible) {
    424    return;
    425  }
    426 
    427  var attrs = null;
    428  try {
    429    attrs = accessible.attributes;
    430  } catch (e) {}
    431 
    432  if (!attrs) {
    433    ok(false, "Can't get object attributes for " + prettyName(aAccOrElmOrID));
    434    return;
    435  }
    436 
    437  var errorMsg = " for " + prettyName(aAccOrElmOrID);
    438  compareAttrs(
    439    errorMsg,
    440    attrs,
    441    aAttrs,
    442    aSkipUnexpectedAttrs,
    443    aAbsentAttrs,
    444    aTodo
    445  );
    446 }
    447 
    448 function compareAttrs(
    449  aErrorMsg,
    450  aAttrs,
    451  aExpectedAttrs,
    452  aSkipUnexpectedAttrs,
    453  aAbsentAttrs,
    454  aTodo
    455 ) {
    456  // Check if all obtained attributes are expected and have expected value.
    457  let attrObject = {};
    458  for (let prop of aAttrs.enumerate()) {
    459    attrObject[prop.key] = prop.value;
    460  }
    461 
    462  // Create expected attributes set by using the return values from
    463  // embedded functions to determine the entry's value.
    464  let expectedObj = Object.fromEntries(
    465    Object.entries(aExpectedAttrs).map(([k, v]) => {
    466      if (v instanceof Function) {
    467        // If value is a function that returns true given the received
    468        // attribute value, assign the attribute value to the entry.
    469        // If it is false, stringify the function for good error reporting.
    470        let value = v(attrObject[k]) ? attrObject[k] : v.toString();
    471        return [k, value];
    472      }
    473 
    474      return [k, v];
    475    })
    476  );
    477 
    478  compareSimpleObjects(
    479    attrObject,
    480    expectedObj,
    481    aSkipUnexpectedAttrs,
    482    aErrorMsg,
    483    aTodo
    484  );
    485 
    486  // Check if all unexpected attributes are absent.
    487  if (aAbsentAttrs) {
    488    let presentAttrs = Object.keys(attrObject).filter(
    489      k => aAbsentAttrs[k] !== undefined
    490    );
    491    if (presentAttrs.length) {
    492      (aTodo ? todo : ok)(
    493        false,
    494        `There were unexpected attributes: ${presentAttrs}`
    495      );
    496    }
    497  }
    498 }
    499 
    500 function compareSimpleObjects(
    501  aObj,
    502  aExpectedObj,
    503  aSkipUnexpectedAttrs,
    504  aMessage,
    505  aTodo
    506 ) {
    507  let keys = aSkipUnexpectedAttrs
    508    ? Object.keys(aExpectedObj).sort()
    509    : Object.keys(aObj).sort();
    510  let o1 = JSON.stringify(aObj, keys);
    511  let o2 = JSON.stringify(aExpectedObj, keys);
    512 
    513  if (aTodo) {
    514    todo_is(o1, o2, `${aMessage} - Got ${o1}, expected ${o2}`);
    515  } else {
    516    is(o1, o2, aMessage);
    517  }
    518 }