tor-browser

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

test_permissions_api.html (9194B)


      1 <!--
      2  Any copyright is dedicated to the Public Domain.
      3  http://creativecommons.org/publicdomain/zero/1.0/
      4 -->
      5 <!DOCTYPE HTML>
      6 <html>
      7 
      8 <head>
      9  <meta charset="utf-8">
     10  <title>Test for Permissions API</title>
     11  <script src="/tests/SimpleTest/SimpleTest.js"></script>
     12  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
     13 </head>
     14 
     15 <body>
     16  <pre id="test"></pre>
     17  <script type="application/javascript">
     18  /*globals SpecialPowers, SimpleTest, is, ok, */
     19  'use strict';
     20 
     21  const {
     22    UNKNOWN_ACTION,
     23    PROMPT_ACTION,
     24    ALLOW_ACTION,
     25    DENY_ACTION
     26  } = SpecialPowers.Ci.nsIPermissionManager;
     27 
     28  SimpleTest.waitForExplicitFinish();
     29 
     30  const OTHER_PERMISSIONS = [{
     31    name: 'geolocation',
     32    type: 'geo'
     33  }, {
     34    name: 'notifications',
     35    type: 'desktop-notification'
     36  }, {
     37    name: 'push',
     38    type: 'desktop-notification'
     39  }, {
     40    name: 'persistent-storage',
     41    type: 'persistent-storage'
     42  }, {
     43    name: 'midi',
     44    type: 'midi'
     45  }, ];
     46 
     47  const MEDIA_PERMISSIONS = [{
     48    name: 'camera',
     49    type: 'camera'
     50  }, {
     51    name: 'microphone',
     52    type: 'microphone'
     53  }, ];
     54 
     55  const PERMISSIONS = [...OTHER_PERMISSIONS, ...MEDIA_PERMISSIONS];
     56 
     57  const UNSUPPORTED_PERMISSIONS = [
     58    { name: 'foobarbaz' }, // Not in spec, for testing only.
     59  ];
     60 
     61  // Create a closure, so that tests are run on the correct window object.
     62  function createPermissionTester(iframe) {
     63    const iframeWindow = iframe.contentWindow;
     64    return {
     65      async setPermissions(allow, context = iframeWindow.document) {
     66        const permissions = PERMISSIONS.map(({ type }) => {
     67          return {
     68            type,
     69            allow,
     70            context,
     71          };
     72        });
     73        await SpecialPowers.popPermissions();
     74        return SpecialPowers.pushPermissions(permissions);
     75      },
     76      checkPermissions(permissions, expectedState, mediaExpectedState = expectedState) {
     77        const promisesToQuery = permissions.map(({ name: expectedName }) => {
     78          return iframeWindow.navigator.permissions
     79            .query({ name: expectedName })
     80            .then(
     81              ({ state, name }) => {
     82                is(name, expectedName, `correct name for '${expectedName}'`);
     83                if (['camera', 'microphone'].includes(expectedName)) {
     84                  is(state, mediaExpectedState, `correct state for '${expectedName}'`);
     85                } else {
     86                  is(state, expectedState, `correct state for '${expectedName}'`);
     87                }
     88              },
     89              () => ok(false, `query should not have rejected for '${name}'`)
     90            );
     91          });
     92        return Promise.all(promisesToQuery);
     93      },
     94      checkUnsupportedPermissions(permissions) {
     95        const promisesToQuery = permissions.map(({ name }) => {
     96          return iframeWindow.navigator.permissions
     97            .query({ name })
     98            .then(
     99              () => ok(false, `query should not have resolved for '${name}'`),
    100              error => {
    101                is(error.name, 'TypeError',
    102                  `query should have thrown TypeError for '${name}'`);
    103              }
    104            );
    105          });
    106        return Promise.all(promisesToQuery);
    107      },
    108      promiseStateChanged(name, state) {
    109        return iframeWindow.navigator.permissions
    110          .query({ name })
    111          .then(status => {
    112            return new Promise( resolve => {
    113              status.onchange = () => {
    114                status.onchange = null;
    115                is(status.state, state, `state changed for '${name}'`);
    116                resolve();
    117              };
    118            });
    119          },
    120          () => ok(false, `query should not have rejected for '${name}'`));
    121      },
    122      testStatusOnChange() {
    123        return new Promise((resolve) => {
    124          SpecialPowers.popPermissions(() => {
    125            const permission = 'geolocation';
    126            const promiseGranted = this.promiseStateChanged(permission, 'granted');
    127            this.setPermissions(ALLOW_ACTION);
    128            promiseGranted.then(async () => {
    129              const promisePrompt = this.promiseStateChanged(permission, 'prompt');
    130              await SpecialPowers.popPermissions();
    131              return promisePrompt;
    132            }).then(resolve);
    133          });
    134        });
    135      },
    136      testInvalidQuery() {
    137        return iframeWindow.navigator.permissions
    138          .query({ name: 'invalid' })
    139          .then(
    140            () => ok(false, 'invalid query should not have resolved'),
    141            () => ok(true, 'invalid query should have rejected')
    142          );
    143      },
    144      async testNotFullyActiveDoc() {
    145        const iframe1 = await createIframe();
    146        const expectedErrorClass = iframe1.contentWindow.DOMException;
    147        const permAPI = iframe1.contentWindow.navigator.permissions;
    148        // Document no longer fully active
    149        iframe1.remove();
    150        await new Promise((res) => {
    151          permAPI.query({ name: "geolocation" }).catch((error) => {
    152            ok(
    153              error instanceof expectedErrorClass,
    154              "DOMException from other realm"
    155            );
    156            is(
    157              error.name,
    158              "InvalidStateError",
    159              "Must reject with a InvalidStateError"
    160            );
    161            iframe1.remove();
    162            res();
    163          });
    164        });
    165      },
    166      async testNotFullyActiveChange() {
    167        await SpecialPowers.popPermissions();
    168        const iframe2 = await createIframe();
    169        const initialStatus = await iframe2.contentWindow.navigator.permissions.query(
    170          { name: "geolocation" }
    171        );
    172        await SpecialPowers.pushPermissions([
    173          {
    174            type: "geo",
    175            allow: PROMPT_ACTION,
    176            context: iframe2.contentWindow.document,
    177          },
    178        ]);
    179        is(
    180          initialStatus.state,
    181          "prompt",
    182          "Initially the iframe's permission is prompt"
    183        );
    184 
    185        // Document no longer fully active
    186        const stolenDoc = iframe2.contentWindow.document;
    187        iframe2.remove();
    188        initialStatus.onchange = () => {
    189          ok(false, "onchange must not fire when document is not fully active.");
    190        };
    191        // We set it to grant for this origin, but the PermissionStatus doesn't change.
    192        await SpecialPowers.pushPermissions([
    193          {
    194            type: "geo",
    195            allow: ALLOW_ACTION,
    196            context: stolenDoc,
    197          },
    198        ]);
    199        is(
    200          initialStatus.state,
    201          "prompt",
    202          "Inactive document's permission must not change"
    203        );
    204 
    205        // Re-attach the iframe
    206        document.body.appendChild(iframe2);
    207        await new Promise((res) => (iframe2.onload = res));
    208        // Fully active again
    209        const newStatus = await iframe2.contentWindow.navigator.permissions.query({
    210          name: "geolocation",
    211        });
    212        is(newStatus.state, "granted", "Reflect that we are granted");
    213 
    214        const newEventPromise = new Promise((res) => (newStatus.onchange = res));
    215        await SpecialPowers.pushPermissions([
    216          {
    217            type: "geo",
    218            allow: DENY_ACTION,
    219            context: iframe2.contentWindow.document,
    220          },
    221        ]);
    222        // Event fires...
    223        await newEventPromise;
    224        is(initialStatus.state, "prompt", "Remains prompt, as it's actually dead.");
    225        is(newStatus.state, "denied", "New status must be 'denied'.");
    226        iframe2.remove();
    227      },
    228    };
    229  }
    230 
    231  function createIframe() {
    232    return new Promise((resolve) => {
    233      const iframe = document.createElement('iframe');
    234      iframe.src = 'file_empty.html';
    235      iframe.onload = () => resolve(iframe);
    236      document.body.appendChild(iframe);
    237    });
    238  }
    239 
    240  window.onload = async () => {
    241    try {
    242      const tester = createPermissionTester(await createIframe());
    243      await tester.checkUnsupportedPermissions(UNSUPPORTED_PERMISSIONS);
    244      await tester.setPermissions(UNKNOWN_ACTION);
    245      await tester.checkPermissions(PERMISSIONS, 'prompt');
    246      await tester.setPermissions(PROMPT_ACTION);
    247      await tester.checkPermissions(PERMISSIONS, 'prompt', 'granted');
    248      await tester.setPermissions(ALLOW_ACTION);
    249      await tester.checkPermissions(PERMISSIONS, 'granted');
    250      await tester.setPermissions(DENY_ACTION);
    251      await tester.checkPermissions(PERMISSIONS, 'denied');
    252      await tester.testStatusOnChange();
    253      await tester.testInvalidQuery();
    254      await tester.testNotFullyActiveDoc();
    255      await tester.testNotFullyActiveChange();
    256 
    257      await SpecialPowers.pushPrefEnv({
    258        set: [
    259          ["privacy.resistFingerprinting", true]
    260        ]
    261      });
    262      await tester.setPermissions(PROMPT_ACTION);
    263      await tester.checkPermissions(PERMISSIONS, 'prompt', 'prompt');
    264      await SpecialPowers.popPrefEnv();
    265 
    266      await SpecialPowers.pushPrefEnv({
    267        set: [
    268          ["permissions.media.query.enabled", false]
    269        ]
    270      });
    271      await tester.setPermissions(UNKNOWN_ACTION);
    272      await tester.checkUnsupportedPermissions(MEDIA_PERMISSIONS);
    273      await tester.checkPermissions(OTHER_PERMISSIONS, 'prompt');
    274    } finally {
    275      SimpleTest.finish();
    276    }
    277  };
    278  </script>
    279 </body>
    280 
    281 </html>