tor-browser

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

featurepolicy.js (18645B)


      1 // Feature test to avoid timeouts
      2 function assert_feature_policy_supported() {
      3  assert_not_equals(document.featurePolicy, undefined,
      4                    'Feature Policy is supported');
      5 }
      6 // Tests whether a feature that is enabled/disabled by feature policy works
      7 // as expected.
      8 // Arguments:
      9 //    feature_description: a short string describing what feature is being
     10 //        tested. Examples: "usb.GetDevices()", "PaymentRequest()".
     11 //    test: test created by testharness. Examples: async_test, promise_test.
     12 //    src: URL where a feature's availability is checked. Examples:
     13 //        "/feature-policy/resources/feature-policy-payment.html",
     14 //        "/feature-policy/resources/feature-policy-usb.html".
     15 //    expect_feature_available: a callback(data, feature_description) to
     16 //        verify if a feature is available or unavailable as expected.
     17 //        The file under the path "src" defines what "data" is sent back as a
     18 //        postMessage. Inside the callback, some tests (e.g., EXPECT_EQ,
     19 //        EXPECT_TRUE, etc) are run accordingly to test a feature's
     20 //        availability.
     21 //        Example: expect_feature_available_default(data, feature_description).
     22 //    feature_name: Optional argument, only provided when testing iframe allow
     23 //      attribute. "feature_name" is the feature name of a policy controlled
     24 //      feature (https://wicg.github.io/feature-policy/#features).
     25 //      See examples at:
     26 //      https://github.com/WICG/feature-policy/blob/master/features.md
     27 //    allow_attribute: Optional argument, only used for testing fullscreen
     28 //      by passing "allowfullscreen".
     29 function test_feature_availability(
     30    feature_description, test, src, expect_feature_available, feature_name,
     31    allow_attribute) {
     32  let frame = document.createElement('iframe');
     33  frame.src = src;
     34 
     35  if (typeof feature_name !== 'undefined') {
     36    frame.allow = frame.allow.concat(";" + feature_name);
     37  }
     38 
     39  if (typeof allow_attribute !== 'undefined') {
     40    frame.setAttribute(allow_attribute, true);
     41  }
     42 
     43  window.addEventListener('message', test.step_func(function handler(evt) {
     44    if (evt.source === frame.contentWindow) {
     45      expect_feature_available(evt.data, feature_description);
     46      document.body.removeChild(frame);
     47      window.removeEventListener('message', handler);
     48      test.done();
     49    }
     50  }));
     51 
     52  document.body.appendChild(frame);
     53 }
     54 
     55 // Default helper functions to test a feature's availability:
     56 function expect_feature_available_default(data, feature_description) {
     57  assert_true(data.enabled, feature_description);
     58 }
     59 
     60 function expect_feature_unavailable_default(data, feature_description) {
     61  assert_false(data.enabled, feature_description);
     62 }
     63 
     64 // This is the same as test_feature_availability() but instead of passing in a
     65 // function to check the result of the message sent back from an iframe, instead
     66 // just compares the result to an expected result passed in.
     67 // Arguments:
     68 //     test: test created by testharness. Examples: async_test, promise_test.
     69 //     src: the URL to load in an iframe in which to test the feature.
     70 //     expected_result: the expected value to compare to the data passed back
     71 //         from the src page by postMessage.
     72 //     allow_attribute: Optional argument, only provided when an allow
     73 //         attribute should be specified on the iframe.
     74 function test_feature_availability_with_post_message_result(
     75    test, src, expected_result, allow_attribute) {
     76  var test_result = function(data, feature_description) {
     77    assert_equals(data, expected_result);
     78  };
     79  test_feature_availability(null, test, src, test_result, allow_attribute);
     80 }
     81 
     82 // If this page is intended to test the named feature (according to the URL),
     83 // tests the feature availability and posts the result back to the parent.
     84 // Otherwise, does nothing.
     85 function test_feature_in_iframe(feature_name, feature_promise_factory) {
     86  if (location.hash.endsWith(`#${feature_name}`)) {
     87    feature_promise_factory().then(
     88        () => window.parent.postMessage('#OK', '*'),
     89        (e) => window.parent.postMessage('#' + e.name, '*'));
     90  }
     91 }
     92 
     93 // Returns true if the URL for this page indicates that it is embedded in an
     94 // iframe.
     95 function page_loaded_in_iframe() {
     96  return location.hash.startsWith('#iframe');
     97 }
     98 
     99 // Returns a same-origin (relative) URL suitable for embedding in an iframe for
    100 // testing the availability of the feature.
    101 function same_origin_url(feature_name) {
    102  // Append #iframe to the URL so we can detect the iframe'd version of the
    103  // page.
    104  return location.pathname + '#iframe#' + feature_name;
    105 }
    106 
    107 // Returns a cross-origin (absolute) URL suitable for embedding in an iframe for
    108 // testing the availability of the feature.
    109 function cross_origin_url(base_url, feature_name) {
    110  return base_url + same_origin_url(feature_name);
    111 }
    112 
    113 // This function runs all feature policy tests for a particular feature that
    114 // has a default policy of "self". This includes testing:
    115 // 1. Feature usage succeeds by default in the top level frame.
    116 // 2. Feature usage succeeds by default in a same-origin iframe.
    117 // 3. Feature usage fails by default in a cross-origin iframe.
    118 // 4. Feature usage suceeds when an allow attribute is specified on a
    119 //    cross-origin iframe.
    120 //
    121 // The same page which called this function will be loaded in the iframe in
    122 // order to test feature usage there. When this function is called in that
    123 // context it will simply run the feature and return a result back via
    124 // postMessage.
    125 //
    126 // Arguments:
    127 //     cross_origin: A cross-origin URL base to be used to load the page which
    128 //         called into this function.
    129 //     feature_name: The name of the feature as it should be specified in an
    130 //         allow attribute.
    131 //     error_name: If feature usage does not succeed, this is the string
    132 //         representation of the error that will be passed in the rejected
    133 //         promise.
    134 //     feature_promise_factory: A function which returns a promise which tests
    135 //         feature usage. If usage succeeds, the promise should resolve. If it
    136 //         fails, the promise should reject with an error that can be
    137 //         represented as a string.
    138 function run_all_fp_tests_allow_self(
    139    cross_origin, feature_name, error_name, feature_promise_factory) {
    140  // This may be the version of the page loaded up in an iframe. If so, just
    141  // post the result of running the feature promise back to the parent.
    142  if (page_loaded_in_iframe()) {
    143    test_feature_in_iframe(feature_name, feature_promise_factory);
    144    return;
    145  }
    146 
    147  // Run the various tests.
    148  // 1. Allowed in top-level frame.
    149  promise_test(
    150      () => feature_promise_factory(),
    151      'Default "' + feature_name +
    152          '" feature policy ["self"] allows the top-level document.');
    153 
    154  // 2. Allowed in same-origin iframe.
    155  const same_origin_frame_pathname = same_origin_url(feature_name);
    156  async_test(
    157      t => {
    158        test_feature_availability_with_post_message_result(
    159            t, same_origin_frame_pathname, '#OK');
    160      },
    161      'Default "' + feature_name +
    162          '" feature policy ["self"] allows same-origin iframes.');
    163 
    164  // 3. Blocked in cross-origin iframe.
    165  const cross_origin_frame_url = cross_origin_url(cross_origin, feature_name);
    166  async_test(
    167      t => {
    168        test_feature_availability_with_post_message_result(
    169            t, cross_origin_frame_url, '#' + error_name);
    170      },
    171      'Default "' + feature_name +
    172          '" feature policy ["self"] disallows cross-origin iframes.');
    173 
    174  // 4. Allowed in cross-origin iframe with "allow" attribute.
    175  async_test(
    176      t => {
    177        test_feature_availability_with_post_message_result(
    178            t, cross_origin_frame_url, '#OK', feature_name);
    179      },
    180      'Feature policy "' + feature_name +
    181          '" can be enabled in cross-origin iframes using "allow" attribute.');
    182 }
    183 
    184 // This function runs all feature policy tests for a particular feature that
    185 // has a default policy of "*". This includes testing:
    186 // 1. Feature usage succeeds by default in the top level frame.
    187 // 2. Feature usage succeeds by default in a same-origin iframe.
    188 // 3. Feature usage succeeds by default in a cross-origin iframe.
    189 // 4. Feature usage fails when an allow attribute is specified on a
    190 //    cross-origin iframe with a value of "feature-name 'none'".
    191 //
    192 // The same page which called this function will be loaded in the iframe in
    193 // order to test feature usage there. When this function is called in that
    194 // context it will simply run the feature and return a result back via
    195 // postMessage.
    196 //
    197 // Arguments:
    198 //     cross_origin: A cross-origin URL base to be used to load the page which
    199 //         called into this function.
    200 //     feature_name: The name of the feature as it should be specified in an
    201 //         allow attribute.
    202 //     error_name: If feature usage does not succeed, this is the string
    203 //         representation of the error that will be passed in the rejected
    204 //         promise.
    205 //     feature_promise_factory: A function which returns a promise which tests
    206 //         feature usage. If usage succeeds, the promise should resolve. If it
    207 //         fails, the promise should reject with an error that can be
    208 //         represented as a string.
    209 function run_all_fp_tests_allow_all(
    210    cross_origin, feature_name, error_name, feature_promise_factory) {
    211  // This may be the version of the page loaded up in an iframe. If so, just
    212  // post the result of running the feature promise back to the parent.
    213  if (page_loaded_in_iframe()) {
    214    test_feature_in_iframe(feature_name, feature_promise_factory);
    215    return;
    216  }
    217 
    218  // Run the various tests.
    219  // 1. Allowed in top-level frame.
    220  promise_test(
    221      () => feature_promise_factory(),
    222      'Default "' + feature_name +
    223          '" feature policy ["*"] allows the top-level document.');
    224 
    225  // 2. Allowed in same-origin iframe.
    226  const same_origin_frame_pathname = same_origin_url(feature_name);
    227  async_test(
    228      t => {
    229        test_feature_availability_with_post_message_result(
    230            t, same_origin_frame_pathname, '#OK');
    231      },
    232      'Default "' + feature_name +
    233          '" feature policy ["*"] allows same-origin iframes.');
    234 
    235  // 3. Allowed in cross-origin iframe.
    236  const cross_origin_frame_url = cross_origin_url(cross_origin, feature_name);
    237  async_test(
    238      t => {
    239        test_feature_availability_with_post_message_result(
    240            t, cross_origin_frame_url, '#OK');
    241      },
    242      'Default "' + feature_name +
    243          '" feature policy ["*"] allows cross-origin iframes.');
    244 
    245  // 4. Blocked in cross-origin iframe with "allow" attribute set to 'none'.
    246  async_test(
    247      t => {
    248        test_feature_availability_with_post_message_result(
    249            t, cross_origin_frame_url, '#' + error_name,
    250            feature_name + " 'none'");
    251      },
    252      'Feature policy "' + feature_name +
    253          '" can be disabled in cross-origin iframes using "allow" attribute.');
    254 
    255  // 5. Blocked in same-origin iframe with "allow" attribute set to 'none'.
    256  async_test(
    257      t => {
    258        test_feature_availability_with_post_message_result(
    259            t, same_origin_frame_pathname, '#' + error_name,
    260            feature_name + " 'none'");
    261      },
    262      'Feature policy "' + feature_name +
    263          '" can be disabled in same-origin iframes using "allow" attribute.');
    264 }
    265 
    266 // This function tests that a given policy allows each feature for the correct
    267 // list of origins specified by the |expected_policy|.
    268 // Arguments:
    269 //     expected_policy: A list of {feature, allowlist} pairs where the feature is
    270 //         enabled for every origin in the allowlist, in the |policy|.
    271 //     policy: Either a document.featurePolicy or an iframe.featurePolicy to be
    272 //         tested.
    273 //     message: A short description of what policy is being tested.
    274 function test_allowlists(expected_policy, policy, message) {
    275  for (var allowlist of allowlists) {
    276    test(function() {
    277      assert_array_equals(
    278        policy.getAllowlistForFeature(allowlist.feature),
    279        allowlist.allowlist);
    280    }, message + ' for feature ' + allowlist.feature);
    281  }
    282 }
    283 
    284 // This function tests that a subframe's document policy allows a given feature.
    285 // A feature is allowed in a frame either through inherited policy or specified
    286 // by iframe allow attribute.
    287 // Arguments:
    288 //     test: test created by testharness. Examples: async_test, promise_test.
    289 //     feature: feature name that should be allowed in the frame.
    290 //     src: the URL to load in the frame.
    291 //     allow: the allow attribute (container policy) of the iframe
    292 function test_allowed_feature_for_subframe(message, feature, src, allow) {
    293  let frame = document.createElement('iframe');
    294  if (typeof allow !== 'undefined') {
    295    frame.allow = allow;
    296  }
    297  promise_test(function() {
    298    assert_feature_policy_supported();
    299    frame.src = src;
    300    return new Promise(function(resolve, reject) {
    301      window.addEventListener('message', function handler(evt) {
    302        resolve(evt.data);
    303      }, { once: true });
    304      document.body.appendChild(frame);
    305    }).then(function(data) {
    306      assert_true(data.includes(feature), feature);
    307    });
    308  }, message);
    309 }
    310 
    311 // This function tests that a subframe's document policy disallows a given
    312 // feature. A feature is allowed in a frame either through inherited policy or
    313 // specified by iframe allow attribute.
    314 // Arguments:
    315 //     test: test created by testharness. Examples: async_test, promise_test.
    316 //     feature: feature name that should not be allowed in the frame.
    317 //     src: the URL to load in the frame.
    318 //     allow: the allow attribute (container policy) of the iframe
    319 function test_disallowed_feature_for_subframe(message, feature, src, allow) {
    320  let frame = document.createElement('iframe');
    321  if (typeof allow !== 'undefined') {
    322    frame.allow = allow;
    323  }
    324  promise_test(function() {
    325    assert_feature_policy_supported();
    326    frame.src = src;
    327    return new Promise(function(resolve, reject) {
    328      window.addEventListener('message', function handler(evt) {
    329        resolve(evt.data);
    330      }, { once: true });
    331      document.body.appendChild(frame);
    332    }).then(function(data) {
    333      assert_false(data.includes(feature), feature);
    334    });
    335  }, message);
    336 }
    337 
    338 // This function tests that a subframe with header policy defined on a given
    339 // feature allows and disallows the feature as expected.
    340 // Arguments:
    341 //     feature: feature name.
    342 //     frame_header_policy: either *, 'self' or 'none', defines the frame
    343 //                          document's header policy on |feature|.
    344 //     src: the URL to load in the frame.
    345 //     test_expects: contains 6 expected results of either |feature| is allowed
    346 //                   or not inside of a local or remote iframe nested inside
    347 //                   the subframe given the header policy to be either *,
    348 //                   'slef', or 'none'.
    349 //     test_name: name of the test.
    350 function test_subframe_header_policy(
    351    feature, frame_header_policy, src, test_expects, test_name) {
    352  let frame = document.createElement('iframe');
    353  promise_test(function() {
    354    assert_feature_policy_supported()
    355    frame.src = src + '?pipe=sub|header(Feature-Policy,' + feature + ' '
    356        + frame_header_policy + ';)';
    357    return new Promise(function(resolve) {
    358      window.addEventListener('message', function handler(evt) {
    359        resolve(evt.data);
    360      });
    361      document.body.appendChild(frame);
    362    }).then(function(results) {
    363      for (var j = 0; j < results.length; j++) {
    364        var data = results[j];
    365 
    366        function test_result(message, test_expect) {
    367          if (test_expect) {
    368            assert_true(data.allowedfeatures.includes(feature), message);
    369          } else {
    370            assert_false(data.allowedfeatures.includes(feature), message);
    371          }
    372        }
    373 
    374        if (data.frame === 'local') {
    375          if (data.policy === '*') {
    376            test_result('local_all:', test_expects.local_all);
    377          }
    378          if (data.policy === '\'self\'') {
    379            test_result('local_self:', test_expects.local_self);
    380          }
    381          if (data.policy === '\'none\'') {
    382            test_result('local_none:', test_expects.local_none);
    383          }
    384        }
    385 
    386        if (data.frame === 'remote') {
    387          if (data.policy === '*') {
    388            test_result('remote_all:', test_expects.remote_all);
    389          }
    390          if (data.policy === '\'self\'') {
    391            test_result('remote_self:', test_expects.remote_self);
    392          }
    393          if (data.policy === '\'none\'') {
    394            test_result('remote_none:', test_expects.remote_none);
    395          }
    396        }
    397      }
    398    });
    399  }, test_name);
    400 }
    401 
    402 // This function tests that frame policy allows a given feature correctly. A
    403 // feature is allowed in a frame either through inherited policy or specified
    404 // by iframe allow attribute.
    405 // Arguments:
    406 //     feature: feature name.
    407 //     src: the URL to load in the frame. If undefined, the iframe will have a
    408 //         srcdoc="" attribute
    409 //     test_expect: boolean value of whether the feature should be allowed.
    410 //     allow: optional, the allow attribute (container policy) of the iframe.
    411 //     allowfullscreen: optional, boolean value of allowfullscreen attribute.
    412 //     sandbox: optional boolean. If true, the frame will be sandboxed (with
    413 //         allow-scripts, so that tests can run in it.)
    414 function test_frame_policy(
    415    feature, src, srcdoc, test_expect, allow, allowfullscreen, sandbox) {
    416  let frame = document.createElement('iframe');
    417  document.body.appendChild(frame);
    418  // frame_policy should be dynamically updated as allow and allowfullscreen is
    419  // updated.
    420  var frame_policy = frame.featurePolicy;
    421  if (typeof allow !== 'undefined') {
    422    frame.setAttribute('allow', allow);
    423  }
    424  if (!!allowfullscreen) {
    425    frame.setAttribute('allowfullscreen', true);
    426  }
    427  if (!!sandbox) {
    428    frame.setAttribute('sandbox', 'allow-scripts');
    429  }
    430  if (!!src) {
    431    frame.src = src;
    432  }
    433  if (!!srcdoc) {
    434    frame.srcdoc = "<h1>Hello world!</h1>";
    435  }
    436  if (test_expect) {
    437    assert_true(frame_policy.allowedFeatures().includes(feature));
    438  } else {
    439    assert_false(frame_policy.allowedFeatures().includes(feature));
    440  }
    441 }
    442 
    443 function expect_reports(report_count, policy_name, description) {
    444  async_test(t => {
    445    var num_received_reports = 0;
    446    new ReportingObserver(t.step_func((reports, observer) => {
    447        const relevant_reports = reports.filter(r => (r.body.featureId === policy_name));
    448        num_received_reports += relevant_reports.length;
    449        if (num_received_reports >= report_count) {
    450            t.done();
    451        }
    452   }), {types: ['permissions-policy-violation'], buffered: true}).observe();
    453  }, description);
    454 }