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
This commit is contained in:
alwu 2021-08-03 21:32:26 +00:00
parent 1c18fb5f4d
commit 683790582e
3 changed files with 35 additions and 39 deletions

View File

@ -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<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
NS_ENSURE_TRUE(inner, NS_ERROR_FAILURE);
*aResult = inner->GetWasSuspendedByGroup();
return NS_OK;
}

View File

@ -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)]

View File

@ -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}'`);
});
}