tor-browser

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

browser.js (21890B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 // NOTE: If you're adding new test harness functionality to this file -- first,
      7 //       should you at all?  Most stuff is better in specific tests, or in
      8 //       nested shell.js/browser.js.  Second, can you instead add it to
      9 //       shell.js?  Our goal is to unify these two files for readability, and
     10 //       the plan is to empty out this file into that one over time.  Third,
     11 //       supposing you must add to this file, please add it to this IIFE for
     12 //       better modularity/resilience against tests that must do particularly
     13 //       bizarre things that might break the harness.
     14 
     15 (function initializeUtilityExports(global, parent) {
     16  "use strict";
     17 
     18  /**********************************************************************
     19   * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
     20   **********************************************************************/
     21 
     22  var Error = global.Error;
     23  var String = global.String;
     24  var GlobalEval = global.eval;
     25  var ReflectApply = global.Reflect.apply;
     26  var FunctionToString = global.Function.prototype.toString;
     27  var ObjectDefineProperty = global.Object.defineProperty;
     28 
     29  // BEWARE: ObjectGetOwnPropertyDescriptor is only safe to use if its result
     30  //         is inspected using own-property-examining functionality.  Directly
     31  //         accessing properties on a returned descriptor without first
     32  //         verifying the property's existence can invoke user-modifiable
     33  //         behavior.
     34  var ObjectGetOwnPropertyDescriptor = global.Object.getOwnPropertyDescriptor;
     35 
     36  var {get: ArrayBufferByteLength} =
     37    ObjectGetOwnPropertyDescriptor(global.ArrayBuffer.prototype, "byteLength");
     38 
     39  var Worker = global.Worker;
     40  var Blob = global.Blob;
     41  var URL = global.URL;
     42 
     43  var document = global.document;
     44  var documentDocumentElement = global.document.documentElement;
     45  var DocumentCreateElement = global.document.createElement;
     46 
     47  var DocumentPrototypeAllGetter = ObjectGetOwnPropertyDescriptor(global.Document.prototype, "all").get;
     48  var EventTargetPrototypeAddEventListener = global.EventTarget.prototype.addEventListener;
     49  var HTMLElementPrototypeStyleSetter =
     50    ObjectGetOwnPropertyDescriptor(global.HTMLElement.prototype, "style").set;
     51  var HTMLIFramePrototypeContentWindowGetter =
     52    ObjectGetOwnPropertyDescriptor(global.HTMLIFrameElement.prototype, "contentWindow").get;
     53  var HTMLScriptElementTextSetter =
     54    ObjectGetOwnPropertyDescriptor(global.HTMLScriptElement.prototype, "text").set;
     55  var NodePrototypeAppendChild = global.Node.prototype.appendChild;
     56  var NodePrototypeRemoveChild = global.Node.prototype.removeChild;
     57  var {get: WindowOnErrorGetter, set: WindowOnErrorSetter} =
     58    ObjectGetOwnPropertyDescriptor(global, "onerror");
     59  var WorkerPrototypePostMessage = Worker.prototype.postMessage;
     60  var URLCreateObjectURL = URL.createObjectURL;
     61 
     62  // List of saved window.onerror handlers.
     63  var savedGlobalOnError = [];
     64 
     65  // Set |newOnError| as the current window.onerror handler.
     66  function setGlobalOnError(newOnError) {
     67    var currentOnError = ReflectApply(WindowOnErrorGetter, global, []);
     68    ArrayPush(savedGlobalOnError, currentOnError);
     69    ReflectApply(WindowOnErrorSetter, global, [newOnError]);
     70  }
     71 
     72  // Restore the previous window.onerror handler.
     73  function restoreGlobalOnError() {
     74    var previousOnError = ArrayPop(savedGlobalOnError);
     75    ReflectApply(WindowOnErrorSetter, global, [previousOnError]);
     76  }
     77 
     78  /****************************
     79   * GENERAL HELPER FUNCTIONS *
     80   ****************************/
     81 
     82  function ArrayPush(array, value) {
     83    ReflectApply(ObjectDefineProperty, null, [
     84      array, array.length,
     85      {__proto__: null, value, writable: true, enumerable: true, configurable: true}
     86    ]);
     87  }
     88 
     89  function ArrayPop(array) {
     90    if (array.length) {
     91      var item = array[array.length - 1];
     92      array.length -= 1;
     93      return item;
     94    }
     95  }
     96 
     97  function AppendChild(elt, kid) {
     98    ReflectApply(NodePrototypeAppendChild, elt, [kid]);
     99  }
    100 
    101  function CreateElement(name) {
    102    return ReflectApply(DocumentCreateElement, document, [name]);
    103  }
    104 
    105  function RemoveChild(elt, kid) {
    106    ReflectApply(NodePrototypeRemoveChild, elt, [kid]);
    107  }
    108 
    109  function CreateWorker(script) {
    110    var blob = new Blob([script], {__proto__: null, type: "text/javascript"});
    111    return new Worker(URLCreateObjectURL(blob));
    112  }
    113 
    114  /****************************
    115   * UTILITY FUNCTION EXPORTS *
    116   ****************************/
    117 
    118  var evaluate = global.evaluate;
    119  if (typeof evaluate !== "function") {
    120    // Shim in "evaluate".
    121    evaluate = function evaluate(code) {
    122      if (typeof code !== "string")
    123        throw Error("Expected string argument for evaluate()");
    124 
    125      return GlobalEval(code);
    126    };
    127 
    128    global.evaluate = evaluate;
    129  }
    130 
    131  var evaluateScript = global.evaluateScript;
    132  if (typeof evaluateScript !== "function") {
    133    evaluateScript = function evaluateScript(code) {
    134      code = String(code);
    135      var script = CreateElement("script");
    136 
    137      // Temporarily install a new onerror handler to catch script errors.
    138      var hasUncaughtError = false;
    139      var uncaughtError;
    140 
    141      setGlobalOnError(function(messageOrEvent, source, lineno, colno, error) {
    142        hasUncaughtError = true;
    143        uncaughtError = error;
    144        return true;
    145      });
    146      ReflectApply(HTMLScriptElementTextSetter, script, [code]);
    147      AppendChild(documentDocumentElement, script);
    148      RemoveChild(documentDocumentElement, script);
    149      restoreGlobalOnError();
    150 
    151      if (hasUncaughtError)
    152        throw uncaughtError;
    153    };
    154 
    155    global.evaluateScript = evaluateScript;
    156  }
    157 
    158  var newGlobal = global.newGlobal;
    159  if (typeof newGlobal !== "function") {
    160    // Reuse the parent's newGlobal to ensure iframes can be added to the DOM.
    161    newGlobal = parent ? parent.newGlobal : function newGlobal() {
    162      var iframe = CreateElement("iframe");
    163      AppendChild(documentDocumentElement, iframe);
    164      var win =
    165        ReflectApply(HTMLIFramePrototypeContentWindowGetter, iframe, []);
    166 
    167      // Removing the iframe breaks evaluateScript() and detachArrayBuffer().
    168      ReflectApply(HTMLElementPrototypeStyleSetter, iframe, ["display:none"]);
    169 
    170      // Create utility functions in the new global object.
    171      var initFunction = ReflectApply(FunctionToString, initializeUtilityExports, []);
    172      win.Function("parent", initFunction + "; initializeUtilityExports(this, parent);")(global);
    173 
    174      return win;
    175    };
    176 
    177    global.newGlobal = newGlobal;
    178  }
    179 
    180  var detachArrayBuffer = global.detachArrayBuffer;
    181  if (typeof detachArrayBuffer !== "function") {
    182    var worker = null;
    183    detachArrayBuffer = function detachArrayBuffer(arrayBuffer) {
    184      if (worker === null) {
    185        worker = CreateWorker("/* black hole */");
    186      }
    187      try {
    188        ReflectApply(WorkerPrototypePostMessage, worker, ["detach", [arrayBuffer]]);
    189      } catch (e) {
    190        // postMessage throws an error if the array buffer was already detached.
    191        // Test for this condition by checking if the byte length is zero.
    192        if (ReflectApply(ArrayBufferByteLength, arrayBuffer, []) !== 0) {
    193          throw e;
    194        }
    195      }
    196    };
    197 
    198    global.detachArrayBuffer = detachArrayBuffer;
    199  }
    200 
    201  var createIsHTMLDDA = global.createIsHTMLDDA;
    202  if (typeof createIsHTMLDDA !== "function") {
    203    createIsHTMLDDA = function() {
    204      return ReflectApply(DocumentPrototypeAllGetter, document, []);
    205    };
    206 
    207    global.createIsHTMLDDA = createIsHTMLDDA;
    208  }
    209 })(this);
    210 
    211 (function(global) {
    212  "use strict";
    213 
    214  /**********************************************************************
    215   * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
    216   **********************************************************************/
    217 
    218  var undefined; // sigh
    219 
    220  var Error = global.Error;
    221  var Number = global.Number;
    222  var Object = global.Object;
    223  var String = global.String;
    224 
    225  var decodeURIComponent = global.decodeURIComponent;
    226  var ReflectApply = global.Reflect.apply;
    227  var ObjectDefineProperty = Object.defineProperty;
    228  var ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
    229  var ObjectPrototypeIsPrototypeOf = Object.prototype.isPrototypeOf;
    230 
    231  // BEWARE: ObjectGetOwnPropertyDescriptor is only safe to use if its result
    232  //         is inspected using own-property-examining functionality.  Directly
    233  //         accessing properties on a returned descriptor without first
    234  //         verifying the property's existence can invoke user-modifiable
    235  //         behavior.
    236  var ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
    237 
    238  var window = global.window;
    239  var document = global.document;
    240  var documentDocumentElement = document.documentElement;
    241  var DocumentCreateElement = document.createElement;
    242  var ElementSetClassName =
    243    ObjectGetOwnPropertyDescriptor(global.Element.prototype, "className").set;
    244  var NodePrototypeAppendChild = global.Node.prototype.appendChild;
    245  var NodePrototypeTextContentSetter =
    246    ObjectGetOwnPropertyDescriptor(global.Node.prototype, "textContent").set;
    247  var setTimeout = global.setTimeout;
    248 
    249  // Saved harness functions.
    250  var dump = global.dump;
    251  var gczeal = global.gczeal;
    252  var print = global.print;
    253  var reportFailure = global.reportFailure;
    254  var TestCase = global.TestCase;
    255 
    256  var SpecialPowers = global.SpecialPowers;
    257  var SpecialPowersCu = SpecialPowers.Cu;
    258  var SpecialPowersForceGC = SpecialPowers.forceGC;
    259  var TestingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
    260  var ClearKeptObjects = TestingFunctions.clearKeptObjects;
    261 
    262  // Cached DOM nodes used by the test harness itself.  (We assume the test
    263  // doesn't misbehave in a way that actively interferes with what the test
    264  // harness runner observes, e.g. navigating the page to a different location.
    265  // Short of running every test in a worker -- which has its own problems --
    266  // there's no way to isolate a test from the page to that extent.)
    267  var printOutputContainer =
    268    global.document.getElementById("jsreftest-print-output-container");
    269 
    270  /****************************
    271   * GENERAL HELPER FUNCTIONS *
    272   ****************************/
    273 
    274  function ArrayPush(array, value) {
    275    ReflectApply(ObjectDefineProperty, null, [
    276      array, array.length,
    277      {__proto__: null, value, writable: true, enumerable: true, configurable: true}
    278    ]);
    279  }
    280 
    281  function ArrayPop(array) {
    282    if (array.length) {
    283      var item = array[array.length - 1];
    284      array.length -= 1;
    285      return item;
    286    }
    287  }
    288 
    289  function HasOwnProperty(object, property) {
    290    return ReflectApply(ObjectPrototypeHasOwnProperty, object, [property]);
    291  }
    292 
    293  function AppendChild(elt, kid) {
    294    ReflectApply(NodePrototypeAppendChild, elt, [kid]);
    295  }
    296 
    297  function CreateElement(name) {
    298    return ReflectApply(DocumentCreateElement, document, [name]);
    299  }
    300 
    301  function SetTextContent(element, text) {
    302    ReflectApply(NodePrototypeTextContentSetter, element, [text]);
    303  }
    304 
    305  // Object containing the set options.
    306  var currentOptions;
    307 
    308  // browser.js version of shell.js' |shellOptionsClear| function.
    309  function browserOptionsClear() {
    310    for (var optionName in currentOptions) {
    311      delete currentOptions[optionName];
    312      SpecialPowersCu[optionName] = false;
    313    }
    314  }
    315 
    316  // This function is *only* used by shell.js's for-browsers |print()| function!
    317  // It's only defined/exported here because it needs CreateElement and friends,
    318  // only defined here, and we're not yet ready to move them to shell.js.
    319  function AddPrintOutput(s) {
    320    var msgDiv = CreateElement("div");
    321    SetTextContent(msgDiv, s);
    322    AppendChild(printOutputContainer, msgDiv);
    323  }
    324  global.AddPrintOutput = AddPrintOutput;
    325 
    326  /*************************************************************************
    327   * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) *
    328   *************************************************************************/
    329 
    330  // This overwrites shell.js's version that merely prints the given string.
    331  function writeHeaderToLog(string) {
    332    string = String(string);
    333 
    334    // First dump to the console.
    335    dump(string + "\n");
    336 
    337    // Then output to the page.
    338    var h2 = CreateElement("h2");
    339    SetTextContent(h2, string);
    340    AppendChild(printOutputContainer, h2);
    341  }
    342  global.writeHeaderToLog = writeHeaderToLog;
    343 
    344  /*************************
    345   * GLOBAL ERROR HANDLING *
    346   *************************/
    347 
    348  // Possible values:
    349  // - "Unknown" if no error is expected,
    350  // - "error" if no specific error type is expected,
    351  // - otherwise the error name, e.g. "TypeError" or "RangeError".
    352  var expectedError;
    353 
    354  window.onerror = function (msg, page, line, column, error) {
    355    // Unset all options even when the test finished with an error.
    356    browserOptionsClear();
    357 
    358    if (DESCRIPTION === undefined) {
    359      DESCRIPTION = "Unknown";
    360    }
    361 
    362    var actual = "error";
    363    var expected = expectedError;
    364    if (expected !== "error" && expected !== "Unknown") {
    365      // Check the error type when an actual Error object is available.
    366      // NB: The |error| parameter of the onerror handler is not required to
    367      // be an Error instance.
    368      if (ReflectApply(ObjectPrototypeIsPrototypeOf, Error.prototype, [error])) {
    369        actual = error.constructor.name;
    370      } else {
    371        expected = "error";
    372      }
    373    }
    374 
    375    var reason = `${page}:${line}: ${msg}`;
    376    new TestCase(DESCRIPTION, expected, actual, reason);
    377 
    378    reportFailure(msg);
    379  };
    380 
    381  /**********************************************
    382   * BROWSER IMPLEMENTATION FOR SHELL FUNCTIONS *
    383   **********************************************/
    384 
    385  function gc() {
    386    try {
    387      SpecialPowersForceGC();
    388    } catch (ex) {
    389      print("gc: " + ex);
    390    }
    391  }
    392  global.gc = gc;
    393 
    394  global.clearKeptObjects = ClearKeptObjects;
    395 
    396  function options(aOptionName) {
    397    // return value of options() is a comma delimited list
    398    // of the previously set values
    399 
    400    var value = "";
    401    for (var optionName in currentOptions) {
    402      if (value)
    403        value += ",";
    404      value += optionName;
    405    }
    406 
    407    if (aOptionName) {
    408      if (!HasOwnProperty(SpecialPowersCu, aOptionName)) {
    409        // This test is trying to flip an unsupported option, so it's
    410        // likely no longer testing what it was supposed to.  Fail it
    411        // hard.
    412        throw "Unsupported JSContext option '" + aOptionName + "'";
    413      }
    414 
    415      if (aOptionName in currentOptions) {
    416        // option is set, toggle it to unset
    417        delete currentOptions[aOptionName];
    418        SpecialPowersCu[aOptionName] = false;
    419      } else {
    420        // option is not set, toggle it to set
    421        currentOptions[aOptionName] = true;
    422        SpecialPowersCu[aOptionName] = true;
    423      }
    424    }
    425 
    426    return value;
    427  }
    428  global.options = options;
    429 
    430  /****************************************
    431   * HARNESS SETUP AND TEARDOWN FUNCTIONS *
    432   ****************************************/
    433 
    434  function jsTestDriverBrowserInit() {
    435    // Initialize with an empty set, because we just turned off all options.
    436    currentOptions = Object.create(null);
    437 
    438    if (document.location.search.indexOf("?") !== 0) {
    439      // not called with a query string
    440      return;
    441    }
    442 
    443    var properties = Object.create(null);
    444    var fields = document.location.search.slice(1).split(";");
    445    for (var i = 0; i < fields.length; i++) {
    446      var propertycaptures = /^([^=]+)=(.*)$/.exec(fields[i]);
    447      if (propertycaptures === null) {
    448        properties[fields[i]] = true;
    449      } else {
    450        properties[propertycaptures[1]] = decodeURIComponent(propertycaptures[2]);
    451      }
    452    }
    453 
    454    // The test path may contain \ separators for the path.
    455    // Bug 1877606: use / consistently
    456    properties.test = properties.test.replace(/\\/g, "/");
    457 
    458    global.gTestPath = properties.test;
    459 
    460    var testpathparts = properties.test.split("/");
    461    if (testpathparts.length < 2) {
    462      // must have at least suitepath/testcase.js
    463      return;
    464    }
    465 
    466    var testFileName = testpathparts[testpathparts.length - 1];
    467 
    468    if (testFileName.endsWith("-n.js")) {
    469      // Negative test without a specific error type.
    470      expectedError = "error";
    471    } else if (properties.error) {
    472      // Negative test which expects a specific error type.
    473      expectedError = properties.error;
    474    } else {
    475      // No error is expected.
    476      expectedError = "Unknown";
    477    }
    478 
    479    if (properties.gczeal) {
    480      gczeal(Number(properties.gczeal));
    481    }
    482 
    483    // Display the test path in the title.
    484    document.title = properties.test;
    485 
    486    // Output script tags for shell.js, then browser.js, at each level of the
    487    // test path hierarchy.
    488    var prepath = "";
    489    var scripts = [];
    490    for (var i = 0; i < testpathparts.length - 1; i++) {
    491      prepath += testpathparts[i] + "/";
    492 
    493      if (properties["test262-raw"]) {
    494        // Skip running test harness files (shell.js and browser.js) if the
    495        // test has the raw flag.
    496        continue;
    497      }
    498 
    499      scripts.push({src: prepath + "shell.js", module: false});
    500      scripts.push({src: prepath + "browser.js", module: false});
    501    }
    502 
    503    // Output the test script itself.
    504    var moduleTest = !!properties.module;
    505    scripts.push({src: prepath + testFileName, module: moduleTest});
    506 
    507    // Finally output the driver-end script to advance to the next test.
    508    scripts.push({src: "js-test-driver-end.js", module: false});
    509 
    510    if (properties.async) {
    511      gDelayTestDriverEnd = true;
    512    }
    513 
    514    if (!moduleTest) {
    515      for (var i = 0; i < scripts.length; i++) {
    516        var src = scripts[i].src;
    517        document.write(`<script src="${src}" charset="utf-8"><\/script>`);
    518      }
    519    } else {
    520      // Modules are loaded asynchronously by default, but for the test harness
    521      // we need to execute all scripts and modules one after the other.
    522 
    523      // Appends the next script element to the DOM.
    524      function appendScript(index) {
    525        var script = scriptElements[index];
    526        scriptElements[index] = null;
    527        if (script !== null) {
    528          ReflectApply(NodePrototypeAppendChild, documentDocumentElement, [script]);
    529        }
    530      }
    531 
    532      // Create all script elements upfront, so we don't need to worry about
    533      // modified built-ins.
    534      var scriptElements = [];
    535      for (var i = 0; i < scripts.length; i++) {
    536        var spec = scripts[i];
    537 
    538        var script = document.createElement("script");
    539        script.charset = "utf-8";
    540        if (spec.module) {
    541          script.type = "module";
    542        }
    543        script.src = spec.src;
    544 
    545        let nextScriptIndex = i + 1;
    546        if (nextScriptIndex < scripts.length) {
    547          var callNextAppend = () => appendScript(nextScriptIndex);
    548          script.addEventListener("load", callNextAppend, {once: true});
    549          script.addEventListener("error", callNextAppend, {once: true});
    550        }
    551 
    552        scriptElements[i] = script;
    553      }
    554 
    555      // Append the first script.
    556      appendScript(0);
    557    }
    558  }
    559 
    560  global.gDelayTestDriverEnd = false;
    561 
    562  function jsTestDriverEnd() {
    563    // gDelayTestDriverEnd is used to delay collection of the test result and
    564    // signal to Spider so that tests can continue to run after page load has
    565    // fired. They are responsible for setting gDelayTestDriverEnd = true then
    566    // when completed, setting gDelayTestDriverEnd = false then calling
    567    // jsTestDriverEnd()
    568 
    569    if (gDelayTestDriverEnd) {
    570      return;
    571    }
    572 
    573    window.onerror = null;
    574 
    575    // Unset all options when the test has finished.
    576    browserOptionsClear();
    577 
    578    if (window.opener && window.opener.runNextTest) {
    579      if (window.opener.reportCallBack) {
    580        window.opener.reportCallBack(window.opener.gWindow);
    581      }
    582 
    583      setTimeout("window.opener.runNextTest()", 250);
    584    } else {
    585      // tell reftest the test is complete.
    586      ReflectApply(ElementSetClassName, documentDocumentElement, [""]);
    587      // tell Spider page is complete
    588      gPageCompleted = true;
    589    }
    590  }
    591  global.jsTestDriverEnd = jsTestDriverEnd;
    592 
    593  /***************************************************************************
    594   * DIALOG CLOSER, PRESUMABLY TO CLOSE SLOW SCRIPT DIALOGS AND OTHER POPUPS *
    595   ***************************************************************************/
    596 
    597  // dialog closer from http://bclary.com/projects/spider/spider/chrome/content/spider/dialog-closer.js
    598 
    599  // Use an array to handle the case where multiple dialogs appear at one time.
    600  var dialogCloserSubjects = [];
    601  var dialogCloser = SpecialPowers
    602                     .Cc["@mozilla.org/embedcomp/window-watcher;1"]
    603                     .getService(SpecialPowers.Ci.nsIWindowWatcher);
    604  var dialogCloserObserver = {
    605    observe(subject, topic, data) {
    606      if (topic === "domwindowopened" && subject.isChromeWindow) {
    607        ArrayPush(dialogCloserSubjects, subject);
    608 
    609        // Timeout of 0 needed when running under reftest framework.
    610        subject.setTimeout(closeDialog, 0);
    611      }
    612    }
    613  };
    614 
    615  function closeDialog() {
    616    while (dialogCloserSubjects.length > 0) {
    617      var subject = ArrayPop(dialogCloserSubjects);
    618      subject.close();
    619    }
    620  }
    621 
    622  function unregisterDialogCloser() {
    623    gczeal(0);
    624 
    625    if (!dialogCloserObserver || !dialogCloser) {
    626      return;
    627    }
    628 
    629    dialogCloser.unregisterNotification(dialogCloserObserver);
    630 
    631    dialogCloserObserver = null;
    632    dialogCloser = null;
    633  }
    634 
    635  dialogCloser.registerNotification(dialogCloserObserver);
    636  window.addEventListener("unload", unregisterDialogCloser, true);
    637 
    638  /*******************************************
    639   * RUN ONCE CODE TO SETUP ADDITIONAL STATE *
    640   *******************************************/
    641 
    642  jsTestDriverBrowserInit();
    643 
    644 })(this);
    645 
    646 var gPageCompleted;