tor-browser

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

helpers.js (11263B)


      1 'use strict';
      2 
      3 function processQueryParams() {
      4  const url = new URL(window.location);
      5  const queryParams = url.searchParams;
      6  return {
      7    topLevelDocument: window === window.top,
      8    testPrefix: queryParams.get("testCase") || "top-level-context",
      9  };
     10 }
     11 
     12 // Create an iframe element, set it up using `setUpFrame`, and optionally fetch
     13 // tests in it. Returns the created frame, after it has loaded.
     14 async function CreateFrameHelper(setUpFrame, fetchTests) {
     15  const frame = document.createElement('iframe');
     16  const promise = new Promise((resolve, reject) => {
     17    frame.onload = () => resolve(frame);
     18    frame.onerror = reject;
     19  });
     20 
     21  setUpFrame(frame);
     22 
     23  if (fetchTests) {
     24    await fetch_tests_from_window(frame.contentWindow);
     25  }
     26  return promise;
     27 }
     28 
     29 // Create an iframe element with content loaded from `sourceURL`, append it to
     30 // the document, and optionally fetch tests. Returns the loaded frame, once
     31 // ready.
     32 function CreateFrame(
     33  sourceURL, fetchTests = false, frameSandboxAttribute = undefined, frameAllowAttribute = undefined) {
     34  return CreateFrameHelper((frame) => {
     35    if (frameSandboxAttribute !== undefined) {
     36      frame.sandbox = frameSandboxAttribute;
     37    }
     38    if (frameAllowAttribute !== undefined) {
     39      frame.setAttribute("allow", frameAllowAttribute);
     40    }
     41 
     42    frame.src = sourceURL;
     43    document.body.appendChild(frame);
     44  }, fetchTests);
     45 }
     46 
     47 // Create a new iframe with content loaded from `sourceURL`, and fetches tests.
     48 // Returns the loaded frame, once ready.
     49 function RunTestsInIFrame(sourceURL, frameSandboxAttribute = undefined) {
     50  return CreateFrame(sourceURL, true, frameSandboxAttribute);
     51 }
     52 
     53 function RunTestsInNestedIFrame(sourceURL) {
     54  return CreateFrameHelper((frame) => {
     55    document.body.appendChild(frame);
     56    frame.contentDocument.write(`
     57      <script src="/resources/testharness.js"></script>
     58      <script src="helpers.js"></script>
     59      <body>
     60      <script>
     61        RunTestsInIFrame("${sourceURL}");
     62      </script>
     63    `);
     64    frame.contentDocument.close();
     65  }, true);
     66 }
     67 
     68 function CreateDetachedFrame() {
     69  const frame = document.createElement('iframe');
     70  document.body.append(frame);
     71  const inner_doc = frame.contentDocument;
     72  frame.remove();
     73  return inner_doc;
     74 }
     75 
     76 function CreateDocumentViaDOMParser() {
     77  const parser = new DOMParser();
     78  const doc = parser.parseFromString('<html></html>', 'text/html');
     79  return doc;
     80 }
     81 
     82 function RunCallbackWithGesture(callback) {
     83  return test_driver.bless('run callback with user gesture', callback);
     84 }
     85 
     86 // Sends a message to the given target window and returns a promise that
     87 // resolves when a reply was sent.
     88 function PostMessageAndAwaitReply(message, targetWindow) {
     89  const timestamp = window.performance.now();
     90  const reply = ReplyPromise(timestamp);
     91  targetWindow.postMessage({timestamp, ...message}, "*");
     92  return reply;
     93 }
     94 
     95 // Returns a promise that resolves when the next "reply" is received via
     96 // postMessage. Takes a "timestamp" argument to validate that the received
     97 // message belongs to its original counterpart.
     98 function ReplyPromise(timestamp) {
     99  return new Promise((resolve) => {
    100    const listener = (event) => {
    101      if (event.data.timestamp == timestamp) {
    102        window.removeEventListener("message", listener);
    103        resolve(event.data.data);
    104      }
    105    };
    106    window.addEventListener("message", listener);
    107  });
    108 }
    109 
    110 // Returns a promise that resolves when the given frame fires its load event.
    111 function LoadPromise(frame) {
    112  return new Promise((resolve) => {
    113    frame.addEventListener("load", (event) => {
    114      resolve();
    115    }, { once: true });
    116  });
    117 }
    118 
    119 // Writes cookies via document.cookie in the given frame.
    120 function SetDocumentCookieFromFrame(frame, cookie) {
    121  return PostMessageAndAwaitReply(
    122    { command: "write document.cookie", cookie }, frame.contentWindow);
    123 }
    124 
    125 // Reads cookies via document.cookie in the given frame.
    126 function GetJSCookiesFromFrame(frame) {
    127  return PostMessageAndAwaitReply(
    128      { command: "document.cookie" }, frame.contentWindow);
    129 }
    130 
    131 async function DeleteCookieInFrame(frame, name, params) {
    132  await SetDocumentCookieFromFrame(frame, `${name}=0; expires=${new Date(0).toUTCString()}; ${params};`);
    133  assert_false(cookieStringHasCookie(name, '0', await GetJSCookiesFromFrame(frame)), `Verify that cookie '${name}' has been deleted.`);
    134 }
    135 
    136 // Sets a cookie in an unpartitioned context by opening a window that
    137 // writes a cookie using document.cookie.
    138 async function SetFirstPartyCookie(origin, cookie="cookie=unpartitioned;Secure;SameSite=None;Path=/") {
    139  return new Promise((resolve) => {
    140    const onMessage = (event) => {
    141      if (event && event.data === 'set-document-cookie-complete') {
    142        window.removeEventListener('message', onMessage);
    143        resolve();
    144      }
    145    };
    146    window.addEventListener('message', onMessage, { once: true });
    147 
    148    RunCallbackWithGesture(() => {
    149      window.open(`${origin}/storage-access-api/resources/set-document-cookie.html?${cookie}`);
    150    });
    151  });
    152 }
    153 
    154 // Tests for the presence of the unpartitioned cookie set by SetFirstPartyCookie
    155 // in both the `document.cookie` variable and same-origin subresource \
    156 // Request Headers in the given frame
    157 async function HasUnpartitionedCookie(frame) {
    158  let frameDocumentCookie = await GetJSCookiesFromFrame(frame);
    159  let jsAccess = cookieStringHasCookie("cookie", "unpartitioned", frameDocumentCookie);
    160  const httpCookie = await FetchSubresourceCookiesFromFrame(frame, "");
    161  let httpAccess = cookieStringHasCookie("cookie", "unpartitioned", httpCookie);
    162  assert_equals(jsAccess, httpAccess, "HTTP and Javascript cookies must be in sync");
    163  return jsAccess && httpAccess;
    164 }
    165 
    166 // Tests whether the current frame can read and write cookies via HTTP headers.
    167 // This deletes, writes, reads, then deletes a cookie named "cookie".
    168 async function CanAccessCookiesViaHTTP() {
    169  // We avoid reusing SetFirstPartyCookie here, since that bypasses the
    170  // cookie-accessibility settings that we want to check here.
    171  await fetch(`${window.location.origin}/storage-access-api/resources/set-cookie-header.py?cookie=1;path=/;SameSite=None;Secure`);
    172  const http_cookies = await fetch(`${window.location.origin}/storage-access-api/resources/echo-cookie-header.py`)
    173      .then((resp) => resp.text());
    174  const can_access = cookieStringHasCookie("cookie", "1", http_cookies);
    175 
    176  erase_cookie_from_js("cookie", "SameSite=None;Secure;Path=/");
    177 
    178  return can_access;
    179 }
    180 
    181 // Tests whether the current frame can read and write cookies via
    182 // document.cookie. This deletes, writes, reads, then deletes a cookie named
    183 // "cookie".
    184 function CanAccessCookiesViaJS() {
    185  erase_cookie_from_js("cookie", "SameSite=None;Secure;Path=/");
    186  assert_false(cookieStringHasCookie("cookie", "1", document.cookie));
    187 
    188  document.cookie = "cookie=1;SameSite=None;Secure;Path=/";
    189  const can_access = cookieStringHasCookie("cookie", "1", document.cookie);
    190 
    191  erase_cookie_from_js("cookie", "SameSite=None;Secure;Path=/");
    192  assert_false(cookieStringHasCookie("cookie", "1", document.cookie));
    193 
    194  return can_access;
    195 }
    196 
    197 // Reads cookies via the `httpCookies` variable in the given frame.
    198 function GetHTTPCookiesFromFrame(frame) {
    199  return PostMessageAndAwaitReply(
    200      { command: "httpCookies" }, frame.contentWindow);
    201 }
    202 
    203 // Executes document.hasStorageAccess in the given frame.
    204 function FrameHasStorageAccess(frame) {
    205  return PostMessageAndAwaitReply(
    206      { command: "hasStorageAccess" }, frame.contentWindow);
    207 }
    208 
    209 // Executes document.requestStorageAccess in the given frame.
    210 function RequestStorageAccessInFrame(frame) {
    211  return PostMessageAndAwaitReply(
    212      { command: "requestStorageAccess" }, frame.contentWindow);
    213 }
    214 
    215 function GetPermissionInFrame(frame) {
    216  return PostMessageAndAwaitReply(
    217    { command: "get_permission" }, frame.contentWindow);
    218 }
    219 
    220 // Executes test_driver.set_permission in the given frame, with the provided
    221 // arguments.
    222 function SetPermissionInFrame(frame, args = []) {
    223  return PostMessageAndAwaitReply(
    224      { command: "set_permission", args }, frame.contentWindow);
    225 }
    226 
    227 // Waits for a storage-access permission change and resolves with the current
    228 // state.
    229 function ObservePermissionChange(frame, args = []) {
    230  return PostMessageAndAwaitReply(
    231      { command: "observe_permission_change", args }, frame.contentWindow);
    232 }
    233 
    234 // Executes `location.reload()` in the given frame. The returned promise
    235 // resolves when the frame has finished reloading.
    236 function FrameInitiatedReload(frame) {
    237  const reload = LoadPromise(frame);
    238  frame.contentWindow.postMessage({ command: "reload" }, "*");
    239  return reload;
    240 }
    241 
    242 // Executes `location.href = url` in the given frame. The returned promise
    243 // resolves when the frame has finished navigating.
    244 function FrameInitiatedNavigation(frame, url) {
    245  const load = LoadPromise(frame);
    246  frame.contentWindow.postMessage({ command: "navigate", url }, "*");
    247  return load;
    248 }
    249 
    250 // Makes a subresource request to the provided host in the given frame, and
    251 // returns the cookies that were included in the request.
    252 function FetchSubresourceCookiesFromFrame(frame, host) {
    253  return FetchFromFrame(frame, `${host}/storage-access-api/resources/echo-cookie-header.py`);
    254 }
    255 
    256 // Makes a subresource request to the provided host in the given frame, and
    257 // returns the response.
    258 function FetchFromFrame(frame, url) {
    259  return PostMessageAndAwaitReply(
    260    { command: "cors fetch", url }, frame.contentWindow);
    261 }
    262 
    263 // Makes a subresource request to the provided host in the given frame with the
    264 // mode set to 'no-cors'. Returns a promise that resolves with undefined, since
    265 // no-cors responses are opaque to JavaScript.
    266 function NoCorsFetchFromFrame(frame, url) {
    267  return PostMessageAndAwaitReply(
    268    { command: "no-cors fetch", url }, frame.contentWindow);
    269 }
    270 
    271 // Tries to set storage access policy, ignoring any errors.
    272 //
    273 // Note: to discourage the writing of tests that assume unpartitioned cookie
    274 // access by default, any test that calls this with `value` == "blocked" should
    275 // do so as the first step in the test.
    276 async function MaybeSetStorageAccess(origin, embedding_origin, value) {
    277  try {
    278    await test_driver.set_storage_access(origin, embedding_origin, value);
    279  } catch (e) {
    280    // Ignore, can be unimplemented if the platform blocks cross-site cookies
    281    // by default. If this failed without default blocking we'll notice it later
    282    // in the test.
    283  }
    284 }
    285 
    286 
    287 // Navigate the inner iframe using the given frame.
    288 function NavigateChild(frame, url) {
    289  return PostMessageAndAwaitReply(
    290    { command: "navigate_child", url }, frame.contentWindow);
    291 }
    292 
    293 // Starts a dedicated worker in the given frame.
    294 function StartDedicatedWorker(frame) {
    295  return PostMessageAndAwaitReply(
    296    { command: "start_dedicated_worker" }, frame.contentWindow);
    297 }
    298 
    299 // Sends a message to the dedicated worker in the given frame.
    300 function MessageWorker(frame, message = {}) {
    301  return PostMessageAndAwaitReply(
    302    { command: "message_worker", message }, frame.contentWindow);
    303 }
    304 // Opens a WebSocket connection to origin from within frame, and
    305 // returns the cookie header that was sent during the handshake.
    306 function ReadCookiesFromWebSocketConnection(frame, origin) {
    307  return PostMessageAndAwaitReply(
    308   { command: "get_cookie_via_websocket", origin}, frame.contentWindow);
    309 }