Bug 1945643 - Update to mozilla-nimbus-schemas 2025.1.1 r=chumphreys
[gecko.git] / dom / permission / tests / test_permissions_api.html
blob952ad813e783146f37dbb235af87c8af2c194ae3
1 <!--
2 Any copyright is dedicated to the Public Domain.
3 http://creativecommons.org/publicdomain/zero/1.0/
4 -->
5 <!DOCTYPE HTML>
6 <html>
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>
15 <body>
16 <pre id="test"></pre>
17 <script type="application/javascript">
18 /*globals SpecialPowers, SimpleTest, is, ok, */
19 'use strict';
21 const {
22 UNKNOWN_ACTION,
23 PROMPT_ACTION,
24 ALLOW_ACTION,
25 DENY_ACTION
26 } = SpecialPowers.Ci.nsIPermissionManager;
28 SimpleTest.waitForExplicitFinish();
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 }, ];
47 const MEDIA_PERMISSIONS = [{
48 name: 'camera',
49 type: 'camera'
50 }, {
51 name: 'microphone',
52 type: 'microphone'
53 }, ];
55 const PERMISSIONS = [...OTHER_PERMISSIONS, ...MEDIA_PERMISSIONS];
57 const UNSUPPORTED_PERMISSIONS = [
58 { name: 'foobarbaz' }, // Not in spec, for testing only.
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,
72 });
73 await SpecialPowers.popPermissions();
74 return SpecialPowers.pushPermissions(permissions);
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}'`);
89 () => ok(false, `query should not have rejected for '${name}'`)
91 });
92 return Promise.all(promisesToQuery);
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}'`);
106 return Promise.all(promisesToQuery);
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();
120 () => ok(false, `query should not have rejected for '${name}'`));
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);
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')
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) => {
153 error instanceof expectedErrorClass,
154 "DOMException from other realm"
157 error.name,
158 "InvalidStateError",
159 "Must reject with a InvalidStateError"
161 iframe1.remove();
162 res();
166 async testNotFullyActiveChange() {
167 await SpecialPowers.popPermissions();
168 const iframe2 = await createIframe();
169 const initialStatus = await iframe2.contentWindow.navigator.permissions.query(
170 { name: "geolocation" }
172 await SpecialPowers.pushPermissions([
174 type: "geo",
175 allow: PROMPT_ACTION,
176 context: iframe2.contentWindow.document,
180 initialStatus.state,
181 "prompt",
182 "Initially the iframe's permission is prompt"
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.");
191 // We set it to grant for this origin, but the PermissionStatus doesn't change.
192 await SpecialPowers.pushPermissions([
194 type: "geo",
195 allow: ALLOW_ACTION,
196 context: stolenDoc,
200 initialStatus.state,
201 "prompt",
202 "Inactive document's permission must not change"
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",
212 is(newStatus.state, "granted", "Reflect that we are granted");
214 const newEventPromise = new Promise((res) => (newStatus.onchange = res));
215 await SpecialPowers.pushPermissions([
217 type: "geo",
218 allow: DENY_ACTION,
219 context: iframe2.contentWindow.document,
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();
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);
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();
257 await SpecialPowers.pushPrefEnv({
258 set: [
259 ["privacy.resistFingerprinting", true]
262 await tester.setPermissions(PROMPT_ACTION);
263 await tester.checkPermissions(PERMISSIONS, 'prompt', 'prompt');
264 await SpecialPowers.popPrefEnv();
266 await SpecialPowers.pushPrefEnv({
267 set: [
268 ["permissions.media.query.enabled", false]
271 await tester.setPermissions(UNKNOWN_ACTION);
272 await tester.checkUnsupportedPermissions(MEDIA_PERMISSIONS);
273 await tester.checkPermissions(OTHER_PERMISSIONS, 'prompt');
274 } finally {
275 SimpleTest.finish();
278 </script>
279 </body>
281 </html>