From 683790582ee6cb122aed864e5ce2c25da9ef021e Mon Sep 17 00:00:00 2001 From: alwu Date: Tue, 3 Aug 2021 21:32:26 +0000 Subject: [PATCH] Bug 1719183 - part2 : expose the suspended state of window to chrome window for testing. r=nika Before we use AudioContext's state as a hack to know the suspend status of window, but now we will prevent AudioContext from being suspended in the following patch. So we need to add a chrome-only attribute to expose that to the test. Differential Revision: https://phabricator.services.mozilla.com/D119838 --- dom/base/nsDOMWindowUtils.cpp | 12 ++++ dom/interfaces/base/nsIDOMWindowUtils.idl | 5 +- .../browser/browser_suspend_inactive_tab.js | 57 +++++++------------ 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index c16e2f70a918..e198f92c317f 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -4851,3 +4851,15 @@ nsDOMWindowUtils::ResetMobileViewportManager() { // Unable to reset, so let's error out return NS_ERROR_FAILURE; } + +NS_IMETHODIMP +nsDOMWindowUtils::GetSuspendedByBrowsingContextGroup(bool* aResult) { + nsCOMPtr window = do_QueryReferent(mWindow); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + nsCOMPtr inner = window->GetCurrentInnerWindow(); + NS_ENSURE_TRUE(inner, NS_ERROR_FAILURE); + + *aResult = inner->GetWasSuspendedByGroup(); + return NS_OK; +} diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 64473aa2e412..e60650ec1494 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -753,7 +753,7 @@ interface nsIDOMWindowUtils : nsISupports { /** * Send a native event as if the user double tapped the touchpad with two * fingers. - * + * * Widget support: macOS. * @param aScreenX, aScreenY screen coords of the focus point of this event. * @param aModifierFlags is expected to contain native modifier values. @@ -2228,6 +2228,9 @@ interface nsIDOMWindowUtils : nsISupports { // will intentionally crash any non-parent process that tries to access // it. readonly attribute AString webrtcRawDeviceId; + + // Used for testing to check the suspend status. + readonly attribute bool suspendedByBrowsingContextGroup; }; [scriptable, uuid(c694e359-7227-4392-a138-33c0cc1f15a6)] diff --git a/dom/media/mediacontrol/tests/browser/browser_suspend_inactive_tab.js b/dom/media/mediacontrol/tests/browser/browser_suspend_inactive_tab.js index 867511c6d592..96ae98301f88 100644 --- a/dom/media/mediacontrol/tests/browser/browser_suspend_inactive_tab.js +++ b/dom/media/mediacontrol/tests/browser/browser_suspend_inactive_tab.js @@ -28,11 +28,11 @@ add_task(async function setupTestingPref() { add_task(async function testInactiveTabWouldBeSuspended() { info(`open a tab`); const tab = await createTab(PAGE_NON_AUTOPLAY); - await shouldTabStateEqualTo(tab, "running"); + await assertIfWindowGetSuspended(tab, { shouldBeSuspended: false }); info(`tab should be suspended when it becomes inactive`); setTabActive(tab, false); - await shouldTabStateEqualTo(tab, "suspended"); + await assertIfWindowGetSuspended(tab, { shouldBeSuspended: true }); info(`remove tab`); await tab.close(); @@ -41,28 +41,28 @@ add_task(async function testInactiveTabWouldBeSuspended() { add_task(async function testInactiveTabEverStartPlayingWontBeSuspended() { info(`open tab1 and play media`); const tab1 = await createTab(PAGE_NON_AUTOPLAY, { needCheck: true }); - await shouldTabStateEqualTo(tab1, "running"); + await assertIfWindowGetSuspended(tab1, { shouldBeSuspended: false }); await playMedia(tab1, VIDEO_ID); info(`tab with playing media won't be suspended when it becomes inactive`); setTabActive(tab1, false); - await shouldTabStateEqualTo(tab1, "running"); + await assertIfWindowGetSuspended(tab1, { shouldBeSuspended: false }); info( `even if media is paused, keep tab running so that it could listen to media keys to control media in the future` ); await pauseMedia(tab1, VIDEO_ID); - await shouldTabStateEqualTo(tab1, "running"); + await assertIfWindowGetSuspended(tab1, { shouldBeSuspended: false }); info(`open tab2 and play media`); const tab2 = await createTab(PAGE_NON_AUTOPLAY, { needCheck: true }); - await shouldTabStateEqualTo(tab2, "running"); + await assertIfWindowGetSuspended(tab2, { shouldBeSuspended: false }); await playMedia(tab2, VIDEO_ID); info( `as inactive tab1 doesn't own main controller, it should be suspended again` ); - await shouldTabStateEqualTo(tab1, "suspended"); + await assertIfWindowGetSuspended(tab1, { shouldBeSuspended: true }); info(`remove tabs`); await Promise.all([tab1.close(), tab2.close()]); @@ -73,43 +73,24 @@ add_task(async function testInactiveTabEverStartPlayingWontBeSuspended() { */ async function createTab(url, needCheck = false) { const tab = await createLoadedTabWrapper(url, { needCheck }); - await createStateObserver(tab); return tab; } -function createStateObserver(tab) { - return SpecialPowers.spawn(tab.linkedBrowser, [], _ => { - /** - * Currently there is no API allowing us to observe tab's suspend state - * directly, so we use a tricky way to observe that by checking AudioContext - * state. Because AudioContext would change its state when tab is being - * suspended or resumed. - */ - class StateObserver { - constructor() { - this.ac = new content.AudioContext(); - } - getState() { - return this.ac.state; - } - async waitUntilStateEqualTo(expectedState) { - while (this.ac.state != expectedState) { - info(`wait until tab state changes to '${expectedState}'`); - await new Promise(r => (this.ac.onstatechange = r)); - } - } +function assertIfWindowGetSuspended(tab, { shouldBeSuspended }) { + return SpecialPowers.spawn( + tab.linkedBrowser, + [shouldBeSuspended], + expectedSuspend => { + const isSuspended = content.windowUtils.suspendedByBrowsingContextGroup; + is( + expectedSuspend, + isSuspended, + `window suspended state (${isSuspended}) is equal to the expected` + ); } - content.obs = new StateObserver(); - }); + ); } function setTabActive(tab, isActive) { tab.linkedBrowser.docShellIsActive = isActive; } - -function shouldTabStateEqualTo(tab, state) { - return SpecialPowers.spawn(tab.linkedBrowser, [state], async state => { - await content.obs.waitUntilStateEqualTo(state); - ok(content.obs.getState() == state, `correct tab state '${state}'`); - }); -}