forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			464 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			464 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* Any copyright is dedicated to the Public Domain.
 | 
						|
 * http://creativecommons.org/publicdomain/zero/1.0/ */
 | 
						|
 | 
						|
// This file spawns content tasks.
 | 
						|
/* eslint-env mozilla/frame-script */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
requestLongerTimeout(10);
 | 
						|
 | 
						|
const PAGE_1 = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
 | 
						|
const PAGE_2 = "data:text/html,<html><body>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.
 | 
						|
add_task(async function test_initialize() {
 | 
						|
  await SpecialPowers.pushPrefEnv({
 | 
						|
    set: [
 | 
						|
      [ "toolkit.cosmeticAnimations.enabled", false]
 | 
						|
  ] });
 | 
						|
});
 | 
						|
 | 
						|
// 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);
 | 
						|
 | 
						|
  let frame_script = (buttonId) => {
 | 
						|
    let button = content.document.getElementById(buttonId);
 | 
						|
    button.click();
 | 
						|
  };
 | 
						|
 | 
						|
  let mm = browser.messageManager;
 | 
						|
  mm.loadFrameScript("data:,(" + frame_script.toString() + ")('" + id + "');", false);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Checks the documentURI of the root document of a remote browser
 | 
						|
 * to see if it equals URI. Returns a Promise that resolves if
 | 
						|
 * there is a match, and rejects with an error message if they
 | 
						|
 * do not match.
 | 
						|
 *
 | 
						|
 * @param browser
 | 
						|
 *        The remote <xul:browser> to check the root document URI in.
 | 
						|
 * @param URI
 | 
						|
 *        A string to match the root document URI against.
 | 
						|
 * @return Promise
 | 
						|
 */
 | 
						|
function promiseContentDocumentURIEquals(browser, URI) {
 | 
						|
  return new Promise((resolve, reject) => {
 | 
						|
    let frame_script = () => {
 | 
						|
      sendAsyncMessage("test:documenturi", {
 | 
						|
        uri: content.document.documentURI,
 | 
						|
      });
 | 
						|
    };
 | 
						|
 | 
						|
    let mm = browser.messageManager;
 | 
						|
    mm.addMessageListener("test:documenturi", function onMessage(message) {
 | 
						|
      mm.removeMessageListener("test:documenturi", onMessage);
 | 
						|
      let contentURI = message.data.uri;
 | 
						|
      if (contentURI == URI) {
 | 
						|
        resolve();
 | 
						|
      } else {
 | 
						|
        reject(`Content has URI ${contentURI} which does not match ${URI}`);
 | 
						|
      }
 | 
						|
    });
 | 
						|
 | 
						|
    mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Checks the window.history.length of the root window of a remote
 | 
						|
 * browser to see if it equals length. Returns a Promise that resolves
 | 
						|
 * if there is a match, and rejects with an error message if they
 | 
						|
 * do not match.
 | 
						|
 *
 | 
						|
 * @param browser
 | 
						|
 *        The remote <xul:browser> to check the root window.history.length
 | 
						|
 * @param length
 | 
						|
 *        The expected history length
 | 
						|
 * @return Promise
 | 
						|
 */
 | 
						|
function promiseHistoryLength(browser, length) {
 | 
						|
  return new Promise((resolve, reject) => {
 | 
						|
    let frame_script = () => {
 | 
						|
      sendAsyncMessage("test:historylength", {
 | 
						|
        length: content.history.length,
 | 
						|
      });
 | 
						|
    };
 | 
						|
 | 
						|
    let mm = browser.messageManager;
 | 
						|
    mm.addMessageListener("test:historylength", function onMessage(message) {
 | 
						|
      mm.removeMessageListener("test:historylength", onMessage);
 | 
						|
      let contentLength = message.data.length;
 | 
						|
      if (contentLength == length) {
 | 
						|
        resolve();
 | 
						|
      } else {
 | 
						|
        reject(`Content has window.history.length ${contentLength} which does ` +
 | 
						|
               `not equal expected ${length}`);
 | 
						|
      }
 | 
						|
    });
 | 
						|
 | 
						|
    mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns a Promise that resolves when a browser has fired the
 | 
						|
 * AboutTabCrashedReady event.
 | 
						|
 *
 | 
						|
 * @param browser
 | 
						|
 *        The remote <xul:browser> 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);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_1);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
  await TabStateFlusher.flush(browser);
 | 
						|
 | 
						|
  // Crash the tab
 | 
						|
  await BrowserTestUtils.crashBrowser(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);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_1);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
  await TabStateFlusher.flush(browser);
 | 
						|
 | 
						|
  // Crash the tab
 | 
						|
  await BrowserTestUtils.crashBrowser(browser);
 | 
						|
 | 
						|
  // Browse to a new site that will cause the browser to
 | 
						|
  // become remote again.
 | 
						|
  browser.loadURI(PAGE_2);
 | 
						|
  await promiseTabRestored(newTab);
 | 
						|
  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);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_1);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
  await TabStateFlusher.flush(browser);
 | 
						|
 | 
						|
  // Crash the tab
 | 
						|
  await BrowserTestUtils.crashBrowser(browser);
 | 
						|
 | 
						|
  // Browse to a new site that will not cause the browser to
 | 
						|
  // become remote again.
 | 
						|
  browser.loadURI("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);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_1);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
 | 
						|
  let newTab2 = BrowserTestUtils.addTab(gBrowser, "about:blank", { sameProcessAsFrameLoader: browser.frameLoader });
 | 
						|
  let browser2 = newTab2.linkedBrowser;
 | 
						|
  ok(browser2.isRemoteBrowser, "Should be a remote browser");
 | 
						|
  await promiseBrowserLoaded(browser2);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_1);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_2);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
 | 
						|
  await TabStateFlusher.flush(browser);
 | 
						|
 | 
						|
  // Crash the tab
 | 
						|
  await BrowserTestUtils.crashBrowser(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
 | 
						|
  clickButton(browser, "restoreTab");
 | 
						|
  await promiseTabRestored(newTab);
 | 
						|
  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);
 | 
						|
 | 
						|
  browser.loadURI(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 = win2.gBrowser.addTab(PAGE_1, { sameProcessAsFrameLoader: browser.frameLoader });
 | 
						|
  win2.gBrowser.selectedTab = newTab2;
 | 
						|
  let browser2 = newTab2.linkedBrowser;
 | 
						|
  ok(browser2.isRemoteBrowser, "Should be a remote browser");
 | 
						|
  await promiseBrowserLoaded(browser2);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_1);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_2);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
 | 
						|
  await TabStateFlusher.flush(browser);
 | 
						|
  await TabStateFlusher.flush(browser2);
 | 
						|
 | 
						|
  // Crash the tab
 | 
						|
  await BrowserTestUtils.crashBrowser(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
 | 
						|
  clickButton(browser, "restoreAll");
 | 
						|
  await promiseTabRestored(newTab);
 | 
						|
  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);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_1);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
 | 
						|
  await TabStateFlusher.flush(browser);
 | 
						|
 | 
						|
  // Crash the tab
 | 
						|
  await BrowserTestUtils.crashBrowser(browser);
 | 
						|
 | 
						|
  let promise = promiseEvent(gBrowser.tabContainer, "TabClose");
 | 
						|
 | 
						|
  // Click the close tab button
 | 
						|
  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);
 | 
						|
 | 
						|
  browser.loadURI(PAGE_1);
 | 
						|
  await promiseBrowserLoaded(browser);
 | 
						|
 | 
						|
  await TabStateFlusher.flush(browser);
 | 
						|
 | 
						|
  // Crash the tab
 | 
						|
  await BrowserTestUtils.crashBrowser(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;
 | 
						|
 | 
						|
  browser.loadURI(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 = win2.gBrowser.addTab(PAGE_2, { sameProcessAsFrameLoader: browser.frameLoader });
 | 
						|
  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.crashBrowser(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);
 | 
						|
 | 
						|
  browser.loadURI(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.crashBrowser(browser);
 | 
						|
 | 
						|
  ok(ZoomManager.getZoomForBrowser(browser) === 1, "zoom should have reset on crash");
 | 
						|
 | 
						|
  clickButton(browser, "restoreTab");
 | 
						|
  await promiseTabRestored(newTab);
 | 
						|
 | 
						|
  ok(ZoomManager.getZoomForBrowser(browser) === zoomLevel, "zoom should have gone back to enlarged");
 | 
						|
  FullZoom.reset();
 | 
						|
 | 
						|
  gBrowser.removeTab(newTab);
 | 
						|
});
 |