Bug 1109650: Add a button to restore all crashed tabs to about:tabcrashed. r=ttaubert
authorDave Townsend <dtownsend@oxymoronical.com>
Tue, 13 Jan 2015 12:35:57 -0800
changeset 226896 6d3e147b1539987b699127109eaf5bdae5066723
parent 226895 666746987b4b8c504080c7b4d18580794590e375
child 226897 33e36676db905a7e3189cd64ea2a32d9b7862e60
push id54950
push userphilringnalda@gmail.com
push dateSat, 31 Jan 2015 17:14:09 +0000
treeherdermozilla-inbound@37cbadfe1bc1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert
bugs1109650
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1109650: Add a button to restore all crashed tabs to about:tabcrashed. r=ttaubert
browser/base/content/aboutTabCrashed.js
browser/base/content/aboutTabCrashed.xhtml
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
browser/components/sessionstore/test/browser_crashedTabs.js
browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
--- a/browser/base/content/aboutTabCrashed.js
+++ b/browser/base/content/aboutTabCrashed.js
@@ -33,11 +33,15 @@ function sendEvent(message) {
 function closeTab() {
   sendEvent("closeTab");
 }
 
 function restoreTab() {
   sendEvent("restoreTab");
 }
 
+function restoreAll() {
+  sendEvent("restoreAll");
+}
+
 // Error pages are loaded as LOAD_BACKGROUND, so they don't get load events.
 var event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true});
 document.dispatchEvent(event);
--- a/browser/base/content/aboutTabCrashed.xhtml
+++ b/browser/base/content/aboutTabCrashed.xhtml
@@ -43,13 +43,15 @@
 
       <p id="reportSent">&tabCrashed.reportSent;</p>
 
       <div class="button-container">
         <button id="closeTab" onclick="closeTab()">
           &tabCrashed.closeTab;</button>
         <button id="restoreTab" onclick="restoreTab()">
           &tabCrashed.restoreTab;</button>
+        <button id="restoreAll" onclick="restoreAll()" autofocus="true" class="primary">
+          &tabCrashed.restoreAll;</button>
       </div>
     </div>
   </body>
   <script type="text/javascript;version=1.8" src="chrome://browser/content/aboutTabCrashed.js"/>
 </html>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -266,16 +266,22 @@ XPCOMUtils.defineLazyServiceGetter(this,
 #endif
 
 XPCOMUtils.defineLazyGetter(this, "PageMenuParent", function() {
   let tmp = {};
   Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
   return new tmp.PageMenuParent();
 });
 
+function* browserWindows() {
+  let windows = Services.wm.getEnumerator("navigator:browser");
+  while (windows.hasMoreElements())
+    yield windows.getNext();
+}
+
 /**
 * We can avoid adding multiple load event listeners and save some time by adding
 * one listener that calls all real handlers.
 */
 function pageShowEventHandlers(persisted) {
   XULBrowserWindow.asyncUpdateUI();
 }
 
@@ -1138,16 +1144,23 @@ var gBrowserInit = {
       let tab = gBrowser.getTabForBrowser(browser);
       switch (event.detail.message) {
       case "closeTab":
         gBrowser.removeTab(tab, { animate: true });
         break;
       case "restoreTab":
         SessionStore.reviveCrashedTab(tab);
         break;
+      case "restoreAll":
+        for (let browserWin of browserWindows()) {
+          for (let tab of window.gBrowser.tabs) {
+            SessionStore.reviveCrashedTab(tab);
+          }
+        }
+        break;
       }
     }, false, true);
 
     let uriToLoad = this._getUriToLoad();
     if (uriToLoad && uriToLoad != "about:blank") {
       if (uriToLoad instanceof Ci.nsISupportsArray) {
         let count = uriToLoad.Count();
         let specs = [];
@@ -6474,21 +6487,19 @@ function WindowIsClosing()
 function warnAboutClosingWindow() {
   // Popups aren't considered full browser windows; we also ignore private windows.
   let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window) &&
         !PrivateBrowsingUtils.permanentPrivateBrowsing;
   if (!isPBWindow && !toolbar.visible)
     return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
 
   // Figure out if there's at least one other browser window around.
-  let e = Services.wm.getEnumerator("navigator:browser");
   let otherPBWindowExists = false;
   let nonPopupPresent = false;
-  while (e.hasMoreElements()) {
-    let win = e.getNext();
+  for (let win of browserWindows()) {
     if (!win.closed && win != window) {
       if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win))
         otherPBWindowExists = true;
       if (win.toolbar.visible)
         nonPopupPresent = true;
       // If the current window is not in private browsing mode we don't need to 
       // look for other pb windows, we can leave the loop when finding the 
       // first non-popup window. If however the current window is in private 
@@ -7577,19 +7588,17 @@ function switchToTabHavingURI(aURI, aOpe
     aURI = Services.io.newURI(aURI, null, null);
 
   let isBrowserWindow = !!window.gBrowser;
 
   // Prioritise this window.
   if (isBrowserWindow && switchIfURIInWindow(window))
     return true;
 
-  let winEnum = Services.wm.getEnumerator("navigator:browser");
-  while (winEnum.hasMoreElements()) {
-    let browserWin = winEnum.getNext();
+  for (let browserWin of browserWindows()) {
     // Skip closed (but not yet destroyed) windows,
     // and the current window (which was checked earlier).
     if (browserWin.closed || browserWin == window)
       continue;
     if (switchIfURIInWindow(browserWin))
       return true;
   }
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1483,16 +1483,20 @@
             aBrowser.permanentKey = permanentKey;
             parent.appendChild(aBrowser);
 
             // Restore the progress listener.
             aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
 
             if (aShouldBeRemote) {
               tab.setAttribute("remote", "true");
+              // Switching the browser to be remote will connect to a new child
+              // process so the browser can no longer be considered to be
+              // crashed.
+              tab.removeAttribute("crashed");
             } else {
               tab.removeAttribute("remote");
               aBrowser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
             }
 
             if (wasActive)
               aBrowser.focus();
 
--- a/browser/components/sessionstore/test/browser_crashedTabs.js
+++ b/browser/components/sessionstore/test/browser_crashedTabs.js
@@ -7,16 +7,19 @@ const PAGE_1 = "data:text/html,<html><bo
 const PAGE_2 = "data:text/html,<html><body>Another%20regular,%20everyday,%20normal%20page.";
 
 // Turn off tab animations for testing
 Services.prefs.setBoolPref("browser.tabs.animate", false);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("browser.tabs.animate");
 });
 
+// Allow tabs to restore on demand so we can test pending states
+Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+
 /**
  * Returns a Promise that resolves once a remote <xul:browser> has experienced
  * a crash. Also does the job of cleaning up the minidump of the crash.
  *
  * @param browser
  *        The <xul:browser> that will crash
  * @return Promise
  */
@@ -212,18 +215,16 @@ add_task(function test_crash_page_not_in
   yield promiseBrowserLoaded(browser);
 
   browser.loadURI(PAGE_1);
   yield promiseBrowserLoaded(browser);
   TabState.flush(browser);
 
   // Crash the tab
   yield crashBrowser(browser);
-  // Flush out any notifications from the crashed browser.
-  TabState.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, 1, "Should have a single history entry");
   is(entries[0].url, PAGE_1,
     "Single entry should be the page we visited before crashing");
 
@@ -243,18 +244,16 @@ add_task(function test_revived_history_f
   yield promiseBrowserLoaded(browser);
 
   browser.loadURI(PAGE_1);
   yield promiseBrowserLoaded(browser);
   TabState.flush(browser);
 
   // Crash the tab
   yield crashBrowser(browser);
-  // Flush out any notifications from the crashed browser.
-  TabState.flush(browser);
 
   // Browse to a new site that will cause the browser to
   // become remote again.
   browser.loadURI(PAGE_2);
   yield promiseTabRestored(newTab);
   ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore.");
   ok(browser.isRemoteBrowser, "Should be a remote browser");
   TabState.flush(browser);
@@ -284,18 +283,16 @@ add_task(function test_revived_history_f
   yield promiseBrowserLoaded(browser);
 
   browser.loadURI(PAGE_1);
   yield promiseBrowserLoaded(browser);
   TabState.flush(browser);
 
   // Crash the tab
   yield crashBrowser(browser);
-  // Flush out any notifications from the crashed browser.
-  TabState.flush(browser);
 
   // Browse to a new site that will not cause the browser to
   // become remote again.
   browser.loadURI("about:mozilla");
   yield promiseBrowserLoaded(browser);
   ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore.");
   ok(!browser.isRemoteBrowser, "Should not be a remote browser");
   TabState.flush(browser);
@@ -336,41 +333,94 @@ add_task(function test_revive_tab_from_s
 
   browser.loadURI(PAGE_2);
   yield promiseBrowserLoaded(browser);
 
   TabState.flush(browser);
 
   // Crash the tab
   yield crashBrowser(browser);
-
   is(newTab2.getAttribute("crashed"), "true", "Second tab should be crashed too.");
 
-  // Flush out any notifications from the crashed browser.
-  TabState.flush(browser);
-
   // Use SessionStore to revive the tab
   clickButton(browser, "restoreTab");
-  yield promiseBrowserLoaded(browser);
+  yield promiseTabRestored(newTab);
   ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore.");
   is(newTab2.getAttribute("crashed"), "true", "Second tab should still be crashed though.");
 
   // 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.
   yield promiseContentDocumentURIEquals(browser, PAGE_2);
 
   // We should also have two entries in the browser history.
   yield promiseHistoryLength(browser, 2);
 
   gBrowser.removeTab(newTab);
   gBrowser.removeTab(newTab2);
 });
 
+/**
+ * Checks that we can revive a crashed tab back to the page that
+ * it was on when it crashed.
+ */
+add_task(function test_revive_all_tabs_from_session_store() {
+  let newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  let browser = newTab.linkedBrowser;
+  ok(browser.isRemoteBrowser, "Should be a remote browser");
+  yield promiseBrowserLoaded(browser);
+
+  browser.loadURI(PAGE_1);
+  yield promiseBrowserLoaded(browser);
+
+  let newTab2 = gBrowser.addTab(PAGE_1);
+  let browser2 = newTab2.linkedBrowser;
+  ok(browser2.isRemoteBrowser, "Should be a remote browser");
+  yield promiseBrowserLoaded(browser2);
+
+  browser.loadURI(PAGE_1);
+  yield promiseBrowserLoaded(browser);
+
+  browser.loadURI(PAGE_2);
+  yield promiseBrowserLoaded(browser);
+
+  TabState.flush(browser);
+  TabState.flush(browser2);
+
+  // Crash the tab
+  yield crashBrowser(browser);
+  is(newTab2.getAttribute("crashed"), "true", "Second tab should be crashed too.");
+
+  // Use SessionStore to revive all the tabs
+  clickButton(browser, "restoreAll");
+  yield 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 should be pending.");
+
+  gBrowser.selectedTab = newTab2;
+  yield promiseTabRestored(newTab2);
+  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.
+  yield promiseContentDocumentURIEquals(browser, PAGE_2);
+  yield promiseContentDocumentURIEquals(browser2, PAGE_1);
+
+  // We should also have two entries in the browser history.
+  yield promiseHistoryLength(browser, 2);
+
+  gBrowser.removeTab(newTab);
+  gBrowser.removeTab(newTab2);
+});
 
 /**
  * Checks that about:tabcrashed can close the current tab
  */
 add_task(function test_close_tab_after_crash() {
   let newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   let browser = newTab.linkedBrowser;
@@ -379,18 +429,16 @@ add_task(function test_close_tab_after_c
 
   browser.loadURI(PAGE_1);
   yield promiseBrowserLoaded(browser);
 
   TabState.flush(browser);
 
   // Crash the tab
   yield crashBrowser(browser);
-  // Flush out any notifications from the crashed browser.
-  TabState.flush(browser);
 
   let promise = promiseEvent(gBrowser.tabContainer, "TabClose");
 
   // Click the close tab button
   clickButton(browser, "closeTab");
   yield promise;
 
   is(gBrowser.tabs.length, 1, "Should have closed the tab");
--- a/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
@@ -3,8 +3,9 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY tabCrashed.header "Bad news first: This tab has crashed">
 <!ENTITY tabCrashed.message "Now for the good news: You can just close this tab, restore it or restore all your crashed tabs.">
 <!ENTITY tabCrashed.sendReport "Submit a crash report to help prevent more bad news">
 <!ENTITY tabCrashed.reportSent "Crash report already submitted; thank you for helping make &brandShortName; better!">
 <!ENTITY tabCrashed.closeTab "Close This Tab">
 <!ENTITY tabCrashed.restoreTab "Restore This Tab">
+<!ENTITY tabCrashed.restoreAll "Restore All Crashed Tabs">