зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1655866: Part 7 - Add test. r=nika
Differential Revision: https://phabricator.services.mozilla.com/D88320
This commit is contained in:
Родитель
43d01c93da
Коммит
9f99c21cf1
|
@ -48,6 +48,10 @@ support-files =
|
|||
file_bug1543077-4-child.html
|
||||
file_bug1543077-4.html
|
||||
file_multiple_pushState.html
|
||||
file_onbeforeunload_0.html
|
||||
file_onbeforeunload_1.html
|
||||
file_onbeforeunload_2.html
|
||||
file_onbeforeunload_3.html
|
||||
print_postdata.sjs
|
||||
test-form_sjis.html
|
||||
timelineMarkers-04.html
|
||||
|
@ -123,6 +127,7 @@ support-files =
|
|||
[browser_loadDisallowInherit.js]
|
||||
[browser_loadURI_postdata.js]
|
||||
[browser_multiple_pushState.js]
|
||||
[browser_onbeforeunload.js]
|
||||
[browser_onbeforeunload_navigation.js]
|
||||
skip-if = (os == 'win' && !debug) # bug 1300351
|
||||
[browser_overlink.js]
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
"use strict";
|
||||
|
||||
// We need to test a lot of permutations here, and there isn't any sensible way
|
||||
// to split them up or run them faster.
|
||||
requestLongerTimeout(2);
|
||||
|
||||
const BASE_URL = "http://mochi.test:8888/browser/docshell/test/browser/";
|
||||
|
||||
const TEST_PAGE = BASE_URL + "file_onbeforeunload_0.html";
|
||||
|
||||
const DIALOG_TOPIC = "tabmodal-dialog-loaded";
|
||||
|
||||
async function withTabModalPromptCount(expected, task) {
|
||||
let count = 0;
|
||||
function observer() {
|
||||
count++;
|
||||
}
|
||||
|
||||
Services.obs.addObserver(observer, DIALOG_TOPIC);
|
||||
try {
|
||||
return await task();
|
||||
} finally {
|
||||
Services.obs.removeObserver(observer, DIALOG_TOPIC);
|
||||
is(count, expected, "Should see expected number of tab modal prompts");
|
||||
}
|
||||
}
|
||||
|
||||
function promiseAllowUnloadPrompt(allowNavigation) {
|
||||
return BrowserUtils.promiseObserved(DIALOG_TOPIC).then(
|
||||
({ subject: node }) => {
|
||||
let button = node.querySelector(
|
||||
`.tabmodalprompt-button${allowNavigation ? 0 : 1}`
|
||||
);
|
||||
button.click();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Maintain a pool of background tabs with our test document loaded so
|
||||
// we don't have to wait for a load prior to each test step (potentially
|
||||
// tearing down and recreating content processes in the process).
|
||||
const TabPool = {
|
||||
poolSize: 5,
|
||||
|
||||
pendingCount: 0,
|
||||
|
||||
readyTabs: [],
|
||||
|
||||
readyPromise: null,
|
||||
resolveReadyPromise: null,
|
||||
|
||||
spawnTabs() {
|
||||
while (this.pendingCount + this.readyTabs.length < this.poolSize) {
|
||||
this.pendingCount++;
|
||||
let tab = BrowserTestUtils.addTab(gBrowser, TEST_PAGE);
|
||||
BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
|
||||
this.readyTabs.push(tab);
|
||||
this.pendingCount--;
|
||||
|
||||
if (this.resolveReadyPromise) {
|
||||
this.readyPromise = null;
|
||||
this.resolveReadyPromise();
|
||||
this.resolveReadyPromise = null;
|
||||
}
|
||||
|
||||
this.spawnTabs();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getReadyPromise() {
|
||||
if (!this.readyPromise) {
|
||||
this.readyPromise = new Promise(resolve => {
|
||||
this.resolveReadyPromise = resolve;
|
||||
});
|
||||
}
|
||||
return this.readyPromise;
|
||||
},
|
||||
|
||||
async getTab() {
|
||||
while (!this.readyTabs.length) {
|
||||
this.spawnTabs();
|
||||
await this.getReadyPromise();
|
||||
}
|
||||
|
||||
let tab = this.readyTabs.shift();
|
||||
this.spawnTabs();
|
||||
|
||||
gBrowser.selectedTab = tab;
|
||||
return tab;
|
||||
},
|
||||
|
||||
async cleanup() {
|
||||
this.poolSize = 0;
|
||||
|
||||
while (this.pendingCount) {
|
||||
await this.getReadyPromise();
|
||||
}
|
||||
|
||||
while (this.readyTabs.length) {
|
||||
await BrowserTestUtils.removeTab(this.readyTabs.shift());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const ACTIONS = {
|
||||
NONE: 0,
|
||||
LISTEN_AND_ALLOW: 1,
|
||||
LISTEN_AND_BLOCK: 2,
|
||||
};
|
||||
|
||||
const ACTION_NAMES = new Map(Object.entries(ACTIONS).map(([k, v]) => [v, k]));
|
||||
|
||||
function* generatePermutations(depth) {
|
||||
if (depth == 0) {
|
||||
yield [];
|
||||
return;
|
||||
}
|
||||
for (let subActions of generatePermutations(depth - 1)) {
|
||||
for (let action of Object.values(ACTIONS)) {
|
||||
yield [action, ...subActions];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PERMUTATIONS = Array.from(generatePermutations(3));
|
||||
|
||||
const FRAMES = [
|
||||
{ process: 0 },
|
||||
{ process: SpecialPowers.useRemoteSubframes ? 1 : 0 },
|
||||
{ process: 0 },
|
||||
{ process: SpecialPowers.useRemoteSubframes ? 1 : 0 },
|
||||
];
|
||||
|
||||
function addListener(bc, block) {
|
||||
return SpecialPowers.spawn(bc, [block], block => {
|
||||
return new Promise(resolve => {
|
||||
function onbeforeunload(event) {
|
||||
if (block) {
|
||||
event.preventDefault();
|
||||
}
|
||||
resolve({ event: "beforeunload" });
|
||||
}
|
||||
content.addEventListener("beforeunload", onbeforeunload, { once: true });
|
||||
content.unlisten = () => {
|
||||
content.removeEventListener("beforeunload", onbeforeunload);
|
||||
};
|
||||
|
||||
content.addEventListener(
|
||||
"unload",
|
||||
() => {
|
||||
resolve({ event: "unload" });
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function descendants(bc) {
|
||||
if (bc) {
|
||||
return [bc, ...descendants(bc.children[0])];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async function addListeners(frames, actions, startIdx) {
|
||||
let process = startIdx >= 0 ? FRAMES[startIdx].process : -1;
|
||||
|
||||
let roundTripPromises = [];
|
||||
|
||||
let expectNestedEventLoop = false;
|
||||
let numBlockers = 0;
|
||||
let unloadPromises = [];
|
||||
let beforeUnloadPromises = [];
|
||||
|
||||
for (let [i, frame] of frames.entries()) {
|
||||
let action = actions[i];
|
||||
if (action === ACTIONS.NONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let block = action === ACTIONS.LISTEN_AND_BLOCK;
|
||||
let promise = addListener(frame, block);
|
||||
if (startIdx <= i) {
|
||||
if (block || FRAMES[i].process !== process) {
|
||||
expectNestedEventLoop = true;
|
||||
}
|
||||
beforeUnloadPromises.push(promise);
|
||||
numBlockers += block;
|
||||
} else {
|
||||
unloadPromises.push(promise);
|
||||
}
|
||||
|
||||
roundTripPromises.push(SpecialPowers.spawn(frame, [], () => {}));
|
||||
}
|
||||
|
||||
// Wait for round trip messages to any processes with event listeners to
|
||||
// return so we're sure that all listeners are registered and their state
|
||||
// flags are propagated before we continue.
|
||||
await Promise.all(roundTripPromises);
|
||||
|
||||
return {
|
||||
expectNestedEventLoop,
|
||||
expectPrompt: !!numBlockers,
|
||||
unloadPromises,
|
||||
beforeUnloadPromises,
|
||||
};
|
||||
}
|
||||
|
||||
async function doTest(actions, startIdx, navigate) {
|
||||
let tab = await TabPool.getTab();
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
let frames = descendants(browser.browsingContext);
|
||||
let expected = await addListeners(frames, actions, startIdx);
|
||||
|
||||
let awaitingPrompt = false;
|
||||
let promptPromise;
|
||||
if (expected.expectPrompt) {
|
||||
awaitingPrompt = true;
|
||||
promptPromise = promiseAllowUnloadPrompt(false).then(() => {
|
||||
awaitingPrompt = false;
|
||||
});
|
||||
}
|
||||
|
||||
let promptCount = expected.expectPrompt ? 1 : 0;
|
||||
await withTabModalPromptCount(promptCount, async () => {
|
||||
await navigate(tab, frames).then(result => {
|
||||
ok(
|
||||
!awaitingPrompt,
|
||||
"Navigation should not complete while we're still expecting a prompt"
|
||||
);
|
||||
ok(
|
||||
!awaitingBeforeUnload,
|
||||
"Navigation should not complete while we're still expecting beforeunload events"
|
||||
);
|
||||
|
||||
is(
|
||||
result.eventLoopSpun,
|
||||
expected.expectNestedEventLoop,
|
||||
"Should have nested event loop?"
|
||||
);
|
||||
});
|
||||
|
||||
for (let result of await Promise.all(expected.beforeUnloadPromises)) {
|
||||
is(
|
||||
result.event,
|
||||
"beforeunload",
|
||||
"Should have seen beforeunload event before unload"
|
||||
);
|
||||
}
|
||||
await promptPromise;
|
||||
|
||||
await Promise.all(
|
||||
frames.map(frame =>
|
||||
SpecialPowers.spawn(frame, [], () => {
|
||||
if (content.unlisten) {
|
||||
content.unlisten();
|
||||
}
|
||||
}).catch(() => {})
|
||||
)
|
||||
);
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
for (let result of await Promise.all(expected.unloadPromises)) {
|
||||
is(result.event, "unload", "Should have seen unload event");
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.require_user_interaction_for_beforeunload", false]],
|
||||
});
|
||||
|
||||
for (let actions of PERMUTATIONS) {
|
||||
info(
|
||||
`Testing frame actions: [${actions.map(action =>
|
||||
ACTION_NAMES.get(action)
|
||||
)}]`
|
||||
);
|
||||
|
||||
for (let startIdx = 0; startIdx < FRAMES.length; startIdx++) {
|
||||
info(`Testing content reload from frame ${startIdx}`);
|
||||
|
||||
await doTest(actions, startIdx, (tab, frames) => {
|
||||
return SpecialPowers.spawn(frames[startIdx], [], () => {
|
||||
let eventLoopSpun = false;
|
||||
SpecialPowers.Services.tm.dispatchToMainThread(() => {
|
||||
eventLoopSpun = true;
|
||||
});
|
||||
|
||||
content.location.reload();
|
||||
|
||||
return { eventLoopSpun };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
info(`Testing tab close from parent process`);
|
||||
await doTest(actions, -1, (tab, frames) => {
|
||||
let eventLoopSpun = false;
|
||||
Services.tm.dispatchToMainThread(() => {
|
||||
eventLoopSpun = true;
|
||||
});
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
|
||||
let result = { eventLoopSpun };
|
||||
|
||||
// Make an extra couple of trips through the event loop to give us time
|
||||
// to process SpecialPowers.spawn responses before resolving.
|
||||
return new Promise(resolve => {
|
||||
executeSoon(() => {
|
||||
executeSoon(() => resolve(result));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function cleanup() {
|
||||
await TabPool.cleanup();
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="http://example.com/browser/docshell/test/browser/file_onbeforeunload_1.html"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="http://mochi.test:8888/browser/docshell/test/browser/file_onbeforeunload_2.html"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="http://example.com/browser/docshell/test/browser/file_onbeforeunload_3.html"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
Загрузка…
Ссылка в новой задаче