summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/webrtc/browser_devices_select_audio_output.js
blob: 87d2d42a3a5d9616648324cf8123862b16fe8e96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

requestLongerTimeout(2);

const permissionError =
  "error: NotAllowedError: The request is not allowed " +
  "by the user agent or the platform in the current context.";

async function requestAudioOutput(options) {
  await Promise.all([
    expectObserverCalled("getUserMedia:request"),
    expectObserverCalled("recording-window-ended"),
    promiseRequestAudioOutput(options),
  ]);
}

async function requestAudioOutputExpectingPrompt(options) {
  await Promise.all([
    promisePopupNotificationShown("webRTC-shareDevices"),
    requestAudioOutput(options),
  ]);

  is(
    PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
    "webRTC-shareSpeaker-notification-icon",
    "anchored to device icon"
  );
  checkDeviceSelectors(["speaker"]);
}

async function requestAudioOutputExpectingDeny(options) {
  await Promise.all([
    requestAudioOutput(options),
    expectObserverCalled("getUserMedia:response:deny"),
    promiseMessage(permissionError),
  ]);
}

async function simulateAudioOutputRequest(options) {
  await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [options],
    function simPrompt({ deviceCount, deviceId }) {
      const devices = [...Array(deviceCount).keys()].map(i => ({
        type: "audiooutput",
        rawName: `name ${i}`,
        deviceIndex: i,
        rawId: `rawId ${i}`,
        id: `id ${i}`,
        QueryInterface: ChromeUtils.generateQI([Ci.nsIMediaDevice]),
      }));
      const req = {
        type: "selectaudiooutput",
        windowID: content.windowGlobalChild.outerWindowId,
        devices,
        getConstraints: () => ({}),
        getAudioOutputOptions: () => ({ deviceId }),
        isSecure: true,
        isHandlingUserInput: true,
      };
      const { WebRTCChild } = SpecialPowers.ChromeUtils.importESModule(
        "resource:///actors/WebRTCChild.sys.mjs"
      );
      WebRTCChild.observe(req, "getUserMedia:request");
    }
  );
}

async function allowPrompt() {
  const observerPromise = expectObserverCalled("getUserMedia:response:allow");
  PopupNotifications.panel.firstElementChild.button.click();
  await observerPromise;
}

async function allow() {
  await Promise.all([promiseMessage("ok"), allowPrompt()]);
}

async function denyPrompt() {
  const observerPromise = expectObserverCalled("getUserMedia:response:deny");
  activateSecondaryAction(kActionDeny);
  await observerPromise;
}

async function deny() {
  await Promise.all([promiseMessage(permissionError), denyPrompt()]);
}

async function escapePrompt() {
  const observerPromise = expectObserverCalled("getUserMedia:response:deny");
  EventUtils.synthesizeKey("KEY_Escape");
  await observerPromise;
}

async function escape() {
  await Promise.all([promiseMessage(permissionError), escapePrompt()]);
}

var gTests = [
  {
    desc: 'User clicks "Allow" and revokes',
    run: async function checkAllow() {
      await requestAudioOutputExpectingPrompt();
      await allow();

      info("selectAudioOutput() with no deviceId again should prompt again.");
      await requestAudioOutputExpectingPrompt();
      await allow();

      info("selectAudioOutput() with same deviceId should not prompt again.");
      await Promise.all([
        expectObserverCalled("getUserMedia:response:allow"),
        promiseMessage("ok"),
        requestAudioOutput({ requestSameDevice: true }),
      ]);

      await revokePermission("speaker", true);
      info("Same deviceId should prompt again after revoked permission.");
      await requestAudioOutputExpectingPrompt({ requestSameDevice: true });
      await allow();
      await revokePermission("speaker", true);
    },
  },
  {
    desc: 'User clicks "Not Now"',
    run: async function checkNotNow() {
      await requestAudioOutputExpectingPrompt();
      is(
        PopupNotifications.getNotification("webRTC-shareDevices")
          .secondaryActions[0].label,
        "Not now",
        "first secondary action label"
      );
      await deny();
      info("selectAudioOutput() after Not Now should prompt again.");
      await requestAudioOutputExpectingPrompt();
      await escape();
    },
  },
  {
    desc: 'User presses "Esc"',
    run: async function checkEsc() {
      await requestAudioOutputExpectingPrompt();
      await escape();
      info("selectAudioOutput() after Esc should prompt again.");
      await requestAudioOutputExpectingPrompt();
      await allow();
      await revokePermission("speaker", true);
    },
  },
  {
    desc: 'User clicks "Always Block"',
    run: async function checkAlwaysBlock() {
      await requestAudioOutputExpectingPrompt();
      await Promise.all([
        expectObserverCalled("getUserMedia:response:deny"),
        promiseMessage(permissionError),
        activateSecondaryAction(kActionNever),
      ]);
      info("selectAudioOutput() after Always Block should not prompt again.");
      await requestAudioOutputExpectingDeny();
      await revokePermission("speaker", true);
    },
  },
  {
    desc: "Single Device",
    run: async function checkSingle() {
      await Promise.all([
        promisePopupNotificationShown("webRTC-shareDevices"),
        simulateAudioOutputRequest({ deviceCount: 1 }),
      ]);
      checkDeviceSelectors(["speaker"]);
      await escapePrompt();
    },
  },
  {
    desc: "Multi Device with deviceId",
    run: async function checkMulti() {
      const deviceCount = 4;
      await Promise.all([
        promisePopupNotificationShown("webRTC-shareDevices"),
        simulateAudioOutputRequest({ deviceCount, deviceId: "id 2" }),
      ]);
      const selectorList = document.getElementById(
        `webRTC-selectSpeaker-menulist`
      );
      is(selectorList.selectedIndex, 2, "pre-selected index");
      checkDeviceSelectors(["speaker"]);
      await allowPrompt();

      info("Expect same-device request allowed without prompt");
      await Promise.all([
        expectObserverCalled("getUserMedia:response:allow"),
        simulateAudioOutputRequest({ deviceCount, deviceId: "id 2" }),
      ]);

      info("Expect prompt for different-device request");
      await Promise.all([
        promisePopupNotificationShown("webRTC-shareDevices"),
        simulateAudioOutputRequest({ deviceCount, deviceId: "id 1" }),
      ]);
      await denyPrompt();

      info("Expect prompt again for denied-device request");
      await Promise.all([
        promisePopupNotificationShown("webRTC-shareDevices"),
        simulateAudioOutputRequest({ deviceCount, deviceId: "id 1" }),
      ]);
      await escapePrompt();

      await revokePermission("speaker", true);
    },
  },
  {
    desc: "SitePermissions speaker block",
    run: async function checkPermissionsBlock() {
      SitePermissions.setForPrincipal(
        gBrowser.contentPrincipal,
        "speaker",
        SitePermissions.BLOCK
      );
      await requestAudioOutputExpectingDeny();
      SitePermissions.removeFromPrincipal(gBrowser.contentPrincipal, "speaker");
    },
  },
];

add_task(async function test() {
  await SpecialPowers.pushPrefEnv({ set: [["media.setsinkid.enabled", true]] });
  await runTests(gTests);
});