Bug 1112304: Update about:tabcrashed to match the new UX spec. r=dao
authorDave Townsend <dtownsend@oxymoronical.com>
Tue, 13 Jan 2015 12:33:26 -0800
changeset 226895 666746987b4b8c504080c7b4d18580794590e375
parent 226894 c95bb70fac9875b276289d206bd737204e2aaba3
child 226896 6d3e147b1539987b699127109eaf5bdae5066723
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)
reviewersdao
bugs1112304, 1109650
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 1112304: Update about:tabcrashed to match the new UX spec. r=dao Uses in-content styles for the tab crash page and adds an overlay to the favicon for crashed tabs. Adds support for closing the crashed tab. The strings here also refer to being able to restore all tabs, that will be implemented and landed at the same time in bug 1109650 to avoid l10n churn.
browser/base/content/aboutTabCrashed.css
browser/base/content/aboutTabCrashed.js
browser/base/content/aboutTabCrashed.xhtml
browser/base/content/browser.js
browser/base/content/tabbrowser.css
browser/base/content/tabbrowser.xml
browser/base/jar.mn
browser/components/sessionstore/test/browser_crashedTabs.js
browser/components/sessionstore/test/head.js
browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/jar.mn
browser/modules/TabCrashReporter.jsm
browser/themes/linux/aboutTabCrashed.css
browser/themes/linux/jar.mn
browser/themes/osx/browser.css
browser/themes/osx/jar.mn
browser/themes/shared/aboutTabCrashed.css
browser/themes/shared/tabbrowser/crashed.svg
browser/themes/shared/tabs.inc.css
browser/themes/windows/jar.mn
toolkit/themes/linux/global/in-content/common.css
toolkit/themes/osx/global/in-content/common.css
toolkit/themes/shared/in-content/common.inc.css
toolkit/themes/windows/global/in-content/common.css
new file mode 100644
--- /dev/null
+++ b/browser/base/content/aboutTabCrashed.css
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+html:not(.crashDumpSubmitted) #reportSent,
+html:not(.crashDumpAvailable) #report-box {
+  display: none;
+}
--- a/browser/base/content/aboutTabCrashed.js
+++ b/browser/base/content/aboutTabCrashed.js
@@ -7,27 +7,37 @@ function parseQueryString() {
   let queryString = url.replace(/^about:tabcrashed?e=tabcrashed/, "");
 
   let titleMatch = queryString.match(/d=([^&]*)/);
   return titleMatch && titleMatch[1] ? decodeURIComponent(titleMatch[1]) : "";
 }
 
 document.title = parseQueryString();
 
-addEventListener("DOMContentLoaded", () => {
-  let tryAgain = document.getElementById("tryAgain");
-  let sendCrashReport = document.getElementById("checkSendReport");
+function shouldSendReport() {
+  if (!document.documentElement.classList.contains("crashDumpAvailable"))
+    return false;
+  return document.getElementById("sendReport").checked;
+}
 
-  tryAgain.addEventListener("click", () => {
-    let event = new CustomEvent("AboutTabCrashedTryAgain", {
-      bubbles: true,
-      detail: {
-        sendCrashReport: sendCrashReport.checked,
-      },
-    });
+function sendEvent(message) {
+  let event = new CustomEvent("AboutTabCrashedMessage", {
+    bubbles: true,
+    detail: {
+      message,
+      sendCrashReport: shouldSendReport(),
+    },
+  });
 
-    document.dispatchEvent(event);
-  });
-});
+  document.dispatchEvent(event);
+}
+
+function closeTab() {
+  sendEvent("closeTab");
+}
+
+function restoreTab() {
+  sendEvent("restoreTab");
+}
 
 // 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
@@ -7,43 +7,49 @@
 <!DOCTYPE html [
   <!ENTITY % htmlDTD
     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "DTD/xhtml1-strict.dtd">
   %htmlDTD;
   <!ENTITY % globalDTD
     SYSTEM "chrome://global/locale/global.dtd">
   %globalDTD;
-  <!ENTITY % browserDTD
-    SYSTEM "chrome://browser/locale/browser.dtd">
-  %browserDTD;
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
   %brandDTD;
-
+  <!ENTITY % tabCrashedDTD
+    SYSTEM "chrome://browser/locale/aboutTabCrashed.dtd">
+  %tabCrashedDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <link rel="stylesheet" type="text/css" media="all"
           href="chrome://global/skin/in-content/info-pages.css"/>
     <link rel="stylesheet" type="text/css" media="all"
+          href="chrome://browser/content/aboutTabCrashed.css"/>
+    <link rel="stylesheet" type="text/css" media="all"
           href="chrome://browser/skin/aboutTabCrashed.css"/>
   </head>
 
   <body dir="&locale.dir;">
     <div class="container">
       <div class="title">
         <h1 class="title-text">&tabCrashed.header;</h1>
       </div>
       <p>&tabCrashed.message;</p>
 
       <div id="report-box">
-        <input type="checkbox" id="checkSendReport" checked="checked"/>
-        <label for="checkSendReport">&tabCrashed.checkSendReport;</label>
+        <input type="checkbox" id="sendReport" checked="checked"/>
+        <label for="sendReport">&tabCrashed.sendReport;</label>
       </div>
 
+      <p id="reportSent">&tabCrashed.reportSent;</p>
+
       <div class="button-container">
-        <button id="tryAgain">&tabCrashed.tryAgain;</button>
+        <button id="closeTab" onclick="closeTab()">
+          &tabCrashed.closeTab;</button>
+        <button id="restoreTab" onclick="restoreTab()">
+          &tabCrashed.restoreTab;</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
@@ -1111,17 +1111,17 @@ var gBrowserInit = {
     });
 
     gBrowser.addEventListener("AboutTabCrashedLoad", function(event) {
 #ifdef MOZ_CRASHREPORTER
       TabCrashReporter.onAboutTabCrashedLoad(gBrowser.getBrowserForDocument(event.target));
 #endif
     }, false, true);
 
-    gBrowser.addEventListener("AboutTabCrashedTryAgain", function(event) {
+    gBrowser.addEventListener("AboutTabCrashedMessage", function(event) {
       let ownerDoc = event.originalTarget;
 
       if (!ownerDoc.documentURI.startsWith("about:tabcrashed")) {
         return;
       }
 
       let isTopFrame = (ownerDoc.defaultView.parent === ownerDoc.defaultView);
       if (!isTopFrame) {
@@ -1129,18 +1129,26 @@ var gBrowserInit = {
       }
 
       let browser = gBrowser.getBrowserForDocument(ownerDoc);
 #ifdef MOZ_CRASHREPORTER
       if (event.detail.sendCrashReport) {
         TabCrashReporter.submitCrashReport(browser);
       }
 #endif
+
       let tab = gBrowser.getTabForBrowser(browser);
-      SessionStore.reviveCrashedTab(tab);
+      switch (event.detail.message) {
+      case "closeTab":
+        gBrowser.removeTab(tab, { animate: true });
+        break;
+      case "restoreTab":
+        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 = [];
         for (let i = 0; i < count; i++) {
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -46,19 +46,20 @@ tabpanels {
 
 /* Apply crisp rendering for favicons at exactly 2dppx resolution */
 @media (resolution: 2dppx) {
   .tab-icon-image {
     image-rendering: -moz-crisp-edges;
   }
 }
 
-.tab-icon-image:not([src]):not([pinned]),
+.tab-icon-image:not([src]):not([pinned]):not([crashed]),
 .tab-throbber:not([busy]),
-.tab-throbber[busy] + .tab-icon-image {
+.tab-icon-image[busy],
+.tab-icon-overlay[busy] {
   display: none;
 }
 
 .closing-tabs-spacer {
   pointer-events: none;
 }
 
 .tabbrowser-tabs:not(:hover) > .tabbrowser-arrowscrollbox > .closing-tabs-spacer {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -659,19 +659,23 @@
                 // It's okay to clear what the user typed when we start
                 // loading a document. If the user types, this counter gets
                 // set to zero, if the document load ends without an
                 // onLocationChange, this counter gets decremented
                 // (so we keep it while switching tabs after failed loads)
                 // We need to add 2 because loadURIWithFlags may have
                 // cancelled a pending load which would have cleared
                 // its anchor scroll detection temporary increment.
-                if (aWebProgress.isTopLevel)
+                if (aWebProgress.isTopLevel) {
                   this.mBrowser.userTypedClear += 2;
 
+                  // If the browser is loading it must not be crashed anymore
+                  this.mTab.removeAttribute("crashed");
+                }
+
                 if (this._shouldShowProgress(aRequest)) {
                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
                     this.mTab.setAttribute("busy", "true");
                     if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
                       this.mTabBrowser.setTabTitleLoading(this.mTab);
                   }
 
                   if (this.mTab.selected)
@@ -3574,16 +3578,17 @@
           let icon = browser.mIconURL;
 
           this.updateBrowserRemotenessByURL(browser, "about:tabcrashed");
 
           browser.setAttribute("crashedPageTitle", title);
           browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null);
           browser.removeAttribute("crashedPageTitle");
           let tab = this.getTabForBrowser(browser);
+          tab.setAttribute("crashed", true);
           this.setIcon(tab, icon);
         ]]>
       </handler>
     </handlers>
   </binding>
 
   <binding id="tabbrowser-tabbox"
            extends="chrome://global/content/bindings/tabbox.xml#tabbox">
@@ -4975,21 +4980,24 @@
                     class="tab-background-end"/>
         </xul:hbox>
         <xul:hbox xbl:inherits="pinned,selected,titlechanged"
                   class="tab-content" align="center">
           <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
                      class="tab-throbber"
                      role="presentation"
                      layer="true" />
-          <xul:image xbl:inherits="src=image,fadein,pinned,selected"
+          <xul:image xbl:inherits="src=image,fadein,pinned,selected,busy,crashed"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
                      validate="never"
                      role="presentation"/>
+          <xul:image xbl:inherits="crashed,busy"
+                     class="tab-icon-overlay"
+                     role="presentation"/>
           <xul:label flex="1"
                      anonid="tab-label"
                      xbl:inherits="value=visibleLabel,crop,accesskey,fadein,pinned,selected"
                      class="tab-text tab-label"
                      role="presentation"/>
           <xul:toolbarbutton anonid="close-button"
                              xbl:inherits="fadein,pinned,selected"
                              class="tab-close-button close-icon"/>
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -64,16 +64,17 @@ browser.jar:
 
         content/browser/certerror/aboutCertError.xhtml     (content/aboutcerterror/aboutCertError.xhtml)
         content/browser/certerror/aboutCertError.css       (content/aboutcerterror/aboutCertError.css)
 
         content/browser/aboutRobots-icon.png          (content/aboutRobots-icon.png)
         content/browser/aboutRobots-widget-left.png   (content/aboutRobots-widget-left.png)
         content/browser/aboutSocialError.xhtml        (content/aboutSocialError.xhtml)
         content/browser/aboutProviderDirectory.xhtml  (content/aboutProviderDirectory.xhtml)
+        content/browser/aboutTabCrashed.css           (content/aboutTabCrashed.css)
         content/browser/aboutTabCrashed.js            (content/aboutTabCrashed.js)
         content/browser/aboutTabCrashed.xhtml         (content/aboutTabCrashed.xhtml)
 *       content/browser/browser.css                   (content/browser.css)
 *       content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 *       content/browser/chatWindow.xul                (content/chatWindow.xul)
         content/browser/content.js                    (content/content.js)
--- a/browser/components/sessionstore/test/browser_crashedTabs.js
+++ b/browser/components/sessionstore/test/browser_crashedTabs.js
@@ -1,16 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 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
+Services.prefs.setBoolPref("browser.tabs.animate", false);
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("browser.tabs.animate");
+});
+
 /**
  * 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
  */
@@ -53,34 +59,51 @@ function crashBrowser(browser) {
 
       if (dumpID) {
         let minidumpDirectory = getMinidumpDirectory();
         removeFile(minidumpDirectory, dumpID + '.dmp');
         removeFile(minidumpDirectory, dumpID + '.extra');
       }
 
       Services.obs.removeObserver(observer, 'ipc:content-shutdown');
+      info("Crash cleaned up");
       resolve();
     };
 
     Services.obs.addObserver(observer, 'ipc:content-shutdown');
   });
 
   let aboutTabCrashedLoadPromise = new Promise((resolve, reject) => {
     browser.addEventListener("AboutTabCrashedLoad", function onCrash() {
       browser.removeEventListener("AboutTabCrashedLoad", onCrash, false);
+      info("about:tabcrashed loaded");
       resolve();
     }, false, true);
   });
 
   // This frame script will crash the remote browser as soon as it is
   // evaluated.
   let mm = browser.messageManager;
   mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
-  return Promise.all([crashCleanupPromise, aboutTabCrashedLoadPromise]);
+  return Promise.all([crashCleanupPromise, aboutTabCrashedLoadPromise]).then(() => {
+    let tab = gBrowser.getTabForBrowser(browser);
+    is(tab.getAttribute("crashed"), "true", "Tab should be marked as crashed");
+  });
+}
+
+function clickButton(browser, id) {
+  info("Clicking " + id);
+
+  let frame_script = (id) => {
+    let button = content.document.getElementById(id);
+    button.click();
+  };
+
+  let mm = browser.messageManager;
+  mm.loadFrameScript("data:,(" + frame_script.toString() + ")('" + id + "');", false);
 }
 
 /**
  * Returns the directory where crash dumps are stored.
  *
  * @return nsIFile
  */
 function getMinidumpDirectory() {
@@ -227,16 +250,17 @@ add_task(function test_revived_history_f
   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);
 
   // 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,
@@ -267,16 +291,17 @@ add_task(function test_revived_history_f
   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);
 
   // 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,
@@ -296,33 +321,77 @@ add_task(function test_revive_tab_from_s
   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();
+  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);
 
   // 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
-  SessionStore.reviveCrashedTab(newTab);
+  clickButton(browser, "restoreTab");
   yield promiseBrowserLoaded(browser);
+  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);
-});
\ No newline at end of file
+  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;
+  ok(browser.isRemoteBrowser, "Should be a remote browser");
+  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);
+
+  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/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -483,25 +483,29 @@ function whenDelayedStartupFinished(aWin
       executeSoon(aCallback);
     }
   }, "browser-delayed-startup-finished", false);
 }
 function promiseDelayedStartupFinished(aWindow) {
   return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve));
 }
 
-function promiseTabRestored(tab) {
+function promiseEvent(element, eventType, isCapturing = false) {
   return new Promise(resolve => {
-    tab.addEventListener("SSTabRestored", function onRestored() {
-      tab.removeEventListener("SSTabRestored", onRestored);
-      resolve();
-    });
+    element.addEventListener(eventType, function listener(event) {
+      element.removeEventListener(eventType, listener, isCapturing);
+      resolve(event);
+    }, isCapturing);
   });
 }
 
+function promiseTabRestored(tab) {
+  return promiseEvent(tab, "SSTabRestored");
+}
+
 function sendMessage(browser, name, data = {}) {
   browser.messageManager.sendAsyncMessage(name, data);
   return promiseContentMessage(browser, name);
 }
 
 // This creates list of functions that we will map to their corresponding
 // ss-test:* messages names. Those will be sent to the frame script and
 // be used to read and modify form data.
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
@@ -0,0 +1,10 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - 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">
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -790,21 +790,16 @@ just addresses the organization to follo
 
 <!-- LOCALIZATION NOTE: (pluginNotification.width): This is used to determine the
      width of the plugin popup notification that can appear if a plugin has been
      blocked on a page. Should be wide enough to fit the pluginActivateNow.label
      and pluginActivateAlways.label strings above on a single line. This must be
      a CSS length value. -->
 <!ENTITY pluginNotification.width "28em">
 
-<!ENTITY tabCrashed.header "Tab crashed">
-<!ENTITY tabCrashed.message "Well, this is embarrassing. We tried to display this Web page, but it's not responding.">
-<!ENTITY tabCrashed.checkSendReport "Tell &vendorShortName; about this crash so they can fix it.">
-<!ENTITY tabCrashed.tryAgain "Try Again">
-
 <!ENTITY uiTour.infoPanel.close "Close">
 
 <!ENTITY appMenuSidebars.label         "Sidebars">
 
 <!ENTITY switchToMetroCmd2.label       "Relaunch in &brandShortName; for Windows 8 Touch">
 
 <!-- LOCALIZATION NOTE: (panicButton.view.mainTimeframeDesc, panicButton.view.5min, panicButton.view.2hr, panicButton.view.day):
      The .mainTimeframeDesc string combined with any of the 3 others is meant to form a complete sentence, e.g. "Forget the last: Five minutes".
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -13,16 +13,17 @@
     locale/browser/aboutPrivateBrowsing.properties (%chrome/browser/aboutPrivateBrowsing.properties)
     locale/browser/aboutRobots.dtd                 (%chrome/browser/aboutRobots.dtd)
     locale/browser/aboutHome.dtd                   (%chrome/browser/aboutHome.dtd)
     locale/browser/accounts.properties             (%chrome/browser/accounts.properties)
 #ifdef MOZ_SERVICES_HEALTHREPORT
     locale/browser/aboutHealthReport.dtd           (%chrome/browser/aboutHealthReport.dtd)
 #endif
     locale/browser/aboutSessionRestore.dtd         (%chrome/browser/aboutSessionRestore.dtd)
+    locale/browser/aboutTabCrashed.dtd             (%chrome/browser/aboutTabCrashed.dtd)
 #ifdef MOZ_SERVICES_SYNC
     locale/browser/syncProgress.dtd                (%chrome/browser/syncProgress.dtd)
     locale/browser/syncCustomize.dtd               (%chrome/browser/syncCustomize.dtd)
     locale/browser/aboutSyncTabs.dtd               (%chrome/browser/aboutSyncTabs.dtd)
 #endif
     locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
--- a/browser/modules/TabCrashReporter.jsm
+++ b/browser/modules/TabCrashReporter.jsm
@@ -77,16 +77,17 @@ this.TabCrashReporter = {
 
         let doc = browser.contentDocument;
         if (!doc.documentURI.startsWith("about:tabcrashed"))
           continue;
 
         if (this.browserMap.get(browser) == childID) {
           this.browserMap.delete(browser);
           browser.contentDocument.documentElement.classList.remove("crashDumpAvailable");
+          browser.contentDocument.documentElement.classList.add("crashDumpSubmitted");
         }
       }
     }
   },
 
   onAboutTabCrashedLoad: function (aBrowser) {
     if (!this.childMap)
       return;
deleted file mode 100644
--- a/browser/themes/linux/aboutTabCrashed.css
+++ /dev/null
@@ -1,108 +0,0 @@
-body {
-  background-color: rgb(241, 244, 248);
-  margin-top: 2em;
-  font: message-box;
-  font-size: 100%;
-}
-
-p {
-  font-size: .8em;
-}
-
-#error-box {
-  background: url('chrome://global/skin/icons/information-24.png') no-repeat left 4px;
-  -moz-padding-start: 30px;
-}
-
-#error-box:-moz-locale-dir(rtl) {
-  background-position: right 4px;
-}
-
-#main-error-msg {
-  color: #4b4b4b;
-  font-weight: bold;
-}
-
-#report-box {
-  text-align: center;
-  width: 75%;
-  margin: 0 auto;
-  display: none;
-}
-
-.crashDumpAvailable #report-box {
-  display: block
-}
-
-#button-box {
-  text-align: center;
-  width: 75%;
-  margin: 0 auto;
-}
-
-@media all and (min-width: 300px) {
-  #error-box {
-    max-width: 50%;
-    margin: 0 auto;
-    background-image: url('chrome://global/skin/icons/information-32.png');
-    min-height: 36px;
-    -moz-padding-start: 38px;
-  }
-
-  button {
-    width: auto !important;
-    min-width: 150px;
-  }
-}
-
-@media all and (min-width: 780px) {
-  #error-box {
-    max-width: 30%;
-  }
-}
-
-button {
-  font: message-box;
-  font-size: 0.6875em;
-  -moz-appearance: none;
-  -moz-user-select: none;
-  width: 100%;
-  margin: 2px 0;
-  padding: 2px 6px;
-  line-height: 1.2;
-  background-color: hsla(210,30%,95%,.1);
-  background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
-  background-clip: padding-box;
-  border: 1px solid hsla(210,15%,25%,.4);
-  border-color: hsla(210,15%,25%,.3) hsla(210,15%,25%,.35) hsla(210,15%,25%,.4);
-  border-radius: 3px;
-  box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
-              0 0 0 1px hsla(0,0%,100%,.3) inset,
-              0 1px 0 hsla(0,0%,100%,.1);
-
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 150ms;
-  transition-timing-function: ease;
-
-}
-
-button:hover {
-  background-color: hsla(210,30%,95%,.8);
-  border-color: hsla(210,15%,25%,.45) hsla(210,15%,25%,.5) hsla(210,15%,25%,.55);
-  box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
-              0 0 0 1px hsla(0,0%,100%,.3) inset,
-              0 1px 0 hsla(0,0%,100%,.1),
-              0 0 3px hsla(210,15%,25%,.1);
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 150ms;
-  transition-timing-function: ease;
-}
-
-button:hover:active {
-  background-color: hsla(210,15%,25%,.2);
-  box-shadow: 0 1px 1px hsla(210,15%,25%,.2) inset,
-              0 0 2px hsla(210,15%,25%,.4) inset;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 10ms;
-  transition-timing-function: linear;
-}
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -186,16 +186,17 @@ browser.jar:
   skin/classic/browser/social/share-button.png        (social/share-button.png)
   skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
   skin/classic/browser/social/chat-icons.svg          (../shared/social/chat-icons.svg)
   skin/classic/browser/social/gear_default.png        (../shared/social/gear_default.png)
   skin/classic/browser/social/gear_clicked.png        (../shared/social/gear_clicked.png)
   skin/classic/browser/tabbrowser/alltabs.png         (tabbrowser/alltabs.png)
   skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png)
   skin/classic/browser/tabbrowser/connecting.png      (tabbrowser/connecting.png)
+  skin/classic/browser/tabbrowser/crashed.svg         (../shared/tabbrowser/crashed.svg)
   skin/classic/browser/tabbrowser/loading.png         (tabbrowser/loading.png)
   skin/classic/browser/tabbrowser/tab-active-middle.png     (tabbrowser/tab-active-middle.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.png        (tabbrowser/tab-arrow-left.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
   skin/classic/browser/tabbrowser/tab-background-end.png    (tabbrowser/tab-background-end.png)
   skin/classic/browser/tabbrowser/tab-background-middle.png (tabbrowser/tab-background-middle.png)
   skin/classic/browser/tabbrowser/tab-background-start.png  (tabbrowser/tab-background-start.png)
   skin/classic/browser/tabbrowser/tab-overflow-indicator.png (../shared/tabbrowser/tab-overflow-indicator.png)
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3087,16 +3087,24 @@ toolbarbutton.chevron > .toolbarbutton-m
     background-image: url(chrome://browser/skin/tabbrowser/tab-separator@2x.png);
   }
 }
 
 .tabbrowser-tab:not(:hover) > .tab-stack > .tab-content > .tab-icon-image:not([selected="true"]) {
   opacity: .9;
 }
 
+/*
+ * Force the overlay to create a new stacking context so it always appears on
+ * top of the icon.
+ */
+.tab-icon-overlay {
+  opacity: 0.9999;
+}
+
 .tab-label:not([selected="true"]) {
   opacity: .7;
 }
 
 .tabbrowser-tab,
 .tabs-newtab-button {
   font: message-box;
   border: none;
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -296,16 +296,17 @@ browser.jar:
   skin/classic/browser/social/services-64.png               (social/services-64.png)
   skin/classic/browser/social/services-64@2x.png            (social/services-64@2x.png)
   skin/classic/browser/social/chat-icons.svg                             (../shared/social/chat-icons.svg)
   skin/classic/browser/social/gear_default.png                           (../shared/social/gear_default.png)
   skin/classic/browser/social/gear_clicked.png                           (../shared/social/gear_clicked.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png             (tabbrowser/alltabs-box-bkgnd-icon.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted.png    (tabbrowser/alltabs-box-bkgnd-icon-inverted.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png (tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png)
+  skin/classic/browser/tabbrowser/crashed.svg                            (../shared/tabbrowser/crashed.svg)
   skin/classic/browser/tabbrowser/newtab.png                             (tabbrowser/newtab.png)
   skin/classic/browser/tabbrowser/newtab@2x.png                          (tabbrowser/newtab@2x.png)
   skin/classic/browser/tabbrowser/newtab-inverted.png                    (tabbrowser/newtab-inverted.png)
   skin/classic/browser/tabbrowser/newtab-inverted@2x.png                 (tabbrowser/newtab-inverted@2x.png)
   skin/classic/browser/tabbrowser/connecting.png                         (tabbrowser/connecting.png)
   skin/classic/browser/tabbrowser/connecting@2x.png                      (tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/loading.png                            (tabbrowser/loading.png)
   skin/classic/browser/tabbrowser/loading@2x.png                         (tabbrowser/loading@2x.png)
--- a/browser/themes/shared/aboutTabCrashed.css
+++ b/browser/themes/shared/aboutTabCrashed.css
@@ -1,11 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 .title {
   background-image: url("chrome://browser/skin/tab-crashed.svg");
 }
 
-#report-box {
-  display: none;
+#reportSent {
+  font-weight: bold;
 }
-
-.crashDumpAvailable #report-box {
-  display: block
-}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/crashed.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="22 22 16 16" enable-background="new 22 22 16 16" xml:space="preserve">
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
+	<stop  offset="0" style="stop-color:#E63B2E"/>
+	<stop  offset="1" style="stop-color:#C33931"/>
+</linearGradient>
+<circle fill="url(#SVGID_1_)" cx="30" cy="30" r="7"/>
+<g>
+	<path fill="#FFFFFF" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092
+		c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z
+		"/>
+</g>
+</svg>
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -60,45 +60,58 @@
   margin: 0 -@tabCurveHalfWidth@;
 }
 
 .tab-content {
   -moz-padding-end: 9px;
   -moz-padding-start: 9px;
 }
 
+.tab-content[pinned] {
+  -moz-padding-end: 3px;
+}
+
 .tab-throbber,
 .tab-icon-image,
 .tab-close-button {
   margin-top: 1px;
 }
 
 .tab-throbber,
 .tab-icon-image {
   height: 16px;
   width: 16px;
+  -moz-margin-end: 6px;
 }
 
 .tab-icon-image {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
 
+.tab-icon-overlay {
+  width: 16px;
+  height: 16px;
+  margin-top: 10px;
+  -moz-margin-start: -16px;
+  display: none;
+}
+
+.tab-icon-overlay[crashed] {
+  display: -moz-box;
+  list-style-image: url("chrome://browser/skin/tabbrowser/crashed.svg");
+}
+
 .tab-throbber[busy] {
   list-style-image: url("chrome://browser/skin/tabbrowser/connecting.png");
 }
 
 .tab-throbber[progress] {
   list-style-image: url("chrome://browser/skin/tabbrowser/loading.png");
 }
 
-.tab-throbber:not([pinned]),
-.tab-icon-image:not([pinned]) {
-  -moz-margin-end: 6px;
-}
-
 .tab-label {
   -moz-margin-end: 0;
   -moz-margin-start: 0;
 }
 
 .tab-close-button {
   -moz-margin-start: 4px;
   -moz-margin-end: -2px;
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -214,16 +214,17 @@ browser.jar:
         skin/classic/browser/social/services-16.png                  (social/services-16.png)
         skin/classic/browser/social/services-64.png                  (social/services-64.png)
         skin/classic/browser/social/chat-icons.svg                   (../shared/social/chat-icons.svg)
         skin/classic/browser/social/gear_default.png                 (../shared/social/gear_default.png)
         skin/classic/browser/social/gear_clicked.png                 (../shared/social/gear_clicked.png)
         skin/classic/browser/tabbrowser/newtab.png                   (tabbrowser/newtab-XPVista7.png)
         skin/classic/browser/tabbrowser/newtab-inverted.png          (tabbrowser/newtab-inverted.png)
         skin/classic/browser/tabbrowser/connecting.png               (tabbrowser/connecting.png)
+        skin/classic/browser/tabbrowser/crashed.svg                  (../shared/tabbrowser/crashed.svg)
         skin/classic/browser/tabbrowser/loading.png                  (tabbrowser/loading.png)
         skin/classic/browser/tabbrowser/tab-active-middle.png        (tabbrowser/tab-active-middle.png)
         skin/classic/browser/tabbrowser/tab-active-middle@2x.png     (tabbrowser/tab-active-middle@2x.png)
         skin/classic/browser/tabbrowser/tab-arrow-left.png           (tabbrowser/tab-arrow-left-XPVista7.png)
         skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png  (tabbrowser/tab-arrow-left-inverted.png)
         skin/classic/browser/tabbrowser/tab-background-start.png     (tabbrowser/tab-background-start.png)
         skin/classic/browser/tabbrowser/tab-background-start@2x.png  (tabbrowser/tab-background-start@2x.png)
         skin/classic/browser/tabbrowser/tab-background-middle.png    (tabbrowser/tab-background-middle.png)
@@ -684,16 +685,17 @@ browser.jar:
         skin/classic/aero/browser/social/services-64.png             (social/services-64.png)
         skin/classic/aero/browser/social/chat-icons.svg              (../shared/social/chat-icons.svg)
         skin/classic/aero/browser/social/gear_default.png            (../shared/social/gear_default.png)
         skin/classic/aero/browser/social/gear_clicked.png            (../shared/social/gear_clicked.png)
         skin/classic/aero/browser/tabbrowser/newtab.png              (tabbrowser/newtab.png)
         skin/classic/aero/browser/tabbrowser/newtab-XPVista7.png     (tabbrowser/newtab-XPVista7.png)
         skin/classic/aero/browser/tabbrowser/newtab-inverted.png     (tabbrowser/newtab-inverted.png)
         skin/classic/aero/browser/tabbrowser/connecting.png          (tabbrowser/connecting.png)
+        skin/classic/aero/browser/tabbrowser/crashed.svg             (../shared/tabbrowser/crashed.svg)
         skin/classic/aero/browser/tabbrowser/loading.png             (tabbrowser/loading.png)
         skin/classic/aero/browser/tabbrowser/tab-active-middle.png   (tabbrowser/tab-active-middle.png)
         skin/classic/aero/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)
         skin/classic/aero/browser/tabbrowser/tab-arrow-left.png      (tabbrowser/tab-arrow-left.png)
         skin/classic/aero/browser/tabbrowser/tab-arrow-left-XPVista7.png (tabbrowser/tab-arrow-left-XPVista7.png)
         skin/classic/aero/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
         skin/classic/aero/browser/tabbrowser/tab-background-start.png    (tabbrowser/tab-background-start.png)
         skin/classic/aero/browser/tabbrowser/tab-background-start@2x.png (tabbrowser/tab-background-start@2x.png)
--- a/toolkit/themes/linux/global/in-content/common.css
+++ b/toolkit/themes/linux/global/in-content/common.css
@@ -8,16 +8,17 @@ xul|tab[selected] {
   /* Override styles for tab[selected] from
      toolkit/themes/linux/global/tabbox.css */
   margin-bottom: 0;
   border-bottom-left-radius: 0;
   border-bottom-right-radius: 0;
 }
 
 xul|button,
+html|button,
 xul|colorpicker[type="button"],
 xul|menulist {
   margin: 2px 4px;
 }
 
 xul|button > xul|*.button-box,
 xul|menulist > xul|*.menulist-label-box {
   -moz-appearance: none;
--- a/toolkit/themes/osx/global/in-content/common.css
+++ b/toolkit/themes/osx/global/in-content/common.css
@@ -10,22 +10,24 @@ xul|tabs {
   padding-left: 0;
 }
 
 xul|tab[selected] {
   text-shadow: none;
 }
 
 xul|button,
+html|button,
 xul|colorpicker[type="button"],
 xul|menulist {
   margin-top: 3px;
 }
 
-xul|button {
+xul|button,
+html|button {
   /* use the same margin of other elements for the alignment */
   margin-left: 4px;
   margin-right: 4px;
 }
 
 xul|caption {
   -moz-padding-start: 0;
 }
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -24,16 +24,21 @@ html|h1 {
   font-size: 2.5em;
   font-weight: lighter;
   line-height: 1.2;
   color: #333;
   margin: 0;
   margin-bottom: .5em;
 }
 
+html|hr {
+  border-style: solid none none none;
+  border-color: #c1c1c1;
+}
+
 xul|caption {
   -moz-appearance: none;
   margin: 0;
 }
 
 xul|caption > xul|label {
   font-size: 1.3rem;
   font-weight: bold;
@@ -401,44 +406,68 @@ html|a:hover:active,
 .text-link:hover:active,
 .inline-link:hover:active {
   color: #ff9500;
   text-decoration: none;
 }
 
 /* Checkboxes and radio buttons */
 
+/* Hide the actual checkbox */
+html|input[type="checkbox"] {
+  opacity: 0;
+  position: absolute;
+}
+
+/* Create a box to style as the checkbox */
+html|input[type="checkbox"] + html|label:before {
+  display: inline-block;
+  content: "";
+  vertical-align: middle;
+}
+
+html|input[type="checkbox"] + html|label {
+  line-height: 0px;
+}
+
 xul|checkbox {
   -moz-margin-start: 0;
 }
 
-xul|*.checkbox-check {
+xul|*.checkbox-check,
+html|input[type="checkbox"] + html|label:before {
   -moz-appearance: none;
   width: 23px;
   height: 23px;
   border-radius: 2px;
   border: 1px solid #c1c1c1;
   -moz-margin-end: 10px;
   background-color: #f1f1f1;
   /* !important needed to override toolkit checked !important rule */
   background-image: linear-gradient(#fff, rgba(255,255,255,0.8)) !important;
   background-position: center center;
   background-repeat: no-repeat;
   box-shadow: 0 1px 1px 0 #fff, inset 0 2px 0 0 rgba(0,0,0,0.03);
 }
 
-xul|checkbox:not([disabled="true"]):hover > xul|*.checkbox-check {
+xul|checkbox:not([disabled="true"]):hover > xul|*.checkbox-check,
+html|input[type="checkbox"]:not(:disabled) + html|label:hover:before {
   border-color: #0095dd;
 }
 
 xul|*.checkbox-check[checked] {
   list-style-image: url("chrome://global/skin/in-content/check.svg#check");
 }
 
-xul|checkbox[disabled="true"] > xul|*.checkbox-check {
+html|input[type="checkbox"]:checked + html|label:before {
+  background-image: url("chrome://global/skin/in-content/check.svg#check"), linear-gradient(#fff, rgba(255,255,255,0.8)) !important;
+}
+
+xul|checkbox[disabled="true"] > xul|*.checkbox-check,
+html|input[type="checkbox"]:disabled + html|label {
   opacity: 0.5;
 }
 
 xul|*.checkbox-label-box {
   -moz-margin-start: -1px; /* negative margin for the transparent border */
   -moz-padding-start: 0;
 }
 
--- a/toolkit/themes/windows/global/in-content/common.css
+++ b/toolkit/themes/windows/global/in-content/common.css
@@ -4,16 +4,17 @@
 
 %include ../../../shared/in-content/common.inc.css
 
 xul|caption {
   background-color: transparent;
 }
 
 xul|button,
+html|button,
 xul|colorpicker[type="button"],
 xul|menulist {
   margin: 2px 4px;
 }
 
 xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker {
   margin-top: 1px;
   margin-bottom: 1px;