/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// This file spawns content tasks.
"use strict";
requestLongerTimeout(10);
const PAGE_1 =
"data:text/html,
A%20regular,%20everyday,%20normal%20page.";
const PAGE_2 =
"data:text/html,Another%20regular,%20everyday,%20normal%20page.";
// Turn off tab animations for testing and use a single content process
// for these tests since we want to test tabs within the crashing process here.
gReduceMotionOverride = true;
// Allow tabs to restore on demand so we can test pending states
Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
function clickButton(browser, id) {
info("Clicking " + id);
return SpecialPowers.spawn(browser, [id], buttonId => {
let button = content.document.getElementById(buttonId);
button.click();
});
}
/**
* Checks the documentURI of the root document of a remote browser
* to see if it equals URI.
*
* @param browser
* The remote to check the root document URI in.
* @param URI
* A string to match the root document URI against.
* @return Promise
*/
async function promiseContentDocumentURIEquals(browser, URI) {
let contentURI = await SpecialPowers.spawn(browser, [], () => {
return content.document.documentURI;
});
is(
contentURI,
URI,
`Content has URI ${contentURI} which does not match ${URI}`
);
}
/**
* Checks the window.history.length of the root window of a remote
* browser to see if it equals length.
*
* @param browser
* The remote to check the root window.history.length
* @param length
* The expected history length
* @return Promise
*/
async function promiseHistoryLength(browser, length) {
let contentLength = await SpecialPowers.spawn(browser, [], () => {
return content.history.length;
});
is(
contentLength,
length,
`Content has window.history.length ${contentLength} which does ` +
`not equal expected ${length}`
);
}
/**
* Returns a Promise that resolves when a browser has fired the
* AboutTabCrashedReady event.
*
* @param browser
* The remote that will fire the event.
* @return Promise
*/
function promiseTabCrashedReady(browser) {
return new Promise(resolve => {
browser.addEventListener(
"AboutTabCrashedReady",
function ready(e) {
browser.removeEventListener("AboutTabCrashedReady", ready, false, true);
resolve();
},
false,
true
);
});
}
/**
* Checks that if a tab crashes, that information about the tab crashed
* page does not get added to the tab history.
*/
add_task(async function test_crash_page_not_in_history() {
let newTab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
await TabStateFlusher.flush(browser);
// Crash the tab
await BrowserTestUtils.crashFrame(browser);
// Check the tab state and make sure the tab crashed page isn't
// mentioned.
let { entries } = JSON.parse(ss.getTabState(newTab));
is(entries.length, 1, "Should have a single history entry");
is(
entries[0].url,
PAGE_1,
"Single entry should be the page we visited before crashing"
);
gBrowser.removeTab(newTab);
});
/**
* Checks that if a tab crashes, that when we browse away from that page
* to a non-blacklisted site (so the browser becomes remote again), that
* we record history for that new visit.
*/
add_task(async function test_revived_history_from_remote() {
let newTab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
await TabStateFlusher.flush(browser);
// Crash the tab
await BrowserTestUtils.crashFrame(browser);
// Browse to a new site that will cause the browser to
// become remote again.
BrowserTestUtils.loadURIString(browser, PAGE_2);
await promiseBrowserLoaded(browser);
ok(
!newTab.hasAttribute("crashed"),
"Tab shouldn't be marked as crashed anymore."
);
ok(browser.isRemoteBrowser, "Should be a remote browser");
await TabStateFlusher.flush(browser);
// Check the tab state and make sure the tab crashed page isn't
// mentioned.
let { entries } = JSON.parse(ss.getTabState(newTab));
is(entries.length, 2, "Should have two history entries");
is(
entries[0].url,
PAGE_1,
"First entry should be the page we visited before crashing"
);
is(
entries[1].url,
PAGE_2,
"Second entry should be the page we visited after crashing"
);
gBrowser.removeTab(newTab);
});
/**
* Checks that if a tab crashes, that when we browse away from that page
* to a blacklisted site (so the browser stays non-remote), that
* we record history for that new visit.
*/
add_task(async function test_revived_history_from_non_remote() {
let newTab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
await TabStateFlusher.flush(browser);
// Crash the tab
await BrowserTestUtils.crashFrame(browser);
// Browse to a new site that will not cause the browser to
// become remote again.
BrowserTestUtils.loadURIString(browser, "about:mozilla");
await promiseBrowserLoaded(browser);
ok(
!newTab.hasAttribute("crashed"),
"Tab shouldn't be marked as crashed anymore."
);
ok(!browser.isRemoteBrowser, "Should not be a remote browser");
await TabStateFlusher.flush(browser);
// Check the tab state and make sure the tab crashed page isn't
// mentioned.
let { entries } = JSON.parse(ss.getTabState(newTab));
is(entries.length, 2, "Should have two history entries");
is(
entries[0].url,
PAGE_1,
"First entry should be the page we visited before crashing"
);
is(
entries[1].url,
"about:mozilla",
"Second entry should be the page we visited after crashing"
);
gBrowser.removeTab(newTab);
});
/**
* Checks that we can revive a crashed tab back to the page that
* it was on when it crashed.
*/
add_task(async function test_revive_tab_from_session_store() {
let newTab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
let newTab2 = BrowserTestUtils.addTab(gBrowser, "about:blank", {
remoteType: browser.remoteType,
initialBrowsingContextGroupId: browser.browsingContext.group.id,
});
let browser2 = newTab2.linkedBrowser;
ok(browser2.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser2);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_2);
await promiseBrowserLoaded(browser);
await TabStateFlusher.flush(browser);
// Crash the tab
await BrowserTestUtils.crashFrame(browser);
// Background tabs should not be crashed, but should be in the "to be restored"
// state.
ok(!newTab2.hasAttribute("crashed"), "Second tab should not be crashed.");
ok(newTab2.hasAttribute("pending"), "Second tab should be pending.");
// Use SessionStore to revive the first tab
let tabRestoredPromise = promiseTabRestored(newTab);
await clickButton(browser, "restoreTab");
await tabRestoredPromise;
ok(
!newTab.hasAttribute("crashed"),
"Tab shouldn't be marked as crashed anymore."
);
ok(newTab2.hasAttribute("pending"), "Second tab should still be pending.");
// We can't just check browser.currentURI.spec, because from
// the outside, a crashed tab has the same URI as the page
// it crashed on (much like an about:neterror page). Instead,
// we have to use the documentURI on the content.
await promiseContentDocumentURIEquals(browser, PAGE_2);
// We should also have two entries in the browser history.
await promiseHistoryLength(browser, 2);
gBrowser.removeTab(newTab);
gBrowser.removeTab(newTab2);
});
/**
* Checks that we can revive multiple crashed tabs back to the pages
* that they were on when they crashed.
*/
add_task(async function test_revive_all_tabs_from_session_store() {
let newTab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
// In order to see a second about:tabcrashed page, we'll need
// a second window, since only selected tabs will show
// about:tabcrashed.
let win2 = await BrowserTestUtils.openNewBrowserWindow();
let newTab2 = BrowserTestUtils.addTab(win2.gBrowser, PAGE_1, {
remoteType: browser.remoteType,
initialBrowsingContextGroupId: browser.browsingContext.group.id,
});
win2.gBrowser.selectedTab = newTab2;
let browser2 = newTab2.linkedBrowser;
ok(browser2.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser2);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_2);
await promiseBrowserLoaded(browser);
await TabStateFlusher.flush(browser);
await TabStateFlusher.flush(browser2);
// Crash the tab
await BrowserTestUtils.crashFrame(browser);
// Both tabs should now be crashed.
is(newTab.getAttribute("crashed"), "true", "First tab should be crashed");
is(
newTab2.getAttribute("crashed"),
"true",
"Second window tab should be crashed"
);
// Use SessionStore to revive all the tabs
let tabRestoredPromises = Promise.all([
promiseTabRestored(newTab),
promiseTabRestored(newTab2),
]);
await clickButton(browser, "restoreAll");
await tabRestoredPromises;
ok(
!newTab.hasAttribute("crashed"),
"Tab shouldn't be marked as crashed anymore."
);
ok(!newTab.hasAttribute("pending"), "Tab shouldn't be pending.");
ok(
!newTab2.hasAttribute("crashed"),
"Second tab shouldn't be marked as crashed anymore."
);
ok(!newTab2.hasAttribute("pending"), "Second tab shouldn't be pending.");
// We can't just check browser.currentURI.spec, because from
// the outside, a crashed tab has the same URI as the page
// it crashed on (much like an about:neterror page). Instead,
// we have to use the documentURI on the content.
await promiseContentDocumentURIEquals(browser, PAGE_2);
await promiseContentDocumentURIEquals(browser2, PAGE_1);
// We should also have two entries in the browser history.
await promiseHistoryLength(browser, 2);
await BrowserTestUtils.closeWindow(win2);
gBrowser.removeTab(newTab);
});
/**
* Checks that about:tabcrashed can close the current tab
*/
add_task(async function test_close_tab_after_crash() {
let newTab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
await TabStateFlusher.flush(browser);
// Crash the tab
await BrowserTestUtils.crashFrame(browser);
let promise = BrowserTestUtils.waitForEvent(
gBrowser.tabContainer,
"TabClose"
);
// Click the close tab button
await clickButton(browser, "closeTab");
await promise;
is(gBrowser.tabs.length, 1, "Should have closed the tab");
});
/**
* Checks that "restore all" button is only shown if more than one tab
* is showing about:tabcrashed
*/
add_task(async function test_hide_restore_all_button() {
let newTab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
await TabStateFlusher.flush(browser);
// Crash the tab
await BrowserTestUtils.crashFrame(browser);
let doc = browser.contentDocument;
let restoreAllButton = doc.getElementById("restoreAll");
let restoreOneButton = doc.getElementById("restoreTab");
let restoreAllStyles = window.getComputedStyle(restoreAllButton);
is(restoreAllStyles.display, "none", "Restore All button should be hidden");
ok(
restoreOneButton.classList.contains("primary"),
"Restore Tab button should have the primary class"
);
let newTab2 = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = newTab;
BrowserTestUtils.loadURIString(browser, PAGE_2);
await promiseBrowserLoaded(browser);
// Load up a second window so we can get another tab to show
// about:tabcrashed
let win2 = await BrowserTestUtils.openNewBrowserWindow();
let newTab3 = BrowserTestUtils.addTab(win2.gBrowser, PAGE_2, {
remoteType: browser.remoteType,
initialBrowsingContextGroupId: browser.browsingContext.group.id,
});
win2.gBrowser.selectedTab = newTab3;
let otherWinBrowser = newTab3.linkedBrowser;
await promiseBrowserLoaded(otherWinBrowser);
// We'll need to make sure the second tab's browser has finished
// sending its AboutTabCrashedReady event before we know for
// sure whether or not we're showing the right Restore buttons.
let otherBrowserReady = promiseTabCrashedReady(otherWinBrowser);
// Crash the first tab.
await BrowserTestUtils.crashFrame(browser);
await otherBrowserReady;
doc = browser.contentDocument;
restoreAllButton = doc.getElementById("restoreAll");
restoreOneButton = doc.getElementById("restoreTab");
restoreAllStyles = window.getComputedStyle(restoreAllButton);
isnot(
restoreAllStyles.display,
"none",
"Restore All button should not be hidden"
);
ok(
!restoreOneButton.classList.contains("primary"),
"Restore Tab button should not have the primary class"
);
await BrowserTestUtils.closeWindow(win2);
gBrowser.removeTab(newTab);
gBrowser.removeTab(newTab2);
});
add_task(async function test_aboutcrashedtabzoom() {
let newTab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
await promiseBrowserLoaded(browser);
BrowserTestUtils.loadURIString(browser, PAGE_1);
await promiseBrowserLoaded(browser);
FullZoom.enlarge();
let zoomLevel = ZoomManager.getZoomForBrowser(browser);
ok(zoomLevel !== 1, "should have enlarged");
await TabStateFlusher.flush(browser);
// Crash the tab
await BrowserTestUtils.crashFrame(browser);
ok(
ZoomManager.getZoomForBrowser(browser) === 1,
"zoom should have reset on crash"
);
let tabRestoredPromise = promiseTabRestored(newTab);
await clickButton(browser, "restoreTab");
await tabRestoredPromise;
ok(
ZoomManager.getZoomForBrowser(browser) === zoomLevel,
"zoom should have gone back to enlarged"
);
FullZoom.reset();
gBrowser.removeTab(newTab);
});