tor-browser

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

resource-loaders.js (5483B)


      1 const load = {
      2 
      3  cache_bust: path => {
      4    let url = new URL(path, location.origin);
      5    url.href += (url.href.includes("?")) ? '&' : '?';
      6    // The `Number` type in Javascript, when interpreted as an integer, can only
      7    // safely represent [-2^53 + 1, 2^53 - 1] without the loss of precision [1].
      8    // We do not generate a global value and increment from it, as the increment
      9    // might not have enough precision to be reflected.
     10    //
     11    // [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
     12    url.href += "unique=" + Math.random().toString().substring(2);
     13    return url.href;
     14  },
     15 
     16  image_with_attrs: async (path, attribute_map) => {
     17    return new Promise(resolve => {
     18      const img = new Image();
     19      for (const key in attribute_map)
     20          img[key] = attribute_map[key];
     21      img.onload = img.onerror = resolve;
     22      img.src = load.cache_bust(path);
     23    });
     24  },
     25 
     26  // Returns a promise that settles once the given path has been fetched as an
     27  // image resource.
     28  image: path => {
     29    return load.image_with_attrs(path, undefined);
     30  },
     31 
     32  // Returns a promise that settles once the given path has been fetched as an
     33  // image resource.
     34  image_cors: path => load.image_with_attrs(path, {crossOrigin: "anonymous"}),
     35 
     36  // Returns a promise that settles once the given path has been fetched as a
     37  // font resource.
     38  font: path => {
     39    const div = document.createElement('div');
     40    div.innerHTML = `
     41      <style>
     42      @font-face {
     43          font-family: ahem;
     44          src: url('${load.cache_bust(path)}');
     45      }
     46      </style>
     47      <div style="font-family: ahem;">This fetches ahem font.</div>
     48    `;
     49    document.body.appendChild(div);
     50    return document.fonts.ready.then(() => {
     51      document.body.removeChild(div);
     52    });
     53  },
     54 
     55  stylesheet_with_attrs: async (path, attribute_map) => {
     56    const link = document.createElement("link");
     57    if (attribute_map instanceof Object) {
     58      for (const [key, value] of Object.entries(attribute_map)) {
     59        link[key] = value;
     60      }
     61    }
     62    link.rel = "stylesheet";
     63    link.type = "text/css";
     64    link.href = load.cache_bust(path);
     65 
     66    const loaded = new Promise(resolve => {
     67      link.onload = link.onerror = resolve;
     68    });
     69 
     70    document.head.appendChild(link);
     71    await loaded;
     72    document.head.removeChild(link);
     73  },
     74 
     75  // Returns a promise that settles once the given path has been fetched as a
     76  // stylesheet resource.
     77  stylesheet: async path => {
     78    return load.stylesheet_with_attrs(path, undefined);
     79  },
     80 
     81  iframe_with_attrs: async (path, attribute_map, validator, skip_wait_for_navigation) => {
     82    const frame = document.createElement("iframe");
     83    if (attribute_map instanceof Object) {
     84      for (const [key, value] of Object.entries(attribute_map)) {
     85        frame[key] = value;
     86      }
     87    }
     88    const loaded = new Promise(resolve => {
     89      frame.onload = frame.onerror = resolve;
     90    });
     91    frame.src = load.cache_bust(path);
     92    document.body.appendChild(frame);
     93    if ( !skip_wait_for_navigation ) {
     94      await loaded;
     95    }
     96    if (validator instanceof Function) {
     97      validator(frame);
     98    }
     99    // since we skipped the wait for load animation, we cannot
    100    // remove the iframe here since the request could get cancelled
    101    if ( !skip_wait_for_navigation ) {
    102      document.body.removeChild(frame);
    103    }
    104  },
    105 
    106  // Returns a promise that settles once the given path has been fetched as an
    107  // iframe.
    108  iframe: async (path, validator) => {
    109    return load.iframe_with_attrs(path, undefined, validator);
    110  },
    111 
    112  script_with_attrs: async (path, attribute_map) => {
    113    const script = document.createElement("script");
    114    if (attribute_map instanceof Object) {
    115      for (const [key, value] of Object.entries(attribute_map)) {
    116        script[key] = value;
    117      }
    118    }
    119    const loaded = new Promise(resolve => {
    120      script.onload = script.onerror = resolve;
    121    });
    122    script.src = load.cache_bust(path);
    123    document.body.appendChild(script);
    124    await loaded;
    125    document.body.removeChild(script);
    126  },
    127 
    128  // Returns a promise that settles once the given path has been fetched as a
    129  // script.
    130  script: async path => {
    131    return load.script_with_attrs(path, undefined);
    132  },
    133 
    134  // Returns a promise that settles once the given path has been fetched as an
    135  // object.
    136  object: async (path, type) => {
    137    const object = document.createElement("object");
    138    const object_load_settled = new Promise(resolve => {
    139      object.onload = object.onerror = resolve;
    140    });
    141    object.data = load.cache_bust(path);
    142    if (type) {
    143      object.type = type;
    144    }
    145    document.body.appendChild(object);
    146    await await_with_timeout(2000,
    147      "Timeout was reached before load or error events fired",
    148      object_load_settled,
    149      () => { document.body.removeChild(object) }
    150    );
    151  },
    152 
    153  // Returns a promise that settles once the given path has been fetched
    154  // through a synchronous XMLHttpRequest.
    155  xhr_sync: async (path, headers) => {
    156    const xhr = new XMLHttpRequest;
    157    xhr.open("GET", path, /* async = */ false);
    158    if (headers instanceof Object) {
    159      for (const [key, value] of Object.entries(headers)) {
    160        xhr.setRequestHeader(key, value);
    161      }
    162    }
    163    xhr.send();
    164  },
    165 
    166  xhr_async: path => {
    167    const xhr = new XMLHttpRequest();
    168    xhr.open("GET", path)
    169    xhr.send();
    170    return new Promise(resolve => {
    171      xhr.onload = xhr.onerror = resolve;
    172    });
    173  }
    174 };