tor-browser

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

head.js (9674B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 /* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
      4 
      5 "use strict";
      6 
      7 // shared-head.js handles imports, constants, and utility functions
      8 Services.scriptloader.loadSubScript(
      9  "chrome://mochitests/content/browser/devtools/client/framework/test/head.js",
     10  this
     11 );
     12 
     13 const JSON_VIEW_PREF = "devtools.jsonview.enabled";
     14 
     15 // Enable JSON View for the test
     16 Services.prefs.setBoolPref(JSON_VIEW_PREF, true);
     17 
     18 registerCleanupFunction(() => {
     19  Services.prefs.clearUserPref(JSON_VIEW_PREF);
     20 });
     21 
     22 // XXX move some API into devtools/shared/test/shared-head.js
     23 
     24 /**
     25 * Add a new test tab in the browser and load the given url.
     26 *
     27 * @param {string} url
     28 *   The url to be loaded in the new tab.
     29 *
     30 * @param {object} [optional]
     31 *   An object with the following optional properties:
     32 *   - appReadyState: The readyState of the JSON Viewer app that you want to
     33 *     wait for. Its value can be one of:
     34 *      - "uninitialized": The converter has started the request.
     35 *        If JavaScript is disabled, there will be no more readyState changes.
     36 *      - "loading": RequireJS started loading the scripts for the JSON Viewer.
     37 *        If the load timeouts, there will be no more readyState changes.
     38 *      - "interactive": The JSON Viewer app loaded, but possibly not all the JSON
     39 *        data has been received.
     40 *      - "complete" (default): The app is fully loaded with all the JSON.
     41 *   - docReadyState: The standard readyState of the document that you want to
     42 *     wait for. Its value can be one of:
     43 *      - "loading": The JSON data has not been completely loaded (but the app might).
     44 *      - "interactive": All the JSON data has been received.
     45 *      - "complete" (default): Since there aren't sub-resources like images,
     46 *        behaves as "interactive". Note the app might not be loaded yet.
     47 */
     48 async function addJsonViewTab(
     49  url,
     50  { appReadyState = "complete", docReadyState = "complete" } = {}
     51 ) {
     52  info("Adding a new JSON tab with URL: '" + url + "'");
     53  const tabAdded = BrowserTestUtils.waitForNewTab(gBrowser, url);
     54  const tabLoaded = addTab(url, { waitForLoad: true });
     55 
     56  // The `tabAdded` promise resolves when the JSON Viewer starts loading.
     57  // This is usually what we want, however, it never resolves for unrecognized
     58  // content types that trigger a download.
     59  // On the other hand, `tabLoaded` always resolves, but not until the document
     60  // is fully loaded, which is too late if `docReadyState !== "complete"`.
     61  // Therefore, we race both promises.
     62  const tab = await Promise.race([tabAdded, tabLoaded]);
     63  const browser = tab.linkedBrowser;
     64 
     65  const rootDir = getRootDirectory(gTestPath);
     66 
     67  // Catch RequireJS errors (usually timeouts)
     68  const error = tabLoaded.then(() =>
     69    SpecialPowers.spawn(browser, [], function () {
     70      return new Promise((resolve, reject) => {
     71        const { requirejs } = content.wrappedJSObject;
     72        if (requirejs) {
     73          requirejs.onError = err => {
     74            info(err);
     75            ok(false, "RequireJS error");
     76            reject(err);
     77          };
     78        }
     79      });
     80    })
     81  );
     82 
     83  const data = { rootDir, appReadyState, docReadyState };
     84  await Promise.race([
     85    error,
     86    // eslint-disable-next-line no-shadow
     87    ContentTask.spawn(browser, data, async function (data) {
     88      // Check if there is a JSONView object.
     89      const { JSONView } = content.wrappedJSObject;
     90      if (!JSONView) {
     91        throw new Error("The JSON Viewer did not load.");
     92      }
     93 
     94      const docReadyStates = ["loading", "interactive", "complete"];
     95      const docReadyIndex = docReadyStates.indexOf(data.docReadyState);
     96      const appReadyStates = ["uninitialized", ...docReadyStates];
     97      const appReadyIndex = appReadyStates.indexOf(data.appReadyState);
     98      if (docReadyIndex < 0 || appReadyIndex < 0) {
     99        throw new Error("Invalid app or doc readyState parameter.");
    100      }
    101 
    102      // Wait until the document readyState suffices.
    103      const { document } = content;
    104      while (docReadyStates.indexOf(document.readyState) < docReadyIndex) {
    105        info(
    106          `DocReadyState is "${document.readyState}". Await "${data.docReadyState}"`
    107        );
    108        await new Promise(resolve => {
    109          document.addEventListener("readystatechange", resolve, {
    110            once: true,
    111          });
    112        });
    113      }
    114 
    115      // Wait until the app readyState suffices.
    116      while (appReadyStates.indexOf(JSONView.readyState) < appReadyIndex) {
    117        info(
    118          `AppReadyState is "${JSONView.readyState}". Await "${data.appReadyState}"`
    119        );
    120        await new Promise(resolve => {
    121          content.addEventListener("AppReadyStateChange", resolve, {
    122            once: true,
    123          });
    124        });
    125      }
    126    }),
    127  ]);
    128 
    129  return tab;
    130 }
    131 
    132 /**
    133 * Expanding a node in the JSON tree
    134 */
    135 function clickJsonNode(selector) {
    136  info("Expanding node: '" + selector + "'");
    137 
    138  // eslint-disable-next-line no-shadow
    139  return ContentTask.spawn(gBrowser.selectedBrowser, selector, selector => {
    140    content.document.querySelector(selector).click();
    141  });
    142 }
    143 
    144 /**
    145 * Select JSON View tab (in the content).
    146 */
    147 function selectJsonViewContentTab(name) {
    148  info("Selecting tab: '" + name + "'");
    149 
    150  // eslint-disable-next-line no-shadow
    151  return ContentTask.spawn(gBrowser.selectedBrowser, name, async name => {
    152    const tabsSelector = ".tabs-menu .tabs-menu-item";
    153    const targetTabSelector = `${tabsSelector}.${CSS.escape(name)}`;
    154    const targetTab = content.document.querySelector(targetTabSelector);
    155    const targetTabIndex = Array.prototype.indexOf.call(
    156      content.document.querySelectorAll(tabsSelector),
    157      targetTab
    158    );
    159    const targetTabButton = targetTab.querySelector("a");
    160    await new Promise(resolve => {
    161      content.addEventListener(
    162        "TabChanged",
    163        ({ detail: { index } }) => {
    164          is(index, targetTabIndex, "Hm?");
    165          if (index === targetTabIndex) {
    166            resolve();
    167          }
    168        },
    169        { once: true }
    170      );
    171      targetTabButton.click();
    172    });
    173    is(
    174      targetTabButton.getAttribute("aria-selected"),
    175      "true",
    176      "Tab is now selected"
    177    );
    178  });
    179 }
    180 
    181 function getElementCount(selector) {
    182  info("Get element count: '" + selector + "'");
    183 
    184  return SpecialPowers.spawn(
    185    gBrowser.selectedBrowser,
    186    [selector],
    187    selectorChild => {
    188      return content.document.querySelectorAll(selectorChild).length;
    189    }
    190  );
    191 }
    192 
    193 function getElementText(selector) {
    194  info("Get element text: '" + selector + "'");
    195 
    196  return SpecialPowers.spawn(
    197    gBrowser.selectedBrowser,
    198    [selector],
    199    selectorChild => {
    200      const element = content.document.querySelector(selectorChild);
    201      return element ? element.textContent : null;
    202    }
    203  );
    204 }
    205 
    206 function getElementAttr(selector, attr) {
    207  info("Get attribute '" + attr + "' for element '" + selector + "'");
    208 
    209  return SpecialPowers.spawn(
    210    gBrowser.selectedBrowser,
    211    [selector, attr],
    212    (selectorChild, attrChild) => {
    213      const element = content.document.querySelector(selectorChild);
    214      return element ? element.getAttribute(attrChild) : null;
    215    }
    216  );
    217 }
    218 
    219 /**
    220 * Return the text of a row given its index, e.g. `key: "value"`
    221 *
    222 * @param {number} rowIndex
    223 * @returns {Promise<string>}
    224 */
    225 async function getRowText(rowIndex) {
    226  const key = await getElementText(
    227    `.jsonPanelBox .treeTable .treeRow:nth-of-type(${rowIndex + 1}) .treeLabelCell`
    228  );
    229  const value = await getElementText(
    230    `.jsonPanelBox .treeTable .treeRow:nth-of-type(${rowIndex + 1}) .treeValueCell`
    231  );
    232  return `${key}: ${value}`;
    233 }
    234 
    235 function focusElement(selector) {
    236  info("Focus element: '" + selector + "'");
    237 
    238  return SpecialPowers.spawn(
    239    gBrowser.selectedBrowser,
    240    [selector],
    241    selectorChild => {
    242      const element = content.document.querySelector(selectorChild);
    243      if (element) {
    244        element.focus();
    245      }
    246    }
    247  );
    248 }
    249 
    250 /**
    251 * Send the string aStr to the focused element.
    252 *
    253 * For now this method only works for ASCII characters and emulates the shift
    254 * key state on US keyboard layout.
    255 */
    256 function sendString(str, selector) {
    257  info("Send string: '" + str + "'");
    258 
    259  return SpecialPowers.spawn(
    260    gBrowser.selectedBrowser,
    261    [selector, str],
    262    (selectorChild, strChild) => {
    263      if (selectorChild) {
    264        const element = content.document.querySelector(selectorChild);
    265        if (element) {
    266          element.focus();
    267        }
    268      }
    269 
    270      EventUtils.sendString(strChild, content);
    271    }
    272  );
    273 }
    274 
    275 function waitForTime(delay) {
    276  return new Promise(resolve => setTimeout(resolve, delay));
    277 }
    278 
    279 function waitForFilter() {
    280  return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    281    return new Promise(resolve => {
    282      const firstRow = content.document.querySelector(
    283        ".jsonPanelBox .treeTable .treeRow"
    284      );
    285 
    286      // Check if the filter is already set.
    287      if (firstRow.classList.contains("hidden")) {
    288        resolve();
    289        return;
    290      }
    291 
    292      // Wait till the first row has 'hidden' class set.
    293      const observer = new content.MutationObserver(function (mutations) {
    294        for (let i = 0; i < mutations.length; i++) {
    295          const mutation = mutations[i];
    296          if (mutation.attributeName == "class") {
    297            if (firstRow.classList.contains("hidden")) {
    298              observer.disconnect();
    299              resolve();
    300              break;
    301            }
    302          }
    303        }
    304      });
    305 
    306      observer.observe(firstRow, { attributes: true });
    307    });
    308  });
    309 }
    310 
    311 function normalizeNewLines(value) {
    312  return value.replace("(\r\n|\n)", "\n");
    313 }