tor-browser

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

common.sub.js (48204B)


      1 /**
      2 * @fileoverview Utilities for mixed-content in web-platform-tests.
      3 * @author burnik@google.com (Kristijan Burnik)
      4 * Disclaimer: Some methods of other authors are annotated in the corresponding
      5 *     method's JSDoc.
      6 */
      7 
      8 // ===============================================================
      9 // Types
     10 // ===============================================================
     11 // Objects of the following types are used to represent what kind of
     12 // subresource requests should be sent with what kind of policies,
     13 // from what kind of possibly nested source contexts.
     14 // The objects are represented as JSON objects (not JavaScript/Python classes
     15 // in a strict sense) to be passed between JavaScript/Python code.
     16 //
     17 // See also common/security-features/Types.md for high-level description.
     18 
     19 /**
     20  @typedef PolicyDelivery
     21  @type {object}
     22  Referrer policy etc. can be applied/delivered in several ways.
     23  A PolicyDelivery object specifies what policy is delivered and how.
     24 
     25  @property {string} deliveryType
     26    Specifies how the policy is delivered.
     27    The valid deliveryType are:
     28 
     29     "attr"
     30        [A] DOM attributes e.g. referrerPolicy.
     31 
     32      "rel-noref"
     33        [A] <link rel="noreferrer"> (referrer-policy only).
     34 
     35      "http-rp"
     36        [B] HTTP response headers.
     37 
     38      "meta"
     39        [B] <meta> elements.
     40 
     41  @property {string} key
     42  @property {string} value
     43    Specifies what policy to be delivered. The valid keys are:
     44 
     45      "referrerPolicy"
     46        Referrer Policy
     47        https://w3c.github.io/webappsec-referrer-policy/
     48        Valid values are those listed in
     49        https://w3c.github.io/webappsec-referrer-policy/#referrer-policy
     50        (except that "" is represented as null/None)
     51 
     52  A PolicyDelivery can be specified in several ways:
     53 
     54  - (for [A]) Associated with an individual subresource request and
     55    specified in `Subresource.policies`,
     56    e.g. referrerPolicy attributes of DOM elements.
     57    This is handled in invokeRequest().
     58 
     59  - (for [B]) Associated with an nested environmental settings object and
     60    specified in `SourceContext.policies`,
     61    e.g. HTTP referrer-policy response headers of HTML/worker scripts.
     62    This is handled in server-side under /common/security-features/scope/.
     63 
     64  - (for [B]) Associated with the top-level HTML document.
     65    This is handled by the generators.d
     66 */
     67 
     68 /**
     69  @typedef Subresource
     70  @type {object}
     71  A Subresource represents how a subresource request is sent.
     72 
     73  @property{SubresourceType} subresourceType
     74    How the subresource request is sent,
     75    e.g. "img-tag" for sending a request via <img src>.
     76    See the keys of `subresourceMap` for valid values.
     77 
     78  @property{string} url
     79    subresource's URL.
     80    Typically this is constructed by getRequestURLs() below.
     81 
     82  @property{PolicyDelivery} policyDeliveries
     83    Policies delivered specific to the subresource request.
     84 */
     85 
     86 /**
     87  @typedef SourceContext
     88  @type {object}
     89 
     90  @property {string} sourceContextType
     91    Kind of the source context to be used.
     92    Valid values are the keys of `sourceContextMap` below.
     93 
     94  @property {Array<PolicyDelivery>} policyDeliveries
     95    A list of PolicyDelivery applied to the source context.
     96 */
     97 
     98 // ===============================================================
     99 // General utility functions
    100 // ===============================================================
    101 
    102 function timeoutPromise(t, ms) {
    103  return new Promise(resolve => { t.step_timeout(resolve, ms); });
    104 }
    105 
    106 /**
    107 * Normalizes the target port for use in a URL. For default ports, this is the
    108 *     empty string (omitted port), otherwise it's a colon followed by the port
    109 *     number. Ports 80, 443 and an empty string are regarded as default ports.
    110 * @param {number} targetPort The port to use
    111 * @return {string} The port portion for using as part of a URL.
    112 */
    113 function getNormalizedPort(targetPort) {
    114  return ([80, 443, ""].indexOf(targetPort) >= 0) ? "" : ":" + targetPort;
    115 }
    116 
    117 /**
    118 * Creates a GUID.
    119 *     See: https://en.wikipedia.org/wiki/Globally_unique_identifier
    120 *     Original author: broofa (http://www.broofa.com/)
    121 *     Sourced from: http://stackoverflow.com/a/2117523/4949715
    122 * @return {string} A pseudo-random GUID.
    123 */
    124 function guid() {
    125  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    126    var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
    127    return v.toString(16);
    128  });
    129 }
    130 
    131 /**
    132 * Initiates a new XHR via GET.
    133 * @param {string} url The endpoint URL for the XHR.
    134 * @param {string} responseType Optional - how should the response be parsed.
    135 *     Default is "json".
    136 *     See: https://xhr.spec.whatwg.org/#dom-xmlhttprequest-responsetype
    137 * @return {Promise} A promise wrapping the success and error events.
    138 */
    139 function xhrRequest(url, responseType) {
    140  return new Promise(function(resolve, reject) {
    141    var xhr = new XMLHttpRequest();
    142    xhr.open('GET', url, true);
    143    xhr.responseType = responseType || "json";
    144 
    145    xhr.addEventListener("error", function() {
    146      reject(Error("Network Error"));
    147    });
    148 
    149    xhr.addEventListener("load", function() {
    150      if (xhr.status != 200)
    151        reject(Error(xhr.statusText));
    152      else
    153        resolve(xhr.response);
    154    });
    155 
    156    xhr.send();
    157  });
    158 }
    159 
    160 /**
    161 * Sets attributes on a given DOM element.
    162 * @param {DOMElement} element The element on which to set the attributes.
    163 * @param {object} An object with keys (serving as attribute names) and values.
    164 */
    165 function setAttributes(el, attrs) {
    166  attrs = attrs || {}
    167  for (var attr in attrs) {
    168    if (attr !== 'src')
    169      el.setAttribute(attr.toLowerCase(), attrs[attr]);
    170  }
    171  // Workaround for Chromium: set <img>'s src attribute after all other
    172  // attributes to ensure the policy is applied.
    173  for (var attr in attrs) {
    174    if (attr === 'src')
    175      el.setAttribute(attr, attrs[attr]);
    176  }
    177 }
    178 
    179 /**
    180 * Binds to success and error events of an object wrapping them into a promise
    181 *     available through {@code element.eventPromise}. The success event
    182 *     resolves and error event rejects.
    183 * This method adds event listeners, and then removes all the added listeners
    184 * when one of listened event is fired.
    185 * @param {object} element An object supporting events on which to bind the
    186 *     promise.
    187 * @param {string} resolveEventName [="load"] The event name to bind resolve to.
    188 * @param {string} rejectEventName [="error"] The event name to bind reject to.
    189 */
    190 function bindEvents(element, resolveEventName, rejectEventName) {
    191  element.eventPromise =
    192      bindEvents2(element, resolveEventName, element, rejectEventName);
    193 }
    194 
    195 // Returns a promise wrapping success and error events of objects.
    196 // This is a variant of bindEvents that can accept separate objects for each
    197 // events and two events to reject, and doesn't set `eventPromise`.
    198 //
    199 // When `resolveObject`'s `resolveEventName` event (default: "load") is
    200 // fired, the promise is resolved with the event.
    201 //
    202 // When `rejectObject`'s `rejectEventName` event (default: "error") or
    203 // `rejectObject2`'s `rejectEventName2` event (default: "error") is
    204 // fired, the promise is rejected.
    205 //
    206 // `rejectObject2` is optional.
    207 function bindEvents2(resolveObject, resolveEventName, rejectObject, rejectEventName, rejectObject2, rejectEventName2) {
    208  return new Promise(function(resolve, reject) {
    209    const actualResolveEventName = resolveEventName || "load";
    210    const actualRejectEventName = rejectEventName || "error";
    211    const actualRejectEventName2 = rejectEventName2 || "error";
    212 
    213    const resolveHandler = function(event) {
    214      cleanup();
    215      resolve(event);
    216    };
    217 
    218    const rejectHandler = function(event) {
    219      // Chromium starts propagating errors from worker.onerror to
    220      // window.onerror. This handles the uncaught exceptions in tests.
    221      event.preventDefault();
    222      cleanup();
    223      reject(event);
    224    };
    225 
    226    const cleanup = function() {
    227      resolveObject.removeEventListener(actualResolveEventName, resolveHandler);
    228      rejectObject.removeEventListener(actualRejectEventName, rejectHandler);
    229      if (rejectObject2) {
    230        rejectObject2.removeEventListener(actualRejectEventName2, rejectHandler);
    231      }
    232    };
    233 
    234    resolveObject.addEventListener(actualResolveEventName, resolveHandler);
    235    rejectObject.addEventListener(actualRejectEventName, rejectHandler);
    236    if (rejectObject2) {
    237      rejectObject2.addEventListener(actualRejectEventName2, rejectHandler);
    238    }
    239  });
    240 }
    241 
    242 /**
    243 * Creates a new DOM element.
    244 * @param {string} tagName The type of the DOM element.
    245 * @param {object} attrs A JSON with attributes to apply to the element.
    246 * @param {DOMElement} parent Optional - an existing DOM element to append to
    247 *     If not provided, the returned element will remain orphaned.
    248 * @param {boolean} doBindEvents Optional - Whether to bind to load and error
    249 *     events and provide the promise wrapping the events via the element's
    250 *     {@code eventPromise} property. Default value evaluates to false.
    251 * @return {DOMElement} The newly created DOM element.
    252 */
    253 function createElement(tagName, attrs, parentNode, doBindEvents) {
    254  var element = document.createElement(tagName);
    255 
    256  if (doBindEvents) {
    257    bindEvents(element);
    258    if (element.tagName == "IFRAME" && !('srcdoc' in attrs || 'src' in attrs)) {
    259      // If we're loading a frame, ensure we spin the event loop after load to
    260      // paper over the different event timing in Gecko vs Blink/WebKit
    261      // see https://github.com/whatwg/html/issues/4965
    262      element.eventPromise = element.eventPromise.then(() => {
    263        return new Promise(resolve => setTimeout(resolve, 0))
    264      });
    265    }
    266  }
    267  // We set the attributes after binding to events to catch any
    268  // event-triggering attribute changes. E.g. form submission.
    269  //
    270  // But be careful with images: unlike other elements they will start the load
    271  // as soon as the attr is set, even if not in the document yet, and sometimes
    272  // complete it synchronously, so the append doesn't have the effect we want.
    273  // So for images, we want to set the attrs after appending, whereas for other
    274  // elements we want to do it before appending.
    275  var isImg = (tagName == "img");
    276  if (!isImg)
    277    setAttributes(element, attrs);
    278 
    279  if (parentNode)
    280    parentNode.appendChild(element);
    281 
    282  if (isImg)
    283    setAttributes(element, attrs);
    284 
    285  return element;
    286 }
    287 
    288 function createRequestViaElement(tagName, attrs, parentNode) {
    289  return createElement(tagName, attrs, parentNode, true).eventPromise;
    290 }
    291 
    292 function wrapResult(server_data) {
    293  if (typeof(server_data) === "string") {
    294    throw server_data;
    295  }
    296  return {
    297    referrer: server_data.headers.referer,
    298    headers: server_data.headers
    299  }
    300 }
    301 
    302 // ===============================================================
    303 // Subresources
    304 // ===============================================================
    305 
    306 /**
    307  @typedef RequestResult
    308  @type {object}
    309  Represents the result of sending an request.
    310  All properties are optional. See the comments for
    311  requestVia*() and invokeRequest() below to see which properties are set.
    312 
    313  @property {Array<Object<string, string>>} headers
    314    HTTP request headers sent to server.
    315  @property {string} referrer - Referrer.
    316  @property {string} location - The URL of the subresource.
    317  @property {string} sourceContextUrl
    318    the URL of the global object where the actual request is sent.
    319 */
    320 
    321 /**
    322  requestVia*(url, additionalAttributes) functions send a subresource
    323  request from the current environment settings object.
    324 
    325  @param {string} url
    326    The URL of the subresource.
    327  @param {Object<string, string>} additionalAttributes
    328    Additional attributes set to DOM elements
    329    (element-initiated requests only).
    330 
    331  @returns {Promise} that are resolved with a RequestResult object
    332  on successful requests.
    333 
    334  - Category 1:
    335      `headers`: set.
    336      `referrer`: set via `document.referrer`.
    337      `location`: set via `document.location`.
    338      See `template/document.html.template`.
    339  - Category 2:
    340      `headers`: set.
    341      `referrer`: set to `headers.referer` by `wrapResult()`.
    342      `location`: not set.
    343  - Category 3:
    344      All the keys listed above are NOT set.
    345  `sourceContextUrl` is not set here.
    346 
    347  -------------------------------- -------- --------------------------
    348  Function name                    Category Used in
    349                                            -------- ------- ---------
    350                                            referrer mixed-  upgrade-
    351                                            policy   content insecure-
    352                                            policy   content request
    353  -------------------------------- -------- -------- ------- ---------
    354  requestViaAnchor                 1        Y        Y       -
    355  requestViaArea                   1        Y        Y       -
    356  requestViaAudio                  3        -        Y       -
    357  requestViaDedicatedWorker        2        Y        Y       Y
    358  requestViaFetch                  2        Y        Y       -
    359  requestViaForm                   2        -        Y       -
    360  requestViaIframe                 1        Y        Y       -
    361  requestViaImage                  2        Y        Y       -
    362  requestViaLinkPrefetch           3        -        Y       -
    363  requestViaLinkStylesheet         3        -        Y       -
    364  requestViaObject                 3        -        Y       -
    365  requestViaPicture                3        -        Y       -
    366  requestViaScript                 2        Y        Y       -
    367  requestViaSendBeacon             3        -        Y       -
    368  requestViaSharedWorker           2        Y        Y       Y
    369  requestViaVideo                  3        -        Y       -
    370  requestViaWebSocket              3        -        Y       -
    371  requestViaWorklet                3        -        Y       Y
    372  requestViaXhr                    2        Y        Y       -
    373  -------------------------------- -------- -------- ------- ---------
    374 */
    375 
    376 /**
    377 * Creates a new iframe, binds load and error events, sets the src attribute and
    378 *     appends it to {@code document.body} .
    379 * @param {string} url The src for the iframe.
    380 * @return {Promise} The promise for success/error events.
    381 */
    382 function requestViaIframe(url, additionalAttributes) {
    383  const iframe = createElement(
    384      "iframe",
    385      Object.assign({"src": url}, additionalAttributes),
    386      document.body,
    387      false);
    388  return bindEvents2(window, "message", iframe, "error", window, "error")
    389      .then(event => {
    390          if (event.source !== iframe.contentWindow)
    391            return Promise.reject(new Error('Unexpected event.source'));
    392          return event.data;
    393        });
    394 }
    395 
    396 /**
    397 * Creates a new image, binds load and error events, sets the src attribute and
    398 *     appends it to {@code document.body} .
    399 * @param {string} url The src for the image.
    400 * @return {Promise} The promise for success/error events.
    401 */
    402 function requestViaImage(url, additionalAttributes) {
    403  const img = createElement(
    404      "img",
    405      // crossOrigin attribute is added to read the pixel data of the response.
    406      Object.assign({"src": url, "crossOrigin": "Anonymous"}, additionalAttributes),
    407      document.body, true);
    408  return img.eventPromise.then(() => wrapResult(decodeImageData(img)));
    409 }
    410 
    411 // Helper for requestViaImage().
    412 function decodeImageData(img) {
    413  var canvas = document.createElement("canvas");
    414  var context = canvas.getContext('2d');
    415  context.drawImage(img, 0, 0);
    416  var imgData = context.getImageData(0, 0, img.clientWidth, img.clientHeight);
    417  const rgba = imgData.data;
    418 
    419  let decodedBytes = new Uint8ClampedArray(rgba.length);
    420  let decodedLength = 0;
    421 
    422  for (var i = 0; i + 12 <= rgba.length; i += 12) {
    423    // A single byte is encoded in three pixels. 8 pixel octets (among
    424    // 9 octets = 3 pixels * 3 channels) are used to encode 8 bits,
    425    // the most significant bit first, where `0` and `255` in pixel values
    426    // represent `0` and `1` in bits, respectively.
    427    // This encoding is used to avoid errors due to different color spaces.
    428    const bits = [];
    429    for (let j = 0; j < 3; ++j) {
    430      bits.push(rgba[i + j * 4 + 0]);
    431      bits.push(rgba[i + j * 4 + 1]);
    432      bits.push(rgba[i + j * 4 + 2]);
    433      // rgba[i + j * 4 + 3]: Skip alpha channel.
    434    }
    435    // The last one element is not used.
    436    bits.pop();
    437 
    438    // Decode a single byte.
    439    let byte = 0;
    440    for (let j = 0; j < 8; ++j) {
    441      byte <<= 1;
    442      if (bits[j] >= 128)
    443        byte |= 1;
    444    }
    445 
    446    // Zero is the string terminator.
    447    if (byte == 0)
    448      break;
    449 
    450    decodedBytes[decodedLength++] = byte;
    451  }
    452 
    453  // Remove trailing nulls from data.
    454  decodedBytes = decodedBytes.subarray(0, decodedLength);
    455  var string_data = (new TextDecoder("ascii")).decode(decodedBytes);
    456 
    457  return JSON.parse(string_data);
    458 }
    459 
    460 /**
    461 * Initiates a new XHR GET request to provided URL.
    462 * @param {string} url The endpoint URL for the XHR.
    463 * @return {Promise} The promise for success/error events.
    464 */
    465 function requestViaXhr(url) {
    466  return xhrRequest(url).then(result => wrapResult(result));
    467 }
    468 
    469 /**
    470 * Initiates a new GET request to provided URL via the Fetch API.
    471 * @param {string} url The endpoint URL for the Fetch.
    472 * @return {Promise} The promise for success/error events.
    473 */
    474 function requestViaFetch(url) {
    475  return fetch(url)
    476    .then(res => res.json())
    477    .then(j => wrapResult(j));
    478 }
    479 
    480 function dedicatedWorkerUrlThatFetches(url) {
    481  return `data:text/javascript,
    482    fetch('${url}')
    483      .then(r => r.json())
    484      .then(j => postMessage(j))
    485      .catch((e) => postMessage(e.message));`;
    486 }
    487 
    488 function workerUrlThatImports(url, additionalAttributes) {
    489  let csp = "";
    490  if (additionalAttributes && additionalAttributes.contentSecurityPolicy) {
    491    csp=`&contentSecurityPolicy=${additionalAttributes.contentSecurityPolicy}`;
    492  }
    493  return `/common/security-features/subresource/static-import.py` +
    494      `?import_url=${encodeURIComponent(url)}${csp}`;
    495 }
    496 
    497 function workerDataUrlThatImports(url) {
    498  return `data:text/javascript,import '${url}';`;
    499 }
    500 
    501 /**
    502 * Creates a new Worker, binds message and error events wrapping them into.
    503 *     {@code worker.eventPromise} and posts an empty string message to start
    504 *     the worker.
    505 * @param {string} url The endpoint URL for the worker script.
    506 * @param {object} options The options for Worker constructor.
    507 * @return {Promise} The promise for success/error events.
    508 */
    509 function requestViaDedicatedWorker(url, options) {
    510  var worker;
    511  try {
    512    worker = new Worker(url, options);
    513  } catch (e) {
    514    return Promise.reject(e);
    515  }
    516  worker.postMessage('');
    517  return bindEvents2(worker, "message", worker, "error")
    518    .then(event => wrapResult(event.data));
    519 }
    520 
    521 function requestViaSharedWorker(url, options) {
    522  var worker;
    523  try {
    524    worker = new SharedWorker(url, options);
    525  } catch(e) {
    526    return Promise.reject(e);
    527  }
    528  const promise = bindEvents2(worker.port, "message", worker, "error")
    529    .then(event => wrapResult(event.data));
    530  worker.port.start();
    531  return promise;
    532 }
    533 
    534 // Returns a reference to a worklet object corresponding to a given type.
    535 function get_worklet(type) {
    536  if (type == 'animation')
    537    return CSS.animationWorklet;
    538  if (type == 'layout')
    539    return CSS.layoutWorklet;
    540  if (type == 'paint')
    541    return CSS.paintWorklet;
    542  if (type == 'audio')
    543    return new OfflineAudioContext(2,44100*40,44100).audioWorklet;
    544 
    545  throw new Error('unknown worklet type is passed.');
    546 }
    547 
    548 function requestViaWorklet(type, url) {
    549  try {
    550    return get_worklet(type).addModule(url);
    551  } catch (e) {
    552    return Promise.reject(e);
    553  }
    554 }
    555 
    556 /**
    557 * Creates a navigable element with the name `navigableElementName`
    558 * (<a>, <area>, or <form>) under `parentNode`, and
    559 * performs a navigation by `trigger()` (e.g. clicking <a>).
    560 * To avoid navigating away from the current execution context,
    561 * a target attribute is set to point to a new helper iframe.
    562 * @param {string} navigableElementName
    563 * @param {object} additionalAttributes The attributes of the navigable element.
    564 * @param {DOMElement} parentNode
    565 * @param {function(DOMElement} trigger A callback called after the navigable
    566 * element is inserted and should trigger navigation using the element.
    567 * @return {Promise} The promise for success/error events.
    568 */
    569 function requestViaNavigable(navigableElementName, additionalAttributes,
    570                             parentNode, trigger) {
    571  const name = guid();
    572 
    573  const iframe =
    574    createElement("iframe", {"name": name, "id": name}, parentNode, false);
    575 
    576  const navigable = createElement(
    577      navigableElementName,
    578      Object.assign({"target": name}, additionalAttributes),
    579      parentNode, false);
    580 
    581  const promise =
    582    bindEvents2(window, "message", iframe, "error", window, "error")
    583      .then(event => {
    584          if (event.source !== iframe.contentWindow)
    585            return Promise.reject(new Error('Unexpected event.source'));
    586          return event.data;
    587        });
    588  trigger(navigable);
    589  return promise;
    590 }
    591 
    592 /**
    593 * Creates a new anchor element, appends it to {@code document.body} and
    594 *     performs the navigation.
    595 * @param {string} url The URL to navigate to.
    596 * @return {Promise} The promise for success/error events.
    597 */
    598 function requestViaAnchor(url, additionalAttributes) {
    599  return requestViaNavigable(
    600      "a",
    601      Object.assign({"href": url, "innerHTML": "Link to resource"},
    602                    additionalAttributes),
    603      document.body, a => a.click());
    604 }
    605 
    606 /**
    607 * Creates a new area element, appends it to {@code document.body} and performs
    608 *     the navigation.
    609 * @param {string} url The URL to navigate to.
    610 * @return {Promise} The promise for success/error events.
    611 */
    612 function requestViaArea(url, additionalAttributes) {
    613  // TODO(kristijanburnik): Append to map and add image.
    614  return requestViaNavigable(
    615      "area",
    616      Object.assign({"href": url}, additionalAttributes),
    617      document.body, area => area.click());
    618 }
    619 
    620 /**
    621 * Creates a new script element, sets the src to url, and appends it to
    622 *     {@code document.body}.
    623 * @param {string} url The src URL.
    624 * @return {Promise} The promise for success/error events.
    625 */
    626 function requestViaScript(url, additionalAttributes) {
    627  const script = createElement(
    628      "script",
    629      Object.assign({"src": url}, additionalAttributes),
    630      document.body,
    631      false);
    632 
    633  return bindEvents2(window, "message", script, "error", window, "error")
    634    .then(event => wrapResult(event.data));
    635 }
    636 
    637 /**
    638 * Creates a new script element that performs a dynamic import to `url`, and
    639 * appends the script element to {@code document.body}.
    640 * @param {string} url The src URL.
    641 * @return {Promise} The promise for success/error events.
    642 */
    643 function requestViaDynamicImport(url, additionalAttributes) {
    644  const scriptUrl = `data:text/javascript,import("${url}");`;
    645  const script = createElement(
    646      "script",
    647      Object.assign({"src": scriptUrl}, additionalAttributes),
    648      document.body,
    649      false);
    650 
    651  return bindEvents2(window, "message", script, "error", window, "error")
    652    .then(event => wrapResult(event.data));
    653 }
    654 
    655 /**
    656 * Creates a new form element, sets attributes, appends it to
    657 *     {@code document.body} and submits the form.
    658 * @param {string} url The URL to submit to.
    659 * @return {Promise} The promise for success/error events.
    660 */
    661 function requestViaForm(url, additionalAttributes) {
    662  return requestViaNavigable(
    663      "form",
    664      Object.assign({"action": url, "method": "POST"}, additionalAttributes),
    665      document.body, form => form.submit());
    666 }
    667 
    668 /**
    669 * Creates a new link element for a stylesheet, binds load and error events,
    670 *     sets the href to url and appends it to {@code document.head}.
    671 * @param {string} url The URL for a stylesheet.
    672 * @return {Promise} The promise for success/error events.
    673 */
    674 function requestViaLinkStylesheet(url) {
    675  return createRequestViaElement("link",
    676                                 {"rel": "stylesheet", "href": url},
    677                                 document.head);
    678 }
    679 
    680 /**
    681 * Creates a new link element for a prefetch, binds load and error events, sets
    682 *     the href to url and appends it to {@code document.head}.
    683 * @param {string} url The URL of a resource to prefetch.
    684 * @return {Promise} The promise for success/error events.
    685 */
    686 function requestViaLinkPrefetch(url) {
    687  var link = document.createElement('link');
    688  if (link.relList && link.relList.supports && link.relList.supports("prefetch")) {
    689    return createRequestViaElement("link",
    690                                   {"rel": "prefetch", "href": url},
    691                                   document.head);
    692  } else {
    693    return Promise.reject("This browser does not support 'prefetch'.");
    694  }
    695 }
    696 
    697 /**
    698 * Initiates a new beacon request.
    699 * @param {string} url The URL of a resource to prefetch.
    700 * @return {Promise} The promise for success/error events.
    701 */
    702 async function requestViaSendBeacon(url) {
    703  function wait(ms) {
    704    return new Promise(resolve => step_timeout(resolve, ms));
    705  }
    706  if (!navigator.sendBeacon(url)) {
    707    // If mixed-content check fails, it should return false.
    708    throw new Error('sendBeacon() fails.');
    709  }
    710  // We don't have a means to see the result of sendBeacon() request
    711  // for sure. Let's wait for a while and let the generic test function
    712  // ask the server for the result.
    713  await wait(500);
    714  return 'allowed';
    715 }
    716 
    717 /**
    718 * Creates a new media element with a child source element, binds loadeddata and
    719 *     error events, sets attributes and appends to document.body.
    720 * @param {string} type The type of the media element (audio/video/picture).
    721 * @param {object} media_attrs The attributes for the media element.
    722 * @param {object} source_attrs The attributes for the child source element.
    723 * @return {DOMElement} The newly created media element.
    724 */
    725 function createMediaElement(type, media_attrs, source_attrs) {
    726  var mediaElement = createElement(type, {});
    727 
    728  var sourceElement = createElement("source", {});
    729 
    730  mediaElement.eventPromise = new Promise(function(resolve, reject) {
    731    mediaElement.addEventListener("loadeddata", function (e) {
    732      resolve(e);
    733    });
    734 
    735    // Safari doesn't fire an `error` event when blocking mixed content.
    736    mediaElement.addEventListener("stalled", function(e) {
    737      reject(e);
    738    });
    739 
    740    sourceElement.addEventListener("error", function(e) {
    741      reject(e);
    742    });
    743  });
    744 
    745  setAttributes(mediaElement, media_attrs);
    746  setAttributes(sourceElement, source_attrs);
    747 
    748  mediaElement.appendChild(sourceElement);
    749  document.body.appendChild(mediaElement);
    750 
    751  return mediaElement;
    752 }
    753 
    754 /**
    755 * Creates a new video element, binds loadeddata and error events, sets
    756 *     attributes and source URL and appends to {@code document.body}.
    757 * @param {string} url The URL of the video.
    758 * @return {Promise} The promise for success/error events.
    759 */
    760 function requestViaVideo(url) {
    761  return createMediaElement("video",
    762                            {},
    763                            {"src": url}).eventPromise;
    764 }
    765 
    766 /**
    767 * Creates a new audio element, binds loadeddata and error events, sets
    768 *     attributes and source URL and appends to {@code document.body}.
    769 * @param {string} url The URL of the audio.
    770 * @return {Promise} The promise for success/error events.
    771 */
    772 function requestViaAudio(url) {
    773  return createMediaElement("audio",
    774                            {},
    775                            {"type": "audio/wav", "src": url}).eventPromise;
    776 }
    777 
    778 /**
    779 * Creates a new picture element, binds loadeddata and error events, sets
    780 *     attributes and source URL and appends to {@code document.body}. Also
    781 *     creates new image element appending it to the picture
    782 * @param {string} url The URL of the image for the source and image elements.
    783 * @return {Promise} The promise for success/error events.
    784 */
    785 function requestViaPicture(url) {
    786  var picture = createMediaElement("picture", {}, {"srcset": url,
    787                                                "type": "image/png"});
    788  return createRequestViaElement("img", {"src": url}, picture);
    789 }
    790 
    791 /**
    792 * Creates a new object element, binds load and error events, sets the data to
    793 *     url, and appends it to {@code document.body}.
    794 * @param {string} url The data URL.
    795 * @return {Promise} The promise for success/error events.
    796 */
    797 function requestViaObject(url) {
    798  return createRequestViaElement("object", {"data": url, "type": "text/html"}, document.body);
    799 }
    800 
    801 /**
    802 * Creates a new WebSocket pointing to {@code url} and sends a message string
    803 * "echo". The {@code message} and {@code error} events are triggering the
    804 * returned promise resolve/reject events.
    805 * @param {string} url The URL for WebSocket to connect to.
    806 * @return {Promise} The promise for success/error events.
    807 */
    808 function requestViaWebSocket(url) {
    809  return new Promise(function(resolve, reject) {
    810    var websocket = new WebSocket(url);
    811 
    812    websocket.addEventListener("message", function(e) {
    813      resolve(e.data);
    814    });
    815 
    816    websocket.addEventListener("open", function(e) {
    817      websocket.send("echo");
    818    });
    819 
    820    websocket.addEventListener("error", function(e) {
    821      reject(e)
    822    });
    823  })
    824  .then(data => {
    825      return JSON.parse(data);
    826    });
    827 }
    828 
    829 /**
    830 * Creates a svg anchor element and the corresponding svg setup, appends the
    831 * setup to {@code document.body} and performs the navigation.
    832 * @param {string} url The URL to navigate to.
    833 * @return {Promise} The promise for success/error events.
    834 */
    835 function requestViaSVGAnchor(url, additionalAttributes) {
    836  const name = guid();
    837 
    838  const iframe =
    839    createElement("iframe", { "name": name, "id": name }, document.body, false);
    840 
    841  // Create SVG container
    842  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    843 
    844  // Create SVG anchor element
    845  const svgAnchor = document.createElementNS("http://www.w3.org/2000/svg", "a");
    846  const link_attributes = Object.assign({ "href": url, "target": name }, additionalAttributes);
    847  setAttributes(svgAnchor, link_attributes);
    848 
    849  // Add some text content for the anchor
    850  const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
    851  text.setAttribute("y", "50");
    852  text.textContent = "SVG Link to resource";
    853 
    854  svgAnchor.appendChild(text);
    855  svg.appendChild(svgAnchor);
    856  document.body.appendChild(svg);
    857 
    858  const promise =
    859    bindEvents2(window, "message", iframe, "error", window, "error")
    860      .then(event => {
    861        if (event.source !== iframe.contentWindow)
    862          return Promise.reject(new Error('Unexpected event.source'));
    863        return event.data;
    864      });
    865 
    866  // Simulate a click event on the SVG anchor
    867  const event = new MouseEvent('click', {
    868    view: window,
    869    bubbles: true,
    870    cancelable: true
    871  });
    872  svgAnchor.dispatchEvent(event);
    873 
    874  return promise;
    875 }
    876 
    877 /**
    878  @typedef SubresourceType
    879  @type {string}
    880 
    881  Represents how a subresource is sent.
    882  The keys of `subresourceMap` below are the valid values.
    883 */
    884 
    885 // Subresource paths and invokers.
    886 const subresourceMap = {
    887  "a-tag": {
    888    path: "/common/security-features/subresource/document.py",
    889    invoker: requestViaAnchor,
    890  },
    891  "area-tag": {
    892    path: "/common/security-features/subresource/document.py",
    893    invoker: requestViaArea,
    894  },
    895  "audio-tag": {
    896    path: "/common/security-features/subresource/audio.py",
    897    invoker: requestViaAudio,
    898  },
    899  "beacon": {
    900    path: "/common/security-features/subresource/empty.py",
    901    invoker: requestViaSendBeacon,
    902  },
    903  "fetch": {
    904    path: "/common/security-features/subresource/xhr.py",
    905    invoker: requestViaFetch,
    906  },
    907  "form-tag": {
    908    path: "/common/security-features/subresource/document.py",
    909    invoker: requestViaForm,
    910  },
    911  "iframe-tag": {
    912    path: "/common/security-features/subresource/document.py",
    913    invoker: requestViaIframe,
    914  },
    915  "img-tag": {
    916    path: "/common/security-features/subresource/image.py",
    917    invoker: requestViaImage,
    918  },
    919  "link-css-tag": {
    920    path: "/common/security-features/subresource/empty.py",
    921    invoker: requestViaLinkStylesheet,
    922  },
    923  "link-prefetch-tag": {
    924    path: "/common/security-features/subresource/empty.py",
    925    invoker: requestViaLinkPrefetch,
    926  },
    927  "object-tag": {
    928    path: "/common/security-features/subresource/empty.py",
    929    invoker: requestViaObject,
    930  },
    931  "picture-tag": {
    932    path: "/common/security-features/subresource/image.py",
    933    invoker: requestViaPicture,
    934  },
    935  "script-tag": {
    936    path: "/common/security-features/subresource/script.py",
    937    invoker: requestViaScript,
    938  },
    939  "script-tag-dynamic-import": {
    940    path: "/common/security-features/subresource/script.py",
    941    invoker: requestViaDynamicImport,
    942  },
    943  "svg-a-tag": {
    944    path: "/common/security-features/subresource/document.py",
    945    invoker: requestViaSVGAnchor,
    946  },
    947  "video-tag": {
    948    path: "/common/security-features/subresource/video.py",
    949    invoker: requestViaVideo,
    950  },
    951  "xhr": {
    952    path: "/common/security-features/subresource/xhr.py",
    953    invoker: requestViaXhr,
    954  },
    955 
    956  "worker-classic": {
    957    path: "/common/security-features/subresource/worker.py",
    958    invoker: url => requestViaDedicatedWorker(url),
    959  },
    960  "worker-module": {
    961    path: "/common/security-features/subresource/worker.py",
    962    invoker: url => requestViaDedicatedWorker(url, {type: "module"}),
    963  },
    964  "worker-import": {
    965    path: "/common/security-features/subresource/worker.py",
    966    invoker: (url, additionalAttributes) =>
    967        requestViaDedicatedWorker(workerUrlThatImports(url, additionalAttributes), {type: "module"}),
    968  },
    969  "worker-import-data": {
    970    path: "/common/security-features/subresource/worker.py",
    971    invoker: url =>
    972        requestViaDedicatedWorker(workerDataUrlThatImports(url), {type: "module"}),
    973  },
    974  "sharedworker-classic": {
    975    path: "/common/security-features/subresource/shared-worker.py",
    976    invoker: url => requestViaSharedWorker(url),
    977  },
    978  "sharedworker-module": {
    979    path: "/common/security-features/subresource/shared-worker.py",
    980    invoker: url => requestViaSharedWorker(url, {type: "module"}),
    981  },
    982  "sharedworker-import": {
    983    path: "/common/security-features/subresource/shared-worker.py",
    984    invoker: (url, additionalAttributes) =>
    985        requestViaSharedWorker(workerUrlThatImports(url, additionalAttributes), {type: "module"}),
    986  },
    987  "sharedworker-import-data": {
    988    path: "/common/security-features/subresource/shared-worker.py",
    989    invoker: url =>
    990        requestViaSharedWorker(workerDataUrlThatImports(url), {type: "module"}),
    991  },
    992 
    993  "websocket": {
    994    path: "/stash_responder",
    995    invoker: requestViaWebSocket,
    996  },
    997 };
    998 for (const workletType of ['animation', 'audio', 'layout', 'paint']) {
    999  subresourceMap[`worklet-${workletType}`] = {
   1000      path: "/common/security-features/subresource/worker.py",
   1001      invoker: url => requestViaWorklet(workletType, url)
   1002    };
   1003  subresourceMap[`worklet-${workletType}-import-data`] = {
   1004      path: "/common/security-features/subresource/worker.py",
   1005      invoker: url =>
   1006          requestViaWorklet(workletType, workerDataUrlThatImports(url))
   1007    };
   1008 }
   1009 
   1010 /**
   1011  @typedef RedirectionType
   1012  @type {string}
   1013 
   1014  Represents what redirects should occur to the subresource request
   1015  after initial request.
   1016  See preprocess_redirection() in
   1017  /common/security-features/subresource/subresource.py for valid values.
   1018 */
   1019 
   1020 /**
   1021  Construct subresource (and related) origin.
   1022 
   1023  @param {string} originType
   1024  @returns {object} the origin of the subresource.
   1025 */
   1026 function getSubresourceOrigin(originType) {
   1027  const httpProtocol = "http";
   1028  const httpsProtocol = "https";
   1029  const wsProtocol = "ws";
   1030  const wssProtocol = "wss";
   1031 
   1032  const sameOriginHost = "{{host}}";
   1033  const crossOriginHost = "{{domains[www1]}}";
   1034 
   1035  // These values can evaluate to either empty strings or a ":port" string.
   1036  const httpPort = getNormalizedPort(parseInt("{{ports[http][0]}}", 10));
   1037  const httpsRawPort = parseInt("{{ports[https][0]}}", 10);
   1038  const httpsPort = getNormalizedPort(httpsRawPort);
   1039  const wsPort = getNormalizedPort(parseInt("{{ports[ws][0]}}", 10));
   1040  const wssRawPort = parseInt("{{ports[wss][0]}}", 10);
   1041  const wssPort = getNormalizedPort(wssRawPort);
   1042 
   1043  /**
   1044    @typedef OriginType
   1045    @type {string}
   1046 
   1047    Represents the origin of the subresource request URL.
   1048    The keys of `originMap` below are the valid values.
   1049 
   1050    Note that there can be redirects from the specified origin
   1051    (see RedirectionType), and thus the origin of the subresource
   1052    response URL might be different from what is specified by OriginType.
   1053  */
   1054  const originMap = {
   1055    "same-https": httpsProtocol + "://" + sameOriginHost + httpsPort,
   1056    "same-http": httpProtocol + "://" + sameOriginHost + httpPort,
   1057    "cross-https": httpsProtocol + "://" + crossOriginHost + httpsPort,
   1058    "cross-http": httpProtocol + "://" + crossOriginHost + httpPort,
   1059    "same-wss": wssProtocol + "://" + sameOriginHost + wssPort,
   1060    "same-ws": wsProtocol + "://" + sameOriginHost + wsPort,
   1061    "cross-wss": wssProtocol + "://" + crossOriginHost + wssPort,
   1062    "cross-ws": wsProtocol + "://" + crossOriginHost + wsPort,
   1063 
   1064    // The following origin types are used for upgrade-insecure-requests tests:
   1065    // These rely on some unintuitive cleverness due to WPT's test setup:
   1066    // 'Upgrade-Insecure-Requests' does not upgrade the port number,
   1067    // so we use URLs in the form `http://[domain]:[https-port]`,
   1068    // which will be upgraded to `https://[domain]:[https-port]`.
   1069    // If the upgrade fails, the load will fail, as we don't serve HTTP over
   1070    // the secure port.
   1071    "same-http-downgrade":
   1072        httpProtocol + "://" + sameOriginHost + ":" + httpsRawPort,
   1073    "cross-http-downgrade":
   1074        httpProtocol + "://" + crossOriginHost + ":" + httpsRawPort,
   1075    "same-ws-downgrade":
   1076        wsProtocol + "://" + sameOriginHost + ":" + wssRawPort,
   1077    "cross-ws-downgrade":
   1078        wsProtocol + "://" + crossOriginHost + ":" + wssRawPort,
   1079  };
   1080 
   1081  return originMap[originType];
   1082 }
   1083 
   1084 /**
   1085  Construct subresource (and related) URLs.
   1086 
   1087  @param {SubresourceType} subresourceType
   1088  @param {OriginType} originType
   1089  @param {RedirectionType} redirectionType
   1090  @returns {object} with following properties:
   1091    {string} testUrl
   1092      The subresource request URL.
   1093    {string} announceUrl
   1094    {string} assertUrl
   1095      The URLs to be used for detecting whether `testUrl` is actually sent
   1096      to the server.
   1097      1. Fetch `announceUrl` first,
   1098      2. then possibly fetch `testUrl`, and
   1099      3. finally fetch `assertUrl`.
   1100         The fetch result of `assertUrl` should indicate whether
   1101         `testUrl` is actually sent to the server or not.
   1102 */
   1103 function getRequestURLs(subresourceType, originType, redirectionType) {
   1104  const key = guid();
   1105  const value = guid();
   1106 
   1107  // We use the same stash path for both HTTP/S and WS/S stash requests.
   1108  const stashPath = encodeURIComponent("/mixed-content");
   1109 
   1110  const stashEndpoint = "/common/security-features/subresource/xhr.py?key=" +
   1111                        key + "&path=" + stashPath;
   1112  return {
   1113    testUrl:
   1114      getSubresourceOrigin(originType) +
   1115        subresourceMap[subresourceType].path +
   1116        "?redirection=" + encodeURIComponent(redirectionType) +
   1117        "&action=purge&key=" + key +
   1118        "&path=" + stashPath,
   1119    announceUrl: stashEndpoint + "&action=put&value=" + value,
   1120    assertUrl: stashEndpoint + "&action=take",
   1121  };
   1122 }
   1123 
   1124 // ===============================================================
   1125 // Source Context
   1126 // ===============================================================
   1127 // Requests can be sent from several source contexts,
   1128 // such as the main documents, iframes, workers, or so,
   1129 // possibly nested, and possibly with <meta>/http headers added.
   1130 // invokeRequest() and invokeFrom*() functions handles
   1131 // SourceContext-related setup in client-side.
   1132 
   1133 /**
   1134  invokeRequest() invokes a subresource request
   1135  (specified as `subresource`)
   1136  from a (possibly nested) environment settings object
   1137  (specified as `sourceContextList`).
   1138 
   1139  For nested contexts, invokeRequest() calls an invokeFrom*() function
   1140  that creates a nested environment settings object using
   1141  /common/security-features/scope/, which calls invokeRequest()
   1142  again inside the nested environment settings object.
   1143  This cycle continues until all specified
   1144  nested environment settings object are created, and
   1145  finally invokeRequest() calls a requestVia*() function to start the
   1146  subresource request from the inner-most environment settings object.
   1147 
   1148  @param {Subresource} subresource
   1149  @param {Array<SourceContext>} sourceContextList
   1150 
   1151  @returns {Promise} A promise that is resolved with an RequestResult object.
   1152  `sourceContextUrl` is always set. For whether other properties are set,
   1153  see the comments for requestVia*() above.
   1154 */
   1155 function invokeRequest(subresource, sourceContextList) {
   1156  if (sourceContextList.length === 0) {
   1157    // No further nested global objects. Send the subresource request here.
   1158 
   1159    const additionalAttributes = {};
   1160    /** @type {PolicyDelivery} policyDelivery */
   1161    for (const policyDelivery of (subresource.policyDeliveries || [])) {
   1162      // Depending on the delivery method, extend the subresource element with
   1163      // these attributes.
   1164      if (policyDelivery.deliveryType === "attr") {
   1165        additionalAttributes[policyDelivery.key] = policyDelivery.value;
   1166      } else if (policyDelivery.deliveryType === "rel-noref") {
   1167        additionalAttributes["rel"] = "noreferrer";
   1168      } else if (policyDelivery.deliveryType === "http-rp") {
   1169        additionalAttributes[policyDelivery.key] = policyDelivery.value;
   1170      } else if (policyDelivery.deliveryType === "meta") {
   1171        additionalAttributes[policyDelivery.key] = policyDelivery.value;
   1172      }
   1173    }
   1174 
   1175    return subresourceMap[subresource.subresourceType].invoker(
   1176        subresource.url,
   1177        additionalAttributes)
   1178      .then(result => Object.assign(
   1179          {sourceContextUrl: location.toString()},
   1180          result));
   1181  }
   1182 
   1183  // Defines invokers for each valid SourceContext.sourceContextType.
   1184  const sourceContextMap = {
   1185    "srcdoc": { // <iframe srcdoc></iframe>
   1186      invoker: invokeFromIframe,
   1187    },
   1188    "iframe": { // <iframe src="same-origin-URL"></iframe>
   1189      invoker: invokeFromIframe,
   1190    },
   1191    "iframe-blank": { // <iframe></iframe>
   1192      invoker: invokeFromIframe,
   1193    },
   1194    "worker-classic": {
   1195      // Classic dedicated worker loaded from same-origin.
   1196      invoker: invokeFromWorker.bind(undefined, "worker", false, {}),
   1197    },
   1198    "worker-classic-data": {
   1199      // Classic dedicated worker loaded from data: URL.
   1200      invoker: invokeFromWorker.bind(undefined, "worker", true, {}),
   1201    },
   1202    "worker-module": {
   1203      // Module dedicated worker loaded from same-origin.
   1204      invoker: invokeFromWorker.bind(undefined, "worker", false, {type: 'module'}),
   1205    },
   1206    "worker-module-data": {
   1207      // Module dedicated worker loaded from data: URL.
   1208      invoker: invokeFromWorker.bind(undefined, "worker", true, {type: 'module'}),
   1209    },
   1210    "sharedworker-classic": {
   1211      // Classic shared worker loaded from same-origin.
   1212      invoker: invokeFromWorker.bind(undefined, "sharedworker", false, {}),
   1213    },
   1214    "sharedworker-classic-data": {
   1215      // Classic shared worker loaded from data: URL.
   1216      invoker: invokeFromWorker.bind(undefined, "sharedworker", true, {}),
   1217    },
   1218    "sharedworker-module": {
   1219      // Module shared worker loaded from same-origin.
   1220      invoker: invokeFromWorker.bind(undefined, "sharedworker", false, {type: 'module'}),
   1221    },
   1222    "sharedworker-module-data": {
   1223      // Module shared worker loaded from data: URL.
   1224      invoker: invokeFromWorker.bind(undefined, "sharedworker", true, {type: 'module'}),
   1225    },
   1226  };
   1227 
   1228  return sourceContextMap[sourceContextList[0].sourceContextType].invoker(
   1229      subresource, sourceContextList);
   1230 }
   1231 
   1232 // Quick hack to expose invokeRequest when common.sub.js is loaded either
   1233 // as a classic or module script.
   1234 self.invokeRequest = invokeRequest;
   1235 
   1236 /**
   1237  invokeFrom*() functions are helper functions with the same parameters
   1238  and return values as invokeRequest(), that are tied to specific types
   1239  of top-most environment settings objects.
   1240  For example, invokeFromIframe() is the helper function for the cases where
   1241  sourceContextList[0] is an iframe.
   1242 */
   1243 
   1244 /**
   1245  @param {string} workerType
   1246    "worker" (for dedicated worker) or "sharedworker".
   1247  @param {boolean} isDataUrl
   1248    true if the worker script is loaded from data: URL.
   1249    Otherwise, the script is loaded from same-origin.
   1250  @param {object} workerOptions
   1251    The `options` argument for Worker constructor.
   1252 
   1253  Other parameters and return values are the same as those of invokeRequest().
   1254 */
   1255 function invokeFromWorker(workerType, isDataUrl, workerOptions,
   1256                          subresource, sourceContextList) {
   1257  const currentSourceContext = sourceContextList[0];
   1258  let workerUrl =
   1259    "/common/security-features/scope/worker.py?policyDeliveries=" +
   1260    encodeURIComponent(JSON.stringify(
   1261        currentSourceContext.policyDeliveries || []));
   1262  if (workerOptions.type === 'module') {
   1263    workerUrl += "&type=module";
   1264  }
   1265 
   1266  let promise;
   1267  if (isDataUrl) {
   1268    promise = fetch(workerUrl)
   1269      .then(r => r.text())
   1270      .then(source => {
   1271          return 'data:text/javascript;base64,' + btoa(source);
   1272        });
   1273  } else {
   1274    promise = Promise.resolve(workerUrl);
   1275  }
   1276 
   1277  return promise
   1278    .then(url => {
   1279      if (workerType === "worker") {
   1280        const worker = new Worker(url, workerOptions);
   1281        worker.postMessage({subresource: subresource,
   1282                            sourceContextList: sourceContextList.slice(1)});
   1283        return bindEvents2(worker, "message", worker, "error", window, "error");
   1284      } else if (workerType === "sharedworker") {
   1285        const worker = new SharedWorker(url, workerOptions);
   1286        worker.port.start();
   1287        worker.port.postMessage({subresource: subresource,
   1288                                 sourceContextList: sourceContextList.slice(1)});
   1289        return bindEvents2(worker.port, "message", worker, "error", window, "error");
   1290      } else {
   1291        throw new Error('Invalid worker type: ' + workerType);
   1292      }
   1293    })
   1294    .then(event => {
   1295        if (event.data.error)
   1296          return Promise.reject(event.data.error);
   1297        return event.data;
   1298      });
   1299 }
   1300 
   1301 function invokeFromIframe(subresource, sourceContextList) {
   1302  const currentSourceContext = sourceContextList[0];
   1303  const frameUrl =
   1304    "/common/security-features/scope/document.py?policyDeliveries=" +
   1305    encodeURIComponent(JSON.stringify(
   1306        currentSourceContext.policyDeliveries || []));
   1307 
   1308  let iframe;
   1309  let promise;
   1310  if (currentSourceContext.sourceContextType === 'srcdoc') {
   1311    promise = fetch(frameUrl)
   1312      .then(r => r.text())
   1313      .then(srcdoc => {
   1314          iframe = createElement(
   1315              "iframe", {srcdoc: srcdoc}, document.body, true);
   1316          return iframe.eventPromise;
   1317        });
   1318  } else if (currentSourceContext.sourceContextType === 'iframe') {
   1319    iframe = createElement("iframe", {src: frameUrl}, document.body, true);
   1320    promise = iframe.eventPromise;
   1321  } else if (currentSourceContext.sourceContextType === 'iframe-blank') {
   1322    let frameContent;
   1323    promise = fetch(frameUrl)
   1324      .then(r => r.text())
   1325      .then(t => {
   1326          frameContent = t;
   1327          iframe = createElement("iframe", {}, document.body, true);
   1328          return iframe.eventPromise;
   1329        })
   1330      .then(() => {
   1331          // Reinitialize `iframe.eventPromise` with a new promise
   1332          // that catches the load event for the document.write() below.
   1333          bindEvents(iframe);
   1334 
   1335          iframe.contentDocument.write(frameContent);
   1336          iframe.contentDocument.close();
   1337          return iframe.eventPromise;
   1338        });
   1339  }
   1340 
   1341  return promise
   1342    .then(() => {
   1343        const promise = bindEvents2(
   1344            window, "message", iframe, "error", window, "error");
   1345        iframe.contentWindow.postMessage(
   1346            {subresource: subresource,
   1347             sourceContextList: sourceContextList.slice(1)},
   1348            "*");
   1349        return promise;
   1350      })
   1351    .then(event => {
   1352        if (event.data.error)
   1353          return Promise.reject(event.data.error);
   1354        return event.data;
   1355      });
   1356 }
   1357 
   1358 // SanityChecker does nothing in release mode. See sanity-checker.js for debug
   1359 // mode.
   1360 function SanityChecker() {}
   1361 SanityChecker.prototype.checkScenario = function() {};
   1362 SanityChecker.prototype.setFailTimeout = function(test, timeout) {};
   1363 SanityChecker.prototype.checkSubresourceResult = function() {};