tor-browser

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

test_DOM.js (14595B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const { dom } = ChromeUtils.importESModule(
      6  "chrome://remote/content/shared/DOM.sys.mjs"
      7 );
      8 const { NodeCache } = ChromeUtils.importESModule(
      9  "chrome://remote/content/shared/webdriver/NodeCache.sys.mjs"
     10 );
     11 
     12 class MockElement {
     13  constructor(tagName, attrs = {}) {
     14    this.tagName = tagName;
     15    this.localName = tagName;
     16 
     17    this.isConnected = false;
     18    this.ownerGlobal = {
     19      document: {
     20        isActive() {
     21          return true;
     22        },
     23      },
     24    };
     25 
     26    for (let attr in attrs) {
     27      this[attr] = attrs[attr];
     28    }
     29  }
     30 
     31  get nodeType() {
     32    return 1;
     33  }
     34 
     35  get ELEMENT_NODE() {
     36    return 1;
     37  }
     38 
     39  // this is a severely limited CSS selector
     40  // that only supports lists of tag names
     41  matches(selector) {
     42    let tags = selector.split(",");
     43    return tags.includes(this.localName);
     44  }
     45 }
     46 
     47 class MockXULElement extends MockElement {
     48  constructor(tagName, attrs = {}) {
     49    super(tagName, attrs);
     50    this.namespaceURI = XUL_NS;
     51 
     52    if (typeof this.ownerDocument == "undefined") {
     53      this.ownerDocument = {};
     54    }
     55    if (typeof this.ownerDocument.documentElement == "undefined") {
     56      this.ownerDocument.documentElement = { namespaceURI: XUL_NS };
     57    }
     58  }
     59 }
     60 
     61 const xulEl = new MockXULElement("text");
     62 
     63 const domElInPrivilegedDocument = new MockElement("input", {
     64  nodePrincipal: { isSystemPrincipal: true },
     65 });
     66 const xulElInPrivilegedDocument = new MockXULElement("text", {
     67  nodePrincipal: { isSystemPrincipal: true },
     68 });
     69 
     70 function setupTest() {
     71  const browser = Services.appShell.createWindowlessBrowser(false);
     72 
     73  browser.document.body.innerHTML = `
     74    <div id="foo" style="margin: 50px">
     75      <iframe></iframe>
     76      <video></video>
     77      <svg xmlns="http://www.w3.org/2000/svg"></svg>
     78      <form>
     79        <button/>
     80        <input/>
     81        <fieldset>
     82          <legend><input id="first"/></legend>
     83          <legend><input id="second"/></legend>
     84        </fieldset>
     85        <select>
     86          <optgroup>
     87            <option id="in-group">foo</options>
     88          </optgroup>
     89          <option id="no-group">bar</option>
     90        </select>
     91        <textarea></textarea>
     92      </form>
     93    </div>
     94  `;
     95 
     96  const divEl = browser.document.querySelector("div");
     97  const svgEl = browser.document.querySelector("svg");
     98  const videoEl = browser.document.querySelector("video");
     99  const shadowRoot = videoEl.openOrClosedShadowRoot;
    100 
    101  const buttonEl = browser.document.querySelector("button");
    102  const fieldsetEl = browser.document.querySelector("fieldset");
    103  const inputEl = browser.document.querySelector("input");
    104  const optgroupEl = browser.document.querySelector("optgroup");
    105  const optionInGroupEl = browser.document.querySelector("option#in-group");
    106  const optionNoGroupEl = browser.document.querySelector("option#no-group");
    107  const selectEl = browser.document.querySelector("select");
    108  const textareaEl = browser.document.querySelector("textarea");
    109 
    110  const iframeEl = browser.document.querySelector("iframe");
    111  const childEl = iframeEl.contentDocument.createElement("div");
    112  iframeEl.contentDocument.body.appendChild(childEl);
    113 
    114  return {
    115    browser,
    116    buttonEl,
    117    childEl,
    118    divEl,
    119    inputEl,
    120    fieldsetEl,
    121    iframeEl,
    122    nodeCache: new NodeCache(),
    123    optgroupEl,
    124    optionInGroupEl,
    125    optionNoGroupEl,
    126    selectEl,
    127    shadowRoot,
    128    svgEl,
    129    textareaEl,
    130    videoEl,
    131  };
    132 }
    133 
    134 add_task(function test_findClosest() {
    135  const { divEl, videoEl } = setupTest();
    136 
    137  equal(dom.findClosest(divEl, "foo"), null);
    138  equal(dom.findClosest(videoEl, "div"), divEl);
    139 });
    140 
    141 add_task(function test_isSelected() {
    142  const { browser, divEl } = setupTest();
    143 
    144  const checkbox = browser.document.createElement("input");
    145  checkbox.setAttribute("type", "checkbox");
    146 
    147  ok(!dom.isSelected(checkbox));
    148  checkbox.checked = true;
    149  ok(dom.isSelected(checkbox));
    150 
    151  // selected is not a property of <input type=checkbox>
    152  checkbox.selected = true;
    153  checkbox.checked = false;
    154  ok(!dom.isSelected(checkbox));
    155 
    156  const option = browser.document.createElement("option");
    157 
    158  ok(!dom.isSelected(option));
    159  option.selected = true;
    160  ok(dom.isSelected(option));
    161 
    162  // checked is not a property of <option>
    163  option.checked = true;
    164  option.selected = false;
    165  ok(!dom.isSelected(option));
    166 
    167  // anything else should not be selected
    168  for (const type of [undefined, null, "foo", true, [], {}, divEl]) {
    169    ok(!dom.isSelected(type));
    170  }
    171 });
    172 
    173 add_task(function test_isElement() {
    174  const { divEl, iframeEl, shadowRoot, svgEl } = setupTest();
    175 
    176  ok(dom.isElement(divEl));
    177  ok(dom.isElement(svgEl));
    178  ok(dom.isElement(xulEl));
    179  ok(dom.isElement(domElInPrivilegedDocument));
    180  ok(dom.isElement(xulElInPrivilegedDocument));
    181 
    182  ok(!dom.isElement(shadowRoot));
    183  ok(!dom.isElement(divEl.ownerGlobal));
    184  ok(!dom.isElement(iframeEl.contentWindow));
    185 
    186  for (const type of [true, 42, {}, [], undefined, null]) {
    187    ok(!dom.isElement(type));
    188  }
    189 });
    190 
    191 add_task(function test_isDOMElement() {
    192  const { divEl, iframeEl, shadowRoot, svgEl } = setupTest();
    193 
    194  ok(dom.isDOMElement(divEl));
    195  ok(dom.isDOMElement(svgEl));
    196  ok(dom.isDOMElement(domElInPrivilegedDocument));
    197 
    198  ok(!dom.isDOMElement(shadowRoot));
    199  ok(!dom.isDOMElement(divEl.ownerGlobal));
    200  ok(!dom.isDOMElement(iframeEl.contentWindow));
    201  ok(!dom.isDOMElement(xulEl));
    202  ok(!dom.isDOMElement(xulElInPrivilegedDocument));
    203 
    204  for (const type of [true, 42, "foo", {}, [], undefined, null]) {
    205    ok(!dom.isDOMElement(type));
    206  }
    207 });
    208 
    209 add_task(function test_isXULElement() {
    210  const { divEl, iframeEl, shadowRoot, svgEl } = setupTest();
    211 
    212  ok(dom.isXULElement(xulEl));
    213  ok(dom.isXULElement(xulElInPrivilegedDocument));
    214 
    215  ok(!dom.isXULElement(divEl));
    216  ok(!dom.isXULElement(domElInPrivilegedDocument));
    217  ok(!dom.isXULElement(svgEl));
    218  ok(!dom.isXULElement(shadowRoot));
    219  ok(!dom.isXULElement(divEl.ownerGlobal));
    220  ok(!dom.isXULElement(iframeEl.contentWindow));
    221 
    222  for (const type of [true, 42, "foo", {}, [], undefined, null]) {
    223    ok(!dom.isXULElement(type));
    224  }
    225 });
    226 
    227 add_task(function test_isDOMWindow() {
    228  const { divEl, iframeEl, shadowRoot, svgEl } = setupTest();
    229 
    230  ok(dom.isDOMWindow(divEl.ownerGlobal));
    231  ok(dom.isDOMWindow(iframeEl.contentWindow));
    232 
    233  ok(!dom.isDOMWindow(divEl));
    234  ok(!dom.isDOMWindow(svgEl));
    235  ok(!dom.isDOMWindow(shadowRoot));
    236  ok(!dom.isDOMWindow(domElInPrivilegedDocument));
    237  ok(!dom.isDOMWindow(xulEl));
    238  ok(!dom.isDOMWindow(xulElInPrivilegedDocument));
    239 
    240  for (const type of [true, 42, {}, [], undefined, null]) {
    241    ok(!dom.isDOMWindow(type));
    242  }
    243 });
    244 
    245 add_task(function test_isShadowRoot() {
    246  const { browser, divEl, iframeEl, shadowRoot, svgEl } = setupTest();
    247 
    248  ok(dom.isShadowRoot(shadowRoot));
    249 
    250  ok(!dom.isShadowRoot(divEl));
    251  ok(!dom.isShadowRoot(svgEl));
    252  ok(!dom.isShadowRoot(divEl.ownerGlobal));
    253  ok(!dom.isShadowRoot(iframeEl.contentWindow));
    254  ok(!dom.isShadowRoot(xulEl));
    255  ok(!dom.isShadowRoot(domElInPrivilegedDocument));
    256  ok(!dom.isShadowRoot(xulElInPrivilegedDocument));
    257 
    258  for (const type of [true, 42, "foo", {}, [], undefined, null]) {
    259    ok(!dom.isShadowRoot(type));
    260  }
    261 
    262  const documentFragment = browser.document.createDocumentFragment();
    263  ok(!dom.isShadowRoot(documentFragment));
    264 });
    265 
    266 add_task(function test_isReadOnly() {
    267  const { browser, divEl, textareaEl } = setupTest();
    268 
    269  const input = browser.document.createElement("input");
    270  input.readOnly = true;
    271  ok(dom.isReadOnly(input));
    272 
    273  textareaEl.readOnly = true;
    274  ok(dom.isReadOnly(textareaEl));
    275 
    276  ok(!dom.isReadOnly(divEl));
    277  divEl.readOnly = true;
    278  ok(!dom.isReadOnly(divEl));
    279 
    280  ok(!dom.isReadOnly(null));
    281 });
    282 
    283 add_task(function test_isDisabledSelect() {
    284  const { optgroupEl, optionInGroupEl, optionNoGroupEl, selectEl } =
    285    setupTest();
    286 
    287  optionNoGroupEl.disabled = true;
    288  ok(dom.isDisabled(optionNoGroupEl));
    289  optionNoGroupEl.disabled = false;
    290  ok(!dom.isDisabled(optionNoGroupEl));
    291 
    292  optgroupEl.disabled = true;
    293  ok(dom.isDisabled(optgroupEl));
    294  ok(dom.isDisabled(optionInGroupEl));
    295  optgroupEl.disabled = false;
    296  ok(!dom.isDisabled(optgroupEl));
    297  ok(!dom.isDisabled(optionInGroupEl));
    298 
    299  selectEl.disabled = true;
    300  ok(dom.isDisabled(selectEl));
    301  ok(dom.isDisabled(optgroupEl));
    302  ok(dom.isDisabled(optionNoGroupEl));
    303  selectEl.disabled = false;
    304  ok(!dom.isDisabled(selectEl));
    305  ok(!dom.isDisabled(optgroupEl));
    306  ok(!dom.isDisabled(optionNoGroupEl));
    307 });
    308 
    309 add_task(function test_isDisabledFormControl() {
    310  const { buttonEl, fieldsetEl, inputEl, selectEl, textareaEl } = setupTest();
    311 
    312  for (const elem of [buttonEl, inputEl, selectEl, textareaEl]) {
    313    elem.disabled = true;
    314    ok(dom.isDisabled(elem));
    315    elem.disabled = false;
    316    ok(!dom.isDisabled(elem));
    317  }
    318 
    319  const inputs = fieldsetEl.querySelectorAll("input");
    320  fieldsetEl.disabled = true;
    321  ok(dom.isDisabled(fieldsetEl));
    322  ok(!dom.isDisabled(inputs[0]));
    323  ok(dom.isDisabled(inputs[1]));
    324  fieldsetEl.disabled = false;
    325  ok(!dom.isDisabled(fieldsetEl));
    326  ok(!dom.isDisabled(inputs[0]));
    327  ok(!dom.isDisabled(inputs[1]));
    328 });
    329 
    330 add_task(function test_isDisabledElement() {
    331  const { divEl, svgEl } = setupTest();
    332  const mockXulEl = new MockXULElement("browser", { disabled: true });
    333 
    334  for (const elem of [divEl, svgEl, mockXulEl]) {
    335    ok(!dom.isDisabled(elem));
    336    elem.disabled = true;
    337    ok(!dom.isDisabled(elem));
    338  }
    339 });
    340 
    341 add_task(function test_isDisabledNoDOMElement() {
    342  ok(!dom.isDisabled());
    343 
    344  for (const obj of [null, undefined, 42, "", {}, []]) {
    345    ok(!dom.isDisabled(obj));
    346  }
    347 });
    348 
    349 add_task(function test_isEditingHost() {
    350  const { browser, divEl, svgEl } = setupTest();
    351 
    352  ok(!dom.isEditingHost(null));
    353 
    354  ok(!dom.isEditingHost(divEl));
    355  divEl.contentEditable = true;
    356  ok(dom.isEditingHost(divEl));
    357 
    358  ok(!dom.isEditingHost(svgEl));
    359  browser.document.designMode = "on";
    360  ok(dom.isEditingHost(svgEl));
    361 });
    362 
    363 add_task(function test_isEditable() {
    364  const { browser, divEl, svgEl, textareaEl } = setupTest();
    365 
    366  ok(!dom.isEditable(null));
    367 
    368  for (let type of [
    369    "checkbox",
    370    "radio",
    371    "hidden",
    372    "submit",
    373    "button",
    374    "image",
    375  ]) {
    376    const input = browser.document.createElement("input");
    377    input.setAttribute("type", type);
    378 
    379    ok(!dom.isEditable(input));
    380  }
    381 
    382  const input = browser.document.createElement("input");
    383  ok(dom.isEditable(input));
    384  input.setAttribute("type", "text");
    385  ok(dom.isEditable(input));
    386 
    387  ok(dom.isEditable(textareaEl));
    388 
    389  const textareaDisabled = browser.document.createElement("textarea");
    390  textareaDisabled.disabled = true;
    391  ok(!dom.isEditable(textareaDisabled));
    392 
    393  const textareaReadOnly = browser.document.createElement("textarea");
    394  textareaReadOnly.readOnly = true;
    395  ok(!dom.isEditable(textareaReadOnly));
    396 
    397  ok(!dom.isEditable(divEl));
    398  divEl.contentEditable = true;
    399  ok(dom.isEditable(divEl));
    400 
    401  ok(!dom.isEditable(svgEl));
    402  browser.document.designMode = "on";
    403  ok(dom.isEditable(svgEl));
    404 });
    405 
    406 add_task(function test_isMutableFormControlElement() {
    407  const { browser, divEl, textareaEl } = setupTest();
    408 
    409  ok(!dom.isMutableFormControl(null));
    410 
    411  ok(dom.isMutableFormControl(textareaEl));
    412 
    413  const textareaDisabled = browser.document.createElement("textarea");
    414  textareaDisabled.disabled = true;
    415  ok(!dom.isMutableFormControl(textareaDisabled));
    416 
    417  const textareaReadOnly = browser.document.createElement("textarea");
    418  textareaReadOnly.readOnly = true;
    419  ok(!dom.isMutableFormControl(textareaReadOnly));
    420 
    421  const mutableStates = new Set([
    422    "color",
    423    "date",
    424    "datetime-local",
    425    "email",
    426    "file",
    427    "month",
    428    "number",
    429    "password",
    430    "range",
    431    "search",
    432    "tel",
    433    "text",
    434    "url",
    435    "week",
    436  ]);
    437  for (const type of mutableStates) {
    438    const input = browser.document.createElement("input");
    439    input.setAttribute("type", type);
    440    ok(dom.isMutableFormControl(input));
    441  }
    442 
    443  const inputHidden = browser.document.createElement("input");
    444  inputHidden.setAttribute("type", "hidden");
    445  ok(!dom.isMutableFormControl(inputHidden));
    446 
    447  ok(!dom.isMutableFormControl(divEl));
    448  divEl.contentEditable = true;
    449  ok(!dom.isMutableFormControl(divEl));
    450  browser.document.designMode = "on";
    451  ok(!dom.isMutableFormControl(divEl));
    452 });
    453 
    454 add_task(function test_coordinates() {
    455  const { divEl } = setupTest();
    456 
    457  let coords = dom.coordinates(divEl);
    458  ok(coords.hasOwnProperty("x"));
    459  ok(coords.hasOwnProperty("y"));
    460  equal(typeof coords.x, "number");
    461  equal(typeof coords.y, "number");
    462 
    463  deepEqual(dom.coordinates(divEl), { x: 0, y: 0 });
    464  deepEqual(dom.coordinates(divEl, 10, 10), { x: 10, y: 10 });
    465  deepEqual(dom.coordinates(divEl, -5, -5), { x: -5, y: -5 });
    466 
    467  Assert.throws(() => dom.coordinates(null), /node is null/);
    468 
    469  Assert.throws(
    470    () => dom.coordinates(divEl, "string", undefined),
    471    /Offset must be a number/
    472  );
    473  Assert.throws(
    474    () => dom.coordinates(divEl, undefined, "string"),
    475    /Offset must be a number/
    476  );
    477  Assert.throws(
    478    () => dom.coordinates(divEl, "string", "string"),
    479    /Offset must be a number/
    480  );
    481  Assert.throws(
    482    () => dom.coordinates(divEl, {}, undefined),
    483    /Offset must be a number/
    484  );
    485  Assert.throws(
    486    () => dom.coordinates(divEl, undefined, {}),
    487    /Offset must be a number/
    488  );
    489  Assert.throws(
    490    () => dom.coordinates(divEl, {}, {}),
    491    /Offset must be a number/
    492  );
    493  Assert.throws(
    494    () => dom.coordinates(divEl, [], undefined),
    495    /Offset must be a number/
    496  );
    497  Assert.throws(
    498    () => dom.coordinates(divEl, undefined, []),
    499    /Offset must be a number/
    500  );
    501  Assert.throws(
    502    () => dom.coordinates(divEl, [], []),
    503    /Offset must be a number/
    504  );
    505 });
    506 
    507 add_task(function test_isDetached() {
    508  const { childEl, iframeEl } = setupTest();
    509 
    510  let detachedShadowRoot = childEl.attachShadow({ mode: "open" });
    511  detachedShadowRoot.innerHTML = "<input></input>";
    512 
    513  // Connected to the DOM
    514  ok(!dom.isDetached(detachedShadowRoot));
    515 
    516  // Node document (ownerDocument) is not the active document
    517  iframeEl.remove();
    518  ok(dom.isDetached(detachedShadowRoot));
    519 
    520  // host element is stale (eg. not connected)
    521  detachedShadowRoot.host.remove();
    522  equal(childEl.isConnected, false);
    523  ok(dom.isDetached(detachedShadowRoot));
    524 });
    525 
    526 add_task(function test_isStale() {
    527  const { childEl, iframeEl } = setupTest();
    528 
    529  // Connected to the DOM
    530  ok(!dom.isStale(childEl));
    531 
    532  // Not part of the active document
    533  iframeEl.remove();
    534  ok(dom.isStale(childEl));
    535 
    536  // Not connected to the DOM
    537  childEl.remove();
    538  ok(dom.isStale(childEl));
    539 });