1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
6 const PRIORITY_SET_TOPIC =
7 "process-priority-manager:TEST-ONLY:process-priority-set";
10 const PROCESS_PRIORITY_FOREGROUND = "FOREGROUND";
11 const PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE = "BACKGROUND_PERCEIVABLE";
12 const PROCESS_PRIORITY_BACKGROUND = "BACKGROUND";
14 // This is how many milliseconds we'll wait for a process priority
15 // change before we assume that it's just not happening.
16 const WAIT_FOR_CHANGE_TIME_MS = 2000;
18 // A convenience function for getting the child ID from a browsing context.
19 function browsingContextChildID(bc) {
20 return bc.currentWindowGlobal?.domProcess.childID;
24 * This class is responsible for watching process priority changes, and
25 * mapping them to tabs in a single window.
27 class TabPriorityWatcher {
29 * Constructing a TabPriorityWatcher should happen before any tests
30 * start when there's only a single tab in the window.
32 * Callers must call `destroy()` on any instance that is constructed
33 * when the test is completed.
35 * @param tabbrowser (<tabbrowser>)
36 * The tabbrowser (gBrowser) for the window to be tested.
38 constructor(tabbrowser) {
39 this.tabbrowser = tabbrowser;
41 tabbrowser.tabs.length,
43 "TabPriorityWatcher must be constructed in a window " +
44 "with a single tab to start."
47 // This maps from childIDs to process priorities.
48 this.priorityMap = new Map();
50 // The keys in this map are childIDs we're not expecting to change.
51 // Each value is an array of priorities we've seen the childID changed
52 // to since it was added to the map. If the array is empty, there
53 // have been no changes.
54 this.noChangeChildIDs = new Map();
56 Services.obs.addObserver(this, PRIORITY_SET_TOPIC);
60 * Cleans up lingering references for an instance of
61 * TabPriorityWatcher to avoid leaks. This should be called when
65 Services.obs.removeObserver(this, PRIORITY_SET_TOPIC);
69 * This returns a Promise that resolves when the process with
70 * the given childID reaches the given priority.
71 * This will eventually time out if that priority is never reached.
74 * The childID of the process to wait on.
75 * @param expectedPriority (String)
76 * One of the PROCESS_PRIORITY_ constants defined at the
80 * Once the browser reaches the expected priority.
82 async waitForPriorityChange(childID, expectedPriority) {
83 await TestUtils.waitForCondition(() => {
84 let currentPriority = this.priorityMap.get(childID);
85 if (currentPriority == expectedPriority) {
88 `Process with child ID ${childID} reached expected ` +
89 `priority: ${currentPriority}`
94 }, `Waiting for process with child ID ${childID} to reach priority ${expectedPriority}`);
98 * Returns a Promise that resolves after a duration of
99 * WAIT_FOR_CHANGE_TIME_MS. During that time, if the process
100 * with the passed in child ID changes priority, a test
101 * failure will be registered.
104 * The childID of the process that we expect to not change priority.
106 * @resolves undefined
107 * Once the WAIT_FOR_CHANGE_TIME_MS duration has passed.
109 async ensureNoPriorityChange(childID) {
110 this.noChangeChildIDs.set(childID, []);
111 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
112 await new Promise(resolve => setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS));
113 let priorities = this.noChangeChildIDs.get(childID);
117 `Should have seen no process priority changes for child ID ${childID}`
119 this.noChangeChildIDs.delete(childID);
123 * This returns a Promise that resolves when all of the processes
124 * of the browsing contexts in the browsing context tree
125 * of a particular <browser> have reached a particular priority.
126 * This will eventually time out if that priority is never reached.
128 * @param browser (<browser>)
129 * The <browser> to get the BC tree from.
130 * @param expectedPriority (String)
131 * One of the PROCESS_PRIORITY_ constants defined at the
134 * @resolves undefined
135 * Once the browser reaches the expected priority.
137 async waitForBrowserTreePriority(browser, expectedPriority) {
138 let childIDs = new Set(
139 browser.browsingContext
140 .getAllBrowsingContextsInSubtree()
141 .map(browsingContextChildID)
144 for (let childID of childIDs) {
145 let currentPriority = this.priorityMap.get(childID);
148 currentPriority == expectedPriority
149 ? this.ensureNoPriorityChange(childID)
150 : this.waitForPriorityChange(childID, expectedPriority)
154 await Promise.all(promises);
158 * Synchronously returns the priority of a particular child ID.
161 * The childID to get the content process priority for.
163 * The priority of the child ID's process.
165 currentPriority(childID) {
166 return this.priorityMap.get(childID);
170 * A utility function that takes a string passed via the
171 * PRIORITY_SET_TOPIC observer notification and extracts the
172 * childID and priority string.
174 * @param ppmDataString (String)
175 * The string data passed through the PRIORITY_SET_TOPIC observer
178 * An object with the following properties:
181 * The ID of the content process that changed priority.
184 * The priority that the content process was set to.
186 parsePPMData(ppmDataString) {
187 let [childIDStr, priority] = ppmDataString.split(":");
189 childID: parseInt(childIDStr, 10),
195 observe(subject, topic, data) {
196 if (topic != PRIORITY_SET_TOPIC) {
197 Assert.ok(false, "TabPriorityWatcher is observing the wrong topic");
201 let { childID, priority } = this.parsePPMData(data);
202 if (this.noChangeChildIDs.has(childID)) {
203 this.noChangeChildIDs.get(childID).push(priority);
205 this.priorityMap.set(childID, priority);
209 let gTabPriorityWatcher;
211 add_setup(async function () {
212 // We need to turn on testMode for the process priority manager in
213 // order to receive the observer notifications that this test relies on.
214 await SpecialPowers.pushPrefEnv({
216 ["dom.ipc.processPriorityManager.testMode", true],
217 ["dom.ipc.processPriorityManager.enabled", true],
220 gTabPriorityWatcher = new TabPriorityWatcher(gBrowser);
223 registerCleanupFunction(() => {
224 gTabPriorityWatcher.destroy();
225 gTabPriorityWatcher = null;
229 * Utility function that switches the current tabbrowser from one
230 * tab to another, and ensures that the tab that goes into the background
231 * has (or reaches) a particular content process priority.
233 * It is expected that the fromTab and toTab belong to two separate content
237 * An object with the following properties:
240 * The tab that will be switched from to the toTab. The fromTab
241 * is the one that will be going into the background.
244 * The tab that will be switched to from the fromTab. The toTab
245 * is presumed to start in the background, and will enter the
248 * fromTabExpectedPriority (String)
249 * The priority that the content process for the fromTab is
250 * expected to be (or reach) after the tab goes into the background.
251 * This should be one of the PROCESS_PRIORITY_ strings defined at the
255 * @resolves undefined
256 * Once the tab switch is complete, and the two content processes for the
257 * tabs have reached the expected priority levels.
259 async function assertPriorityChangeOnBackground({
262 fromTabExpectedPriority,
264 let fromBrowser = fromTab.linkedBrowser;
265 let toBrowser = toTab.linkedBrowser;
267 // If the tabs aren't running in separate processes, none of the
268 // rest of this is going to work.
270 toBrowser.frameLoader.remoteTab.osPid,
271 fromBrowser.frameLoader.remoteTab.osPid,
272 "Tabs should be running in separate processes."
275 let fromPromise = gTabPriorityWatcher.waitForBrowserTreePriority(
277 fromTabExpectedPriority
279 let toPromise = gTabPriorityWatcher.waitForBrowserTreePriority(
281 PROCESS_PRIORITY_FOREGROUND
284 await BrowserTestUtils.switchTab(gBrowser, toTab);
285 await Promise.all([fromPromise, toPromise]);
289 * Test that if a normal tab goes into the background,
290 * it has its process priority lowered to PROCESS_PRIORITY_BACKGROUND.
291 * Additionally, test priorityHint flag sets the process priority
292 * appropriately to PROCESS_PRIORITY_BACKGROUND and PROCESS_PRIORITY_FOREGROUND.
294 add_task(async function test_normal_background_tab() {
295 let originalTab = gBrowser.selectedTab;
297 await BrowserTestUtils.withNewTab(
298 "https://example.com/browser/dom/ipc/tests/file_cross_frame.html",
300 let tab = gBrowser.getTabForBrowser(browser);
301 await assertPriorityChangeOnBackground({
304 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
307 await assertPriorityChangeOnBackground({
308 fromTab: originalTab,
310 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
313 let origtabID = browsingContextChildID(
314 originalTab.linkedBrowser.browsingContext
318 originalTab.linkedBrowser.frameLoader.remoteTab.priorityHint,
320 "PriorityHint of the original tab should be false by default"
323 // Changing renderLayers doesn't change priority of the background tab.
324 originalTab.linkedBrowser.preserveLayers(true);
325 originalTab.linkedBrowser.renderLayers = true;
326 await new Promise(resolve =>
327 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
328 setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS)
331 gTabPriorityWatcher.currentPriority(origtabID),
332 PROCESS_PRIORITY_BACKGROUND,
333 "Tab didn't get prioritized only due to renderLayers"
336 // Test when priorityHint is true, the original tab priority
337 // becomes PROCESS_PRIORITY_FOREGROUND.
338 originalTab.linkedBrowser.frameLoader.remoteTab.priorityHint = true;
340 gTabPriorityWatcher.currentPriority(origtabID),
341 PROCESS_PRIORITY_FOREGROUND,
342 "Setting priorityHint to true should set the original tab to foreground priority"
345 // Test when priorityHint is false, the original tab priority
346 // becomes PROCESS_PRIORITY_BACKGROUND.
347 originalTab.linkedBrowser.frameLoader.remoteTab.priorityHint = false;
348 await new Promise(resolve =>
349 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
350 setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS)
353 gTabPriorityWatcher.currentPriority(origtabID),
354 PROCESS_PRIORITY_BACKGROUND,
355 "Setting priorityHint to false should set the original tab to background priority"
358 let tabID = browsingContextChildID(tab.linkedBrowser.browsingContext);
360 // Test when priorityHint is true, the process priority of the
361 // active tab remains PROCESS_PRIORITY_FOREGROUND.
362 tab.linkedBrowser.frameLoader.remoteTab.priorityHint = true;
364 gTabPriorityWatcher.currentPriority(tabID),
365 PROCESS_PRIORITY_FOREGROUND,
366 "Setting priorityHint to true should maintain the new tab priority as foreground"
369 // Test when priorityHint is false, the process priority of the
370 // active tab remains PROCESS_PRIORITY_FOREGROUND.
371 tab.linkedBrowser.frameLoader.remoteTab.priorityHint = false;
373 gTabPriorityWatcher.currentPriority(tabID),
374 PROCESS_PRIORITY_FOREGROUND,
375 "Setting priorityHint to false should maintain the new tab priority as foreground"
378 originalTab.linkedBrowser.preserveLayers(false);
379 originalTab.linkedBrowser.renderLayers = false;
384 // Load a simple page on the given host into a new tab.
385 async function loadKeepAliveTab(host) {
386 let tab = await BrowserTestUtils.openNewForegroundTab(
388 host + "/browser/dom/ipc/tests/file_dummy.html"
390 let childID = browsingContextChildID(
391 gBrowser.selectedBrowser.browsingContext
395 gTabPriorityWatcher.currentPriority(childID),
396 PROCESS_PRIORITY_FOREGROUND,
397 "Loading a new tab should make it prioritized"
400 if (SpecialPowers.useRemoteSubframes) {
401 // There must be only one process with a remote type for the tab we loaded
402 // to ensure that when we load a new page into the iframe with that host
403 // that it will end up in the same process as the initial tab.
404 let remoteType = gBrowser.selectedBrowser.remoteType;
405 await TestUtils.waitForCondition(() => {
407 ChromeUtils.getAllDOMProcesses().filter(
408 process => process.remoteType == remoteType
411 }, `Waiting for there to be only one process with remote type ${remoteType}`);
414 return { tab, childID };
417 const AUDIO_WAKELOCK_NAME = "audio-playing";
418 const VIDEO_WAKELOCK_NAME = "video-playing";
420 // This function was copied from toolkit/content/tests/browser/head.js
421 function wakeLockObserved(powerManager, observeTopic, checkFn) {
422 return new Promise(resolve => {
423 function wakeLockListener() {}
424 wakeLockListener.prototype = {
425 QueryInterface: ChromeUtils.generateQI(["nsIDOMMozWakeLockListener"]),
426 callback(topic, state) {
427 if (topic == observeTopic && checkFn(state)) {
428 powerManager.removeWakeLockListener(wakeLockListener.prototype);
433 powerManager.addWakeLockListener(wakeLockListener.prototype);
437 // This function was copied from toolkit/content/tests/browser/head.js
438 async function waitForExpectedWakeLockState(
440 { needLock, isForegroundLock }
442 const powerManagerService = Cc["@mozilla.org/power/powermanagerservice;1"];
443 const powerManager = powerManagerService.getService(
444 Ci.nsIPowerManagerService
446 const wakelockState = powerManager.getWakeLockState(topic);
447 let expectedLockState = "unlocked";
449 expectedLockState = isForegroundLock
450 ? "locked-foreground"
451 : "locked-background";
453 if (wakelockState != expectedLockState) {
454 info(`wait until wakelock becomes ${expectedLockState}`);
455 await wakeLockObserved(
458 state => state == expectedLockState
462 powerManager.getWakeLockState(topic),
464 `the wakelock state for '${topic}' is equal to '${expectedLockState}'`
469 * If an iframe in a foreground tab is navigated to a new page for
470 * a different site, then the process of the new iframe page should
471 * have priority PROCESS_PRIORITY_FOREGROUND. Additionally, if Fission
472 * is enabled, then the old iframe page's process's priority should be
473 * lowered to PROCESS_PRIORITY_BACKGROUND.
475 add_task(async function test_iframe_navigate() {
476 // This test (eventually) loads a page from the host topHost that has an
477 // iframe from iframe1Host. It then navigates the iframe to iframe2Host.
478 let topHost = "https://example.com";
479 let iframe1Host = "https://example.org";
480 let iframe2Host = "https://example.net";
482 // Before we load the final test page into a tab, we need to load pages
483 // from both iframe hosts into tabs. This is needed so that we are testing
484 // the "load a new page" part of prioritization and not the "initial
485 // process load" part. Additionally, it ensures that the process for the
486 // initial iframe page doesn't shut down once we navigate away from it,
487 // which will also affect its prioritization.
488 let { tab: iframe1Tab, childID: iframe1TabChildID } = await loadKeepAliveTab(
491 let { tab: iframe2Tab, childID: iframe2TabChildID } = await loadKeepAliveTab(
495 await BrowserTestUtils.withNewTab(
496 topHost + "/browser/dom/ipc/tests/file_cross_frame.html",
499 gTabPriorityWatcher.currentPriority(iframe2TabChildID),
500 PROCESS_PRIORITY_BACKGROUND,
501 "Switching to another new tab should deprioritize the old one"
504 let topChildID = browsingContextChildID(browser.browsingContext);
505 let iframe = browser.browsingContext.children[0];
506 let iframe1ChildID = browsingContextChildID(iframe);
509 gTabPriorityWatcher.currentPriority(topChildID),
510 PROCESS_PRIORITY_FOREGROUND,
511 "The top level page in the new tab should be prioritized"
515 gTabPriorityWatcher.currentPriority(iframe1ChildID),
516 PROCESS_PRIORITY_FOREGROUND,
517 "The iframe in the new tab should be prioritized"
520 if (SpecialPowers.useRemoteSubframes) {
521 // Basic process uniqueness checks for the state after all three tabs
522 // are initially loaded.
526 "file_cross_frame.html should be loaded into a different process " +
527 "than its initial iframe"
533 "file_cross_frame.html should be loaded into a different process " +
534 "than the tab containing iframe2Host"
540 "The initial iframe loaded by file_cross_frame.html should be " +
541 "loaded into a different process than the tab containing " +
545 // Note: this assertion depends on our process selection logic.
546 // Specifically, that we reuse an existing process for an iframe if
551 "Both pages loaded in iframe1Host should be in the same process"
555 // Do a cross-origin navigation in the iframe in the foreground tab.
556 let iframe2URI = iframe2Host + "/browser/dom/ipc/tests/file_dummy.html";
557 let loaded = BrowserTestUtils.browserLoaded(browser, true, iframe2URI);
558 await SpecialPowers.spawn(
561 async function (_iframe2URI) {
562 content.location = _iframe2URI;
567 let iframe2ChildID = browsingContextChildID(iframe);
568 let iframe1Priority = gTabPriorityWatcher.currentPriority(iframe1ChildID);
569 let iframe2Priority = gTabPriorityWatcher.currentPriority(iframe2ChildID);
571 if (SpecialPowers.useRemoteSubframes) {
572 // Basic process uniqueness check for the state after navigating the
573 // iframe. There's no need to check the top level pages because they
574 // have not navigated.
576 // iframe1ChildID != iframe2ChildID is implied by:
577 // iframe1ChildID != iframe2TabChildID
578 // iframe2TabChildID == iframe2ChildID
580 // iframe2ChildID != topChildID is implied by:
581 // topChildID != iframe2TabChildID
582 // iframe2TabChildID == iframe2ChildID
584 // Note: this assertion depends on our process selection logic.
585 // Specifically, that we reuse an existing process for an iframe if
586 // possible. If that changes, this test may need to be carefully
587 // rewritten, as the whole point of the test is to check what happens
588 // with the priority manager when an iframe shares a process with
589 // a page in another tab.
593 "Both pages loaded in iframe2Host should be in the same process"
596 // Now that we've established the relationship between the various
597 // processes, we can finally check that the priority manager is doing
601 PROCESS_PRIORITY_BACKGROUND,
602 "The old iframe process should have been deprioritized"
608 "Navigation should not have switched processes"
614 PROCESS_PRIORITY_FOREGROUND,
615 "The new iframe process should be prioritized"
620 await BrowserTestUtils.removeTab(iframe2Tab);
621 await BrowserTestUtils.removeTab(iframe1Tab);
625 * Test that a cross-group navigation properly preserves the process priority.
626 * The goal of this test is to check that the code related to mPriorityActive in
627 * CanonicalBrowsingContext::ReplacedBy works correctly, but in practice the
628 * prioritization code in SetRenderLayers will also make this test pass, though
629 * that prioritization happens slightly later.
631 add_task(async function test_cross_group_navigate() {
632 // This page is same-site with the page we're going to cross-group navigate to.
634 "https://example.com/browser/dom/tests/browser/file_coop_coep.html";
636 // Load it as a top level tab so that we don't accidentally get the initial
637 // load prioritization.
638 let backgroundTab = await BrowserTestUtils.openNewForegroundTab(
642 let backgroundTabChildID = browsingContextChildID(
643 gBrowser.selectedBrowser.browsingContext
647 gTabPriorityWatcher.currentPriority(backgroundTabChildID),
648 PROCESS_PRIORITY_FOREGROUND,
649 "Loading a new tab should make it prioritized"
652 await BrowserTestUtils.withNewTab(
653 "https://example.org/browser/dom/ipc/tests/file_cross_frame.html",
656 gTabPriorityWatcher.currentPriority(backgroundTabChildID),
657 PROCESS_PRIORITY_BACKGROUND,
658 "Switching to a new tab should deprioritize the old one"
661 let dotOrgChildID = browsingContextChildID(browser.browsingContext);
663 // Do a cross-group navigation.
664 BrowserTestUtils.startLoadingURIString(browser, coopPage);
665 await BrowserTestUtils.browserLoaded(browser);
667 let coopChildID = browsingContextChildID(browser.browsingContext);
668 let coopPriority = gTabPriorityWatcher.currentPriority(coopChildID);
669 let dotOrgPriority = gTabPriorityWatcher.currentPriority(dotOrgChildID);
672 backgroundTabChildID,
674 "The same site should get loaded into the same process"
679 "Navigation should have switched processes"
683 PROCESS_PRIORITY_BACKGROUND,
684 "The old page process should have been deprioritized"
688 PROCESS_PRIORITY_FOREGROUND,
689 "The new page process should be prioritized"
694 await BrowserTestUtils.removeTab(backgroundTab);
698 * Test that if a tab with video goes into the background,
699 * it has its process priority lowered to
700 * PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE if it has no audio,
701 * and that it has its priority remain at
702 * PROCESS_PRIORITY_FOREGROUND if it does have audio.
704 add_task(async function test_video_background_tab() {
705 let originalTab = gBrowser.selectedTab;
707 await BrowserTestUtils.withNewTab("https://example.com", async browser => {
708 // Let's load up a video in the tab, but mute it, so that this tab should
709 // reach PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE. We need to wait for the
710 // wakelock changes from the unmuting to get back up to the parent.
711 await SpecialPowers.spawn(browser, [], async () => {
712 let video = content.document.createElement("video");
713 video.src = "https://example.net/browser/dom/ipc/tests/short.mp4";
715 content.document.body.appendChild(video);
716 // We'll loop the video to avoid it ending before the test is done.
721 waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, {
724 waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, {
726 isForegroundLock: true,
730 let tab = gBrowser.getTabForBrowser(browser);
732 // The tab with the muted video should reach
733 // PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE when backgrounded.
734 await assertPriorityChangeOnBackground({
737 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE,
740 // Now switch back. The initial blank tab should reach
741 // PROCESS_PRIORITY_BACKGROUND when backgrounded.
742 await assertPriorityChangeOnBackground({
743 fromTab: originalTab,
745 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
748 // Let's unmute the video now. We need to wait for the wakelock change from
749 // the unmuting to get back up to the parent.
751 waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, {
753 isForegroundLock: true,
755 SpecialPowers.spawn(browser, [], async () => {
756 let video = content.document.querySelector("video");
761 // The tab with the unmuted video should stay at
762 // PROCESS_PRIORITY_FOREGROUND when backgrounded.
763 await assertPriorityChangeOnBackground({
766 fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND,
769 // Now switch back. The initial blank tab should reach
770 // PROCESS_PRIORITY_BACKGROUND when backgrounded.
771 await assertPriorityChangeOnBackground({
772 fromTab: originalTab,
774 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
780 * Test that if a tab with a playing <audio> element goes into
781 * the background, the process priority does not change, unless
782 * that audio is muted (in which case, it reaches
783 * PROCESS_PRIORITY_BACKGROUND).
785 add_task(async function test_audio_background_tab() {
786 let originalTab = gBrowser.selectedTab;
788 await BrowserTestUtils.withNewTab("https://example.com", async browser => {
789 // Let's load up some audio in the tab, but mute it, so that this tab should
790 // reach PROCESS_PRIORITY_BACKGROUND.
791 await SpecialPowers.spawn(browser, [], async () => {
792 let audio = content.document.createElement("audio");
793 audio.src = "https://example.net/browser/dom/ipc/tests/owl.mp3";
795 content.document.body.appendChild(audio);
796 // We'll loop the audio to avoid it ending before the test is done.
801 let tab = gBrowser.getTabForBrowser(browser);
803 // The tab with the muted audio should reach
804 // PROCESS_PRIORITY_BACKGROUND when backgrounded.
805 await assertPriorityChangeOnBackground({
808 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
811 // Now switch back. The initial blank tab should reach
812 // PROCESS_PRIORITY_BACKGROUND when backgrounded.
813 await assertPriorityChangeOnBackground({
814 fromTab: originalTab,
816 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
819 // Now unmute the audio. Unfortuntely, there's a bit of a race here,
820 // since the wakelock on the audio element is released and then
821 // re-acquired if the audio reaches its end and loops around. This
822 // will cause an unexpected priority change on the content process.
824 // To avoid this race, we'll seek the audio back to the beginning,
825 // and lower its playback rate to the minimum to increase the
826 // likelihood that the check completes before the audio loops around.
827 await SpecialPowers.spawn(browser, [], async () => {
828 let audio = content.document.querySelector("audio");
829 let seeked = ContentTaskUtils.waitForEvent(audio, "seeked");
831 // 0.25 is the minimum playback rate that still keeps the audio audible.
832 audio.playbackRate = 0.25;
833 audio.currentTime = 0;
837 // The tab with the unmuted audio should stay at
838 // PROCESS_PRIORITY_FOREGROUND when backgrounded.
839 await assertPriorityChangeOnBackground({
842 fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND,
845 // Now switch back. The initial blank tab should reach
846 // PROCESS_PRIORITY_BACKGROUND when backgrounded.
847 await assertPriorityChangeOnBackground({
848 fromTab: originalTab,
850 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
856 * Test that if a tab with a WebAudio playing goes into the background,
857 * the process priority does not change, unless that WebAudio context is
860 add_task(async function test_web_audio_background_tab() {
861 let originalTab = gBrowser.selectedTab;
863 await BrowserTestUtils.withNewTab("https://example.com", async browser => {
864 // Let's synthesize a basic square wave as WebAudio.
865 await SpecialPowers.spawn(browser, [], async () => {
866 let audioCtx = new content.AudioContext();
867 let oscillator = audioCtx.createOscillator();
868 oscillator.type = "square";
869 oscillator.frequency.setValueAtTime(440, audioCtx.currentTime);
870 oscillator.connect(audioCtx.destination);
872 while (audioCtx.state != "running") {
873 info(`wait until AudioContext starts running`);
874 await new Promise(r => (audioCtx.onstatechange = r));
876 // we'll stash the AudioContext away so that it's easier to access
877 // in the next SpecialPowers.spawn.
878 content.audioCtx = audioCtx;
881 let tab = gBrowser.getTabForBrowser(browser);
883 // The tab with the WebAudio should stay at
884 // PROCESS_PRIORITY_FOREGROUND when backgrounded.
885 await assertPriorityChangeOnBackground({
888 fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND,
891 // Now switch back. The initial blank tab should reach
892 // PROCESS_PRIORITY_BACKGROUND when backgrounded.
893 await assertPriorityChangeOnBackground({
894 fromTab: originalTab,
896 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
899 // Now suspend the WebAudio. This will cause it to stop
901 await SpecialPowers.spawn(browser, [], async () => {
902 content.audioCtx.suspend();
905 // The tab with the suspended WebAudio should reach
906 // PROCESS_PRIORITY_BACKGROUND when backgrounded.
907 await assertPriorityChangeOnBackground({
910 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
913 // Now switch back. The initial blank tab should reach
914 // PROCESS_PRIORITY_BACKGROUND when backgrounded.
915 await assertPriorityChangeOnBackground({
916 fromTab: originalTab,
918 fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
924 * Test that foreground tab's process priority isn't changed when going back to
925 * a bfcached session history entry.
927 add_task(async function test_audio_background_tab() {
928 let page1 = "https://example.com";
929 let page2 = page1 + "/?2";
931 await BrowserTestUtils.withNewTab(page1, async browser => {
932 let childID = browsingContextChildID(browser.browsingContext);
934 gTabPriorityWatcher.currentPriority(childID),
935 PROCESS_PRIORITY_FOREGROUND,
936 "Loading a new tab should make it prioritized."
938 let loaded = BrowserTestUtils.browserLoaded(browser, false, page2);
939 BrowserTestUtils.startLoadingURIString(browser, page2);
942 childID = browsingContextChildID(browser.browsingContext);
944 gTabPriorityWatcher.currentPriority(childID),
945 PROCESS_PRIORITY_FOREGROUND,
946 "Loading a new page should keep the tab prioritized."
949 let pageShowPromise = BrowserTestUtils.waitForContentEvent(
954 await pageShowPromise;
956 childID = browsingContextChildID(browser.browsingContext);
958 gTabPriorityWatcher.currentPriority(childID),
959 PROCESS_PRIORITY_FOREGROUND,
960 "Loading a page from the bfcache should keep the tab prioritized."