Merge m-c to elm
authorNick Alexander <nalexander@mozilla.com>
Thu, 12 Dec 2013 08:21:15 -0800
changeset 161494 645dd937f6a68b9aaaceacaf514e59c7332a0f4f
parent 161493 460a43f82d0c4a6de0aa70e5f8a77df6d988f280 (current diff)
parent 160127 07e7a99841a64c59faf7ba1bd007e791c0c07b83 (diff)
child 161495 1b9829ecb1ce7222394b962d97383aec2b4a1e2f
push idunknown
push userunknown
push dateunknown
milestone29.0a1
Merge m-c to elm
build/docs/Vagrantfile
build/docs/conf.py
build/docs/python/makeutils.rst
build/docs/python/mozbuild.action.rst
build/docs/python/mozbuild.backend.rst
build/docs/python/mozbuild.compilation.rst
build/docs/python/mozbuild.controller.rst
build/docs/python/mozbuild.frontend.rst
build/docs/python/mozbuild.rst
build/docs/python/mozpack.chrome.rst
build/docs/python/mozpack.packager.rst
build/docs/python/mozpack.rst
build/docs/python/mozversioncontrol.rst
extensions/widgetutils/Makefile.in
extensions/widgetutils/install.rdf
extensions/widgetutils/moz.build
extensions/widgetutils/src/Makefile.in
extensions/widgetutils/src/moz.build
extensions/widgetutils/src/nsWidgetUtils.cpp
mobile/android/base/DoorHanger.java
mobile/android/base/Makefile.in
mobile/android/base/SiteIdentityPopup.java
mobile/android/base/android-services.mozbuild
mobile/android/base/crashreporter/res/drawable-mdpi/crash_reporter.png
mobile/android/base/crashreporter/res/layout/crash_reporter.xml
mobile/android/base/resources/drawable-hdpi/awesomebar_sep_left.9.png
mobile/android/base/resources/drawable-hdpi/awesomebar_sep_right.9.png
mobile/android/base/resources/drawable-hdpi/awesomebar_tab_center.9.png
mobile/android/base/resources/drawable-hdpi/awesomebar_tab_left.9.png
mobile/android/base/resources/drawable-hdpi/awesomebar_tab_right.9.png
mobile/android/base/resources/drawable-hdpi/ic_notif_button_cancel.png
mobile/android/base/resources/drawable-hdpi/ic_notif_button_pause.png
mobile/android/base/resources/drawable-hdpi/ic_notif_button_resume.png
mobile/android/base/resources/drawable-mdpi/awesomebar_sep_left.9.png
mobile/android/base/resources/drawable-mdpi/awesomebar_sep_right.9.png
mobile/android/base/resources/drawable-mdpi/awesomebar_tab_center.9.png
mobile/android/base/resources/drawable-mdpi/awesomebar_tab_left.9.png
mobile/android/base/resources/drawable-mdpi/awesomebar_tab_right.9.png
mobile/android/base/resources/drawable-mdpi/crash_reporter.png
mobile/android/base/resources/drawable-mdpi/ic_notif_button_cancel.png
mobile/android/base/resources/drawable-mdpi/ic_notif_button_pause.png
mobile/android/base/resources/drawable-mdpi/ic_notif_button_resume.png
mobile/android/base/resources/drawable-xhdpi/awesomebar_sep_left.9.png
mobile/android/base/resources/drawable-xhdpi/awesomebar_sep_right.9.png
mobile/android/base/resources/drawable-xhdpi/awesomebar_tab_center.9.png
mobile/android/base/resources/drawable-xhdpi/awesomebar_tab_left.9.png
mobile/android/base/resources/drawable-xhdpi/awesomebar_tab_right.9.png
mobile/android/base/resources/drawable-xhdpi/ic_notif_button_cancel.png
mobile/android/base/resources/drawable-xhdpi/ic_notif_button_pause.png
mobile/android/base/resources/drawable-xhdpi/ic_notif_button_resume.png
mobile/android/base/resources/drawable-xlarge-hdpi-v11/awesomebar_sep_left.9.png
mobile/android/base/resources/drawable-xlarge-hdpi-v11/awesomebar_sep_right.9.png
mobile/android/base/resources/drawable-xlarge-hdpi-v11/awesomebar_tab_center.9.png
mobile/android/base/resources/drawable-xlarge-hdpi-v11/awesomebar_tab_left.9.png
mobile/android/base/resources/drawable-xlarge-hdpi-v11/awesomebar_tab_right.9.png
mobile/android/base/resources/drawable-xlarge-mdpi-v11/awesomebar_sep_left.9.png
mobile/android/base/resources/drawable-xlarge-mdpi-v11/awesomebar_sep_right.9.png
mobile/android/base/resources/drawable-xlarge-mdpi-v11/awesomebar_tab_center.9.png
mobile/android/base/resources/drawable-xlarge-mdpi-v11/awesomebar_tab_left.9.png
mobile/android/base/resources/drawable-xlarge-mdpi-v11/awesomebar_tab_right.9.png
mobile/android/base/resources/drawable-xlarge-xhdpi-v11/awesomebar_sep_left.9.png
mobile/android/base/resources/drawable-xlarge-xhdpi-v11/awesomebar_sep_right.9.png
mobile/android/base/resources/drawable-xlarge-xhdpi-v11/awesomebar_tab_center.9.png
mobile/android/base/resources/drawable-xlarge-xhdpi-v11/awesomebar_tab_left.9.png
mobile/android/base/resources/drawable-xlarge-xhdpi-v11/awesomebar_tab_right.9.png
mobile/android/base/resources/layout/crash_reporter.xml
mobile/android/branding/aurora/content/fennec_144x144.png
mobile/android/branding/aurora/content/fennec_48x48.png
mobile/android/branding/aurora/content/fennec_72x72.png
mobile/android/branding/aurora/content/fennec_96x96.png
mobile/android/branding/aurora/res/drawable-hdpi/icon.png
mobile/android/branding/aurora/res/drawable-mdpi/icon.png
mobile/android/branding/aurora/res/drawable-xhdpi/icon.png
mobile/android/branding/aurora/res/drawable-xxhdpi/icon.png
mobile/android/branding/beta/content/fennec_144x144.png
mobile/android/branding/beta/content/fennec_48x48.png
mobile/android/branding/beta/content/fennec_72x72.png
mobile/android/branding/beta/content/fennec_96x96.png
mobile/android/branding/beta/res/drawable-hdpi/icon.png
mobile/android/branding/beta/res/drawable-mdpi/icon.png
mobile/android/branding/beta/res/drawable-xhdpi/icon.png
mobile/android/branding/beta/res/drawable-xxhdpi/icon.png
mobile/android/branding/nightly/content/fennec_144x144.png
mobile/android/branding/nightly/content/fennec_48x48.png
mobile/android/branding/nightly/content/fennec_72x72.png
mobile/android/branding/nightly/content/fennec_96x96.png
mobile/android/branding/nightly/res/drawable-hdpi/icon.png
mobile/android/branding/nightly/res/drawable-mdpi/icon.png
mobile/android/branding/nightly/res/drawable-xhdpi/icon.png
mobile/android/branding/nightly/res/drawable-xxhdpi/icon.png
mobile/android/branding/official/content/fennec_144x144.png
mobile/android/branding/official/content/fennec_48x48.png
mobile/android/branding/official/content/fennec_72x72.png
mobile/android/branding/official/content/fennec_96x96.png
mobile/android/branding/official/res/drawable-hdpi/icon.png
mobile/android/branding/official/res/drawable-mdpi/icon.png
mobile/android/branding/official/res/drawable-xhdpi/icon.png
mobile/android/branding/official/res/drawable-xxhdpi/icon.png
mobile/android/branding/unofficial/content/fennec_144x144.png
mobile/android/branding/unofficial/content/fennec_48x48.png
mobile/android/branding/unofficial/content/fennec_72x72.png
mobile/android/branding/unofficial/content/fennec_96x96.png
mobile/android/branding/unofficial/res/drawable-hdpi/icon.png
mobile/android/branding/unofficial/res/drawable-mdpi/icon.png
mobile/android/branding/unofficial/res/drawable-xhdpi/icon.png
mobile/android/branding/unofficial/res/drawable-xxhdpi/icon.png
netwerk/sctp/datachannel/Makefile.in
services/datareporting/README.rst
services/healthreport/README.rst
services/metrics/README.rst
services/moz.build
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,12 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-Bug 946067 required a clobber on Windows because bug 928195
+Bug 934646 needs a clobber -- the icon resources previously copied
+into $OBJDIR/mobile/android/base/res will conflict with those in
+$BRANDING_DIRECTORY/res.
+
--- a/accessible/src/html/HTMLFormControlAccessible.cpp
+++ b/accessible/src/html/HTMLFormControlAccessible.cpp
@@ -9,28 +9,28 @@
 #include "nsAccUtils.h"
 #include "nsEventShell.h"
 #include "nsTextEquivUtils.h"
 #include "Relation.h"
 #include "Role.h"
 #include "States.h"
 
 #include "nsContentList.h"
-#include "nsCxPusher.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "nsIAccessibleRelation.h"
 #include "nsIDOMNSEditableElement.h"
 #include "nsIDOMHTMLTextAreaElement.h"
 #include "nsIEditor.h"
 #include "nsIFormControl.h"
 #include "nsINameSpaceManager.h"
 #include "nsIPersistentProperties2.h"
 #include "nsISelectionController.h"
 #include "nsIServiceManager.h"
 #include "nsITextControlFrame.h"
+#include "mozilla/dom/ScriptSettings.h"
 
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::a11y;
 
@@ -463,18 +463,17 @@ HTMLTextFieldAccessible::GetEditor() con
 {
   nsCOMPtr<nsIDOMNSEditableElement> editableElt(do_QueryInterface(mContent));
   if (!editableElt)
     return nullptr;
 
   // nsGenericHTMLElement::GetEditor has a security check.
   // Make sure we're not restricted by the permissions of
   // whatever script is currently running.
-  nsCxPusher pusher;
-  pusher.PushNull();
+  mozilla::dom::AutoSystemCaller asc;
 
   nsCOMPtr<nsIEditor> editor;
   editableElt->GetEditor(getter_AddRefs(editor));
 
   return editor.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
--- a/b2g/app/ua-update.json.in
+++ b/b2g/app/ua-update.json.in
@@ -82,18 +82,16 @@
   // bug 828380, deser.pl
   "deser.pl": "\\(Mobile#(Android; Mobile",
   // bug 828386, ebay.es
   "ebay.es": "\\(Mobile#(Android; Mobile",
   // bug 828392, infojobs.net
   "infojobs.net": "\\(Mobile#(Android; Mobile",
   // bug 828399, antena3.com
   "antena3.com": "\\(Mobile#(Android; Mobile",
-  // bug 828401, ingdirect.es
-  "ingdirect.es": "\\(Mobile#(Android; Mobile",
   // bug 828403, fotocasa.es
   "fotocasa.es": "\\(Mobile#(Android; Mobile",
   // bug 828406, orange.es
   "orange.es": "\\(Mobile#(Android; Mobile",
   // bug 828416, loteriasyapuestas.es
   "loteriasyapuestas.es": "\\(Mobile#(Android; Mobile",
   // bug 828418, bbva.es
   "bbva.es": "\\(Mobile#(Android; Mobile",
@@ -102,18 +100,16 @@
   // bug 828425, mercadolibre.com.ve
   "mercadolibre.com.ve": "\\(Mobile#(Android; Mobile",
   // bug 828433, olx.com.ve
   "olx.com.ve": "\\(Mobile#(Android; Mobile",
   // bug 828439, movistar.com.ve
   "movistar.com.ve": "\\(Mobile#(Android; Mobile",
   // bug 828445, bumeran.com.ve
   "bumeran.com.ve": "\\(Mobile#(Android; Mobile",
-  // bug 828448, petardas.com
-  "petardas.com": "\\(Mobile#(Android; Mobile",
   // bug 843112, movil.bankinter.es
   "movil.bankinter.es": "\\(Mobile#(Android; Mobile",
   // bug 843114, einforma.com
   "einforma.com": "\\(Mobile#(Android; Mobile",
   // bug 843116, wwwhatsnew.com
   "wwwhatsnew.com": "\\(Mobile#(Android; Mobile",
   // bug 843119, askthebuilder.com
   "askthebuilder.com": "\\(Mobile#(Android; Mobile",
@@ -134,26 +130,22 @@
   // bug 843139, consumersearch.com
   "consumersearch.com": "\\(Mobile#(Android; Mobile",
   // bug 843141, foodily.com
   "foodily.com": "\\(Mobile#(Android; Mobile",
   // bug 843151, citibank.com
   "citibank.com": "\\(Mobile#(Android; Mobile",
   // bug 843153, games.com
   "games.com": "\\(Mobile#(Android; Mobile",
-  // bug 843156, orbitz.com
-  "orbitz.com": "\\(Mobile#(Android; Mobile",
   // bug 843160, ehow.com
   "ehow.com": "\\(Mobile#(Android; Mobile",
   // bug 843162, urbanspoon.com
   "urbanspoon.com": "\\(Mobile#(Android; Mobile",
   // bug 843165, virginatlantic.com
   "virginatlantic.com": "\\(Mobile#(Android; Mobile",
-  // bug 843168, cheaptickets.com
-  "cheaptickets.com": "\\(Mobile#(Android; Mobile",
   // bug 843172, zimbio.com
   "zimbio.com": "\\(Mobile#(Android; Mobile",
   // bug 843176, tylted.com
   "tylted.com": "\\(Mobile#(Android; Mobile",
   // bug 843178, txt2nite.com
   "txt2nite.com": "\\(Mobile#(Android; Mobile",
   // bug 843181, slashgear.com
   "slashgear.com": "\\(Mobile#(Android; Mobile",
@@ -166,18 +158,16 @@
   // bug 878228, blikk.hu
   "blikk.hu": "\\(Mobile#(Android; Mobile",
   // bug 878230, citromail.hu
   "citromail.hu": "\\(Mobile#(Android; Mobile",
   // bug 878232, hazipatika.com
   "hazipatika.com": "\\(Mobile#(Android; Mobile",
   // bug 878234, hvg.hu
   "hvg.hu": "\\(Mobile#(Android; Mobile",
-  // bug 878236, jofogas.hu
-  "jofogas.hu": "\\(Mobile#(Android; Mobile",
   // bug 878238, koponyeg.hu
   "koponyeg.hu": "\\(Mobile#(Android; Mobile",
   // bug 878240, kuruc.info
   "kuruc.info": "\\(Mobile#(Android; Mobile",
   // bug 878242, nemzetisport.hu
   "nemzetisport.hu": "\\(Mobile#(Android; Mobile",
   // bug 878244, nlcafe.hu
   "nlcafe.hu": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
@@ -200,26 +190,22 @@
   // bug 878271, kurir-info.rs
   "kurir-info.rs": "\\(Mobile#(Android; Mobile",
   // bug 878273, livescore.com
   "livescore.com": "\\(Mobile#(Android; Mobile",
   // bug 878275, mondo.rs
   "mondo.rs": "\\(Mobile#(Android; Mobile",
   // bug 878277, naslovi.net
   "naslovi.net": "\\(Mobile#(Android; Mobile",
-  // bug 878284, softonic.com
-  "softonic.com": "\\(Mobile#(Android; Mobile",
   // bug 878630, ask.com
   "ask.com": "\\(Mobile#(Android; Mobile",
   // bug 878632, banorte.com
   "banorte.com": "\\(Mobile#(Android; Mobile",
   // bug 878637, eluniversal.com.mx
   "eluniversal.com.mx": "\\(Mobile#(Android; Mobile",
-  // bug 878640, hootsuite.com
-  "hootsuite.com": "\\(Mobile#(Android; Mobile",
   // bug 878642, mercadolibre.com.mx
   "mercadolibre.com.mx": "\\(Mobile#(Android; Mobile",
   // bug 878645, olx.com.mx
   "olx.com.mx": "\\(Mobile#(Android; Mobile",
   // bug 878647, sat.gob.mx
   "sat.gob.mx": "\\(Mobile#(Android; Mobile",
   // bug 878649, univision.com
   "univision.com": "\\(Mobile#(Android; Mobile",
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "336e9464c144268a8902bbb2d9026be3ff2b327f", 
+    "revision": "5bfef5faac50d14e055f642a44ed2df8483fb2fe", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -52,13 +52,13 @@ MOZ_TIME_MANAGER=1
 
 MOZ_B2G_CERTDATA=1
 MOZ_PAY=1
 MOZ_TOOLKIT_SEARCH=
 MOZ_PLACES=
 MOZ_B2G=1
 
 if test "$OS_TARGET" = "Android"; then
-MOZ_NUWA_PROCESS=0
+MOZ_NUWA_PROCESS=
 fi
 MOZ_FOLD_LIBS=1
 
 MOZ_JSDOWNLOADS=1
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -101,54 +101,52 @@ function appUpdater()
                                      "nsIUpdateChecker");
   XPCOMUtils.defineLazyServiceGetter(this, "um",
                                      "@mozilla.org/updates/update-manager;1",
                                      "nsIUpdateManager");
 
   this.bundle = Services.strings.
                 createBundle("chrome://browser/locale/browser.properties");
 
-  this.updateBtn = document.getElementById("updateButton");
-
-  // The button label value must be set so its height is correct.
-  this.setupUpdateButton("update.checkInsideButton");
-
   let manualURL = Services.urlFormatter.formatURLPref("app.update.url.manual");
   let manualLink = document.getElementById("manualLink");
   manualLink.value = manualURL;
   manualLink.href = manualURL;
   document.getElementById("failedLink").href = manualURL;
 
   if (this.updateDisabledAndLocked) {
     this.selectPanel("adminDisabled");
     return;
   }
 
   if (this.isPending || this.isApplied) {
-    this.setupUpdateButton("update.restart." +
-                           (this.isMajor ? "upgradeButton" : "updateButton"));
+    this.selectPanel("apply");
     return;
   }
 
   if (this.aus.isOtherInstanceHandlingUpdates) {
     this.selectPanel("otherInstanceHandlingUpdates");
     return;
   }
 
   if (this.isDownloading) {
     this.startDownload();
+    // selectPanel("downloading") is called from setupDownloadingUI().
     return;
   }
 
-  if (this.updateEnabled && this.updateAuto) {
-    this.selectPanel("checkingForUpdates");
-    this.isChecking = true;
-    this.checker.checkForUpdates(this.updateCheckListener, true);
-    return;
-  }
+  // If app.update.enabled is false, we don't pop up an update dialog
+  // automatically, but opening the About dialog is considered manually
+  // checking for updates, so we always check.
+  // If app.update.auto is false, we ask before downloading though,
+  // in onCheckComplete.
+  this.selectPanel("checkingForUpdates");
+  this.isChecking = true;
+  this.checker.checkForUpdates(this.updateCheckListener, true);
+  // after checking, onCheckComplete() is called
 }
 
 appUpdater.prototype =
 {
   // true when there is an update check in progress.
   isChecking: false,
 
   // true when there is an update already staged / ready to be applied.
@@ -175,23 +173,16 @@ appUpdater.prototype =
   // true when there is an update download in progress.
   get isDownloading() {
     if (this.update)
       return this.update.state == "downloading";
     return this.um.activeUpdate &&
            this.um.activeUpdate.state == "downloading";
   },
 
-  // true when the update type is major.
-  get isMajor() {
-    if (this.update)
-      return this.update.type == "major";
-    return this.um.activeUpdate.type == "major";
-  },
-
   // true when updating is disabled by an administrator.
   get updateDisabledAndLocked() {
     return !this.updateEnabled &&
            Services.prefs.prefIsLocked("app.update.enabled");
   },
 
   // true when updating is enabled.
   get updateEnabled() {
@@ -213,46 +204,64 @@ appUpdater.prototype =
     try {
       return Services.prefs.getBoolPref("app.update.auto");
     }
     catch (e) { }
     return true; // Firefox default is true
   },
 
   /**
-   * Sets the deck's selected panel.
+   * Sets the panel of the updateDeck.
    *
    * @param  aChildID
-   *         The id of the deck's child to select.
+   *         The id of the deck's child to select, e.g. "apply".
    */
   selectPanel: function(aChildID) {
-    this.updateDeck.selectedPanel = document.getElementById(aChildID);
-    this.updateBtn.disabled = (aChildID != "updateButtonBox");
+    let panel = document.getElementById(aChildID);
+
+    let button = panel.querySelector("button");
+    if (button) {
+      if (aChildID == "downloadAndInstall") {
+        let updateVersion = gAppUpdater.update.displayVersion;
+        button.label = this.bundle.formatStringFromName("update.downloadAndInstallButton.label", [updateVersion], 1);
+        button.accessKey = this.bundle.GetStringFromName("update.downloadAndInstallButton.accesskey");
+      }
+      this.updateDeck.selectedPanel = panel;
+      if (!document.commandDispatcher.focusedElement || // don't steal the focus
+          document.commandDispatcher.focusedElement.localName == "button") // except from the other buttons
+        button.focus();
+
+    } else {
+      this.updateDeck.selectedPanel = panel;
+    }
   },
 
   /**
-   * Sets the update button's label and accesskey.
-   *
-   * @param  aKeyPrefix
-   *         The prefix for the properties file entry to use for setting the
-   *         label and accesskey.
+   * Check for addon compat, or start the download right away
    */
-  setupUpdateButton: function(aKeyPrefix) {
-    this.updateBtn.label = this.bundle.GetStringFromName(aKeyPrefix + ".label");
-    this.updateBtn.accessKey = this.bundle.GetStringFromName(aKeyPrefix + ".accesskey");
-    if (!document.commandDispatcher.focusedElement ||
-        document.commandDispatcher.focusedElement == this.updateBtn)
-      this.updateBtn.focus();
+  doUpdate: function() {
+    // skip the compatibility check if the update doesn't provide appVersion,
+    // or the appVersion is unchanged, e.g. nightly update
+    if (!this.update.appVersion ||
+        Services.vc.compare(gAppUpdater.update.appVersion,
+                            Services.appinfo.version) == 0) {
+      this.startDownload();
+    } else {
+      this.checkAddonCompatibility();
+    }
   },
 
   /**
-   * Handles oncommand for the update button.
+   * Handles oncommand for the "Restart to Update" button
+   * which is presented after the download has been downloaded.
    */
-  buttonOnCommand: function() {
-    if (this.isPending || this.isApplied) {
+  buttonRestartAfterDownload: function() {
+    if (!this.isPending && !this.isApplied)
+      return;
+
       // Notify all windows that an application quit has been requested.
       let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
                        createInstance(Components.interfaces.nsISupportsPRBool);
       Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
 
       // Something aborted the quit process.
       if (cancelQuit.data)
         return;
@@ -263,37 +272,31 @@ appUpdater.prototype =
       // If already in safe mode restart in safe mode (bug 327119)
       if (Services.appinfo.inSafeMode) {
         appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
         return;
       }
 
       appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
                       Components.interfaces.nsIAppStartup.eRestart);
-      return;
-    }
+    },
 
+  /**
+   * Handles oncommand for the "Apply Update…" button
+   * which is presented if we need to show the billboard or license.
+   */
+  buttonApplyBillboard: function() {
     const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
-    // Firefox no longer displays a license for updates and the licenseURL check
-    // is just in case a distibution does.
-    if (this.update && (this.update.billboardURL || this.update.licenseURL ||
-        this.addons.length != 0)) {
-      var ary = null;
-      ary = Components.classes["@mozilla.org/supports-array;1"].
-            createInstance(Components.interfaces.nsISupportsArray);
-      ary.AppendElement(this.update);
-      var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
-      Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, "", openFeatures, ary);
-      window.close();
-      return;
-    }
-
-    this.selectPanel("checkingForUpdates");
-    this.isChecking = true;
-    this.checker.checkForUpdates(this.updateCheckListener, true);
+    var ary = null;
+    ary = Components.classes["@mozilla.org/supports-array;1"].
+          createInstance(Components.interfaces.nsISupportsArray);
+    ary.AppendElement(this.update);
+    var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
+    Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, "", openFeatures, ary);
+    window.close(); // close the "About" window; updates.xul takes over.
   },
 
   /**
    * Implements nsIUpdateCheckListener. The methods implemented by
    * nsIUpdateCheckListener are in a different scope from nsIIncrementalDownload
    * to make it clear which are used by each interface.
    */
   updateCheckListener: {
@@ -321,31 +324,24 @@ appUpdater.prototype =
       if (!gAppUpdater.aus.canApplyUpdates) {
         gAppUpdater.selectPanel("manualUpdate");
         return;
       }
 
       // Firefox no longer displays a license for updates and the licenseURL
       // check is just in case a distibution does.
       if (gAppUpdater.update.billboardURL || gAppUpdater.update.licenseURL) {
-        gAppUpdater.selectPanel("updateButtonBox");
-        gAppUpdater.setupUpdateButton("update.openUpdateUI." +
-                                      (this.isMajor ? "upgradeButton"
-                                                    : "applyButton"));
+        gAppUpdater.selectPanel("applyBillboard");
         return;
       }
 
-      if (!gAppUpdater.update.appVersion ||
-          Services.vc.compare(gAppUpdater.update.appVersion,
-                              Services.appinfo.version) == 0) {
-        gAppUpdater.startDownload();
-        return;
-      }
-
-      gAppUpdater.checkAddonCompatibility();
+      if (gAppUpdater.updateAuto) // automatically download and install
+        gAppUpdater.doUpdate();
+      else // ask
+        gAppUpdater.selectPanel("downloadAndInstall");
     },
 
     /**
      * See nsIUpdateService.idl
      */
     onError: function(aRequest, aUpdate) {
       // Errors in the update check are treated as no updates found. If the
       // update check fails repeatedly without a success the user will be
@@ -469,19 +465,17 @@ appUpdater.prototype =
       return;
 
     if (this.addons.length == 0) {
       // Compatibility updates or new version updates were found for all add-ons
       this.startDownload();
       return;
     }
 
-    this.selectPanel("updateButtonBox");
-    this.setupUpdateButton("update.openUpdateUI." +
-                           (this.isMajor ? "upgradeButton" : "applyButton"));
+    this.selectPanel("apply");
   },
 
   /**
    * Starts the download of an update mar.
    */
   startDownload: function() {
     if (!this.update)
       this.update = this.um.activeUpdate;
@@ -548,46 +542,41 @@ appUpdater.prototype =
         let update = this.um.activeUpdate;
         let self = this;
         Services.obs.addObserver(function (aSubject, aTopic, aData) {
           // Update the UI when the background updater is finished
           let status = aData;
           if (status == "applied" || status == "applied-service" ||
               status == "pending" || status == "pending-service") {
             // If the update is successfully applied, or if the updater has
-            // fallen back to non-staged updates, show the Restart to Update
+            // fallen back to non-staged updates, show the "Restart to Update"
             // button.
-            self.selectPanel("updateButtonBox");
-            self.setupUpdateButton("update.restart." +
-                                   (self.isMajor ? "upgradeButton" : "updateButton"));
+            self.selectPanel("apply");
           } else if (status == "failed") {
             // Background update has failed, let's show the UI responsible for
             // prompting the user to update manually.
             self.selectPanel("downloadFailed");
           } else if (status == "downloading") {
             // We've fallen back to downloading the full update because the
             // partial update failed to get staged in the background.
             // Therefore we need to keep our observer.
             self.setupDownloadingUI();
             return;
           }
           Services.obs.removeObserver(arguments.callee, "update-staged");
         }, "update-staged", false);
       } else {
-        this.selectPanel("updateButtonBox");
-        this.setupUpdateButton("update.restart." +
-                               (this.isMajor ? "upgradeButton" : "updateButton"));
+        this.selectPanel("apply");
       }
       break;
     default:
       this.removeDownloadListener();
       this.selectPanel("downloadFailed");
       break;
     }
-
   },
 
   /**
    * See nsIProgressEventSink.idl
    */
   onStatus: function(aRequest, aContext, aStatus, aStatusArg) {
   },
 
--- a/browser/base/content/aboutDialog.xul
+++ b/browser/base/content/aboutDialog.xul
@@ -45,27 +45,39 @@
 #expand <label id="version">__MOZ_APP_VERSION__</label>
         <label id="distribution" class="text-blurb"/>
         <label id="distributionId" class="text-blurb"/>
 
         <vbox id="detailsBox">
           <vbox id="updateBox">
 #ifdef MOZ_UPDATER
             <deck id="updateDeck" orient="vertical">
-              <hbox id="updateButtonBox" align="center">
+              <hbox id="downloadAndInstall" align="center">
+                <button id="downloadAndInstallButton" align="start"
+                        oncommand="gAppUpdater.doUpdate();"/>
+                        <!-- label and accesskey will be filled by JS -->
+                <spacer flex="1"/>
+              </hbox>
+              <hbox id="apply" align="center">
                 <button id="updateButton" align="start"
-                        oncommand="gAppUpdater.buttonOnCommand();"/>
+                        label="&update.updateButton.label;"
+                        accesskey="&update.updateButton.accesskey;"
+                        oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
+                <spacer flex="1"/>
+              </hbox>
+              <hbox id="applyBillboard" align="center">
+                <button id="applyButtonBillboard" align="start"
+                        label="&update.applyButtonBillboard.label;"
+                        accesskey="&update.applyButtonBillboard.accesskey;"
+                        oncommand="gAppUpdater.buttonApplyBillboard();"/>
                 <spacer flex="1"/>
               </hbox>
               <hbox id="checkingForUpdates" align="center">
                 <image class="update-throbber"/><label>&update.checkingForUpdates;</label>
               </hbox>
-              <hbox id="checkingAddonCompat" align="center">
-                <image class="update-throbber"/><label>&update.checkingAddonCompat;</label>
-              </hbox>
               <hbox id="downloading" align="center">
                 <image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
               </hbox>
               <hbox id="applying" align="center">
                 <image class="update-throbber"/><label>&update.applying;</label>
               </hbox>
               <hbox id="downloadFailed" align="center">
                 <label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -452,20 +452,27 @@ let CustomizableUIInternal = {
         let [provider, node] = this.getWidgetNode(id, window);
         if (!node) {
           LOG("Unknown widget: " + id);
           continue;
         }
 
         // If the placements have items in them which are (now) no longer removable,
         // we shouldn't be moving them:
-        if (node.parentNode != container && !this.isWidgetRemovable(node)) {
+        if (provider == CustomizableUI.PROVIDER_API) {
+          let widgetInfo = gPalette.get(id);
+          if (!widgetInfo.removable && aArea != widgetInfo.defaultArea) {
+            placementsToRemove.add(id);
+            continue;
+          }
+        } else if (provider == CustomizableUI.PROVIDER_XUL &&
+                   node.parentNode != container && !this.isWidgetRemovable(node)) {
           placementsToRemove.add(id);
           continue;
-        }
+        } // Special widgets are always removable, so no need to check them
 
         if (inPrivateWindow && provider == CustomizableUI.PROVIDER_API) {
           let widget = gPalette.get(id);
           if (!widget.showInPrivateBrowsing && inPrivateWindow) {
             continue;
           }
         }
 
@@ -1731,17 +1738,17 @@ let CustomizableUIInternal = {
 
   //XXXunf Log some warnings here, when the data provided isn't up to scratch.
   normalizeWidget: function(aData, aSource) {
     let widget = {
       implementation: aData,
       source: aSource || "addon",
       instances: new Map(),
       currentArea: null,
-      removable: false,
+      removable: true,
       overflows: true,
       defaultArea: null,
       shortcutId: null,
       tooltiptext: null,
       showInPrivateBrowsing: true,
     };
 
     if (typeof aData.id != "string" || !/^[a-z0-9-_]{1,}$/i.test(aData.id)) {
@@ -1773,16 +1780,21 @@ let CustomizableUIInternal = {
     for (let prop of kOptBoolProps) {
       if (typeof aData[prop] == "boolean") {
         widget[prop] = aData[prop];
       }
     }
 
     if (aData.defaultArea && gAreas.has(aData.defaultArea)) {
       widget.defaultArea = aData.defaultArea;
+    } else if (!widget.removable) {
+      ERROR("Widget '" + widget.id + "' is not removable but does not specify " +
+            "a valid defaultArea. That's not possible; it must specify a " +
+            "valid defaultArea as well.");
+      return null;
     }
 
     if ("type" in aData && gSupportedWidgetTypes.has(aData.type)) {
       widget.type = aData.type;
     } else {
       widget.type = "button";
     }
 
@@ -2378,21 +2390,23 @@ this.CustomizableUI = {
    * - onClick(aEvt): Attached to all widgets; a function that will be invoked
    *                  when the user clicks the widget.
    * - onViewShowing(aEvt): Only useful for views; a function that will be
    *                  invoked when a user shows your view.
    * - onViewHiding(aEvt): Only useful for views; a function that will be
    *                  invoked when a user hides your view.
    * - tooltiptext:   string to use for the tooltip of the widget
    * - label:         string to use for the label of the widget
-   * - removable:     whether the widget is removable (optional, default: false)
+   * - removable:     whether the widget is removable (optional, default: true)
+   *                  NB: if you specify false here, you must provide a
+   *                  defaultArea, too.
    * - overflows:     whether widget can overflow when in an overflowable
    *                  toolbar (optional, default: true)
    * - defaultArea:   default area to add the widget to
-   *                  (optional, default: none)
+   *                  (optional, default: none; required if non-removable)
    * - shortcutId:    id of an element that has a shortcut for this widget
    *                  (optional, default: null). This is only used to display
    *                  the shortcut as part of the tooltip for builtin widgets
    *                  (which have strings inside
    *                  customizableWidgets.properties). If you're in an add-on,
    *                  you should not set this property.
    * - showInPrivateBrowsing: whether to show the widget in private browsing
    *                          mode (optional, default: true)
--- a/browser/components/customizableui/src/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/src/CustomizableWidgets.jsm
@@ -56,17 +56,16 @@ function updateCombinedWidgetStyle(aNode
   }
 }
 
 const CustomizableWidgets = [{
     id: "history-panelmenu",
     type: "view",
     viewId: "PanelUI-history",
     shortcutId: "key_gotoHistory",
-    removable: true,
     defaultArea: CustomizableUI.AREA_PANEL,
     onViewShowing: function(aEvent) {
       // Populate our list of history
       const kMaxResults = 15;
       let doc = aEvent.detail.ownerDocument;
 
       let options = PlacesUtils.history.getNewQueryOptions();
       options.excludeQueries = true;
@@ -143,71 +142,66 @@ const CustomizableWidgets = [{
       separator.hidden = !windowsFragment.childElementCount;
       recentlyClosedWindows.appendChild(windowsFragment);
     },
     onViewHiding: function(aEvent) {
       LOG("History view is being hidden!");
     }
   }, {
     id: "privatebrowsing-button",
-    removable: true,
     shortcutId: "key_privatebrowsing",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(e) {
       if (e.target && e.target.ownerDocument && e.target.ownerDocument.defaultView) {
         let win = e.target.ownerDocument.defaultView;
         if (typeof win.OpenBrowserWindow == "function") {
           win.OpenBrowserWindow({private: true});
         }
       }
     }
   }, {
     id: "save-page-button",
-    removable: true,
     shortcutId: "key_savePage",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
       if (win && typeof win.saveDocument == "function") {
         win.saveDocument(win.content.document);
       }
     }
   }, {
     id: "find-button",
-    removable: true,
     shortcutId: "key_find",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
       if (win && win.gFindBar) {
         win.gFindBar.onFindCommand();
       }
     }
   }, {
     id: "open-file-button",
-    removable: true,
     shortcutId: "openFileKb",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target
                 && aEvent.target.ownerDocument
                 && aEvent.target.ownerDocument.defaultView;
       if (win && typeof win.BrowserOpenFileWindow == "function") {
         win.BrowserOpenFileWindow();
       }
     }
   }, {
     id: "developer-button",
     type: "view",
     viewId: "PanelUI-developer",
-    removable: true,
     shortcutId: "key_devToolboxMenuItem",
     defaultArea: CustomizableUI.AREA_PANEL,
     onViewShowing: function(aEvent) {
       // Populate the subview with whatever menuitems are in the developer
       // menu. We skip menu elements, because the menu panel has no way
       // of dealing with those right now.
       let doc = aEvent.target.ownerDocument;
       let win = doc.defaultView;
@@ -260,47 +254,44 @@ const CustomizableWidgets = [{
       }
 
       parent.appendChild(items);
       aEvent.target.removeEventListener("command",
                                         win.PanelUI.onCommandHandler);
     }
   }, {
     id: "add-ons-button",
-    removable: true,
     shortcutId: "key_openAddons",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
       if (win && typeof win.BrowserOpenAddonsMgr == "function") {
         win.BrowserOpenAddonsMgr();
       }
     }
   }, {
     id: "preferences-button",
-    removable: true,
     defaultArea: CustomizableUI.AREA_PANEL,
 #ifdef XP_WIN
     label: "preferences-button.labelWin",
     tooltiptext: "preferences-button.tooltipWin",
 #endif
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
       if (win && typeof win.openPreferences == "function") {
         win.openPreferences();
       }
     }
   }, {
     id: "zoom-controls",
     type: "custom",
-    removable: true,
     defaultArea: CustomizableUI.AREA_PANEL,
     onBuild: function(aDocument) {
       const kPanelId = "PanelUI-popup";
       let inPanel = (this.currentArea == CustomizableUI.AREA_PANEL);
       let noautoclose = inPanel ? "true" : null;
       let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1";
 
       if (!this.currentArea)
@@ -436,17 +427,16 @@ const CustomizableWidgets = [{
       };
       CustomizableUI.addListener(listener);
 
       return node;
     }
   }, {
     id: "edit-controls",
     type: "custom",
-    removable: true,
     defaultArea: CustomizableUI.AREA_PANEL,
     onBuild: function(aDocument) {
       let inPanel = (this.currentArea == CustomizableUI.AREA_PANEL);
       let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1";
 
       if (!this.currentArea)
         cls = null;
 
@@ -531,17 +521,16 @@ const CustomizableWidgets = [{
 
       return node;
     }
   },
   {
     id: "feed-button",
     type: "view",
     viewId: "PanelUI-feeds",
-    removable: true,
     defaultArea: CustomizableUI.AREA_PANEL,
     onClick: function(aEvent) {
       let win = aEvent.target.ownerDocument.defaultView;
       let feeds = win.gBrowser.selectedBrowser.feeds;
 
       // Here, we only care about the case where we have exactly 1 feed and the
       // user clicked...
       let isClick = (aEvent.button == 0 || aEvent.button == 1);
@@ -571,17 +560,16 @@ const CustomizableWidgets = [{
       if (!feeds || !feeds.length) {
         node.setAttribute("disabled", "true");
       }
     }
   }, {
     id: "characterencoding-button",
     type: "view",
     viewId: "PanelUI-characterEncodingView",
-    removable: true,
     defaultArea: CustomizableUI.AREA_PANEL,
     maybeDisableMenu: function(aDocument) {
       let window = aDocument.defaultView;
       return !(window.gBrowser &&
                window.gBrowser.docShell &&
                window.gBrowser.docShell.mayEnableCharacterEncodingMenu);
     },
     getCharsetList: function(aSection, aDocument) {
@@ -778,17 +766,16 @@ const CustomizableWidgets = [{
           let panel = aDoc.getElementById(kPanelId);
           panel.removeEventListener("popupshowing", updateButton);
         }
       };
       CustomizableUI.addListener(listener);
     }
   }, {
     id: "email-link-button",
-    removable: true,
     onCommand: function(aEvent) {
       let win = aEvent.view;
       win.MailIntegration.sendLinkForWindow(win.content);
     }
   }];
 
 #ifdef XP_WIN
 #ifdef MOZ_METRO
@@ -796,17 +783,16 @@ if (Services.sysinfo.getProperty("hasWin
   let widgetArgs = {tooltiptext: "switch-to-metro-button2.tooltiptext"};
   let brandShortName = BrandBundle.GetStringFromName("brandShortName");
   let metroTooltip = CustomizableUI.getLocalizedProperty(widgetArgs, "tooltiptext",
                                                          [brandShortName]);
   CustomizableWidgets.push({
     id: "switch-to-metro-button",
     label: "switch-to-metro-button2.label",
     tooltiptext: metroTooltip,
-    removable: true,
     defaultArea: CustomizableUI.AREA_PANEL,
     showInPrivateBrowsing: false, /* See bug 928068 */
     onCommand: function(aEvent) {
       let win = aEvent.view;
       if (win && typeof win.SwitchToMetro == "function") {
         win.SwitchToMetro();
       }
     }
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -40,9 +40,10 @@ skip-if = os == "mac"
 [browser_938980_navbar_collapsed.js]
 [browser_938995_indefaultstate_nonremovable.js]
 [browser_940013_registerToolbarNode_calls_registerArea.js]
 [browser_940946_removable_from_navbar_customizemode.js]
 [browser_941083_invalidate_wrapper_cache_createWidget.js]
 [browser_942581_unregisterArea_keeps_placements.js]
 [browser_943683_migration_test.js]
 [browser_944887_destroyWidget_should_destroy_in_palette.js]
+[browser_947987_removable_default.js]
 [browser_panel_toggle.js]
--- a/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js
+++ b/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js
@@ -5,26 +5,27 @@
 const kWidgetId = "test-non-removable-widget";
 let gTests = [
   {
     desc: "Adding non-removable items to a toolbar or the panel shouldn't change inDefaultState",
     run: function() {
       let navbar = document.getElementById("nav-bar");
       ok(CustomizableUI.inDefaultState, "Should start in default state");
 
-      CustomizableUI.createWidget({id: kWidgetId, removable: false, label: "Test"});
+      let button = createDummyXULButton(kWidgetId, "Test non-removable inDefaultState handling");
       CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
+      button.setAttribute("removable", "false");
       ok(CustomizableUI.inDefaultState, "Should still be in default state after navbar addition");
-      CustomizableUI.destroyWidget(kWidgetId);
+      button.remove();
 
-      CustomizableUI.createWidget({id: kWidgetId, removable: false, label: "Test"});
+      button = createDummyXULButton(kWidgetId, "Test non-removable inDefaultState handling");
       CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_PANEL);
+      button.setAttribute("removable", "false");
       ok(CustomizableUI.inDefaultState, "Should still be in default state after panel addition");
-      CustomizableUI.destroyWidget(kWidgetId);
-
+      button.remove();
       ok(CustomizableUI.inDefaultState, "Should be in default state after destroying both widgets");
     },
     teardown: null
   },
 ];
 
 function test() {
   waitForExplicitFinish();
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947987_removable_default.js
@@ -0,0 +1,79 @@
+/* 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/. */
+
+let kWidgetId = "test-removable-widget-default";
+const kNavBar = CustomizableUI.AREA_NAVBAR;
+let widgetCounter = 0;
+let gTests = [
+  {
+    desc: "Sanity checks",
+    run: function() {
+      let brokenSpec = {id: kWidgetId + (widgetCounter++), removable: false};
+      SimpleTest.doesThrow(function() CustomizableUI.createWidget(brokenSpec),
+                           "Creating non-removable widget without defaultArea should throw.");
+
+      // Widget without removable set should be removable:
+      let wrapper = CustomizableUI.createWidget({id: kWidgetId + (widgetCounter++)});
+      ok(CustomizableUI.isWidgetRemovable(wrapper.id), "Should be removable by default.");
+      CustomizableUI.destroyWidget(wrapper.id);
+    }
+  },
+  {
+    desc: "Test non-removable widget with defaultArea",
+    run: function() {
+      // Non-removable widget with defaultArea should work:
+      let spec = {id: kWidgetId + (widgetCounter++), removable: false,
+                  defaultArea: kNavBar};
+      let widgetWrapper;
+      try {
+        widgetWrapper = CustomizableUI.createWidget(spec);
+      } catch (ex) {
+        ok(false, "Creating a non-removable widget with a default area should not throw.");
+        return;
+      }
+
+      let placement = CustomizableUI.getPlacementOfWidget(spec.id);
+      ok(placement, "Widget should be placed.");
+      is(placement.area, kNavBar, "Widget should be in navbar");
+      let singleWrapper = widgetWrapper.forWindow(window);
+      ok(singleWrapper, "Widget should exist in window.");
+      ok(singleWrapper.node, "Widget node should exist in window.");
+      let expectedParent = CustomizableUI.getCustomizeTargetForArea(kNavBar, window);
+      is(singleWrapper.node.parentNode, expectedParent, "Widget should be in navbar.");
+
+      let otherWin = yield openAndLoadWindow(true);
+      placement = CustomizableUI.getPlacementOfWidget(spec.id);
+      ok(placement, "Widget should be placed.");
+      is(placement && placement.area, kNavBar, "Widget should be in navbar");
+
+      singleWrapper = widgetWrapper.forWindow(otherWin);
+      ok(singleWrapper, "Widget should exist in other window.");
+      if (singleWrapper) {
+        ok(singleWrapper.node, "Widget node should exist in other window.");
+        if (singleWrapper.node) {
+          let expectedParent = CustomizableUI.getCustomizeTargetForArea(kNavBar, otherWin);
+          is(singleWrapper.node.parentNode, expectedParent,
+             "Widget should be in navbar in other window.");
+        }
+      }
+      otherWin.close();
+    }
+  },
+];
+
+function asyncCleanup() {
+  yield resetCustomization();
+}
+
+function cleanup() {
+  removeCustomToolbars();
+}
+
+function test() {
+  waitForExplicitFinish();
+  registerCleanupFunction(cleanup);
+  runTests(gTests, asyncCleanup);
+}
+
+
--- a/browser/devtools/commandline/test/browser_cmd_screenshot.js
+++ b/browser/devtools/commandline/test/browser_cmd_screenshot.js
@@ -165,31 +165,27 @@ function addTabWithToolbarRunTests(win) 
 }
 
 function addWindow(windowOptions, callback) {
   waitForExplicitFinish();
   let deferred = promise.defer();
 
   let win = OpenBrowserWindow(windowOptions);
 
-  let onLoad = function() {
-    win.removeEventListener("load", onLoad, false);
-
+  whenDelayedStartupFinished(win, function() {
     // Would like to get rid of this executeSoon, but without it the url
     // (TEST_URI) provided in addTabWithToolbarRunTests hasn't loaded
     executeSoon(function() {
       try {
         let reply = callback(win);
         promise.resolve(reply).then(function() {
           win.close();
           deferred.resolve();
         });
       }
       catch (ex) {
         deferred.reject(ex);
       }
     });
-  };
-
-  win.addEventListener("load", onLoad, false);
+  });
 
   return deferred.promise;
 }
--- a/browser/devtools/commandline/test/head.js
+++ b/browser/devtools/commandline/test/head.js
@@ -5,16 +5,25 @@
 const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/commandline/test/";
 const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/commandline/test/";
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
 Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this);
 
+function whenDelayedStartupFinished(aWindow, aCallback) {
+  Services.obs.addObserver(function observer(aSubject, aTopic) {
+    if (aWindow == aSubject) {
+      Services.obs.removeObserver(observer, aTopic);
+      executeSoon(aCallback);
+    }
+  }, "browser-delayed-startup-finished", false);
+}
+
 /**
  * Force GC on shutdown, because it seems that GCLI can outrun the garbage
  * collector in some situations, which causes test failures in later tests
  * Bug 774619 is an example.
  */
 registerCleanupFunction(function tearDown() {
   window.QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIDOMWindowUtils)
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -26,8 +26,9 @@ support-files =
 [browser_telemetry_toolboxtabs_options.js]
 [browser_telemetry_toolboxtabs_styleeditor.js]
 [browser_telemetry_toolboxtabs_webconsole.js]
 [browser_templater_basic.js]
 [browser_toolbar_basic.js]
 [browser_toolbar_tooltip.js]
 [browser_toolbar_webconsole_errors_count.js]
 [browser_spectrum.js]
+[browser_csstransformpreview.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_csstransformpreview.js
@@ -0,0 +1,139 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the spectrum color picker works correctly
+
+const TEST_URI = "data:text/html;charset=utf-8,<div></div>";
+const {CSSTransformPreviewer} = devtools.require("devtools/shared/widgets/CSSTransformPreviewer");
+
+let doc, root;
+
+function test() {
+  waitForExplicitFinish();
+  addTab(TEST_URI, () => {
+    doc = content.document;
+    root = doc.querySelector("div");
+    startTests();
+  });
+}
+
+function endTests() {
+  doc = root = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function startTests() {
+  testCreateAndDestroyShouldAppendAndRemoveElements();
+}
+
+function testCreateAndDestroyShouldAppendAndRemoveElements() {
+  ok(root, "We have the root node to append the preview to");
+  is(root.childElementCount, 0, "Root node is empty");
+
+  let p = new CSSTransformPreviewer(root);
+  p.preview("matrix(1, -0.2, 0, 1, 0, 0)");
+  ok(root.childElementCount > 0, "Preview has appended elements");
+  ok(root.querySelector("canvas"), "Canvas preview element is here");
+
+  p.destroy();
+  is(root.childElementCount, 0, "Destroying preview removed all nodes");
+
+  testCanvasDimensionIsConstrainedByMaxDim();
+}
+
+function testCanvasDimensionIsConstrainedByMaxDim() {
+  let p = new CSSTransformPreviewer(root);
+  p.MAX_DIM = 500;
+  p.preview("scale(1)", "center", 1000, 1000);
+
+  let canvas = root.querySelector("canvas");
+  is(canvas.width, 500, "Canvas width is correct");
+  is(canvas.height, 500, "Canvas height is correct");
+
+  p.destroy();
+
+  testCallingPreviewSeveralTimesReusesTheSameCanvas();
+}
+
+function testCallingPreviewSeveralTimesReusesTheSameCanvas() {
+  let p = new CSSTransformPreviewer(root);
+
+  p.preview("scale(1)", "center", 1000, 1000);
+  let canvas = root.querySelector("canvas");
+
+  p.preview("rotate(90deg)");
+  let canvases = root.querySelectorAll("canvas");
+  is(canvases.length, 1, "Still one canvas element");
+  is(canvases[0], canvas, "Still the same canvas element");
+  p.destroy();
+
+  testCanvasDimensionAreCorrect();
+}
+
+function testCanvasDimensionAreCorrect() {
+  // Only test a few simple transformations
+  let p = new CSSTransformPreviewer(root);
+
+  // Make sure we have a square
+  let w = 200, h = w;
+  p.MAX_DIM = w;
+
+  // We can't test the content of the canvas here, just that, given a max width
+  // the aspect ratio of the canvas seems correct.
+
+  // Translate a square by its width, should be a rectangle
+  p.preview("translateX(200px)", "center", w, h);
+  let canvas = root.querySelector("canvas");
+  is(canvas.width, w, "width is correct");
+  is(canvas.height, h/2, "height is half of the width");
+
+  // Rotate on the top right corner, should be a rectangle
+  p.preview("rotate(-90deg)", "top right", w, h);
+  is(canvas.width, w, "width is correct");
+  is(canvas.height, h/2, "height is half of the width");
+
+  // Rotate on the bottom left corner, should be a rectangle
+  p.preview("rotate(90deg)", "top right", w, h);
+  is(canvas.width, w/2, "width is half of the height");
+  is(canvas.height, h, "height is correct");
+
+  // Scale from center, should still be a square
+  p.preview("scale(2)", "center", w, h);
+  is(canvas.width, w, "width is correct");
+  is(canvas.height, h, "height is correct");
+
+  // Skew from center, 45deg, should be a rectangle
+  p.preview("skew(45deg)", "center", w, h);
+  is(canvas.width, w, "width is correct");
+  is(canvas.height, h/2, "height is half of the height");
+
+  p.destroy();
+
+  testPreviewingInvalidTransformReturnsFalse();
+}
+
+function testPreviewingInvalidTransformReturnsFalse() {
+  let p = new CSSTransformPreviewer(root);
+  ok(!p.preview("veryWow(muchPx) suchTransform(soDeg)"), "Returned false for invalid transform");
+  ok(!p.preview("rotae(3deg)"), "Returned false for invalid transform");
+
+  // Verify the canvas is empty by checking the image data
+  let canvas = root.querySelector("canvas"), ctx = canvas.getContext("2d");
+  let data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+  for (let i = 0, n = data.length; i < n; i += 4) {
+    // Let's not log 250*250*4 asserts! Instead, just log when it fails
+    let red = data[i];
+    let green = data[i + 1];
+    let blue = data[i + 2];
+    let alpha = data[i + 3];
+    if (red !== 0 || green !== 0 || blue !== 0 || alpha !== 0) {
+      ok(false, "Image data is not empty after an invalid transformed was previewed");
+      break;
+    }
+  }
+
+  is(p.preview("translateX(30px)"), true, "Returned true for a valid transform");
+  endTests();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/widgets/CSSTransformPreviewer.js
@@ -0,0 +1,389 @@
+/* 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/. */
+
+"use strict";
+
+/**
+ * The CSSTransformPreview module displays, using a <canvas> a rectangle, with
+ * a given width and height and its transformed version, given a css transform
+ * property and origin. It also displays arrows from/to each corner.
+ *
+ * It is useful to visualize how a css transform affected an element. It can
+ * help debug tricky transformations. It is used today in a tooltip, and this
+ * tooltip is shown when hovering over a css transform declaration in the rule
+ * and computed view panels.
+ *
+ * TODO: For now, it multiplies matrices itself to calculate the coordinates of
+ * the transformed box, but that should be removed as soon as we can get access
+ * to getQuads().
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+/**
+ * The TransformPreview needs an element to output a canvas tag.
+ *
+ * Usage example:
+ *
+ * let t = new CSSTransformPreviewer(myRootElement);
+ * t.preview("rotate(45deg)", "top left", 200, 400);
+ * t.preview("skew(19deg)", "center", 100, 500);
+ * t.preview("matrix(1, -0.2, 0, 1, 0, 0)");
+ * t.destroy();
+ *
+ * @param {nsIDOMElement} parentEl
+ *        Where the canvas will go
+ */
+function CSSTransformPreviewer(parentEl) {
+  this.parentEl = parentEl;
+  this.doc = this.parentEl.ownerDocument;
+  this.canvas = null;
+  this.ctx = null;
+}
+
+module.exports.CSSTransformPreviewer = CSSTransformPreviewer;
+
+CSSTransformPreviewer.prototype = {
+  /**
+   * The preview look-and-feel can be changed using these properties
+   */
+  MAX_DIM: 250,
+  PAD: 5,
+  ORIGINAL_FILL: "#1F303F",
+  ORIGINAL_STROKE: "#B2D8FF",
+  TRANSFORMED_FILL: "rgba(200, 200, 200, .5)",
+  TRANSFORMED_STROKE: "#B2D8FF",
+  ARROW_STROKE: "#329AFF",
+  ORIGIN_STROKE: "#329AFF",
+  ARROW_TIP_HEIGHT: 10,
+  ARROW_TIP_WIDTH: 8,
+  CORNER_SIZE_RATIO: 6,
+
+  /**
+   * Destroy removes the canvas from the parentelement passed in the constructor
+   */
+  destroy: function() {
+    if (this.canvas) {
+      this.parentEl.removeChild(this.canvas);
+    }
+    if (this._hiddenDiv) {
+      this.parentEl.removeChild(this._hiddenDiv);
+    }
+    this.parentEl = this.canvas = this.ctx = this.doc = null;
+  },
+
+  _createMarkup: function() {
+    this.canvas = this.doc.createElementNS(HTML_NS, "canvas");
+
+    this.canvas.setAttribute("id", "canvas");
+    this.canvas.setAttribute("width", this.MAX_DIM);
+    this.canvas.setAttribute("height", this.MAX_DIM);
+    this.canvas.style.position = "relative";
+    this.parentEl.appendChild(this.canvas);
+
+    this.ctx = this.canvas.getContext("2d");
+  },
+
+  _getComputed: function(name, value, width, height) {
+    if (!this._hiddenDiv) {
+      // Create a hidden element to apply the style to
+      this._hiddenDiv = this.doc.createElementNS(HTML_NS, "div");
+      this._hiddenDiv.style.visibility = "hidden";
+      this._hiddenDiv.style.position = "absolute";
+      this.parentEl.appendChild(this._hiddenDiv);
+    }
+
+    // Camelcase the name
+    name = name.replace(/-([a-z]{1})/g, (m, letter) => letter.toUpperCase());
+
+    // Apply width and height to make sure computation is made correctly
+    this._hiddenDiv.style.width = width + "px";
+    this._hiddenDiv.style.height = height + "px";
+
+    // Show the hidden div, apply the style, read the computed style, hide the
+    // hidden div again
+    this._hiddenDiv.style.display = "block";
+    this._hiddenDiv.style[name] = value;
+    let computed = this.doc.defaultView.getComputedStyle(this._hiddenDiv);
+    let computedValue = computed[name];
+    this._hiddenDiv.style.display = "none";
+
+    return computedValue;
+  },
+
+  _getMatrixFromTransformString: function(transformStr) {
+    let matrix = transformStr.substring(0, transformStr.length - 1).
+      substring(transformStr.indexOf("(") + 1).split(",");
+
+    matrix.forEach(function(value, index) {
+      matrix[index] = parseFloat(value, 10);
+    });
+
+    let transformMatrix = null;
+
+    if (matrix.length === 6) {
+      // 2d transform
+      transformMatrix = [
+        [matrix[0], matrix[2], matrix[4], 0],
+        [matrix[1], matrix[3], matrix[5], 0],
+        [0,     0,     1,     0],
+        [0,     0,     0,     1]
+      ];
+    } else {
+      // 3d transform
+      transformMatrix = [
+        [matrix[0], matrix[4], matrix[8],  matrix[12]],
+        [matrix[1], matrix[5], matrix[9],  matrix[13]],
+        [matrix[2], matrix[6], matrix[10], matrix[14]],
+        [matrix[3], matrix[7], matrix[11], matrix[15]]
+      ];
+    }
+
+    return transformMatrix;
+  },
+
+  _getOriginFromOriginString: function(originStr) {
+    let offsets = originStr.split(" ");
+    offsets.forEach(function(item, index) {
+      offsets[index] = parseInt(item, 10);
+    });
+
+    return offsets;
+  },
+
+  _multiply: function(m1, m2) {
+    let m = [];
+    for (let m1Line = 0; m1Line < m1.length; m1Line++) {
+      m[m1Line] = 0;
+      for (let m2Col = 0; m2Col < m2.length; m2Col++) {
+        m[m1Line] += m1[m1Line][m2Col] * m2[m2Col];
+      }
+    }
+    return [m[0], m[1]];
+  },
+
+  _getTransformedPoint: function(matrix, point, origin) {
+    let pointMatrix = [point[0] - origin[0], point[1] - origin[1], 1, 1];
+    return this._multiply(matrix, pointMatrix);
+  },
+
+  _getTransformedPoints: function(matrix, rect, origin) {
+    return rect.map(point => {
+      let tPoint = this._getTransformedPoint(matrix, [point[0], point[1]], origin);
+      return [tPoint[0] + origin[0], tPoint[1] + origin[1]];
+    });
+  },
+
+  /**
+   * For canvas to avoid anti-aliasing
+   */
+  _round: x => Math.round(x) + .5,
+
+  _drawShape: function(points, fillStyle, strokeStyle) {
+    this.ctx.save();
+
+    this.ctx.lineWidth = 1;
+    this.ctx.strokeStyle = strokeStyle;
+    this.ctx.fillStyle = fillStyle;
+
+    this.ctx.beginPath();
+    this.ctx.moveTo(this._round(points[0][0]), this._round(points[0][1]));
+    for (var i = 1; i < points.length; i++) {
+      this.ctx.lineTo(this._round(points[i][0]), this._round(points[i][1]));
+    }
+    this.ctx.lineTo(this._round(points[0][0]), this._round(points[0][1]));
+    this.ctx.fill();
+    this.ctx.stroke();
+
+    this.ctx.restore();
+  },
+
+  _drawArrow: function(x1, y1, x2, y2) {
+    // do not draw if the line is too small
+    if (Math.abs(x2-x1) < 20 && Math.abs(y2-y1) < 20) {
+      return;
+    }
+
+    this.ctx.save();
+
+    this.ctx.strokeStyle = this.ARROW_STROKE;
+    this.ctx.fillStyle = this.ARROW_STROKE;
+    this.ctx.lineWidth = 1;
+
+    this.ctx.beginPath();
+    this.ctx.moveTo(this._round(x1), this._round(y1));
+    this.ctx.lineTo(this._round(x2), this._round(y2));
+    this.ctx.stroke();
+
+    this.ctx.beginPath();
+    this.ctx.translate(x2, y2);
+    let radians = Math.atan((y1 - y2) / (x1 - x2));
+    radians += ((x1 >= x2) ? -90 : 90) * Math.PI / 180;
+    this.ctx.rotate(radians);
+    this.ctx.moveTo(0, 0);
+    this.ctx.lineTo(this.ARROW_TIP_WIDTH / 2, this.ARROW_TIP_HEIGHT);
+    this.ctx.lineTo(-this.ARROW_TIP_WIDTH / 2, this.ARROW_TIP_HEIGHT);
+    this.ctx.closePath();
+    this.ctx.fill();
+
+    this.ctx.restore();
+  },
+
+  _drawOrigin: function(x, y) {
+    this.ctx.save();
+
+    this.ctx.strokeStyle = this.ORIGIN_STROKE;
+    this.ctx.fillStyle = this.ORIGIN_STROKE;
+
+    this.ctx.beginPath();
+    this.ctx.arc(x, y, 4, 0, 2 * Math.PI, false);
+    this.ctx.stroke();
+    this.ctx.fill();
+
+    this.ctx.restore();
+  },
+
+  /**
+   * Computes the largest width and height of all the given shapes and changes
+   * all of the shapes' points (by reference) so they fit into the configured
+   * MAX_DIM - 2*PAD area.
+   * @return {Object} A {w, h} giving the size the canvas should be
+   */
+  _fitAllShapes: function(allShapes) {
+    let allXs = [], allYs = [];
+    for (let shape of allShapes) {
+      for (let point of shape) {
+        allXs.push(point[0]);
+        allYs.push(point[1]);
+      }
+    }
+    let minX = Math.min.apply(Math, allXs);
+    let maxX = Math.max.apply(Math, allXs);
+    let minY = Math.min.apply(Math, allYs);
+    let maxY = Math.max.apply(Math, allYs);
+
+    let spanX = maxX - minX;
+    let spanY = maxY - minY;
+    let isWide = spanX > spanY;
+
+    let cw = isWide ? this.MAX_DIM :
+      this.MAX_DIM * Math.min(spanX, spanY) / Math.max(spanX, spanY);
+    let ch = !isWide ? this.MAX_DIM :
+      this.MAX_DIM * Math.min(spanX, spanY) / Math.max(spanX, spanY);
+
+    let mapX = x => this.PAD + ((cw - 2 * this.PAD) / (maxX - minX)) * (x - minX);
+    let mapY = y => this.PAD + ((ch - 2 * this.PAD) / (maxY - minY)) * (y - minY);
+
+    for (let shape of allShapes) {
+      for (let point of shape) {
+        point[0] = mapX(point[0]);
+        point[1] = mapY(point[1]);
+      }
+    }
+
+    return {w: cw, h: ch};
+  },
+
+  _drawShapes: function(shape, corner, transformed, transformedCorner) {
+    this._drawOriginal(shape);
+    this._drawOriginalCorner(corner);
+    this._drawTransformed(transformed);
+    this._drawTransformedCorner(transformedCorner);
+  },
+
+  _drawOriginal: function(points) {
+    this._drawShape(points, this.ORIGINAL_FILL, this.ORIGINAL_STROKE);
+  },
+
+  _drawTransformed: function(points) {
+    this._drawShape(points, this.TRANSFORMED_FILL, this.TRANSFORMED_STROKE);
+  },
+
+  _drawOriginalCorner: function(points) {
+    this._drawShape(points, this.ORIGINAL_STROKE, this.ORIGINAL_STROKE);
+  },
+
+  _drawTransformedCorner: function(points) {
+    this._drawShape(points, this.TRANSFORMED_STROKE, this.TRANSFORMED_STROKE);
+  },
+
+  _drawArrows: function(shape, transformed) {
+    this._drawArrow(shape[0][0], shape[0][1], transformed[0][0], transformed[0][1]);
+    this._drawArrow(shape[1][0], shape[1][1], transformed[1][0], transformed[1][1]);
+    this._drawArrow(shape[2][0], shape[2][1], transformed[2][0], transformed[2][1]);
+    this._drawArrow(shape[3][0], shape[3][1], transformed[3][0], transformed[3][1]);
+  },
+
+  /**
+   * Draw a transform preview
+   *
+   * @param {String} transform
+   *        The css transform value as a string, as typed by the user, as long
+   *        as it can be computed by the browser
+   * @param {String} origin
+   *        Same as above for the transform-origin value. Defaults to "center"
+   * @param {Number} width
+   *        The width of the container. Defaults to 200
+   * @param {Number} height
+   *        The height of the container. Defaults to 200
+   * @return {Boolean} Whether or not the preview could be created. Will return
+   *         false for instance if the transform is invalid
+   */
+  preview: function(transform, origin="center", width=200, height=200) {
+    // Create/clear the canvas
+    if (!this.canvas) {
+      this._createMarkup();
+    }
+    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+
+    // Get computed versions of transform and origin
+    transform = this._getComputed("transform", transform, width, height);
+    if (transform && transform !== "none") {
+      origin = this._getComputed("transform-origin", origin, width, height);
+
+      // Get the matrix, origin and width height data for the previewed element
+      let originData = this._getOriginFromOriginString(origin);
+      let matrixData = this._getMatrixFromTransformString(transform);
+
+      // Compute the original box rect and transformed box rect
+      let shapePoints = [
+        [0, 0],
+        [width, 0],
+        [width, height],
+        [0, height]
+      ];
+      let transformedPoints = this._getTransformedPoints(matrixData, shapePoints, originData);
+
+      // Do the same for the corner triangle shape
+      let cornerSize = Math.min(shapePoints[2][1] - shapePoints[1][1],
+        shapePoints[1][0] - shapePoints[0][0]) / this.CORNER_SIZE_RATIO;
+      let cornerPoints = [
+        [shapePoints[1][0], shapePoints[1][1]],
+        [shapePoints[1][0], shapePoints[1][1] + cornerSize],
+        [shapePoints[1][0] - cornerSize, shapePoints[1][1]]
+      ];
+      let transformedCornerPoints = this._getTransformedPoints(matrixData, cornerPoints, originData);
+
+      // Resize points to fit everything in the canvas
+      let {w, h} = this._fitAllShapes([
+        shapePoints,
+        transformedPoints,
+        cornerPoints,
+        transformedCornerPoints,
+        [originData]
+      ]);
+
+      this.canvas.setAttribute("width", w);
+      this.canvas.setAttribute("height", h);
+
+      this._drawShapes(shapePoints, cornerPoints, transformedPoints, transformedCornerPoints)
+      this._drawArrows(shapePoints, transformedPoints);
+      this._drawOrigin(originData[0], originData[1]);
+
+      return true;
+    } else {
+      return false;
+    }
+  }
+};
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -7,16 +7,17 @@
 const {Cc, Cu, Ci} = require("chrome");
 const promise = require("sdk/core/promise");
 const IOService = Cc["@mozilla.org/network/io-service;1"]
   .getService(Ci.nsIIOService);
 const {Spectrum} = require("devtools/shared/widgets/Spectrum");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {colorUtils} = require("devtools/css-color");
 const Heritage = require("sdk/core/heritage");
+const {CSSTransformPreviewer} = require("devtools/shared/widgets/CSSTransformPreviewer");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "setNamedTimeout",
   "resource:///modules/devtools/ViewHelpers.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "clearNamedTimeout",
   "resource:///modules/devtools/ViewHelpers.jsm");
@@ -95,17 +96,16 @@ let PanelFactory = {
    *        An options store to get some configuration from
    */
   get: function(doc, options) {
     // Create the tooltip
     let panel = doc.createElement("panel");
     panel.setAttribute("hidden", true);
     panel.setAttribute("ignorekeys", true);
 
-    // Prevent the click used to close the panel from being consumed
     panel.setAttribute("consumeoutsideclicks", options.get("consumeOutsideClick"));
     panel.setAttribute("noautofocus", options.get("noAutoFocus"));
     panel.setAttribute("type", "arrow");
     panel.setAttribute("level", "top");
 
     panel.setAttribute("class", "devtools-tooltip theme-tooltip-panel");
     doc.querySelector("window").appendChild(panel);
 
@@ -224,16 +224,20 @@ Tooltip.prototype = {
     this.panel.hidden = true;
     this.panel.hidePopup();
   },
 
   isShown: function() {
     return this.panel.state !== "closed" && this.panel.state !== "hiding";
   },
 
+  setSize: function(width, height) {
+    this.panel.sizeTo(width, height);
+  },
+
   /**
    * Empty the tooltip's content
    */
   empty: function() {
     while (this.panel.hasChildNodes()) {
       this.panel.removeChild(this.panel.firstChild);
     }
   },
@@ -299,25 +303,26 @@ Tooltip.prototype = {
    *
    * Note that if you call this function a second time, it will itself call
    * stopTogglingOnHover before adding mouse tracking listeners again.
    *
    * @param {node} baseNode
    *        The container for all target nodes
    * @param {Function} targetNodeCb
    *        A function that accepts a node argument and returns true or false
-   *        to signify if the tooltip should be shown on that node or not.
+   *        (or a promise that resolves or rejects) to signify if the tooltip
+   *        should be shown on that node or not.
    *        Additionally, the function receives a second argument which is the
    *        tooltip instance itself, to be used to add/modify the content of the
    *        tooltip if needed. If omitted, the tooltip will be shown everytime.
    * @param {Number} showDelay
    *        An optional delay that will be observed before showing the tooltip.
    *        Defaults to this.defaultShowDelay.
    */
-  startTogglingOnHover: function(baseNode, targetNodeCb, showDelay = this.defaultShowDelay) {
+  startTogglingOnHover: function(baseNode, targetNodeCb, showDelay=this.defaultShowDelay) {
     if (this._basedNode) {
       this.stopTogglingOnHover();
     }
 
     this._basedNode = baseNode;
     this._showDelay = showDelay;
     this._targetNodeCb = targetNodeCb || (() => true);
 
@@ -352,17 +357,22 @@ Tooltip.prototype = {
       this._lastHovered = event.target;
       setNamedTimeout(this.uid, this._showDelay, () => {
         this._showOnHover(event.target);
       });
     }
   },
 
   _showOnHover: function(target) {
-    if (this._targetNodeCb(target, this)) {
+    let res = this._targetNodeCb(target, this);
+    if (res && res.then) {
+      res.then(() => {
+        this.show(target);
+      });
+    } else if (res) {
       this.show(target);
     }
   },
 
   _onBaseNodeMouseLeave: function() {
     clearNamedTimeout(this.uid);
     this._lastHovered = null;
     this.hide();
@@ -522,16 +532,18 @@ Tooltip.prototype = {
     imgObj.src = imageUrl;
     imgObj.onload = () => {
       imgObj.onload = null;
 
       // Display dimensions
       let w = options.naturalWidth || imgObj.naturalWidth;
       let h = options.naturalHeight || imgObj.naturalHeight;
       label.textContent = w + " x " + h;
+
+      this.setSize(vbox.width, vbox.height);
     }
   },
 
   /**
    * Exactly the same as the `image` function but takes a css background image
    * value instead : url(....)
    */
   setCssBackgroundImageContent: function(cssBackground, sheetHref, maxDim=400) {
@@ -579,16 +591,64 @@ Tooltip.prototype = {
     }
     iframe.addEventListener("load", onLoad, true);
     iframe.setAttribute("src", SPECTRUM_FRAME);
 
     // Put the iframe in the tooltip
     this.content = iframe;
 
     return def.promise;
+  },
+
+  /**
+   * Set the content of the tooltip to be the result of CSSTransformPreviewer.
+   * Meaning a canvas previewing a css transformation.
+   *
+   * @param {String} transform
+   *        The CSS transform value (e.g. "rotate(45deg) translateX(50px)")
+   * @param {PageStyleActor} pageStyle
+   *        An instance of the PageStyleActor that will be used to retrieve
+   *        computed styles
+   * @param {NodeActor} node
+   *        The NodeActor for the currently selected node
+   * @return A promise that resolves when the tooltip content is ready, or
+   *         rejects if no transform is provided or is invalid
+   */
+  setCssTransformContent: function(transform, pageStyle, node) {
+    let def = promise.defer();
+
+    if (transform) {
+      // Look into the computed styles to find the width and height and possibly
+      // the origin if it hadn't been provided
+      pageStyle.getComputed(node, {
+        filter: "user",
+        markMatched: false,
+        onlyMatched: false
+      }).then(styles => {
+        let origin = styles["transform-origin"].value;
+        let width = parseInt(styles["width"].value);
+        let height = parseInt(styles["height"].value);
+
+        let root = this.doc.createElementNS(XHTML_NS, "div");
+        let previewer = new CSSTransformPreviewer(root);
+        this.content = root;
+        if (!previewer.preview(transform, origin, width, height)) {
+          // If the preview didn't work, reject the promise
+          def.reject();
+        } else {
+          // Else, make sure the tooltip has the right size and resolve
+          this.setSize(previewer.canvas.width, previewer.canvas.height);
+          def.resolve();
+        }
+      });
+    } else {
+      def.reject();
+    }
+
+    return def.promise;
   }
 };
 
 /**
  * Base class for all (color, gradient, ...)-swatch based value editors inside
  * tooltips
  *
  * @param {XULDocument} doc
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -292,16 +292,18 @@ CssHtmlTree.prototype = {
       }
       // Hiding all properties
       for (let propView of this.propertyViews) {
         propView.refresh();
       }
       return promise.resolve(undefined);
     }
 
+    this.tooltip.hide();
+
     if (aElement === this.viewedElement) {
       return promise.resolve(undefined);
     }
 
     this.viewedElement = aElement;
     this.refreshSourceFilter();
 
     return this.refreshPanel();
@@ -502,31 +504,37 @@ CssHtmlTree.prototype = {
   },
 
   /**
    * Verify that target is indeed a css value we want a tooltip on, and if yes
    * prepare some content for the tooltip
    */
   _buildTooltipContent: function(target)
   {
-    // If the hovered element is not a property view and is not a background
-    // image, then don't show a tooltip
-    let isPropertyValue = target.classList.contains("property-value");
-    if (!isPropertyValue) {
-      return false;
-    }
-    let propName = target.parentNode.querySelector(".property-name");
-    let isBackgroundImage = propName.textContent === "background-image";
-    if (!isBackgroundImage) {
-      return false;
+    // Test for image url
+    if (target.classList.contains("theme-link")) {
+      let propValue = target.parentNode;
+      let propName = propValue.parentNode.querySelector(".property-name");
+      if (propName.textContent === "background-image") {
+        this.tooltip.setCssBackgroundImageContent(propValue.textContent);
+        return true;
+      }
     }
 
-    // Fill some content
-    this.tooltip.setCssBackgroundImageContent(target.textContent);
-    return true;
+    // Test for css transform
+    if (target.classList.contains("property-value")) {
+      let def = promise.defer();
+      let propValue = target;
+      let propName = target.parentNode.querySelector(".property-name");
+      if (propName.textContent === "transform") {
+        this.tooltip.setCssTransformContent(propValue.textContent,
+          this.pageStyle, this.viewedElement).then(def.resolve);
+        return def.promise;
+      }
+    }
   },
 
   /**
    * Create a context menu.
    */
   _buildContextMenu: function()
   {
     let doc = this.styleDocument.defaultView.parent.document;
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1149,37 +1149,42 @@ CssRuleView.prototype = {
     popupset.appendChild(this._contextmenu);
   },
 
   /**
    * Verify that target is indeed a css value we want a tooltip on, and if yes
    * prepare some content for the tooltip
    */
   _buildTooltipContent: function(target) {
-    let isImageHref = target.classList.contains("theme-link") &&
-      target.parentNode.classList.contains("ruleview-propertyvalue");
+    let property = target.textProperty, def = promise.defer(), hasTooltip = false;
 
-    // If the inplace-editor is visible or if this is not a background image
-    // don't show the tooltip
-    if (!isImageHref) {
-      return false;
+    // Test for css transform
+    if (property && property.name === "transform") {
+      this.previewTooltip.setCssTransformContent(property.value, this.pageStyle,
+        this._viewedElement).then(def.resolve);
+      hasTooltip = true;
     }
 
-    // Retrieve the TextProperty for the hovered element
-    let property = target.parentNode.textProperty;
-    let href = property.rule.domRule.href;
+    // Test for image
+    let isImageHref = target.classList.contains("theme-link") &&
+      target.parentNode.classList.contains("ruleview-propertyvalue");
+    if (isImageHref) {
+      property = target.parentNode.textProperty;
+      this.previewTooltip.setCssBackgroundImageContent(property.value,
+        property.rule.domRule.href);
+      def.resolve();
+      hasTooltip = true;
+    }
 
-    // Fill some content
-    this.previewTooltip.setCssBackgroundImageContent(property.value, href);
+    if (hasTooltip) {
+      this.colorPicker.revert();
+      this.colorPicker.hide();
+    }
 
-    // Hide the color picker tooltip if shown and revert changes
-    this.colorPicker.revert();
-    this.colorPicker.hide();
-
-    return true;
+    return def.promise;
   },
 
   /**
    * Update the context menu. This means enabling or disabling menuitems as
    * appropriate.
    */
   _contextMenuUpdate: function() {
     let win = this.doc.defaultView;
@@ -1325,17 +1330,17 @@ CssRuleView.prototype = {
     }
 
     this.popup.destroy();
   },
 
   /**
    * Update the highlighted element.
    *
-   * @param {nsIDOMElement} aElement
+   * @param {NodeActor} aElement
    *        The node whose style rules we'll inspect.
    */
   highlight: function CssRuleView_highlight(aElement)
   {
     if (this._viewedElement === aElement) {
       return promise.resolve(undefined);
     }
 
@@ -1419,16 +1424,19 @@ CssRuleView.prototype = {
   /**
    * Clear the rule view.
    */
   clear: function CssRuleView_clear()
   {
     this._clearRules();
     this._viewedElement = null;
     this._elementStyle = null;
+
+    this.previewTooltip.hide();
+    this.colorPicker.hide();
   },
 
   /**
    * Called when the user has made changes to the ElementStyle.
    * Emits an event that clients can listen to.
    */
   _changed: function CssRuleView_changed()
   {
--- a/browser/devtools/styleinspector/test/browser.ini
+++ b/browser/devtools/styleinspector/test/browser.ini
@@ -48,16 +48,19 @@ support-files =
 [browser_bug894376_css_value_completion_existing_property_value_pair.js]
 [browser_ruleview_bug_902966_revert_value_on_ESC.js]
 [browser_ruleview_pseudoelement.js]
 support-files = browser_ruleview_pseudoelement.html
 [browser_computedview_bug835808_keyboard_nav.js]
 [browser_bug913014_matched_expand.js]
 [browser_bug765105_background_image_tooltip.js]
 [browser_bug889638_rule_view_color_picker.js]
+[browser_bug726427_csstransform_tooltip.js]
+
 [browser_bug940500_rule_view_pick_gradient_color.js]
 [browser_ruleview_original_source_link.js]
 support-files =
   sourcemaps.html
   sourcemaps.css
   sourcemaps.css.map
   sourcemaps.scss
 [browser_computedview_original_source_link.js]
+[browser_bug946331_close_tooltip_on_new_selection.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug726427_csstransform_tooltip.js
@@ -0,0 +1,206 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let contentDoc;
+let inspector;
+let ruleView;
+let computedView;
+
+const PAGE_CONTENT = [
+  '<style type="text/css">',
+  '  #testElement {',
+  '    width: 500px;',
+  '    height: 300px;',
+  '    background: red;',
+  '    transform: skew(16deg);',
+  '  }',
+  '  .test-element {',
+  '    transform-origin: top left;',
+  '    transform: rotate(45deg);',
+  '  }',
+  '  div {',
+  '    transform: scaleX(1.5);',
+  '    transform-origin: bottom right;',
+  '  }',
+  '  [attr] {',
+  '  }',
+  '</style>',
+  '<div id="testElement" class="test-element" attr="value">transformed element</div>'
+].join("\n");
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    contentDoc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,rule view css transform tooltip test";
+}
+
+function createDocument() {
+  contentDoc.body.innerHTML = PAGE_CONTENT;
+
+  openRuleView((aInspector, aRuleView) => {
+    inspector = aInspector;
+    ruleView = aRuleView;
+    startTests();
+  });
+}
+
+function startTests() {
+  inspector.selection.setNode(contentDoc.querySelector("#testElement"));
+  inspector.once("inspector-updated", testTransformTooltipOnIDSelector);
+}
+
+function endTests() {
+  contentDoc = inspector = ruleView = computedView = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function testTransformTooltipOnIDSelector() {
+  info("Testing that a transform tooltip appears on the #ID rule");
+
+  let panel = ruleView.previewTooltip.panel;
+  ok(panel, "The XUL panel exists for the rule-view preview tooltips");
+
+  let {valueSpan} = getRuleViewProperty("#testElement", "transform");
+  assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
+    // The transform preview is canvas, so there's not much we can test, so for
+    // now, let's just be happy with the fact that the tooltips is shown!
+    ok(true, "Tooltip shown on the transform property of the #ID rule");
+    ruleView.previewTooltip.hide();
+    executeSoon(testTransformTooltipOnClassSelector);
+  });
+}
+
+function testTransformTooltipOnClassSelector() {
+  info("Testing that a transform tooltip appears on the .class rule");
+
+  let {valueSpan} = getRuleViewProperty(".test-element", "transform");
+  assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
+    // The transform preview is canvas, so there's not much we can test, so for
+    // now, let's just be happy with the fact that the tooltips is shown!
+    ok(true, "Tooltip shown on the transform property of the .class rule");
+    ruleView.previewTooltip.hide();
+    executeSoon(testTransformTooltipOnTagSelector);
+  });
+}
+
+function testTransformTooltipOnTagSelector() {
+  info("Testing that a transform tooltip appears on the tag rule");
+
+  let {valueSpan} = getRuleViewProperty("div", "transform");
+  assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
+    // The transform preview is canvas, so there's not much we can test, so for
+    // now, let's just be happy with the fact that the tooltips is shown!
+    ok(true, "Tooltip shown on the transform property of the tag rule");
+    ruleView.previewTooltip.hide();
+    executeSoon(testTransformTooltipNotShownOnInvalidTransform);
+  });
+}
+
+function testTransformTooltipNotShownOnInvalidTransform() {
+  info("Testing that a transform tooltip does not appear for invalid values");
+
+  let ruleEditor;
+  for (let rule of ruleView._elementStyle.rules) {
+    if (rule.matchedSelectors[0] === "[attr]") {
+      ruleEditor = rule.editor;
+    }
+  }
+  ruleEditor.addProperty("transform", "muchTransform(suchAngle)", "");
+
+  let {valueSpan} = getRuleViewProperty("[attr]", "transform");
+  assertTooltipNotShownOn(ruleView.previewTooltip, valueSpan, () => {
+    executeSoon(testTransformTooltipOnComputedView);
+  });
+}
+
+function testTransformTooltipOnComputedView() {
+  info("Testing that a transform tooltip appears in the computed view too");
+
+  inspector.sidebar.select("computedview");
+  computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
+  let doc = computedView.styleDocument;
+
+  let panel = computedView.tooltip.panel;
+  let {valueSpan} = getComputedViewProperty("transform");
+
+  assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
+    // The transform preview is canvas, so there's not much we can test, so for
+    // now, let's just be happy with the fact that the tooltips is shown!
+    ok(true, "Tooltip shown on the computed transform property");
+    computedView.tooltip.hide();
+    executeSoon(endTests);
+  });
+}
+
+function assertTooltipShownOn(tooltip, element, cb) {
+  // If there is indeed a show-on-hover on element, the xul panel will be shown
+  tooltip.panel.addEventListener("popupshown", function shown() {
+    tooltip.panel.removeEventListener("popupshown", shown, true);
+    cb();
+  }, true);
+  tooltip._showOnHover(element);
+}
+
+function assertTooltipNotShownOn(tooltip, element, cb) {
+  // The only way to make sure the tooltip is not shown is try and show it, wait
+  // for a given amount of time, and then check if it's shown or not
+  tooltip._showOnHover(element);
+  setTimeout(() => {
+    ok(!tooltip.isShown(), "The tooltip did not appear on hover of the element");
+    cb();
+  }, tooltip.defaultShowDelay + 100);
+}
+
+function getRule(selectorText) {
+  let rule;
+
+  [].forEach.call(ruleView.doc.querySelectorAll(".ruleview-rule"), aRule => {
+    let selector = aRule.querySelector(".ruleview-selector-matched");
+    if (selector && selector.textContent === selectorText) {
+      rule = aRule;
+    }
+  });
+
+  return rule;
+}
+
+function getRuleViewProperty(selectorText, propertyName) {
+  let prop;
+
+  let rule = getRule(selectorText);
+  if (rule) {
+    // Look for the propertyName in that rule element
+    [].forEach.call(rule.querySelectorAll(".ruleview-property"), property => {
+      let nameSpan = property.querySelector(".ruleview-propertyname");
+      let valueSpan = property.querySelector(".ruleview-propertyvalue");
+
+      if (nameSpan.textContent === propertyName) {
+        prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+      }
+    });
+  }
+
+  return prop;
+}
+
+function getComputedViewProperty(name) {
+  let prop;
+  [].forEach.call(computedView.styleDocument.querySelectorAll(".property-view"), property => {
+    let nameSpan = property.querySelector(".property-name");
+    let valueSpan = property.querySelector(".property-value");
+
+    if (nameSpan.textContent === name) {
+      prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+    }
+  });
+  return prop;
+}
--- a/browser/devtools/styleinspector/test/browser_bug765105_background_image_tooltip.js
+++ b/browser/devtools/styleinspector/test/browser_bug765105_background_image_tooltip.js
@@ -137,18 +137,19 @@ function testComputedView() {
   info("Testing tooltips in the computed view");
 
   inspector.sidebar.select("computedview");
   computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
   let doc = computedView.styleDocument;
 
   let panel = computedView.tooltip.panel;
   let {valueSpan} = getComputedViewProperty("background-image");
+  let uriSpan = valueSpan.querySelector(".theme-link");
 
-  assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
+  assertTooltipShownOn(computedView.tooltip, uriSpan, () => {
     let images = panel.getElementsByTagName("image");
     is(images.length, 1, "Tooltip contains an image");
     ok(images[0].src === "chrome://global/skin/icons/warning-64.png");
 
     computedView.tooltip.hide();
 
     endTests();
   });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug946331_close_tooltip_on_new_selection.js
@@ -0,0 +1,82 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let contentDoc;
+let inspector;
+let ruleView;
+let computedView;
+
+const PAGE_CONTENT = '<div class="one">el 1</div><div class="two">el 2</div>';
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    contentDoc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,rule/computed views tooltip hiding test";
+}
+
+function createDocument() {
+  contentDoc.body.innerHTML = PAGE_CONTENT;
+
+  openRuleView((aInspector, aRuleView) => {
+    inspector = aInspector;
+    ruleView = aRuleView;
+    inspector.sidebar.once("computedview-ready", () => {
+      computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
+      startTests();
+    });
+  });
+}
+
+function startTests() {
+  inspector.selection.setNode(contentDoc.querySelector(".one"));
+  inspector.once("inspector-updated", testRuleView);
+}
+
+function endTests() {
+  contentDoc = inspector = ruleView = computedView = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function testRuleView() {
+  info("Testing rule view tooltip closes on new selection");
+
+  // Show the rule view tooltip
+  let tooltip = ruleView.previewTooltip;
+  tooltip.show();
+  tooltip.once("shown", () => {
+    // Select a new node and assert that the tooltip closes
+    tooltip.once("hidden", () => {
+      ok(true, "Rule view tooltip closed after a new node got selected");
+      inspector.once("inspector-updated", testComputedView);
+    });
+    inspector.selection.setNode(contentDoc.querySelector(".two"));
+  });
+}
+
+function testComputedView() {
+  info("Testing computed view tooltip closes on new selection");
+
+  inspector.sidebar.select("computedview");
+  computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
+
+  // Show the computed view tooltip
+  let tooltip = computedView.tooltip;
+  tooltip.show();
+  tooltip.once("shown", () => {
+    // Select a new node and assert that the tooltip closes
+    tooltip.once("hidden", () => {
+      ok(true, "Computed view tooltip closed after a new node got selected");
+      inspector.once("inspector-updated", endTests);
+    });
+    inspector.selection.setNode(contentDoc.querySelector(".one"));
+  });
+}
--- a/browser/locales/en-US/chrome/browser/aboutDialog.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutDialog.dtd
@@ -1,13 +1,24 @@
 <!-- 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 aboutDialog.title          "About &brandFullName;">
 
+<!-- LOCALIZATION NOTE update.applyButton.*, update.upgradeButton.*):
+# Only one button is present at a time.
+# The button when displayed is located directly under the Firefox version in
+# the about dialog (see bug 596813 for screenshots).
+-->
+<!ENTITY update.updateButton.label                "Restart to Update">
+<!ENTITY update.updateButton.accesskey            "R">
+<!ENTITY update.applyButtonBillboard.label        "Apply Update…">
+<!ENTITY update.applyButtonBillboard.accesskey    "A">
+
+
 <!-- LOCALIZATION NOTE (warningDesc.version): This is a warning about the experimental nature of Nightly and Aurora builds. It is only shown in those versions. -->
 <!ENTITY warningDesc.version        "&brandShortName; is experimental and may be unstable.">
 <!-- LOCALIZATION NOTE (warningDesc.telemetryDesc): This is a notification that Nightly/Aurora builds automatically send Telemetry data back to Mozilla. It is only shown in those versions. "It" refers to brandShortName. -->
 <!ENTITY warningDesc.telemetryDesc  "It automatically sends information about performance, hardware, usage and customizations back to &vendorShortName; to help make &brandShortName; better.">
 
 <!-- LOCALIZATION NOTE (community.exp.*) This paragraph is shown in "experimental" builds, i.e. Nightly and Aurora builds, instead of the other "community.*" strings below. -->
 <!ENTITY community.exp.start        "">
 <!-- LOCALIZATION NOTE (community.exp.mozillaLink): This is a link title that links to http://www.mozilla.org/. -->
@@ -36,18 +47,16 @@
 <!-- LOCALIZATION NOTE (bottomLinks.rights): This is a link title that links to about:rights. -->
 <!ENTITY bottomLinks.rights         "End-User Rights">
 
 <!-- LOCALIZATION NOTE (bottomLinks.privacy): This is a link title that links to https://www.mozilla.org/legal/privacy/. -->
 <!ENTITY bottomLinks.privacy        "Privacy Policy">
 
 <!-- LOCALIZATION NOTE (update.checkingForUpdates): try to make the localized text short (see bug 596813 for screenshots). -->
 <!ENTITY update.checkingForUpdates  "Checking for updates…">
-<!-- LOCALIZATION NOTE (update.checkingAddonCompat): try to make the localized text short (see bug 596813 for screenshots). -->
-<!ENTITY update.checkingAddonCompat "Checking Add-on compatibility…">
 <!-- LOCALIZATION NOTE (update.noUpdatesFound): try to make the localized text short (see bug 596813 for screenshots). -->
 <!ENTITY update.noUpdatesFound      "&brandShortName; is up to date">
 <!-- LOCALIZATION NOTE (update.adminDisabled): try to make the localized text short (see bug 596813 for screenshots). -->
 <!ENTITY update.adminDisabled       "Updates disabled by your system administrator">
 <!-- LOCALIZATION NOTE (update.otherInstanceHandlingUpdates): try to make the localized text short -->
 <!ENTITY update.otherInstanceHandlingUpdates "&brandShortName; is being updated by another instance">
 
 <!-- LOCALIZATION NOTE (update.failed.start,update.failed.linkText,update.failed.end):
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -186,34 +186,20 @@ sanitizeButtonClearing=Clearing
 # "Time range to clear" is set to "Everything" in Clear Recent History dialog,
 # provided that the user has not modified the default set of history items to clear.
 sanitizeEverythingWarning2=All history will be cleared.
 # LOCALIZATION NOTE (sanitizeSelectedWarning): Warning that appears when
 # "Time range to clear" is set to "Everything" in Clear Recent History dialog,
 # provided that the user has modified the default set of history items to clear.
 sanitizeSelectedWarning=All selected items will be cleared.
 
-# Check for Updates in the About Dialog - button labels and accesskeys
-# LOCALIZATION NOTE - all of the following update buttons labels will only be
-# displayed one at a time. So, if a button is displayed nothing else will
-# be displayed alongside of the button. The button when displayed is located
-# directly under the Firefox version in the about dialog (see bug 596813 for
-# screenshots).
-update.checkInsideButton.label=Check for Updates
-update.checkInsideButton.accesskey=C
-update.resumeButton.label=Resume Downloading %S…
-update.resumeButton.accesskey=D
-update.openUpdateUI.applyButton.label=Apply Update…
-update.openUpdateUI.applyButton.accesskey=A
-update.restart.updateButton.label=Restart to Update
-update.restart.updateButton.accesskey=R
-update.openUpdateUI.upgradeButton.label=Upgrade Now…
-update.openUpdateUI.upgradeButton.accesskey=U
-update.restart.upgradeButton.label=Upgrade Now
-update.restart.upgradeButton.accesskey=U
+# LOCALIZATION NOTE (downloadAndInstallButton.label): %S is replaced by the
+# version of the update: "Update to 28.0".
+update.downloadAndInstallButton.label=Update to %S
+update.downloadAndInstallButton.accesskey=U
 
 # RSS Pretty Print
 feedShowFeedNew=Subscribe to '%S'…
 
 menuOpenAllInTabs.label=Open All in Tabs
 
 # History menu
 menuRestoreAllTabs.label=Restore All Tabs
--- a/browser/metro/base/content/startui/Start.xul
+++ b/browser/metro/base/content/startui/Start.xul
@@ -95,9 +95,10 @@
           <richgriditem/>
           <richgriditem/>
         </richgrid>
 
       </vbox>
 #endif
   </hbox>
   </html:body>
+  <observes element="bcast_windowState" attribute="*"/>
 </html:html>
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -196,16 +196,18 @@ pref("signon.rememberSignons", true);
 // this will automatically enable inline spellchecking (if it is available) for
 // editable elements in HTML
 // 0 = spellcheck nothing
 // 1 = check multi-line controls [default]
 // 2 = check multi/single line controls
 pref("layout.spellcheckDefault", 1);
 
 /* extension manager and xpinstall */
+// Completely disable extensions
+pref("extensions.defaultProviders.enabled", false);
 // Disable all add-on locations other than the profile
 pref("extensions.enabledScopes", 1);
 // Auto-disable any add-ons that are "dropped in" to the profile
 pref("extensions.autoDisableScopes", 1);
 // Disable add-on installation via the web-exposed APIs
 pref("xpinstall.enabled", false);
 pref("xpinstall.whitelist.add", "addons.mozilla.org");
 pref("extensions.autoupdate.enabled", false);
--- a/browser/metro/shell/commandexecutehandler/Makefile.in
+++ b/browser/metro/shell/commandexecutehandler/Makefile.in
@@ -1,14 +1,12 @@
 # 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/.
 
-NO_PROFILE_GUIDED_OPTIMIZE = 1
-
 include $(topsrcdir)/config/config.mk
 
 DIST_PROGRAM = CommandExecuteHandler$(BIN_SUFFIX)
 
 # Don't link against mozglue.dll
 MOZ_GLUE_LDFLAGS =
 MOZ_GLUE_PROGRAM_LDFLAGS =
 
--- a/browser/metro/shell/commandexecutehandler/moz.build
+++ b/browser/metro/shell/commandexecutehandler/moz.build
@@ -11,8 +11,10 @@ SOURCES += [
     'CommandExecuteHandler.cpp',
 ]
 
 # We want this exe in dist/bin
 DIST_SUBDIR = ''
 
 for var in ('UNICODE', '_UNICODE', 'NS_NO_XPCOM'):
     DEFINES[var] = True
+
+NO_PGO = True
--- a/browser/metro/shell/linktool/Makefile.in
+++ b/browser/metro/shell/linktool/Makefile.in
@@ -1,14 +1,12 @@
 # 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/.
 
-NO_PROFILE_GUIDED_OPTIMIZE = 1
-
 include $(topsrcdir)/config/config.mk
 
 OS_LIBS = \
 	kernel32.lib \
 	user32.lib \
 	ole32.lib \
 	shlwapi.lib \
 	shell32.lib \
--- a/browser/metro/shell/linktool/moz.build
+++ b/browser/metro/shell/linktool/moz.build
@@ -9,8 +9,10 @@ PROGRAM = 'linktool'
 SOURCES += [
     'linktool.cpp',
 ]
 
 DIST_SUBDIR = 'metro/install'
 
 for var in ('UNICODE', '_UNICODE'):
     DEFINES[var] = True
+
+NO_PGO = True
--- a/browser/metro/shell/testing/Makefile.in
+++ b/browser/metro/shell/testing/Makefile.in
@@ -4,18 +4,16 @@
 
 # static win runtime linking
 USE_STATIC_LIBS = 1
 
 # don't use moz glue libs
 MOZ_GLUE_LDFLAGS =
 MOZ_GLUE_PROGRAM_LDFLAGS =
 
-NO_PROFILE_GUIDED_OPTIMIZE = 1
-
 include $(topsrcdir)/config/config.mk
 
 OS_LIBS = \
 	kernel32.lib \
 	user32.lib \
 	ole32.lib \
 	shlwapi.lib \
 	propsys.lib \
--- a/browser/metro/shell/testing/moz.build
+++ b/browser/metro/shell/testing/moz.build
@@ -10,8 +10,10 @@ SOURCES += [
     'metrotestharness.cpp',
 ]
 
 # We want this exe in dist/bin
 DIST_SUBDIR = ''
 
 for var in ('UNICODE', '_UNICODE'):
     DEFINES[var] = True
+
+NO_PGO = True
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -192,16 +192,22 @@ documenttab[selected] .documenttab-selec
 }
 
 #startui-page {
   overflow-x: scroll;
   overflow-y: hidden;
   height: 100%;
 }
 
+#startui-page[viewstate="snapped"],
+#startui-page[viewstate="portrait"] {
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+
 #startui-body {
   height: 100%;
   margin: 0;
 }
 
 #start-container {
   height: 100%;
 }
--- a/build/ConfigStatus.py
+++ b/build/ConfigStatus.py
@@ -59,17 +59,19 @@ def config_status(topobjdir='.', topsrcd
 
     parser = OptionParser()
     parser.add_option('--recheck', dest='recheck', action='store_true',
                       help='update config.status by reconfiguring in the same conditions')
     parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
                       help='display verbose output')
     parser.add_option('-n', dest='not_topobjdir', action='store_true',
                       help='do not consider current directory as top object directory')
-    (options, args) = parser.parse_args()
+    parser.add_option('-d', '--diff', action='store_true',
+                      help='print diffs of changed files.')
+    options, args = parser.parse_args()
 
     # Without -n, the current directory is meant to be the top object directory
     if not options.not_topobjdir:
         topobjdir = os.path.abspath('.')
 
     env = ConfigEnvironment(topsrcdir, topobjdir, defines=defines,
             non_global_defines=non_global_defines, substs=substs)
 
@@ -93,8 +95,12 @@ def config_status(topobjdir='.', topsrcd
     log_manager.add_terminal_logging(level=log_level)
     log_manager.enable_unstructured()
 
     print('Reticulating splines...', file=sys.stderr)
     summary = backend.consume(definitions)
 
     for line in summary.summaries():
         print(line, file=sys.stderr)
+
+    if options.diff:
+        for path, diff in sorted(summary.file_diffs.items()):
+            print(diff)
deleted file mode 100644
--- a/build/docs/Vagrantfile
+++ /dev/null
@@ -1,13 +0,0 @@
-# -*- mode: ruby -*-
-# vi: set ft=ruby :
-
-# We intentionally use the old config format because Mozilla's Jenkins
-# server doesn't run a modern Vagrant.
-Vagrant::Config.run do |config|
-  config.vm.box = "precise64"
-  config.vm.box_url = "http://files.vagrantup.com/precise64.box"
-  config.vm.share_folder("gecko", "/gecko", "../..")
-  # Doxygen needs more than the default memory or it will swap and be
-  # extremely slow.
-  config.vm.customize ["modifyvm", :id, "--memory", 2048]
-end
deleted file mode 100644
--- a/build/docs/conf.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# 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/.
-
-from __future__ import unicode_literals
-
-import os
-import re
-
-from datetime import datetime
-
-
-here = os.path.abspath(os.path.dirname(__file__))
-mozilla_dir = os.path.normpath(os.path.join(here, '..', '..'))
-
-import mdn_theme
-
-extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.graphviz',
-    'sphinx.ext.todo',
-    'mozbuild.sphinx',
-]
-
-templates_path = ['_templates']
-source_suffix = '.rst'
-master_doc = 'index'
-project = u'Mozilla Build System'
-year = datetime.now().year
-
-# Grab the version from the source tree's milestone.
-with open(os.path.join(mozilla_dir, 'config', 'milestone.txt'), 'rt') as fh:
-    for line in fh:
-        line = line.strip()
-
-        if not line or line.startswith('#'):
-            continue
-
-        release = line
-        break
-
-version = re.sub(r'[ab]\d+$', '', release)
-
-exclude_patterns = ['_build']
-pygments_style = 'sphinx'
-
-html_theme_path = [mdn_theme.get_theme_dir()]
-html_theme = 'mdn'
-
-html_static_path = ['_static']
-htmlhelp_basename = 'MozillaBuildSystemdoc'
--- a/build/docs/index.rst
+++ b/build/docs/index.rst
@@ -1,25 +1,18 @@
-==================================
-Mozilla Build System Documentation
-==================================
-
-Overview
-========
-
-.. toctree::
-   :maxdepth: 1
-
-   glossary
+============
+Build System
+============
 
 Important Concepts
 ==================
 .. toctree::
    :maxdepth: 1
 
+   glossary
    build-overview
    supported-configurations
    Mozconfig Files <mozconfigs>
    mozbuild-files
    mozbuild-symbols
    Profile Guided Optimization <pgo>
    slow
    environment-variables
@@ -36,29 +29,8 @@ mozbuild
 mozbuild is a Python package containing a lot of the code for the
 Mozilla build system.
 
 .. toctree::
    :maxdepth: 1
 
    mozbuild/index
    mozbuild/dumbmake
-
-Python Packages
-===============
-
-.. toctree::
-   :maxdepth: 2
-
-   python/codegen
-   python/makeutils
-   python/mozbuild
-   python/mozpack
-   python/mozversioncontrol
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
deleted file mode 100644
--- a/build/docs/python/makeutils.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-makeutils Module
-================
-
-.. automodule:: makeutils
-    :members:
-    :undoc-members:
-    :show-inheritance:
deleted file mode 100644
--- a/build/docs/python/mozbuild.action.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-action Package
-==============
-
-:mod:`link_deps` Module
------------------------
-
-.. automodule:: mozbuild.action.link_deps
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`process_install_manifest` Module
---------------------------------------
-
-.. automodule:: mozbuild.action.process_install_manifest
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`xpccheck` Module
-----------------------
-
-.. automodule:: mozbuild.action.xpccheck
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`xpidl-process` Module
----------------------------
-
-.. automodule:: mozbuild.action.xpidl-process
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
deleted file mode 100644
--- a/build/docs/python/mozbuild.backend.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-backend Package
-===============
-
-:mod:`base` Module
-------------------
-
-.. automodule:: mozbuild.backend.base
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`common` Module
---------------------
-
-.. automodule:: mozbuild.backend.common
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`configenvironment` Module
--------------------------------
-
-.. automodule:: mozbuild.backend.configenvironment
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`recursivemake` Module
----------------------------
-
-.. automodule:: mozbuild.backend.recursivemake
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
deleted file mode 100644
--- a/build/docs/python/mozbuild.compilation.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-compilation Package
-===================
-
-:mod:`warnings` Module
-----------------------
-
-.. automodule:: mozbuild.compilation.warnings
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
deleted file mode 100644
--- a/build/docs/python/mozbuild.controller.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-controller Package
-==================
-
-:mod:`building` Module
-----------------------
-
-.. automodule:: mozbuild.controller.building
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`clobber` Module
----------------------
-
-.. automodule:: mozbuild.controller.clobber
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
deleted file mode 100644
--- a/build/docs/python/mozbuild.frontend.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-frontend Package
-================
-
-:mod:`data` Module
-------------------
-
-.. automodule:: mozbuild.frontend.data
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`emitter` Module
----------------------
-
-.. automodule:: mozbuild.frontend.emitter
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`mach_commands` Module
----------------------------
-
-.. automodule:: mozbuild.frontend.mach_commands
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`reader` Module
---------------------
-
-.. automodule:: mozbuild.frontend.reader
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`sandbox` Module
----------------------
-
-.. automodule:: mozbuild.frontend.sandbox
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`sandbox_symbols` Module
------------------------------
-
-.. automodule:: mozbuild.frontend.sandbox_symbols
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
deleted file mode 100644
--- a/build/docs/python/mozbuild.rst
+++ /dev/null
@@ -1,103 +0,0 @@
-mozbuild Package
-================
-
-:mod:`base` Module
-------------------
-
-.. automodule:: mozbuild.base
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`config` Module
---------------------
-
-.. automodule:: mozbuild.config
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`html_build_viewer` Module
--------------------------------
-
-.. automodule:: mozbuild.html_build_viewer
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`mach_commands` Module
----------------------------
-
-.. automodule:: mozbuild.mach_commands
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`makeutil` Module
-----------------------
-
-.. automodule:: mozbuild.makeutil
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`mozconfig` Module
------------------------
-
-.. automodule:: mozbuild.mozconfig
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`mozinfo` Module
----------------------
-
-.. automodule:: mozbuild.mozinfo
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`pythonutil` Module
-------------------------
-
-.. automodule:: mozbuild.pythonutil
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`sphinx` Module
---------------------
-
-.. automodule:: mozbuild.sphinx
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`util` Module
-------------------
-
-.. automodule:: mozbuild.util
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`virtualenv` Module
-------------------------
-
-.. automodule:: mozbuild.virtualenv
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-Subpackages
------------
-
-.. toctree::
-
-    mozbuild.action
-    mozbuild.backend
-    mozbuild.compilation
-    mozbuild.controller
-    mozbuild.frontend
-    mozbuild.test
-
deleted file mode 100644
--- a/build/docs/python/mozpack.chrome.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-chrome Package
-==============
-
-:mod:`flags` Module
--------------------
-
-.. automodule:: mozpack.chrome.flags
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`manifest` Module
-----------------------
-
-.. automodule:: mozpack.chrome.manifest
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
deleted file mode 100644
--- a/build/docs/python/mozpack.packager.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-packager Package
-================
-
-:mod:`packager` Package
------------------------
-
-.. automodule:: mozpack.packager
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`formats` Module
----------------------
-
-.. automodule:: mozpack.packager.formats
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`l10n` Module
-------------------
-
-.. automodule:: mozpack.packager.l10n
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`unpack` Module
---------------------
-
-.. automodule:: mozpack.packager.unpack
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
deleted file mode 100644
--- a/build/docs/python/mozpack.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-mozpack Package
-===============
-
-:mod:`copier` Module
---------------------
-
-.. automodule:: mozpack.copier
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`errors` Module
---------------------
-
-.. automodule:: mozpack.errors
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`executables` Module
--------------------------
-
-.. automodule:: mozpack.executables
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`files` Module
--------------------
-
-.. automodule:: mozpack.files
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`manifests` Module
------------------------
-
-.. automodule:: mozpack.manifests
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`mozjar` Module
---------------------
-
-.. automodule:: mozpack.mozjar
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`path` Module
-------------------
-
-.. automodule:: mozpack.path
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`unify` Module
--------------------
-
-.. automodule:: mozpack.unify
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-Subpackages
------------
-
-.. toctree::
-
-    mozpack.chrome
-    mozpack.packager
-    mozpack.test
-
deleted file mode 100644
--- a/build/docs/python/mozversioncontrol.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-mozversioncontrol Package
-=========================
-
-:mod:`repoupdate` Module
-------------------------
-
-.. automodule:: mozversioncontrol.repoupdate
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -70,16 +70,17 @@ MACH_MODULES = [
     'python/mozbuild/mozbuild/mach_commands.py',
     'python/mozbuild/mozbuild/frontend/mach_commands.py',
     'testing/mach_commands.py',
     'testing/marionette/mach_commands.py',
     'testing/mochitest/mach_commands.py',
     'testing/xpcshell/mach_commands.py',
     'testing/talos/mach_commands.py',
     'testing/xpcshell/mach_commands.py',
+    'tools/docs/mach_commands.py',
     'tools/mercurial/mach_commands.py',
     'tools/mach_commands.py',
 ]
 
 
 CATEGORIES = {
     'build': {
         'short': 'Build Commands',
--- a/build/mobile/robocop/moz.build
+++ b/build/mobile/robocop/moz.build
@@ -1,11 +1,7 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-ANDROID_RESFILES = [
-    'res/values/strings.xml',
-]
-
 DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
--- a/build/mobile/sutagent/android/fencp/moz.build
+++ b/build/mobile/sutagent/android/fencp/moz.build
@@ -1,13 +1,5 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
-
-ANDROID_RESFILES = [
-    'res/drawable-hdpi/icon.png',
-    'res/drawable-ldpi/icon.png',
-    'res/drawable-mdpi/icon.png',
-    'res/layout/main.xml',
-    'res/values/strings.xml',
-]
--- a/build/mobile/sutagent/android/ffxcp/moz.build
+++ b/build/mobile/sutagent/android/ffxcp/moz.build
@@ -1,13 +1,5 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
-
-ANDROID_RESFILES = [
-    'res/drawable-hdpi/icon.png',
-    'res/drawable-ldpi/icon.png',
-    'res/drawable-mdpi/icon.png',
-    'res/layout/main.xml',
-    'res/values/strings.xml',
-]
--- a/build/mobile/sutagent/android/moz.build
+++ b/build/mobile/sutagent/android/moz.build
@@ -1,15 +1,5 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
-
-ANDROID_RESFILES = [
-    'res/drawable/ateamlogo.png',
-    'res/drawable/ic_stat_first.png',
-    'res/drawable/ic_stat_neterror.png',
-    'res/drawable/ic_stat_warning.png',
-    'res/drawable/icon.png',
-    'res/layout/main.xml',
-    'res/values/strings.xml',
-]
--- a/build/mobile/sutagent/android/watcher/moz.build
+++ b/build/mobile/sutagent/android/watcher/moz.build
@@ -1,16 +1,5 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
-
-ANDROID_RESFILES = [
-    'res/drawable-hdpi/ateamlogo.png',
-    'res/drawable-hdpi/icon.png',
-    'res/drawable-ldpi/ateamlogo.png',
-    'res/drawable-ldpi/icon.png',
-    'res/drawable-mdpi/ateamlogo.png',
-    'res/drawable-mdpi/icon.png',
-    'res/layout/main.xml',
-    'res/values/strings.xml',
-]
--- a/build/moz.build
+++ b/build/moz.build
@@ -1,14 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+SPHINX_TREES['build'] = 'docs'
+
 if CONFIG['OS_ARCH'] not in ('WINNT', 'OS2'):
     DIRS += ['unix']
 elif CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['win32']
 
 if CONFIG['OS_TARGET'] == 'Android' and not CONFIG['MOZ_ANDROID_LIBSTDCXX']:
     DIRS += ['stlport']
 
--- a/build/unix/elfhack/Makefile.in
+++ b/build/unix/elfhack/Makefile.in
@@ -1,17 +1,15 @@
 #
 # 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/.
 
 INTERNAL_TOOLS = 1
 
-NO_PROFILE_GUIDED_OPTIMIZE = 1
-
 VPATH += $(topsrcdir)/build
 
 OS_CXXFLAGS := $(filter-out -fno-exceptions,$(OS_CXXFLAGS)) -fexceptions
 
 WRAP_LDFLAGS=
 
 include $(topsrcdir)/config/rules.mk
 
--- a/build/unix/elfhack/inject/Makefile.in
+++ b/build/unix/elfhack/inject/Makefile.in
@@ -1,15 +1,14 @@
 #
 # 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/.
 
 INTERNAL_TOOLS = 1
-NO_PROFILE_GUIDED_OPTIMIZE = 1
 
 include $(topsrcdir)/config/rules.mk
 
 export:: $(CSRCS:.c=.$(OBJ_SUFFIX))
 
 $(CSRCS): %.c: ../inject.c
 	cp $< $@
 
--- a/build/unix/elfhack/inject/moz.build
+++ b/build/unix/elfhack/inject/moz.build
@@ -13,8 +13,10 @@ elif CONFIG['TARGET_CPU'].startswith('ar
 else:
     cpu = CONFIG['TARGET_CPU']
 
 GENERATED_SOURCES += [
     "%s.c" % cpu,
 ]
 
 DEFINES['ELFHACK_BUILD'] = True
+
+NO_PGO = True
--- a/build/unix/elfhack/moz.build
+++ b/build/unix/elfhack/moz.build
@@ -20,8 +20,10 @@ if not CONFIG['CROSS_COMPILE']:
 HOST_SOURCES += [
     'elf.cpp',
     'elfhack.cpp',
 ]
 
 HOST_PROGRAM = 'elfhack'
 
 DEFINES['ELFHACK_BUILD'] = True
+
+NO_PGO = True
--- a/build/unix/stdc++compat/Makefile.in
+++ b/build/unix/stdc++compat/Makefile.in
@@ -1,12 +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/.
 
 STL_FLAGS =
 NO_EXPAND_LIBS = 1
-NO_PROFILE_GUIDED_OPTIMIZE = 1
 
 include $(topsrcdir)/config/rules.mk
 
 CXXFLAGS += -DMOZ_LIBSTDCXX_VERSION=$(MOZ_LIBSTDCXX_TARGET_VERSION)
 HOST_CXXFLAGS += -DMOZ_LIBSTDCXX_VERSION=$(MOZ_LIBSTDCXX_HOST_VERSION)
--- a/build/unix/stdc++compat/moz.build
+++ b/build/unix/stdc++compat/moz.build
@@ -10,8 +10,10 @@ if CONFIG['MOZ_LIBSTDCXX_TARGET_VERSION'
 
 if CONFIG['MOZ_LIBSTDCXX_HOST_VERSION']:
     HOST_LIBRARY_NAME = 'host_stdc++compat'
     HOST_SOURCES += [
         'stdc++compat.cpp',
     ]
 
 FORCE_STATIC_LIB = True
+
+NO_PGO = True
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -8,11 +8,12 @@ optional:setup.py:python/psutil:build_ex
 optional:psutil.pth:python/psutil
 which.pth:python/which
 ply.pth:other-licenses/ply/
 codegen.pth:python/codegen/
 mock.pth:python/mock-1.0.0
 mozilla.pth:build
 mozilla.pth:config
 mozilla.pth:xpcom/typelib/xpt/tools
+moztreedocs.pth:tools/docs
 copy:build/buildconfig.py
 packages.txt:testing/mozbase/packages.txt
 objdir:build
--- a/build/win32/Makefile.in
+++ b/build/win32/Makefile.in
@@ -1,14 +1,12 @@
 # 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/.
 
-NO_PROFILE_GUIDED_OPTIMIZE = 1
-
 ifdef ENABLE_TESTS
 
 USE_STATIC_LIBS = 1
 
 endif # ENABLE_TESTS
 
 MOZ_GLUE_LDFLAGS =
 
--- a/build/win32/moz.build
+++ b/build/win32/moz.build
@@ -9,8 +9,10 @@ if CONFIG['_MSC_VER'] and CONFIG['OS_TES
 
 TEST_DIRS += ['crashinjectdll']
 
 if CONFIG['ENABLE_TESTS']:
     PROGRAM = 'crashinject'
     SOURCES += [
         'crashinject.cpp',
     ]
+
+NO_PGO = True
--- a/config/config.mk
+++ b/config/config.mk
@@ -30,17 +30,17 @@ endif
 -include $(DEPTH)/.mozconfig.mk
 
 # Integrate with mozbuild-generated make files. We first verify that no
 # variables provided by the automatically generated .mk files are
 # present. If they are, this is a violation of the separation of
 # responsibility between Makefile.in and mozbuild files.
 _MOZBUILD_EXTERNAL_VARIABLES := \
   ANDROID_GENERATED_RESFILES \
-  ANDROID_RESFILES \
+  ANDROID_RES_DIRS \
   CMSRCS \
   CMMSRCS \
   CPP_UNIT_TESTS \
   DIRS \
   EXTRA_PP_COMPONENTS \
   EXTRA_PP_JS_MODULES \
   FORCE_SHARED_LIB \
   FORCE_STATIC_LIB \
@@ -65,16 +65,17 @@ endif
   TEST_DIRS \
   TIERS \
   TOOL_DIRS \
   XPCSHELL_TESTS \
   XPIDL_MODULE \
   $(NULL)
 
 _DEPRECATED_VARIABLES := \
+  ANDROID_RESFILES \
   MOCHITEST_FILES_PARTS \
   MOCHITEST_BROWSER_FILES_PARTS \
   SHORT_LIBNAME \
   $(NULL)
 
 ifndef EXTERNALLY_MANAGED_MAKE_FILE
 # Using $(firstword) may not be perfect. But it should be good enough for most
 # scenarios.
--- a/config/makefiles/java-build.mk
+++ b/config/makefiles/java-build.mk
@@ -2,49 +2,27 @@
 # vim:set ts=8 sw=8 sts=8 noet:
 #
 # 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/.
 
 ifndef INCLUDED_JAVA_BUILD_MK #{
 
-ifdef ANDROID_RESFILES #{
-ifndef IGNORE_ANDROID_RESFILES #{
-res-dep := .deps-copy-java-res
-
-GENERATED_DIRS += res
-GARBAGE        += $(res-dep)
-
-export:: $(res-dep)
-
-res-dep-preqs := \
-  $(addprefix $(srcdir)/,$(ANDROID_RESFILES)) \
-  $(call mkdir_deps,res) \
-  $(if $(IS_LANGUAGE_REPACK),FORCE) \
-  $(NULL)
-
-# nop-build: only copy res/ files when needed
-$(res-dep): $(res-dep-preqs)
-	$(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
-	@$(TOUCH) $@
-endif #} IGNORE_ANDROID_RESFILES
-endif #} ANDROID_RESFILES
-
-
 ifdef JAVAFILES #{
 GENERATED_DIRS += classes
 
 export:: classes
 classes: $(call mkdir_deps,classes)
 endif #} JAVAFILES
 
 
 ifdef ANDROID_APK_NAME #{
-_ANDROID_RES_FLAG := -S $(or $(ANDROID_RES_DIR),res)
+android_res_dirs := $(addprefix $(srcdir)/,$(or $(ANDROID_RES_DIRS),res))
+_ANDROID_RES_FLAG := $(addprefix -S ,$(android_res_dirs))
 _ANDROID_ASSETS_FLAG := $(addprefix -A ,$(ANDROID_ASSETS_DIR))
 
 GENERATED_DIRS += classes
 
 classes.dex: $(call mkdir_deps,classes)
 classes.dex: R.java
 classes.dex: $(ANDROID_APK_NAME).ap_
 classes.dex: $(JAVAFILES)
@@ -52,17 +30,21 @@ classes.dex: $(JAVAFILES)
 	$(DX) --dex --output=$@ classes $(ANDROID_EXTRA_JARS)
 
 # R.java and $(ANDROID_APK_NAME).ap_ are both produced by aapt.  To
 # save an aapt invocation, we produce them both at the same time.
 
 R.java: .aapt.deps
 $(ANDROID_APK_NAME).ap_: .aapt.deps
 
-.aapt.deps: AndroidManifest.xml $(wildcard $(ANDROID_RES_DIR)) $(wildcard $(ANDROID_ASSETS_DIR))
+# This uses the fact that Android resource directories list all
+# resource files one subdirectory below the parent resource directory.
+android_res_files := $(wildcard $(addsuffix /*,$(wildcard $(addsuffix /*,$(android_res_dirs)))))
+
+.aapt.deps: AndroidManifest.xml $(android_res_files) $(wildcard $(ANDROID_ASSETS_DIR))
 	$(AAPT) package -f -M $< -I $(ANDROID_SDK)/android.jar $(_ANDROID_RES_FLAG) $(_ANDROID_ASSETS_FLAG) \
 		-J ${@D} \
 		-F $(ANDROID_APK_NAME).ap_
 	@$(TOUCH) $@
 
 $(ANDROID_APK_NAME)-unsigned-unaligned.apk: $(ANDROID_APK_NAME).ap_ classes.dex
 	cp $< $@
 	$(ZIP) -0 $@ classes.dex
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -637,17 +637,17 @@ endif
 ifdef IS_TOOL_DIR
 # One would think "tools:: libs" would work, but it turns out that combined with
 # bug 907365, this makes make forget to run some rules sometimes.
 tools::
 	@$(MAKE) libs
 endif
 
 ##############################################
-ifndef NO_PROFILE_GUIDED_OPTIMIZE
+ifneq (1,$(NO_PROFILE_GUIDED_OPTIMIZE))
 ifdef MOZ_PROFILE_USE
 ifeq ($(OS_ARCH)_$(GNU_CC), WINNT_)
 # When building with PGO, we have to make sure to re-link
 # in the MOZ_PROFILE_USE phase if we linked in the
 # MOZ_PROFILE_GENERATE phase. We'll touch this pgo.relink
 # file in the link rule in the GENERATE phase to indicate
 # that we need a relink.
 ifdef SHARED_LIBRARY
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1540,16 +1540,17 @@ public:
   static nsresult ProcessViewportInfo(nsIDocument *aDocument,
                                       const nsAString &viewportInfo);
 
   static nsIScriptContext* GetContextForEventHandlers(nsINode* aNode,
                                                       nsresult* aRv);
 
   static JSContext *GetCurrentJSContext();
   static JSContext *GetSafeJSContext();
+  static JSContext *GetCurrentJSContextForThread();
   static JSContext *GetDefaultJSContextForThread();
 
   /**
    * Case insensitive comparison between two strings. However it only ignores
    * case for ASCII characters a-z.
    */
   static bool EqualsIgnoreASCIICase(const nsAString& aStr1,
                                     const nsAString& aStr2);
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -5258,16 +5258,27 @@ nsContentUtils::GetDefaultJSContextForTh
   if (MOZ_LIKELY(NS_IsMainThread())) {
     return GetSafeJSContext();
   } else {
     return workers::GetCurrentThreadJSContext();
   }
 }
 
 /* static */
+JSContext *
+nsContentUtils::GetCurrentJSContextForThread()
+{
+  if (MOZ_LIKELY(NS_IsMainThread())) {
+    return GetCurrentJSContext();
+  } else {
+    return workers::GetCurrentThreadJSContext();
+  }
+}
+
+/* static */
 nsresult
 nsContentUtils::ASCIIToLower(nsAString& aStr)
 {
   PRUnichar* iter = aStr.BeginWriting();
   PRUnichar* end = aStr.EndWriting();
   if (MOZ_UNLIKELY(!iter || !end)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -34,24 +34,24 @@
 
 #include "nsIChannel.h"
 #include "nsIStreamListener.h"
 
 #include "nsIFrame.h"
 #include "nsIDOMNode.h"
 
 #include "nsContentUtils.h"
-#include "nsCxPusher.h"
 #include "nsLayoutUtils.h"
 #include "nsIContentPolicy.h"
 #include "nsEventDispatcher.h"
 #include "nsSVGEffects.h"
 
 #include "mozAutoDocUpdate.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h"
 
 #if defined(XP_WIN)
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
 using namespace mozilla;
 
@@ -1189,22 +1189,16 @@ nsImageLoadingContent::ClearCurrentReque
 
 void
 nsImageLoadingContent::ClearPendingRequest(nsresult aReason,
                                            uint32_t aFlags)
 {
   if (!mPendingRequest)
     return;
 
-  // Push a null JSContext on the stack so that code that runs within
-  // the below code doesn't think it's being called by JS. See bug
-  // 604262.
-  nsCxPusher pusher;
-  pusher.PushNull();
-
   // Deregister this image from the refresh driver so it no longer receives
   // notifications.
   nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest,
                                         &mPendingRequestRegistered);
 
   UntrackImage(mPendingRequest, aFlags);
   mPendingRequest->CancelAndForgetObserver(aReason);
   mPendingRequest = nullptr;
@@ -1254,41 +1248,31 @@ nsImageLoadingContent::BindToTree(nsIDoc
                                   nsIContent* aBindingParent,
                                   bool aCompileEventHandlers)
 {
   // We may be entering the document, so if our image should be tracked,
   // track it.
   if (!aDocument)
     return;
 
-  // Push a null JSContext on the stack so that callbacks triggered by the
-  // below code won't think they're being called from JS.
-  nsCxPusher pusher;
-  pusher.PushNull();
-
   TrackImage(mCurrentRequest);
   TrackImage(mPendingRequest);
 
   if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD)
     aDocument->BlockOnload();
 }
 
 void
 nsImageLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // We may be leaving the document, so if our image is tracked, untrack it.
   nsCOMPtr<nsIDocument> doc = GetOurCurrentDoc();
   if (!doc)
     return;
 
-  // Push a null JSContext on the stack so that callbacks triggered by the
-  // below code won't think they're being called from JS.
-  nsCxPusher pusher;
-  pusher.PushNull();
-
   UntrackImage(mCurrentRequest);
   UntrackImage(mPendingRequest);
 
   if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD)
     doc->UnblockOnload(false);
 }
 
 void
--- a/content/canvas/src/CanvasImageCache.cpp
+++ b/content/canvas/src/CanvasImageCache.cpp
@@ -9,35 +9,37 @@
 #include "imgIRequest.h"
 #include "gfxASurface.h"
 #include "gfxPoint.h"
 #include "mozilla/dom/Element.h"
 #include "nsTHashtable.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsContentUtils.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/gfx/2D.h"
 
 namespace mozilla {
 
 using namespace dom;
+using namespace gfx;
 
 struct ImageCacheKey {
   ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas)
     : mImage(aImage), mCanvas(aCanvas) {}
   Element* mImage;
   HTMLCanvasElement* mCanvas;
 };
 
 struct ImageCacheEntryData {
   ImageCacheEntryData(const ImageCacheEntryData& aOther)
     : mImage(aOther.mImage)
     , mILC(aOther.mILC)
     , mCanvas(aOther.mCanvas)
     , mRequest(aOther.mRequest)
-    , mSurface(aOther.mSurface)
+    , mSourceSurface(aOther.mSourceSurface)
     , mSize(aOther.mSize)
   {}
   ImageCacheEntryData(const ImageCacheKey& aKey)
     : mImage(aKey.mImage)
     , mILC(nullptr)
     , mCanvas(aKey.mCanvas)
   {}
 
@@ -46,17 +48,17 @@ struct ImageCacheEntryData {
   size_t SizeInBytes() { return mSize.width * mSize.height * 4; }
 
   // Key
   nsRefPtr<Element> mImage;
   nsIImageLoadingContent* mILC;
   nsRefPtr<HTMLCanvasElement> mCanvas;
   // Value
   nsCOMPtr<imgIRequest> mRequest;
-  nsRefPtr<gfxASurface> mSurface;
+  RefPtr<SourceSurface> mSourceSurface;
   gfxIntSize mSize;
   nsExpirationState mState;
 };
 
 class ImageCacheEntry : public PLDHashEntryHdr {
 public:
   typedef ImageCacheKey KeyType;
   typedef const ImageCacheKey* KeyTypePointer;
@@ -122,54 +124,54 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 };
 
 void
 CanvasImageCache::NotifyDrawImage(Element* aImage,
                                   HTMLCanvasElement* aCanvas,
                                   imgIRequest* aRequest,
-                                  gfxASurface* aSurface,
+                                  SourceSurface* aSource,
                                   const gfxIntSize& aSize)
 {
   if (!gImageCache) {
     gImageCache = new ImageCache();
     nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver());
   }
 
   ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas));
   if (entry) {
-    if (entry->mData->mSurface) {
+    if (entry->mData->mSourceSurface) {
       // We are overwriting an existing entry.
       gImageCache->mTotal -= entry->mData->SizeInBytes();
       gImageCache->RemoveObject(entry->mData);
     }
     gImageCache->AddObject(entry->mData);
 
     nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
     if (ilc) {
       ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                       getter_AddRefs(entry->mData->mRequest));
     }
     entry->mData->mILC = ilc;
-    entry->mData->mSurface = aSurface;
+    entry->mData->mSourceSurface = aSource;
     entry->mData->mSize = aSize;
 
     gImageCache->mTotal += entry->mData->SizeInBytes();
   }
 
   if (!sCanvasImageCacheLimit)
     return;
 
   // Expire the image cache early if its larger than we want it to be.
   while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit))
     gImageCache->AgeOneGeneration();
 }
 
-gfxASurface*
+SourceSurface*
 CanvasImageCache::Lookup(Element* aImage,
                          HTMLCanvasElement* aCanvas,
                          gfxIntSize* aSize)
 {
   if (!gImageCache)
     return nullptr;
 
   ImageCacheEntry* entry = gImageCache->mCache.GetEntry(ImageCacheKey(aImage, aCanvas));
@@ -179,17 +181,17 @@ CanvasImageCache::Lookup(Element* aImage
   nsCOMPtr<imgIRequest> request;
   entry->mData->mILC->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(request));
   if (request != entry->mData->mRequest)
     return nullptr;
 
   gImageCache->MarkUsed(entry->mData);
 
   *aSize = entry->mData->mSize;
-  return entry->mData->mSurface;
+  return entry->mData->mSourceSurface;
 }
 
 NS_IMPL_ISUPPORTS1(CanvasImageCacheShutdownObserver, nsIObserver)
 
 NS_IMETHODIMP
 CanvasImageCacheShutdownObserver::Observe(nsISupports *aSubject,
                                           const char *aTopic,
                                           const PRUnichar *aData)
--- a/content/canvas/src/CanvasImageCache.h
+++ b/content/canvas/src/CanvasImageCache.h
@@ -6,43 +6,47 @@
 #ifndef CANVASIMAGECACHE_H_
 #define CANVASIMAGECACHE_H_
 
 namespace mozilla {
 namespace dom {
 class Element;
 class HTMLCanvasElement;
 } // namespace dom
+namespace gfx {
+class SourceSurface;
+} // namespace gfx
 } // namespace mozilla
 class imgIRequest;
 class gfxASurface;
 
 #include "gfxPoint.h"
 
 namespace mozilla {
 
 class CanvasImageCache {
+  typedef mozilla::gfx::SourceSurface SourceSurface;
 public:
   /**
    * Notify that image element aImage was (or is about to be) drawn to aCanvas
    * using the first frame of aRequest's image. The data for the surface is
    * in aSurface, and the image size is in aSize.
    */
   static void NotifyDrawImage(dom::Element* aImage,
                               dom::HTMLCanvasElement* aCanvas,
                               imgIRequest* aRequest,
-                              gfxASurface* aSurface,
+                              SourceSurface* aSource,
                               const gfxIntSize& aSize);
 
   /**
    * Check whether aImage has recently been drawn into aCanvas. If we return
    * a non-null surface, then the image was recently drawn into the canvas
    * (with the same image request) and the returned surface contains the image
    * data, and the image size will be returned in aSize.
    */
-  static gfxASurface* Lookup(dom::Element* aImage,
-                             dom::HTMLCanvasElement* aCanvas,
-                             gfxIntSize* aSize);
+  static SourceSurface* Lookup(dom::Element* aImage,
+                               dom::HTMLCanvasElement* aCanvas,
+                               gfxIntSize* aSize);
 };
 
 }
 
 #endif /* CANVASIMAGECACHE_H_ */
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -1445,39 +1445,31 @@ CanvasRenderingContext2D::CreatePattern(
       return pat.forget();
     }
   } else if (element.IsHTMLImageElement()) {
     htmlElement = &element.GetAsHTMLImageElement();
   } else {
     htmlElement = &element.GetAsHTMLVideoElement();
   }
 
+  EnsureTarget();
+
   // The canvas spec says that createPattern should use the first frame
   // of animated images
   nsLayoutUtils::SurfaceFromElementResult res =
     nsLayoutUtils::SurfaceFromElement(htmlElement,
-      nsLayoutUtils::SFE_WANT_FIRST_FRAME | nsLayoutUtils::SFE_WANT_NEW_SURFACE);
-
-  if (!res.mSurface) {
+      nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget);
+
+  if (!res.mSourceSurface) {
     error.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
-  // Ignore nullptr cairo surfaces! See bug 666312.
-  if (!res.mSurface->CairoSurface() || res.mSurface->CairoStatus()) {
-    error.Throw(NS_ERROR_NOT_AVAILABLE);
-    return nullptr;
-  }
-
-  EnsureTarget();
-  RefPtr<SourceSurface> srcSurf =
-    gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface);
-
   nsRefPtr<CanvasPattern> pat =
-    new CanvasPattern(this, srcSurf, repeatMode, res.mPrincipal,
+    new CanvasPattern(this, res.mSourceSurface, repeatMode, res.mPrincipal,
                              res.mIsWriteOnly, res.mCORSUsed);
 
   return pat.forget();
 }
 
 //
 // shadows
 //
@@ -3079,43 +3071,35 @@ CanvasRenderingContext2D::DrawImage(cons
     if (image.IsHTMLImageElement()) {
       HTMLImageElement* img = &image.GetAsHTMLImageElement();
       element = img;
     } else {
       HTMLVideoElement* video = &image.GetAsHTMLVideoElement();
       element = video;
     }
 
-    gfxASurface* imgsurf =
+    srcSurf =
       CanvasImageCache::Lookup(element, mCanvasElement, &imgSize);
-    if (imgsurf) {
-      srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, imgsurf);
-    }
   }
 
   if (!srcSurf) {
     // The canvas spec says that drawImage should draw the first frame
     // of animated images
     uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME;
     nsLayoutUtils::SurfaceFromElementResult res =
-      nsLayoutUtils::SurfaceFromElement(element, sfeFlags);
-
-    if (!res.mSurface) {
+      nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
+
+    if (!res.mSourceSurface) {
       // Spec says to silently do nothing if the element is still loading.
       if (!res.mIsStillLoading) {
         error.Throw(NS_ERROR_NOT_AVAILABLE);
       }
       return;
     }
 
-    // Ignore cairo surfaces that are bad! See bug 666312.
-    if (res.mSurface->CairoStatus()) {
-      return;
-    }
-
     imgSize = res.mSize;
 
     // Scale sw/sh based on aspect ratio
     if (image.IsHTMLVideoElement()) {
       HTMLVideoElement* video = &image.GetAsHTMLVideoElement();
       int32_t displayWidth = video->VideoWidth();
       int32_t displayHeight = video->VideoHeight();
       sw *= (double)imgSize.width / (double)displayWidth;
@@ -3124,21 +3108,21 @@ CanvasRenderingContext2D::DrawImage(cons
 
     if (mCanvasElement) {
       CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
                                             res.mPrincipal, res.mIsWriteOnly,
                                             res.mCORSUsed);
     }
 
     if (res.mImageRequest) {
-      CanvasImageCache::NotifyDrawImage(element, mCanvasElement,
-                                        res.mImageRequest, res.mSurface, imgSize);
+      CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mImageRequest,
+                                        res.mSourceSurface, imgSize);
     }
 
-    srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface);
+    srcSurf = res.mSourceSurface;
   }
 
   if (optional_argc == 0) {
     sx = sy = 0.0;
     dw = sw = (double) imgSize.width;
     dh = sh = (double) imgSize.height;
   } else if (optional_argc == 2) {
     sx = sy = 0.0;
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -23,16 +23,17 @@
 #include "nsLayoutUtils.h"
 
 #include "GLContextProvider.h"
 #include "gfxImageSurface.h"
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Scoped.h"
+#include "mozilla/gfx/2D.h"
 
 #ifdef XP_MACOSX
 #include "ForceDiscreteGPUHelperCGL.h"
 #endif
 
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/ErrorResult.h"
 
@@ -415,28 +416,29 @@ public:
     // us in TexImage2D
     template<class ElementType>
     void TexImage2D(GLenum target, GLint level,
                     GLenum internalformat, GLenum format, GLenum type,
                     ElementType& elt, ErrorResult& rv)
     {
         if (IsContextLost())
             return;
-        nsRefPtr<gfxImageSurface> isurf;
+        RefPtr<gfx::DataSourceSurface> data;
         WebGLTexelFormat srcFormat;
         nsLayoutUtils::SurfaceFromElementResult res = SurfaceFromElement(elt);
-        rv = SurfaceFromElementResultToImageSurface(res, getter_AddRefs(isurf),
+        rv = SurfaceFromElementResultToImageSurface(res, data,
                                                     &srcFormat);
-        if (rv.Failed() || !isurf)
+        if (rv.Failed() || !data)
             return;
 
-        uint32_t byteLength = isurf->Stride() * isurf->Height();
+        gfx::IntSize size = data->GetSize();
+        uint32_t byteLength = data->Stride() * size.height;
         return TexImage2D_base(target, level, internalformat,
-                               isurf->Width(), isurf->Height(), isurf->Stride(),
-                               0, format, type, isurf->Data(), byteLength,
+                               size.width, size.height, data->Stride(),
+                               0, format, type, data->GetData(), byteLength,
                                -1, srcFormat, mPixelStorePremultiplyAlpha);
     }
     void TexParameterf(GLenum target, GLenum pname, GLfloat param) {
         TexParameter_base(target, pname, nullptr, &param);
     }
     void TexParameteri(GLenum target, GLenum pname, GLint param) {
         TexParameter_base(target, pname, &param, nullptr);
     }
@@ -454,29 +456,30 @@ public:
     // us in TexSubImage2D
     template<class ElementType>
     void TexSubImage2D(GLenum target, GLint level,
                        GLint xoffset, GLint yoffset, GLenum format,
                        GLenum type, ElementType& elt, ErrorResult& rv)
     {
         if (IsContextLost())
             return;
-        nsRefPtr<gfxImageSurface> isurf;
+        RefPtr<gfx::DataSourceSurface> data;
         WebGLTexelFormat srcFormat;
         nsLayoutUtils::SurfaceFromElementResult res = SurfaceFromElement(elt);
-        rv = SurfaceFromElementResultToImageSurface(res, getter_AddRefs(isurf),
+        rv = SurfaceFromElementResultToImageSurface(res, data,
                                                     &srcFormat);
-        if (rv.Failed() || !isurf)
+        if (rv.Failed() || !data)
             return;
 
-        uint32_t byteLength = isurf->Stride() * isurf->Height();
+        gfx::IntSize size = data->GetSize();
+        uint32_t byteLength = data->Stride() * size.height;
         return TexSubImage2D_base(target, level, xoffset, yoffset,
-                                  isurf->Width(), isurf->Height(),
-                                  isurf->Stride(), format, type,
-                                  isurf->Data(), byteLength,
+                                  size.width, size.height,
+                                  data->Stride(), format, type,
+                                  data->GetData(), byteLength,
                                   -1, srcFormat, mPixelStorePremultiplyAlpha);
         
     }
 
     void Uniform1i(WebGLUniformLocation* location, GLint x);
     void Uniform2i(WebGLUniformLocation* location, GLint x, GLint y);
     void Uniform3i(WebGLUniformLocation* location, GLint x, GLint y,
                    GLint z);
@@ -987,32 +990,32 @@ protected:
                       WebGLTexelFormat srcFormat, bool srcPremultiplied,
                       WebGLTexelFormat dstFormat, bool dstPremultiplied,
                       size_t dstTexelSize);
 
     template<class ElementType>
     nsLayoutUtils::SurfaceFromElementResult SurfaceFromElement(ElementType* aElement) {
         MOZ_ASSERT(aElement);
         uint32_t flags =
-            nsLayoutUtils::SFE_WANT_IMAGE_SURFACE;
+             nsLayoutUtils::SFE_WANT_IMAGE_SURFACE;
 
         if (mPixelStoreColorspaceConversion == LOCAL_GL_NONE)
             flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
         if (!mPixelStorePremultiplyAlpha)
             flags |= nsLayoutUtils::SFE_NO_PREMULTIPLY_ALPHA;
         return nsLayoutUtils::SurfaceFromElement(aElement, flags);
     }
     template<class ElementType>
     nsLayoutUtils::SurfaceFromElementResult SurfaceFromElement(ElementType& aElement)
     {
       return SurfaceFromElement(&aElement);
     }
 
     nsresult SurfaceFromElementResultToImageSurface(nsLayoutUtils::SurfaceFromElementResult& res,
-                                                    gfxImageSurface **imageOut,
+                                                    RefPtr<gfx::DataSourceSurface>& imageOut,
                                                     WebGLTexelFormat *format);
 
     void CopyTexSubImage2D_base(GLenum target,
                                 GLint level,
                                 GLenum internalformat,
                                 GLint xoffset,
                                 GLint yoffset,
                                 GLint x,
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -43,16 +43,17 @@
 #endif
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ImageData.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gl;
+using namespace mozilla::gfx;
 
 static bool BaseTypeAndSizeFromUniformType(GLenum uType, GLenum *baseType, GLint *unitSize);
 static GLenum InternalFormatForFormatAndType(GLenum format, GLenum type, bool isGLES2);
 
 inline const WebGLRectangleObject *WebGLContext::FramebufferRectangleObject() const {
     return mBoundFramebuffer ? mBoundFramebuffer->RectangleObject()
                              : static_cast<const WebGLRectangleObject*>(this);
 }
@@ -2635,24 +2636,24 @@ WebGLContext::StencilOpSeparate(GLenum f
         return;
 
     MakeContextCurrent();
     gl->fStencilOpSeparate(face, sfail, dpfail, dppass);
 }
 
 nsresult
 WebGLContext::SurfaceFromElementResultToImageSurface(nsLayoutUtils::SurfaceFromElementResult& res,
-                                                     gfxImageSurface **imageOut, WebGLTexelFormat *format)
+                                                     RefPtr<DataSourceSurface>& imageOut, WebGLTexelFormat *format)
 {
-   *imageOut = nullptr;
    *format = WebGLTexelFormat::None;
 
-    if (!res.mSurface)
+    if (!res.mSourceSurface)
         return NS_OK;
-    if (res.mSurface->GetType() != gfxSurfaceTypeImage) {
+    RefPtr<DataSourceSurface> data = res.mSourceSurface->GetDataSurface();
+    if (!data) {
         // SurfaceFromElement lied!
         return NS_OK;
     }
 
     // We disallow loading cross-domain images and videos that have not been validated
     // with CORS as WebGL textures. The reason for doing that is that timing
     // attacks on WebGL shaders are able to retrieve approximations of the
     // pixel values in WebGL textures; see bug 655987.
@@ -2682,39 +2683,36 @@ WebGLContext::SurfaceFromElementResultTo
                         "See https://developer.mozilla.org/en/WebGL/Cross-Domain_Textures");
         return NS_ERROR_DOM_SECURITY_ERR;
     }
 
     // End of security checks, now we should be safe regarding cross-domain images
     // Notice that there is never a need to mark the WebGL canvas as write-only, since we reject write-only/cross-domain
     // texture sources in the first place.
 
-    gfxImageSurface* surf = static_cast<gfxImageSurface*>(res.mSurface.get());
-
-    res.mSurface.forget();
-    *imageOut = surf;
-
-    switch (surf->Format()) {
-        case gfxImageFormatARGB32:
+    switch (data->GetFormat()) {
+        case FORMAT_B8G8R8A8:
             *format = WebGLTexelFormat::BGRA8; // careful, our ARGB means BGRA
             break;
-        case gfxImageFormatRGB24:
+        case FORMAT_B8G8R8X8:
             *format = WebGLTexelFormat::BGRX8; // careful, our RGB24 is not tightly packed. Whence BGRX8.
             break;
-        case gfxImageFormatA8:
+        case FORMAT_A8:
             *format = WebGLTexelFormat::A8;
             break;
-        case gfxImageFormatRGB16_565:
+        case FORMAT_R5G6B5:
             *format = WebGLTexelFormat::RGB565;
             break;
         default:
             NS_ASSERTION(false, "Unsupported image format. Unimplemented.");
             return NS_ERROR_NOT_IMPLEMENTED;
     }
 
+    imageOut = data;
+
     return NS_OK;
 }
 
 
 
 void
 WebGLContext::Uniform1i(WebGLUniformLocation *location_object, GLint a1)
 {
--- a/content/events/src/nsDOMEventTargetHelper.cpp
+++ b/content/events/src/nsDOMEventTargetHelper.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsDOMEventTargetHelper.h"
 #include "nsContentUtils.h"
 #include "nsEventDispatcher.h"
 #include "nsIDocument.h"
 #include "prprf.h"
 #include "nsGlobalWindow.h"
+#include "ScriptSettings.h"
 #include "mozilla/Likely.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMEventTargetHelper)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMEventTargetHelper)
@@ -266,20 +267,20 @@ nsDOMEventTargetHelper::DispatchTrustedE
 }
 
 nsresult
 nsDOMEventTargetHelper::SetEventHandler(nsIAtom* aType,
                                         JSContext* aCx,
                                         const JS::Value& aValue)
 {
   nsRefPtr<EventHandlerNonNull> handler;
-  JSObject* callable;
+  JS::Rooted<JSObject*> callable(aCx);
   if (aValue.isObject() &&
       JS_ObjectIsCallable(aCx, callable = &aValue.toObject())) {
-    handler = new EventHandlerNonNull(callable);
+    handler = new EventHandlerNonNull(callable, mozilla::dom::GetIncumbentGlobal());
   }
   SetEventHandler(aType, EmptyString(), handler);
   return NS_OK;
 }
 
 void
 nsDOMEventTargetHelper::GetEventHandler(nsIAtom* aType,
                                         JSContext* aCx,
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -878,29 +878,33 @@ nsEventListenerManager::CompileEventHand
 
   if (handler) {
     nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mTarget);
     // Bind it
     JS::Rooted<JSObject*> boundHandler(cx);
     JS::Rooted<JSObject*> scope(cx, listener->GetEventScope());
     context->BindCompiledEventHandler(mTarget, scope, handler, &boundHandler);
     aListenerStruct = nullptr;
+    // Note - We pass null for aIncumbentGlobal below. We could also pass the
+    // compilation global, but since the handler is guaranteed to be scripted,
+    // there's no need to use an override, since the JS engine will always give
+    // us the right answer.
     if (!boundHandler) {
       listener->ForgetHandler();
     } else if (listener->EventName() == nsGkAtoms::onerror && win) {
       nsRefPtr<OnErrorEventHandlerNonNull> handlerCallback =
-        new OnErrorEventHandlerNonNull(boundHandler);
+        new OnErrorEventHandlerNonNull(boundHandler, /* aIncumbentGlobal = */ nullptr);
       listener->SetHandler(handlerCallback);
     } else if (listener->EventName() == nsGkAtoms::onbeforeunload && win) {
       nsRefPtr<OnBeforeUnloadEventHandlerNonNull> handlerCallback =
-        new OnBeforeUnloadEventHandlerNonNull(boundHandler);
+        new OnBeforeUnloadEventHandlerNonNull(boundHandler, /* aIncumbentGlobal = */ nullptr);
       listener->SetHandler(handlerCallback);
     } else {
       nsRefPtr<EventHandlerNonNull> handlerCallback =
-        new EventHandlerNonNull(boundHandler);
+        new EventHandlerNonNull(boundHandler, /* aIncumbentGlobal = */ nullptr);
       listener->SetHandler(handlerCallback);
     }
   }
 
   return result;
 }
 
 nsresult
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -2499,21 +2499,21 @@ nsEventStateManager::DoScrollHistory(int
     }
   }
 }
 
 void
 nsEventStateManager::DoScrollZoom(nsIFrame *aTargetFrame,
                                   int32_t adjustment)
 {
-  // Exclude form controls and XUL content.
+  // Exclude form controls and content in chrome docshells.
   nsIContent *content = aTargetFrame->GetContent();
   if (content &&
       !content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) &&
-      !content->OwnerDoc()->IsXUL())
+      !nsContentUtils::IsInChromeDocshell(content->OwnerDoc()))
     {
       // positive adjustment to decrease zoom, negative to increase
       int32_t change = (adjustment > 0) ? -1 : 1;
 
       if (Preferences::GetBool("browser.zoom.full") || content->GetCurrentDoc()->IsSyntheticDocument()) {
         ChangeFullZoom(change);
       } else {
         ChangeTextSize(change);
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -202,20 +202,16 @@ public:
   // in the common case where we don't have a "MozAudioAvailable" listener.
   void NotifyAudioAvailableListener();
 
   // Called by the media decoder and the video frame to get the
   // ImageContainer containing the video data.
   virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE;
   layers::ImageContainer* GetImageContainer();
 
-  // Called by the video frame to get the print surface, if this is
-  // a static document and we're not actually playing video
-  gfxASurface* GetPrintSurface() { return mPrintSurface; }
-
   // Dispatch events
   using nsGenericHTMLElement::DispatchEvent;
   virtual nsresult DispatchEvent(const nsAString& aName) MOZ_FINAL MOZ_OVERRIDE;
   virtual nsresult DispatchAsyncEvent(const nsAString& aName) MOZ_FINAL MOZ_OVERRIDE;
   nsresult DispatchAudioAvailableEvent(float* aFrameBuffer,
                                        uint32_t aFrameBufferLength,
                                        float aTime);
 
@@ -1008,18 +1004,16 @@ protected:
   // defaultPlaybackRate, then the implication is that the user is using a
   // feature such as fast forward or slow motion playback.
   double mPlaybackRate;
 
   // True if pitch correction is applied when playbackRate is set to a
   // non-intrinsic value.
   bool mPreservesPitch;
 
-  nsRefPtr<gfxASurface> mPrintSurface;
-
   // Reference to the source element last returned by GetNextSource().
   // This is the child source element which we're trying to load from.
   nsCOMPtr<nsIContent> mSourceLoadCandidate;
 
   // An audio stream for writing audio directly from JS.
   nsAutoPtr<AudioStream> mAudioStream;
 
   // Range of time played.
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -3218,21 +3218,16 @@ VideoFrameContainer* HTMLMediaElement::G
   if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA &&
       mMediaSize == nsIntSize(-1, -1)) {
     return nullptr;
   }
 
   if (mVideoFrameContainer)
     return mVideoFrameContainer;
 
-  // If we have a print surface, this is just a static image so
-  // no image container is required
-  if (mPrintSurface)
-    return nullptr;
-
   // Only video frames need an image container.
   nsCOMPtr<nsIDOMHTMLVideoElement> video = do_QueryObject(this);
   if (!video)
     return nullptr;
 
   mVideoFrameContainer =
     new VideoFrameContainer(this, LayerManager::CreateAsynchronousImageContainer());
 
@@ -3599,36 +3594,17 @@ already_AddRefed<nsILoadGroup> HTMLMedia
 
 nsresult
 HTMLMediaElement::CopyInnerTo(Element* aDest)
 {
   nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
   NS_ENSURE_SUCCESS(rv, rv);
   if (aDest->OwnerDoc()->IsStaticDocument()) {
     HTMLMediaElement* dest = static_cast<HTMLMediaElement*>(aDest);
-    if (mPrintSurface) {
-      dest->mPrintSurface = mPrintSurface;
-      dest->mMediaSize = mMediaSize;
-    } else {
-      nsIFrame* frame = GetPrimaryFrame();
-      Element* element;
-      if (frame && frame->GetType() == nsGkAtoms::HTMLVideoFrame &&
-          static_cast<nsVideoFrame*>(frame)->ShouldDisplayPoster()) {
-        nsIContent* content = static_cast<nsVideoFrame*>(frame)->GetPosterImage();
-        element = content ? content->AsElement() : nullptr;
-      } else {
-        element = const_cast<HTMLMediaElement*>(this);
-      }
-
-      nsLayoutUtils::SurfaceFromElementResult res =
-        nsLayoutUtils::SurfaceFromElement(element,
-                                          nsLayoutUtils::SFE_WANT_NEW_SURFACE);
-      dest->mPrintSurface = res.mSurface;
-      dest->mMediaSize = nsIntSize(res.mSize.width, res.mSize.height);
-    }
+    dest->mMediaSize = mMediaSize;
   }
   return rv;
 }
 
 already_AddRefed<TimeRanges>
 HTMLMediaElement::Buffered() const
 {
   nsRefPtr<TimeRanges> ranges = new TimeRanges();
--- a/content/html/content/src/nsTextEditorState.cpp
+++ b/content/html/content/src/nsTextEditorState.cpp
@@ -33,21 +33,21 @@
 #include "nsISelectionPrivate.h"
 #include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIEditor.h"
 #include "nsTextEditRules.h"
 #include "mozilla/Selection.h"
 #include "nsEventListenerManager.h"
 #include "nsContentUtils.h"
-#include "nsCxPusher.h"
 #include "mozilla/Preferences.h"
 #include "nsTextNode.h"
 #include "nsIController.h"
 #include "mozilla/TextEvents.h"
+#include "mozilla/dom/ScriptSettings.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static NS_DEFINE_CID(kTextEditorCID, NS_TEXTEDITOR_CID);
 
 static nsINativeKeyBindings *sNativeInputBindings = nullptr;
 static nsINativeKeyBindings *sNativeTextAreaBindings = nullptr;
@@ -1278,23 +1278,22 @@ nsTextEditorState::PrepareEditor(const n
 
     // Get the DOM document
     nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(shell->GetDocument());
     if (!domdoc)
       return NS_ERROR_FAILURE;
 
     // What follows is a bit of a hack.  The editor uses the public DOM APIs
     // for its content manipulations, and it causes it to fail some security
-    // checks deep inside when initializing.  So we push a null JSContext
-    // on the JS stack here to make it clear that we're native code.
+    // checks deep inside when initializing. So we explictly make it clear that
+    // we're native code.
     // Note that any script that's directly trying to access our value
     // has to be going through some scriptable object to do that and that
     // already does the relevant security checks.
-    nsCxPusher pusher;
-    pusher.PushNull();
+    AutoSystemCaller asc;
 
     rv = newEditor->Init(domdoc, GetRootNode(), mSelCon, editorFlags);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Initialize the controller for the editor
 
   if (!SuppressEventHandlers(presContext)) {
@@ -1772,19 +1771,18 @@ nsTextEditorState::GetValue(nsAString& a
     // access its own DOM nodes!  Let's try to deal with that by pushing a null
     // JSContext on the JSContext stack to make it clear that we're native
     // code.  Note that any script that's directly trying to access our value
     // has to be going through some scriptable object to do that and that
     // already does the relevant security checks.
     // XXXbz if we could just get the textContent of our anonymous content (eg
     // if plaintext editor didn't create <br> nodes all over), we wouldn't need
     // this.
-    { /* Scope for context pusher */
-      nsCxPusher pusher;
-      pusher.PushNull();
+    { /* Scope for AutoSystemCaller. */
+      AutoSystemCaller asc;
 
       mEditor->OutputToString(NS_LITERAL_STRING("text/plain"), flags,
                               aValue);
     }
     if (canCache) {
       mCachedValue = aValue;
     } else {
       mCachedValue.Truncate();
@@ -1852,19 +1850,18 @@ nsTextEditorState::SetValue(const nsAStr
       if (!domDoc) {
         NS_WARNING("Why don't we have a document?");
         return;
       }
 
       // Time to mess with our security context... See comments in GetValue()
       // for why this is needed.  Note that we have to do this up here, because
       // otherwise SelectAll() will fail.
-      { /* Scope for context pusher */
-        nsCxPusher pusher;
-        pusher.PushNull();
+      {
+        AutoSystemCaller asc;
 
         nsCOMPtr<nsISelection> domSel;
         nsCOMPtr<nsISelectionPrivate> selPriv;
         mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
                               getter_AddRefs(domSel));
         if (domSel)
         {
           selPriv = do_QueryInterface(domSel);
--- a/content/media/encoder/OpusTrackEncoder.cpp
+++ b/content/media/encoder/OpusTrackEncoder.cpp
@@ -27,16 +27,21 @@ static const int MAX_DATA_BYTES = 4096;
 // http://tools.ietf.org/html/draft-ietf-codec-oggopus-00#section-4
 // Second paragraph, " The granule position of an audio data page is in units
 // of PCM audio samples at a fixed rate of 48 kHz."
 static const int kOpusSamplingRate = 48000;
 
 // The duration of an Opus frame, and it must be 2.5, 5, 10, 20, 40 or 60 ms.
 static const int kFrameDurationMs  = 20;
 
+// The supported sampling rate of input signal (Hz),
+// must be one of the following. Will resampled to 48kHz otherwise.
+static const int kOpusSupportedInputSamplingRates[5] =
+                   {8000, 12000, 16000, 24000, 48000};
+
 namespace {
 
 // An endian-neutral serialization of integers. Serializing T in little endian
 // format to aOutput, where T is a 16 bits or 32 bits integer.
 template<typename T>
 static void
 SerializeToBuffer(T aValue, nsTArray<uint8_t>* aOutput)
 {
@@ -141,22 +146,24 @@ OpusTrackEncoder::Init(int aChannels, in
   // This version of encoder API only support 1 or 2 channels,
   // So set the mChannels less or equal 2 and
   // let InterleaveTrackData downmix pcm data.
   mChannels = aChannels > MAX_CHANNELS ? MAX_CHANNELS : aChannels;
 
   if (aChannels <= 0) {
     return NS_ERROR_FAILURE;
   }
-  // The granule position is required to be incremented at a rate of 48KHz, and
-  // it is simply calculated as |granulepos = samples * (48000/source_rate)|,
-  // that is, the source sampling rate must divide 48000 evenly.
-  // If this constraint is not satisfied, we resample the input to 48kHz.
-  if (!((aSamplingRate >= 8000) && (kOpusSamplingRate / aSamplingRate) *
-         aSamplingRate == kOpusSamplingRate)) {
+
+  // According to www.opus-codec.org, creating an opus encoder requires the
+  // sampling rate of source signal be one of 8000, 12000, 16000, 24000, or
+  // 48000. If this constraint is not satisfied, we resample the input to 48kHz.
+  nsTArray<int> supportedSamplingRates;
+  supportedSamplingRates.AppendElements(kOpusSupportedInputSamplingRates,
+                         MOZ_ARRAY_LENGTH(kOpusSupportedInputSamplingRates));
+  if (!supportedSamplingRates.Contains(aSamplingRate)) {
     int error;
     mResampler = speex_resampler_init(mChannels,
                                       aSamplingRate,
                                       kOpusSamplingRate,
                                       SPEEX_RESAMPLER_QUALITY_DEFAULT,
                                       &error);
 
     if (error != RESAMPLER_ERR_SUCCESS) {
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -6,16 +6,17 @@
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
 #include "AbstractMediaDecoder.h"
 #include "MediaResource.h"
 #include "WebMReader.h"
 #include "WebMBufferedParser.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "VorbisUtils.h"
+#include <algorithm>
 
 #define VPX_DONT_DEFINE_STDINT_TYPES
 #include "vpx/vp8dx.h"
 #include "vpx/vpx_decoder.h"
 
 #include "OggReader.h"
 
 using mozilla::NesteggPacketHolder;
@@ -142,16 +143,17 @@ WebMReader::WebMReader(AbstractMediaDeco
   : MediaDecoderReader(aDecoder),
   mContext(nullptr),
   mPacketCount(0),
   mChannels(0),
 #ifdef MOZ_OPUS
   mOpusParser(nullptr),
   mOpusDecoder(nullptr),
   mSkip(0),
+  mSeekPreroll(0),
 #endif
   mVideoTrack(0),
   mAudioTrack(0),
   mAudioStartUsec(-1),
   mAudioFrames(0),
   mHasVideo(false),
   mHasAudio(false)
 {
@@ -213,20 +215,30 @@ nsresult WebMReader::ResetDecode()
 {
   mAudioFrames = 0;
   mAudioStartUsec = -1;
   nsresult res = NS_OK;
   if (NS_FAILED(MediaDecoderReader::ResetDecode())) {
     res = NS_ERROR_FAILURE;
   }
 
-  // Ignore failed results from vorbis_synthesis_restart. They
-  // aren't fatal and it fails when ResetDecode is called at a
-  // time when no vorbis data has been read.
-  vorbis_synthesis_restart(&mVorbisDsp);
+  if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
+    // Ignore failed results from vorbis_synthesis_restart. They
+    // aren't fatal and it fails when ResetDecode is called at a
+    // time when no vorbis data has been read.
+    vorbis_synthesis_restart(&mVorbisDsp);
+#ifdef MOZ_OPUS
+  } else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
+    if (mOpusDecoder) {
+      // Reset the decoder.
+      opus_multistream_decoder_ctl(mOpusDecoder, OPUS_RESET_STATE);
+      mSkip = mOpusParser->mPreSkip;
+    }
+#endif
+  }
 
   mVideoPackets.Reset();
   mAudioPackets.Reset();
 
   return res;
 }
 
 void WebMReader::Cleanup()
@@ -426,16 +438,18 @@ nsresult WebMReader::ReadMetadata(MediaI
           Cleanup();
           return NS_ERROR_FAILURE;
         }
 
         mInfo.mAudio.mRate = mOpusParser->mRate;
 
         mInfo.mAudio.mChannels = mOpusParser->mChannels;
         mInfo.mAudio.mChannels = mInfo.mAudio.mChannels > 2 ? 2 : mInfo.mAudio.mChannels;
+        mChannels = mInfo.mAudio.mChannels;
+        mSeekPreroll = params.seek_preroll;
 #endif
       } else {
         Cleanup();
         return NS_ERROR_FAILURE;
       }
     }
   }
 
@@ -622,33 +636,34 @@ bool WebMReader::DecodeAudioPacket(neste
 #else
       int ret = opus_multistream_decode(mOpusDecoder,
                                         data, length,
                                         buffer, frames, false);
 #endif
       if (ret < 0)
         return false;
       NS_ASSERTION(ret == frames, "Opus decoded too few audio samples");
+      CheckedInt64 startTime = tstamp_usecs;
 
       // Trim the initial frames while the decoder is settling.
       if (mSkip > 0) {
         int32_t skipFrames = std::min(mSkip, frames);
         if (skipFrames == frames) {
           // discard the whole packet
           mSkip -= frames;
           LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames"
                              " (whole packet)", frames));
           return true;
         }
         int32_t keepFrames = frames - skipFrames;
         int samples = keepFrames * channels;
         nsAutoArrayPtr<AudioDataValue> trimBuffer(new AudioDataValue[samples]);
         for (int i = 0; i < samples; i++)
           trimBuffer[i] = buffer[skipFrames*channels + i];
-
+        startTime = startTime + FramesToUsecs(skipFrames, rate);
         frames = keepFrames;
         buffer = trimBuffer;
 
         mSkip -= skipFrames;
         LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames", skipFrames));
       }
 
       int64_t discardPadding = 0;
@@ -703,24 +718,22 @@ bool WebMReader::DecodeAudioPacket(neste
         OggReader::DownmixToStereo(buffer, channels, frames);
       }
 
       CheckedInt64 duration = FramesToUsecs(frames, rate);
       if (!duration.isValid()) {
         NS_WARNING("Int overflow converting WebM audio duration");
         return false;
       }
-
-      CheckedInt64 time = tstamp_usecs;
+      CheckedInt64 time = startTime - (mCodecDelay / NS_PER_USEC);
       if (!time.isValid()) {
-        NS_WARNING("Int overflow adding total_duration and tstamp_usecs");
+        NS_WARNING("Int overflow shifting tstamp by codec delay");
         nestegg_free_packet(aPacket);
         return false;
       };
-
       AudioQueue().Push(new AudioData(mDecoder->GetResource()->Tell(),
                                      time.value(),
                                      duration.value(),
                                      frames,
                                      buffer.forget(),
                                      mChannels));
 
       mAudioFrames += frames;
@@ -977,17 +990,21 @@ nsresult WebMReader::Seek(int64_t aTarge
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: About to seek to %fs",
                      this, mDecoder, aTarget/1000000.0));
   if (NS_FAILED(ResetDecode())) {
     return NS_ERROR_FAILURE;
   }
   uint32_t trackToSeek = mHasVideo ? mVideoTrack : mAudioTrack;
-  int r = nestegg_track_seek(mContext, trackToSeek, aTarget * NS_PER_USEC);
+  uint64_t target = aTarget * NS_PER_USEC;
+  if (mSeekPreroll) {
+    target = std::max(static_cast<uint64_t>(aStartTime * NS_PER_USEC), target - mSeekPreroll);
+  }
+  int r = nestegg_track_seek(mContext, trackToSeek, target);
   if (r != 0) {
     return NS_ERROR_FAILURE;
   }
   return DecodeToTarget(aTarget);
 }
 
 nsresult WebMReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime)
 {
--- a/content/media/webm/WebMReader.h
+++ b/content/media/webm/WebMReader.h
@@ -192,16 +192,17 @@ private:
   uint32_t mChannels;
 
 
 #ifdef MOZ_OPUS
   // Opus decoder state
   nsAutoPtr<OpusParser> mOpusParser;
   OpusMSDecoder *mOpusDecoder;
   int mSkip;        // Number of samples left to trim before playback.
+  uint64_t mSeekPreroll; // Number of nanoseconds that must be discarded after seeking.
 #endif
 
   // Queue of video and audio packets that have been read but not decoded. These
   // must only be accessed from the state machine thread.
   WebMPacketQueue mVideoPackets;
   WebMPacketQueue mAudioPackets;
 
   // Index of video and audio track to play
--- a/content/xbl/src/nsXBLPrototypeHandler.cpp
+++ b/content/xbl/src/nsXBLPrototypeHandler.cpp
@@ -317,17 +317,17 @@ nsXBLPrototypeHandler::ExecuteHandler(Ev
 
   // Now, wrap the bound handler into the content compartment and use it.
   JSAutoCompartment ac2(cx, globalObject);
   if (!JS_WrapObject(cx, &bound)) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<EventHandlerNonNull> handlerCallback =
-    new EventHandlerNonNull(bound);
+    new EventHandlerNonNull(bound, /* aIncumbentGlobal = */ nullptr);
 
   nsEventHandler eventHandler(handlerCallback);
 
   // Execute it.
   nsCOMPtr<nsIJSEventListener> eventListener;
   rv = NS_NewJSEventListener(globalObject,
                              scriptTarget, onEventAtom,
                              eventHandler,
--- a/db/sqlite3/src/Makefile.in
+++ b/db/sqlite3/src/Makefile.in
@@ -41,21 +41,16 @@ MODULE_OPTIMIZE_FLAGS = -O2
 endif
 
 # Force /O2 optimisation on Windows because using the default /O1 causes
 # crashes with MSVC2005 and PGO. See bug 719584.
 ifeq ($(OS_ARCH),WINNT)
 MODULE_OPTIMIZE_FLAGS = -O2
 endif
 
-# disable PGO for Sun Studio
-ifdef SOLARIS_SUNPRO_CC
-NO_PROFILE_GUIDED_OPTIMIZE = 1
-endif
-
 include $(topsrcdir)/config/rules.mk
 
 # next line allows use of MOZ_OBJDIR in .mozconfig with older gcc on BeOS, maybe others
 LOCAL_INCLUDES += -I$(srcdir)
 
 ifeq ($(OS_ARCH),OS2)
 ADD_TO_DEF_FILE = $(PYTHON) -m mozbuild.action.preprocessor $(DEFINES) \
        $(srcdir)/sqlite.def | sed -e '1,/^EXPORTS$$/ d' -e 's,sqlite3,_\0,' \
--- a/db/sqlite3/src/moz.build
+++ b/db/sqlite3/src/moz.build
@@ -57,8 +57,12 @@ if CONFIG['OS_TARGET'] == 'Android':
     # default to user readable only to fit Android security model
     DEFINES['SQLITE_DEFAULT_FILE_PERMISSIONS'] = '0600'
 
 # Force using malloc_usable_size when building with jemalloc because _msize
 # causes assertions on Win64. See bug 719579.
 if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['MOZ_MEMORY']:
     DEFINES['HAVE_MALLOC_USABLE_SIZE'] = True
     DEFINES['SQLITE_WITHOUT_MSIZE'] = True
+
+# disable PGO for Sun Studio
+if CONFIG['SOLARIS_SUNPRO_CC']:
+    NO_PGO = True
new file mode 100644
--- /dev/null
+++ b/dom/base/ScriptSettings.cpp
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim: ft=cpp tw=78 sw=2 et ts=2
+/* 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/. */
+
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/Assertions.h"
+
+#include "jsapi.h"
+#include "xpcpublic.h"
+#include "nsIGlobalObject.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsContentUtils.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+
+class ScriptSettingsStack;
+static mozilla::ThreadLocal<ScriptSettingsStack*> sScriptSettingsTLS;
+
+ScriptSettingsStackEntry ScriptSettingsStackEntry::SystemSingleton;
+
+class ScriptSettingsStack {
+public:
+  static ScriptSettingsStack& Ref() {
+    return *sScriptSettingsTLS.get();
+  }
+  ScriptSettingsStack() {};
+
+  void Push(ScriptSettingsStackEntry* aSettings) {
+    // The bottom-most entry must always be a candidate entry point.
+    MOZ_ASSERT_IF(mStack.Length() == 0 || mStack.LastElement()->IsSystemSingleton(),
+                  aSettings->mIsCandidateEntryPoint);
+    mStack.AppendElement(aSettings);
+  }
+
+  void PushSystem() {
+    mStack.AppendElement(&ScriptSettingsStackEntry::SystemSingleton);
+  }
+
+  void Pop() {
+    MOZ_ASSERT(mStack.Length() > 0);
+    mStack.RemoveElementAt(mStack.Length() - 1);
+  }
+
+  nsIGlobalObject* Incumbent() {
+    if (!mStack.Length()) {
+      return nullptr;
+    }
+    return mStack.LastElement()->mGlobalObject;
+  }
+
+  nsIGlobalObject* EntryPoint() {
+    if (!mStack.Length())
+      return nullptr;
+    for (int i = mStack.Length() - 1; i >= 0; --i) {
+      if (mStack[i]->mIsCandidateEntryPoint) {
+        return mStack[i]->mGlobalObject;
+      }
+    }
+    MOZ_ASSUME_UNREACHABLE("Non-empty stack should always have an entry point");
+  }
+
+private:
+  // These pointers are caller-owned.
+  nsTArray<ScriptSettingsStackEntry*> mStack;
+};
+
+void
+InitScriptSettings()
+{
+  if (!sScriptSettingsTLS.initialized()) {
+    bool success = sScriptSettingsTLS.init();
+    if (!success) {
+      MOZ_CRASH();
+    }
+  }
+
+  ScriptSettingsStack* ptr = new ScriptSettingsStack();
+  sScriptSettingsTLS.set(ptr);
+}
+
+void DestroyScriptSettings()
+{
+  ScriptSettingsStack* ptr = sScriptSettingsTLS.get();
+  MOZ_ASSERT(ptr);
+  sScriptSettingsTLS.set(nullptr);
+  delete ptr;
+}
+
+// Note: When we're ready to expose it, GetEntryGlobal will look similar to
+// GetIncumbentGlobal below.
+
+nsIGlobalObject*
+GetIncumbentGlobal()
+{
+  // We need the current JSContext in order to check the JS for
+  // scripted frames that may have appeared since anyone last
+  // manipulated the stack. If it's null, that means that there
+  // must be no entry point on the stack, and therefore no incumbent
+  // global either.
+  JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
+  if (!cx) {
+    MOZ_ASSERT(ScriptSettingsStack::Ref().EntryPoint() == nullptr);
+    return nullptr;
+  }
+
+  // See what the JS engine has to say. If we've got a scripted caller
+  // override in place, the JS engine will lie to us and pretend that
+  // there's nothing on the JS stack, which will cause us to check the
+  // incumbent script stack below.
+  JS::RootedScript script(cx);
+  if (JS_DescribeScriptedCaller(cx, &script, nullptr)) {
+    JS::RootedObject global(cx, JS_GetGlobalFromScript(script));
+    MOZ_ASSERT(global);
+    return xpc::GetNativeForGlobal(global);
+  }
+
+  // Ok, nothing from the JS engine. Let's use whatever's on the
+  // explicit stack.
+  return ScriptSettingsStack::Ref().Incumbent();
+}
+
+AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
+                                 bool aIsMainThread,
+                                 JSContext* aCx)
+  : mStack(ScriptSettingsStack::Ref())
+  , mEntry(aGlobalObject, /* aCandidate = */ true)
+{
+  MOZ_ASSERT(aGlobalObject);
+  if (!aCx) {
+    // If the caller didn't provide a cx, hunt one down. This isn't exactly
+    // fast, but the callers that care about performance can pass an explicit
+    // cx for now. Eventually, the whole cx pushing thing will go away
+    // entirely.
+    MOZ_ASSERT(aIsMainThread, "cx is mandatory off-main-thread");
+    nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobalObject);
+    if (sgo && sgo->GetScriptContext()) {
+      aCx = sgo->GetScriptContext()->GetNativeContext();
+    }
+    if (!aCx) {
+      aCx = nsContentUtils::GetSafeJSContext();
+    }
+  }
+  if (aIsMainThread) {
+    mCxPusher.Push(aCx);
+  }
+  mAc.construct(aCx, aGlobalObject->GetGlobalJSObject());
+  mStack.Push(&mEntry);
+}
+
+AutoEntryScript::~AutoEntryScript()
+{
+  MOZ_ASSERT(mStack.Incumbent() == mEntry.mGlobalObject);
+  mStack.Pop();
+}
+
+AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
+  : mStack(ScriptSettingsStack::Ref())
+  , mEntry(aGlobalObject, /* aCandidate = */ false)
+  , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread())
+{
+  mStack.Push(&mEntry);
+}
+
+AutoIncumbentScript::~AutoIncumbentScript()
+{
+  MOZ_ASSERT(mStack.Incumbent() == mEntry.mGlobalObject);
+  mStack.Pop();
+}
+
+AutoSystemCaller::AutoSystemCaller(bool aIsMainThread)
+  : mStack(ScriptSettingsStack::Ref())
+{
+  if (aIsMainThread) {
+    mCxPusher.PushNull();
+  }
+  mStack.PushSystem();
+}
+
+AutoSystemCaller::~AutoSystemCaller()
+{
+  mStack.Pop();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/ScriptSettings.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim: ft=cpp tw=78 sw=2 et ts=2
+/* 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/. */
+
+/* Utilities for managing the script settings object stack defined in webapps */
+
+#ifndef mozilla_dom_ScriptSettings_h
+#define mozilla_dom_ScriptSettings_h
+
+#include "nsCxPusher.h"
+#include "MainThreadUtils.h"
+#include "nsIGlobalObject.h"
+
+#include "mozilla/Maybe.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * System-wide setup/teardown routines. Init and Destroy should be invoked
+ * once each, at startup and shutdown (respectively).
+ */
+void InitScriptSettings();
+void DestroyScriptSettings();
+
+// Note: We don't yet expose GetEntryGlobal, because in order for it to be
+// correct, we first need to replace a bunch of explicit cx pushing in the
+// browser with AutoEntryScript. But GetIncumbentGlobal is simpler, because it
+// can mostly be inferred from the JS stack.
+nsIGlobalObject* GetIncumbentGlobal();
+
+class ScriptSettingsStack;
+struct ScriptSettingsStackEntry {
+  nsCOMPtr<nsIGlobalObject> mGlobalObject;
+  bool mIsCandidateEntryPoint;
+
+  ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, bool aCandidate)
+    : mGlobalObject(aGlobal)
+    , mIsCandidateEntryPoint(aCandidate)
+  {
+    MOZ_ASSERT(mGlobalObject);
+    MOZ_ASSERT(mGlobalObject->GetGlobalJSObject(),
+               "Must have an actual JS global for the duration on the stack");
+    MOZ_ASSERT(JS_IsGlobalObject(mGlobalObject->GetGlobalJSObject()),
+               "No outer windows allowed");
+  }
+
+  ~ScriptSettingsStackEntry() {
+    // We must have an actual JS global for the entire time this is on the stack.
+    MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject());
+  }
+
+  bool IsSystemSingleton() { return this == &SystemSingleton; }
+  static ScriptSettingsStackEntry SystemSingleton;
+
+private:
+  ScriptSettingsStackEntry() : mGlobalObject(nullptr)
+                             , mIsCandidateEntryPoint(true)
+  {}
+};
+
+/*
+ * A class that represents a new script entry point.
+ */
+class AutoEntryScript {
+public:
+  AutoEntryScript(nsIGlobalObject* aGlobalObject,
+                  bool aIsMainThread = NS_IsMainThread(),
+                  // Note: aCx is mandatory off-main-thread.
+                  JSContext* aCx = nullptr);
+  ~AutoEntryScript();
+
+private:
+  dom::ScriptSettingsStack& mStack;
+  dom::ScriptSettingsStackEntry mEntry;
+  nsCxPusher mCxPusher;
+  mozilla::Maybe<JSAutoCompartment> mAc; // This can de-Maybe-fy when mCxPusher
+                                         // goes away.
+};
+
+/*
+ * A class that can be used to force a particular incumbent script on the stack.
+ */
+class AutoIncumbentScript {
+public:
+  AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
+  ~AutoIncumbentScript();
+private:
+  dom::ScriptSettingsStack& mStack;
+  dom::ScriptSettingsStackEntry mEntry;
+  JS::AutoHideScriptedCaller mCallerOverride;
+};
+
+/*
+ * A class used for C++ to indicate that existing entry and incumbent scripts
+ * should not apply to anything in scope, and that callees should act as if
+ * they were invoked "from C++".
+ */
+class AutoSystemCaller {
+public:
+  AutoSystemCaller(bool aIsMainThread = NS_IsMainThread());
+  ~AutoSystemCaller();
+private:
+  dom::ScriptSettingsStack& mStack;
+  nsCxPusher mCxPusher;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScriptSettings_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -54,16 +54,17 @@ EXPORTS.mozilla.dom += [
     'DOMError.h',
     'DOMException.h',
     'DOMRequest.h',
     'MessageChannel.h',
     'MessagePort.h',
     'MessagePortList.h',
     'Navigator.h',
     'ScreenOrientation.h',
+    'ScriptSettings.h',
     'StructuredCloneTags.h',
     'URL.h',
 ]
 
 UNIFIED_SOURCES += [
     'BarProps.cpp',
     'CompositionStringSynthesizer.cpp',
     'Crypto.cpp',
@@ -89,16 +90,17 @@ UNIFIED_SOURCES += [
     'nsPerformance.cpp',
     'nsQueryContentEventResult.cpp',
     'nsScreen.cpp',
     'nsScriptNameSpaceManager.cpp',
     'nsStructuredCloneContainer.cpp',
     'nsWindowMemoryReporter.cpp',
     'nsWindowRoot.cpp',
     'nsWrapperCache.cpp',
+    'ScriptSettings.cpp',
     'URL.cpp',
     'WindowNamedPropertiesHandler.cpp',
 ]
 
 # these files couldn't be in UNIFIED_SOURCES for now for reasons given below:
 SOURCES += [
     # this file doesn't like windows.h
     'MessagePort.cpp',
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -88,16 +88,17 @@
 #ifdef XP_WIN
 #undef GetClassName
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 using namespace mozilla::widget;
+using namespace mozilla::gfx;
 
 class gfxContext;
 
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 DOMCI_DATA(WindowUtils, nsDOMWindowUtils)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMWindowUtils)
@@ -1380,31 +1381,30 @@ nsDOMWindowUtils::NodesFromRect(float aX
 
   nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
   NS_ENSURE_STATE(doc);
 
   return doc->NodesFromRectHelper(aX, aY, aTopSize, aRightSize, aBottomSize, aLeftSize, 
                                   aIgnoreRootScrollFrame, aFlushLayout, aReturn);
 }
 
-static already_AddRefed<gfxImageSurface>
-CanvasToImageSurface(nsIDOMHTMLCanvasElement* aCanvas)
+static TemporaryRef<DataSourceSurface>
+CanvasToDataSourceSurface(nsIDOMHTMLCanvasElement* aCanvas)
 {
   nsCOMPtr<nsINode> node = do_QueryInterface(aCanvas);
   if (!node) {
     return nullptr;
   }
 
   NS_ABORT_IF_FALSE(node->IsElement(),
                     "An nsINode that implements nsIDOMHTMLCanvasElement should "
                     "be an element.");
   nsLayoutUtils::SurfaceFromElementResult result =
-    nsLayoutUtils::SurfaceFromElement(node->AsElement(),
-                                      nsLayoutUtils::SFE_WANT_IMAGE_SURFACE);
-  return result.mSurface.forget().downcast<gfxImageSurface>();
+    nsLayoutUtils::SurfaceFromElement(node->AsElement());
+  return result.mSourceSurface->GetDataSurface();
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::CompareCanvases(nsIDOMHTMLCanvasElement *aCanvas1,
                                   nsIDOMHTMLCanvasElement *aCanvas2,
                                   uint32_t* aMaxDifference,
                                   uint32_t* retVal)
 {
@@ -1412,45 +1412,45 @@ nsDOMWindowUtils::CompareCanvases(nsIDOM
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   if (aCanvas1 == nullptr ||
       aCanvas2 == nullptr ||
       retVal == nullptr)
     return NS_ERROR_FAILURE;
 
-  nsRefPtr<gfxImageSurface> img1 = CanvasToImageSurface(aCanvas1);
-  nsRefPtr<gfxImageSurface> img2 = CanvasToImageSurface(aCanvas2);
+  RefPtr<DataSourceSurface> img1 = CanvasToDataSourceSurface(aCanvas1);
+  RefPtr<DataSourceSurface> img2 = CanvasToDataSourceSurface(aCanvas2);
 
   if (img1 == nullptr || img2 == nullptr ||
       img1->GetSize() != img2->GetSize() ||
       img1->Stride() != img2->Stride())
     return NS_ERROR_FAILURE;
 
   int v;
-  gfxIntSize size = img1->GetSize();
+  IntSize size = img1->GetSize();
   uint32_t stride = img1->Stride();
 
   // we can optimize for the common all-pass case
   if (stride == (uint32_t) size.width * 4) {
-    v = memcmp(img1->Data(), img2->Data(), size.width * size.height * 4);
+    v = memcmp(img1->GetData(), img2->GetData(), size.width * size.height * 4);
     if (v == 0) {
       if (aMaxDifference)
         *aMaxDifference = 0;
       *retVal = 0;
       return NS_OK;
     }
   }
 
   uint32_t dc = 0;
   uint32_t different = 0;
 
   for (int j = 0; j < size.height; j++) {
-    unsigned char *p1 = img1->Data() + j*stride;
-    unsigned char *p2 = img2->Data() + j*stride;
+    unsigned char *p1 = img1->GetData() + j*stride;
+    unsigned char *p2 = img2->GetData() + j*stride;
     v = memcmp(p1, p2, stride);
 
     if (v) {
       for (int i = 0; i < size.width; i++) {
         if (*(uint32_t*) p1 != *(uint32_t*) p2) {
 
           different++;
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -38,16 +38,17 @@
 // Helper Classes
 #include "nsJSUtils.h"
 #include "jsapi.h"              // for JSAutoRequest
 #include "js/OldDebugAPI.h"     // for JS_ClearWatchPointsForObject
 #include "jswrapper.h"
 #include "nsReadableUtils.h"
 #include "nsDOMClassInfo.h"
 #include "nsJSEnvironment.h"
+#include "ScriptSettings.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Likely.h"
 
 // Other Classes
 #include "nsEventListenerManager.h"
 #include "mozilla/dom/BarProps.h"
 #include "nsContentCID.h"
 #include "nsLayoutStatics.h"
@@ -204,16 +205,17 @@
 #include "nsSandboxFlags.h"
 #include "TimeChangeObserver.h"
 #include "mozilla/dom/AudioContext.h"
 #include "mozilla/dom/BrowserElementDictionariesBinding.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "nsIDOMMediaQueryList.h"
+#include "mozilla/dom/ScriptSettings.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
 #ifdef MOZ_JSDEBUGGER
 #include "jsdIDebuggerService.h"
 #endif
@@ -4939,18 +4941,19 @@ NS_IMETHODIMP
 nsGlobalWindow::RequestAnimationFrame(const JS::Value& aCallback,
                                       JSContext* cx,
                                       int32_t* aHandle)
 {
   if (!aCallback.isObject() || !JS_ObjectIsCallable(cx, &aCallback.toObject())) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  JS::Rooted<JSObject*> callbackObj(cx, &aCallback.toObject());
   nsRefPtr<FrameRequestCallback> callback =
-    new FrameRequestCallback(&aCallback.toObject());
+    new FrameRequestCallback(callbackObj, GetIncumbentGlobal());
 
   ErrorResult rv;
   *aHandle = RequestAnimationFrame(*callback, rv);
 
   return rv.ErrorCode();
 }
 
 NS_IMETHODIMP
@@ -11206,28 +11209,28 @@ nsGlobalWindow::OpenInternal(const nsASt
     if (!aCalledNoScript) {
       // We asserted at the top of this function that aNavigate is true for
       // !aCalledNoScript.
       rv = pwwatch->OpenWindow2(this, url.get(), name_ptr, options_ptr,
                                 /* aCalledFromScript = */ true,
                                 aDialog, aNavigate, argv,
                                 getter_AddRefs(domReturn));
     } else {
-      // Push a null JSContext here so that the window watcher won't screw us
+      // Force a system caller here so that the window watcher won't screw us
       // up.  We do NOT want this case looking at the JS context on the stack
       // when searching.  Compare comments on
       // nsIDOMWindow::OpenWindow and nsIWindowWatcher::OpenWindow.
 
       // Note: Because nsWindowWatcher is so broken, it's actually important
-      // that we don't push a null cx here, because that screws it up when it
-      // tries to compute the caller principal to associate with dialog
+      // that we don't force a system caller here, because that screws it up
+      // when it tries to compute the caller principal to associate with dialog
       // arguments. That whole setup just really needs to be rewritten. :-(
-      nsCxPusher pusher;
+      Maybe<AutoSystemCaller> asc;
       if (!aContentModal) {
-        pusher.PushNull();
+        asc.construct();
       }
 
 
       rv = pwwatch->OpenWindow2(this, url.get(), name_ptr, options_ptr,
                                 /* aCalledFromScript = */ false,
                                 aDialog, aNavigate, aExtraArgument,
                                 getter_AddRefs(domReturn));
 
@@ -13224,20 +13227,20 @@ nsGlobalWindow::DisableNetworkEvent(uint
                                              JS::Value *vp) {                \
     EventHandlerNonNull* h = GetOn##name_();                                 \
     vp->setObjectOrNull(h ? h->Callable().get() : nullptr);                  \
     return NS_OK;                                                            \
   }                                                                          \
   NS_IMETHODIMP nsGlobalWindow::SetOn##name_(JSContext *cx,                  \
                                              const JS::Value &v) {           \
     nsRefPtr<EventHandlerNonNull> handler;                                   \
-    JSObject *callable;                                                      \
+    JS::Rooted<JSObject*> callable(cx);                                      \
     if (v.isObject() &&                                                      \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                 \
-      handler = new EventHandlerNonNull(callable);                           \
+      handler = new EventHandlerNonNull(callable, GetIncumbentGlobal());     \
     }                                                                        \
     SetOn##name_(handler);                                                   \
     return NS_OK;                                                            \
   }
 #define ERROR_EVENT(name_, id_, type_, struct_)                              \
   NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx,                  \
                                              JS::Value *vp) {                \
     nsEventListenerManager *elm = GetExistingListenerManager();              \
@@ -13254,20 +13257,20 @@ nsGlobalWindow::DisableNetworkEvent(uint
   NS_IMETHODIMP nsGlobalWindow::SetOn##name_(JSContext *cx,                  \
                                              const JS::Value &v) {           \
     nsEventListenerManager *elm = GetOrCreateListenerManager();              \
     if (!elm) {                                                              \
       return NS_ERROR_OUT_OF_MEMORY;                                         \
     }                                                                        \
                                                                              \
     nsRefPtr<OnErrorEventHandlerNonNull> handler;                            \
-    JSObject *callable;                                                      \
+    JS::Rooted<JSObject*> callable(cx);                                      \
     if (v.isObject() &&                                                      \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                 \
-      handler = new OnErrorEventHandlerNonNull(callable);                    \
+      handler = new OnErrorEventHandlerNonNull(callable, GetIncumbentGlobal()); \
     }                                                                        \
     elm->SetEventHandler(handler);                                           \
     return NS_OK;                                                            \
   }
 #define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_)                       \
   NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx,                  \
                                              JS::Value *vp) {                \
     nsEventListenerManager *elm = GetExistingListenerManager();              \
@@ -13285,20 +13288,20 @@ nsGlobalWindow::DisableNetworkEvent(uint
   NS_IMETHODIMP nsGlobalWindow::SetOn##name_(JSContext *cx,                  \
                                              const JS::Value &v) {           \
     nsEventListenerManager *elm = GetOrCreateListenerManager();              \
     if (!elm) {                                                              \
       return NS_ERROR_OUT_OF_MEMORY;                                         \
     }                                                                        \
                                                                              \
     nsRefPtr<OnBeforeUnloadEventHandlerNonNull> handler;                     \
-    JSObject *callable;                                                      \
+    JS::Rooted<JSObject*> callable(cx);                                      \
     if (v.isObject() &&                                                      \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                 \
-      handler = new OnBeforeUnloadEventHandlerNonNull(callable);             \
+      handler = new OnBeforeUnloadEventHandlerNonNull(callable, GetIncumbentGlobal()); \
     }                                                                        \
     elm->SetEventHandler(handler);                                           \
     return NS_OK;                                                            \
   }
 #define WINDOW_ONLY_EVENT EVENT
 #define TOUCH_EVENT EVENT
 #include "nsEventNameList.h"
 #undef TOUCH_EVENT
--- a/dom/base/nsJSTimeoutHandler.cpp
+++ b/dom/base/nsJSTimeoutHandler.cpp
@@ -355,17 +355,17 @@ nsJSScriptTimeoutHandler::Init(nsGlobalW
     if (nsJSUtils::GetCallingLocation(cx, &filename, &mLineNo)) {
       mFileName.Assign(filename);
     }
   } else if (funobj) {
     *aAllowEval = true;
 
     mozilla::HoldJSObjects(this);
 
-    mFunction = new Function(funobj);
+    mFunction = new Function(funobj, GetIncumbentGlobal());
 
     // Create our arg array.  argc is the number of arguments passed
     // to setTimeout or setInterval; the first two are our callback
     // and the delay, so only arguments after that need to go in our
     // array.
     // std::max(argc - 2, 0) wouldn't work right because argc is unsigned.
     uint32_t argCount = std::max(argc, 2u) - 2;
 
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -22,16 +22,17 @@
 #include "nsIXPConnect.h"
 #include "WrapperFactory.h"
 #include "xpcprivate.h"
 #include "XPCQuickStubs.h"
 #include "XrayWrapper.h"
 #include "nsPrintfCString.h"
 #include "prprf.h"
 
+#include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/DOMErrorBinding.h"
 #include "mozilla/dom/HTMLObjectElement.h"
 #include "mozilla/dom/HTMLObjectElementBinding.h"
 #include "mozilla/dom/HTMLSharedObjectElement.h"
 #include "mozilla/dom/HTMLEmbedElementBinding.h"
 #include "mozilla/dom/HTMLAppletElementBinding.h"
 #include "WorkerPrivate.h"
@@ -2010,22 +2011,22 @@ ConstructJSImplementation(JSContext* aCx
 {
   // Get the window to use as a parent and for initialization.
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
   if (!window) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  // Make sure to have nothing on the JS context stack while creating and
+  // Make sure to divorce ourselves from the calling JS while creating and
   // initializing the object, so exceptions from that will get reported
   // properly, since those are never exceptions that a spec wants to be thrown.
-  {  // Scope for the nsCxPusher
-    nsCxPusher pusher;
-    pusher.PushNull();
+  {
+    AutoSystemCaller asc;
+
     // Get the XPCOM component containing the JS implementation.
     nsCOMPtr<nsISupports> implISupports = do_CreateInstance(aContractId);
     if (!implISupports) {
       NS_WARNING("Failed to get JS implementation for contract");
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
     }
     // Initialize the object, if it implements nsIDOMGlobalPropertyInitializer.
--- a/dom/bindings/CallbackFunction.h
+++ b/dom/bindings/CallbackFunction.h
@@ -20,18 +20,19 @@
 #include "mozilla/dom/CallbackObject.h"
 
 namespace mozilla {
 namespace dom {
 
 class CallbackFunction : public CallbackObject
 {
 public:
-  explicit CallbackFunction(JSObject* aCallable)
-    : CallbackObject(aCallable)
+  explicit CallbackFunction(JS::Handle<JSObject*> aCallable,
+                            nsIGlobalObject* aIncumbentGlobal)
+    : CallbackObject(aCallable, aIncumbentGlobal)
   {
     MOZ_ASSERT(JS_ObjectIsCallable(nullptr, mCallback));
   }
 
   JS::Handle<JSObject*> Callable() const
   {
     return Callback();
   }
--- a/dom/bindings/CallbackInterface.h
+++ b/dom/bindings/CallbackInterface.h
@@ -19,18 +19,19 @@
 #include "mozilla/dom/CallbackObject.h"
 
 namespace mozilla {
 namespace dom {
 
 class CallbackInterface : public CallbackObject
 {
 public:
-  explicit CallbackInterface(JSObject* aCallback)
-    : CallbackObject(aCallback)
+  explicit CallbackInterface(JS::Handle<JSObject*> aCallback,
+                             nsIGlobalObject *aIncumbentGlobal)
+    : CallbackObject(aCallback, aIncumbentGlobal)
   {
   }
 
 protected:
   bool GetCallableProperty(JSContext* cx, const char* aPropName,
                            JS::MutableHandle<JS::Value> aCallable);
 
 };
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -14,41 +14,44 @@
 #include "nsIScriptContext.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSUtils.h"
 #include "nsCxPusher.h"
 #include "nsIScriptSecurityManager.h"
 #include "xpcprivate.h"
 #include "WorkerPrivate.h"
 #include "nsGlobalWindow.h"
+#include "WorkerScope.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
   NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject)
   tmp->DropCallback();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
-CallbackObject::CallSetup::CallSetup(JS::Handle<JSObject*> aCallback,
+CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
                                      ErrorResult& aRv,
                                      ExceptionHandling aExceptionHandling,
                                      JSCompartment* aCompartment)
   : mCx(nullptr)
   , mCompartment(aCompartment)
   , mErrorResult(aRv)
   , mExceptionHandling(aExceptionHandling)
   , mIsMainThread(NS_IsMainThread())
@@ -58,18 +61,19 @@ CallbackObject::CallSetup::CallSetup(JS:
   }
   // We need to produce a useful JSContext here.  Ideally one that the callback
   // is in some sense associated with, so that we can sort of treat it as a
   // "script entry point".  Though once we actually have script entry points,
   // we'll need to do the script entry point bits once we have an actual
   // callable.
 
   // First, find the real underlying callback.
-  JSObject* realCallback = js::UncheckedUnwrap(aCallback);
+  JSObject* realCallback = js::UncheckedUnwrap(aCallback->CallbackPreserveColor());
   JSContext* cx = nullptr;
+  nsIGlobalObject* globalObject = nullptr;
 
   if (mIsMainThread) {
     // Now get the global and JSContext for this callback.
     nsGlobalWindow* win = xpc::WindowGlobalOrNull(realCallback);
     if (win) {
       // Make sure that if this is a window it's the current inner, since the
       // nsIScriptContext and hence JSContext are associated with the outer
       // window.  Which means that if someone holds on to a function from a
@@ -80,52 +84,70 @@ CallbackObject::CallSetup::CallSetup(JS:
       if (!outer || win != outer->GetCurrentInnerWindow()) {
         // Just bail out from here
         return;
       }
       cx = win->GetContext() ? win->GetContext()->GetNativeContext()
                              // This happens - Removing it causes
                              // test_bug293235.xul to go orange.
                              : nsContentUtils::GetSafeJSContext();
+      globalObject = win;
     } else {
-      // No DOM Window. Use the SafeJSContext.
+      // No DOM Window. Store the global and use the SafeJSContext.
+      JSObject* glob = js::GetGlobalForObjectCrossCompartment(realCallback);
+      globalObject = xpc::GetNativeForGlobal(glob);
+      MOZ_ASSERT(globalObject);
       cx = nsContentUtils::GetSafeJSContext();
     }
-
-    // Make sure our JSContext is pushed on the stack.
-    mCxPusher.Push(cx);
   } else {
     cx = workers::GetCurrentThreadJSContext();
+    globalObject = workers::GetCurrentThreadWorkerPrivate()->GlobalScope();
   }
 
-  // Unmark the callable, and stick it in a Rooted before it can go gray again.
+  // Bail out if there's no useful global. This seems to happen intermittently
+  // on gaia-ui tests, probably because nsInProcessTabChildGlobal is returning
+  // null in some kind of teardown state.
+  if (!globalObject->GetGlobalJSObject()) {
+    return;
+  }
+
+  mAutoEntryScript.construct(globalObject, mIsMainThread, cx);
+  if (aCallback->IncumbentGlobalOrNull()) {
+    mAutoIncumbentScript.construct(aCallback->IncumbentGlobalOrNull());
+  }
+
+  // Unmark the callable (by invoking Callback() and not the CallbackPreserveColor()
+  // variant), and stick it in a Rooted before it can go gray again.
   // Nothing before us in this function can trigger a CC, so it's safe to wait
   // until here it do the unmark. This allows us to order the following two
   // operations _after_ the Push() above, which lets us take advantage of the
   // JSAutoRequest embedded in the pusher.
   //
   // We can do this even though we're not in the right compartment yet, because
   // Rooted<> does not care about compartments.
-  JS::ExposeObjectToActiveJS(aCallback);
-  mRootedCallable.construct(cx, aCallback);
+  mRootedCallable.construct(cx, aCallback->Callback());
 
   if (mIsMainThread) {
     // Check that it's ok to run this callback at all.
-    // Make sure to unwrap aCallback before passing it in to get the global of
-    // the callback object, not the wrapper.
+    // Make sure to use realCallback to get the global of the callback object,
+    // not the wrapper.
     bool allowed = nsContentUtils::GetSecurityManager()->
-      ScriptAllowed(js::GetGlobalForObjectCrossCompartment(js::UncheckedUnwrap(aCallback)));
+      ScriptAllowed(js::GetGlobalForObjectCrossCompartment(realCallback));
 
     if (!allowed) {
       return;
     }
   }
 
   // Enter the compartment of our callback, so we can actually work with it.
-  mAc.construct(cx, aCallback);
+  //
+  // Note that if the callback is a wrapper, this will not be the same
+  // compartment that we ended up in with mAutoEntryScript above, because the
+  // entry point is based off of the unwrapped callback (realCallback).
+  mAc.construct(cx, mRootedCallable.ref());
 
   // And now we're ready to go.
   mCx = cx;
 
   // Make sure the JS engine doesn't report exceptions we want to re-throw
   if (mExceptionHandling == eRethrowContentExceptions ||
       mExceptionHandling == eRethrowExceptions) {
     mSavedJSContextOptions = JS::ContextOptionsRef(cx);
@@ -189,27 +211,21 @@ CallbackObject::CallSetup::~CallSetup()
       nsJSUtils::ReportPendingException(mCx);
     }
   }
 
   // To get our nesting right we have to destroy our JSAutoCompartment first.
   // But be careful: it might not have been constructed at all!
   mAc.destroyIfConstructed();
 
-  // XXXbz For that matter why do we need to manually call ScriptEvaluated at
-  // all?  nsCxPusher::Pop will do that nowadays if !mScriptIsRunning, so the
-  // concerns from bug 295983 don't seem relevant anymore.  Do we want to make
-  // sure it's still called when !mScriptIsRunning?  I guess play it safe for
-  // now and do what CallEventHandler did, which is call always.
-
-  // Popping an nsCxPusher is safe even if it never got pushed.
-  mCxPusher.Pop();
+  mAutoIncumbentScript.destroyIfConstructed();
+  mAutoEntryScript.destroyIfConstructed();
 
   // It is important that this is the last thing we do, after leaving the
-  // compartment and popping the context.
+  // compartment and undoing all our entry/incumbent script changes
   if (mIsMainThread) {
     nsContentUtils::LeaveMicroTask();
   }
 }
 
 already_AddRefed<nsISupports>
 CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback,
                                           const nsIID& aIID) const
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -19,18 +19,18 @@
 
 #include "nsISupports.h"
 #include "nsISupportsImpl.h"
 #include "nsCycleCollectionParticipant.h"
 #include "jswrapper.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/ScriptSettings.h"
 #include "nsContentUtils.h"
-#include "nsCxPusher.h"
 #include "nsWrapperCache.h"
 #include "nsJSEnvironment.h"
 #include "xpcpublic.h"
 
 namespace mozilla {
 namespace dom {
 
 #define DOM_CALLBACKOBJECT_IID \
@@ -40,19 +40,23 @@ namespace dom {
 class CallbackObject : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(DOM_CALLBACKOBJECT_IID)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackObject)
 
-  explicit CallbackObject(JSObject* aCallback)
+  // The caller may pass a global object which will act as an override for the
+  // incumbent script settings object when the callback is invoked (overriding
+  // the entry point computed from aCallback). If no override is required, the
+  // caller should pass null.
+  explicit CallbackObject(JS::Handle<JSObject*> aCallback, nsIGlobalObject *aIncumbentGlobal)
   {
-    Init(aCallback);
+    Init(aCallback, aIncumbentGlobal);
   }
 
   virtual ~CallbackObject()
   {
     DropCallback();
   }
 
   JS::Handle<JSObject*> Callback() const
@@ -71,69 +75,77 @@ public:
    */
   JS::Handle<JSObject*> CallbackPreserveColor() const
   {
     // Calling fromMarkedLocation() is safe because we trace our mCallback, and
     // because the value of mCallback cannot change after if has been set.
     return JS::Handle<JSObject*>::fromMarkedLocation(mCallback.address());
   }
 
+  nsIGlobalObject* IncumbentGlobalOrNull() const
+  {
+    return mIncumbentGlobal;
+  }
+
   enum ExceptionHandling {
     // Report any exception and don't throw it to the caller code.
     eReportExceptions,
     // Throw an exception to the caller code if the thrown exception is a
     // binding object for a DOMError from the caller's scope, otherwise report
     // it.
     eRethrowContentExceptions,
     // Throw any exception to the caller code.
     eRethrowExceptions
   };
 
 protected:
   explicit CallbackObject(CallbackObject* aCallbackObject)
   {
-    Init(aCallbackObject->mCallback);
+    Init(aCallbackObject->mCallback, aCallbackObject->mIncumbentGlobal);
   }
 
 private:
-  inline void Init(JSObject* aCallback)
+  inline void Init(JSObject* aCallback, nsIGlobalObject* aIncumbentGlobal)
   {
     MOZ_ASSERT(aCallback && !mCallback);
     // Set mCallback before we hold, on the off chance that a GC could somehow
     // happen in there... (which would be pretty odd, granted).
     mCallback = aCallback;
     mozilla::HoldJSObjects(this);
+
+    mIncumbentGlobal = aIncumbentGlobal;
   }
 
   CallbackObject(const CallbackObject&) MOZ_DELETE;
   CallbackObject& operator =(const CallbackObject&) MOZ_DELETE;
 
 protected:
   void DropCallback()
   {
     if (mCallback) {
       mCallback = nullptr;
       mozilla::DropJSObjects(this);
     }
   }
 
   JS::Heap<JSObject*> mCallback;
+  nsCOMPtr<nsIGlobalObject> mIncumbentGlobal;
 
   class MOZ_STACK_CLASS CallSetup
   {
     /**
      * A class that performs whatever setup we need to safely make a
      * call while this class is on the stack, After the constructor
      * returns, the call is safe to make if GetContext() returns
      * non-null.
      */
   public:
     // If aExceptionHandling == eRethrowContentExceptions then aCompartment
     // needs to be set to the caller's compartment.
-    CallSetup(JS::Handle<JSObject*> aCallable, ErrorResult& aRv,
+    CallSetup(CallbackObject* aCallback, ErrorResult& aRv,
               ExceptionHandling aExceptionHandling,
               JSCompartment* aCompartment = nullptr);
     ~CallSetup();
 
     JSContext* GetContext() const
     {
       return mCx;
     }
@@ -147,27 +159,27 @@ protected:
     // Members which can go away whenever
     JSContext* mCx;
 
     // Caller's compartment. This will only have a sensible value if
     // mExceptionHandling == eRethrowContentExceptions.
     JSCompartment* mCompartment;
 
     // And now members whose construction/destruction order we need to control.
-
-    nsCxPusher mCxPusher;
+    Maybe<AutoEntryScript> mAutoEntryScript;
+    Maybe<AutoIncumbentScript> mAutoIncumbentScript;
 
     // Constructed the rooter within the scope of mCxPusher above, so that it's
     // always within a request during its lifetime.
     Maybe<JS::Rooted<JSObject*> > mRootedCallable;
 
     // Can't construct a JSAutoCompartment without a JSContext either.  Also,
-    // Put mAc after mCxPusher so that we exit the compartment before we pop the
-    // JSContext.  Though in practice we'll often manually order those two
-    // things.
+    // Put mAc after mAutoEntryScript so that we exit the compartment before
+    // we pop the JSContext. Though in practice we'll often manually order
+    // those two things.
     Maybe<JSAutoCompartment> mAc;
 
     // An ErrorResult to possibly re-throw exceptions on and whether
     // we should re-throw them.
     ErrorResult& mErrorResult;
     const ExceptionHandling mExceptionHandling;
     JS::ContextOptions mSavedJSContextOptions;
     const bool mIsMainThread;
@@ -331,38 +343,17 @@ public:
 
   // Try to return a WebIDLCallbackT version of this object.
   already_AddRefed<WebIDLCallbackT> ToWebIDLCallback() const
   {
     if (HasWebIDLCallback()) {
       nsRefPtr<WebIDLCallbackT> callback = GetWebIDLCallback();
       return callback.forget();
     }
-
-    XPCOMCallbackT* callback = GetXPCOMCallback();
-    if (!callback) {
-      return nullptr;
-    }
-
-    nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = do_QueryInterface(callback);
-    if (!wrappedJS) {
-      return nullptr;
-    }
-
-    AutoSafeJSContext cx;
-
-    JS::Rooted<JSObject*> obj(cx, wrappedJS->GetJSObject());
-    if (!obj) {
-      return nullptr;
-    }
-
-    JSAutoCompartment ac(cx, obj);
-
-    nsRefPtr<WebIDLCallbackT> newCallback = new WebIDLCallbackT(obj);
-    return newCallback.forget();
+    return nullptr;
   }
 
 private:
   static const uintptr_t XPCOMCallbackFlag = 1u;
 
   friend void
   ImplCycleCollectionUnlink<WebIDLCallbackT,
                             XPCOMCallbackT>(CallbackObjectHolder& aField);
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3391,17 +3391,20 @@ for (uint32_t i = 0; i < length; ++i) {
 
         if descriptor.interface.isCallback():
             name = descriptor.interface.identifier.name
             if type.nullable() or isCallbackReturnValue:
                 declType = CGGeneric("nsRefPtr<%s>" % name);
             else:
                 declType = CGGeneric("OwningNonNull<%s>" % name)
             conversion = (
-                "${declName} = new %s(&${val}.toObject());\n" % name)
+                "{ // Scope for tempRoot\n"
+                "  JS::Rooted<JSObject*> tempRoot(cx, &${val}.toObject());\n"
+                "  ${declName} = new %s(tempRoot, mozilla::dom::GetIncumbentGlobal());\n"
+                "}" % name)
 
             template = wrapObjectTemplate(conversion, type,
                                           "${declName} = nullptr",
                                           failureCode)
             return JSToNativeConversionInfo(template, declType=declType,
                                             dealWithOptional=isOptional)
 
         # This is an interface that we implement as a concrete class
@@ -3724,17 +3727,20 @@ for (uint32_t i = 0; i < length; ++i) {
         assert not type.treatNonCallableAsNull() or type.nullable()
 
         name = type.unroll().identifier.name
         if type.nullable():
             declType = CGGeneric("nsRefPtr<%s>" % name);
         else:
             declType = CGGeneric("OwningNonNull<%s>" % name)
         conversion = (
-            "  ${declName} = new %s(&${val}.toObject());\n" % name)
+            "{ // Scope for tempRoot\n"
+            "  JS::Rooted<JSObject*> tempRoot(cx, &${val}.toObject());\n"
+            "  ${declName} = new %s(tempRoot, mozilla::dom::GetIncumbentGlobal());\n"
+            "}\n" % name)
 
         if allowTreatNonCallableAsNull and type.treatNonCallableAsNull():
             haveCallable = "JS_ObjectIsCallable(cx, &${val}.toObject())"
             if not isDefinitelyObject:
                 haveCallable = "${val}.isObject() && " + haveCallable
             if defaultValue is not None:
                 assert(isinstance(defaultValue, IDLNullValue))
                 haveCallable = "${haveValue} && " + haveCallable
@@ -10544,17 +10550,17 @@ class CGJSImplClass(CGBindingImplClass):
         if descriptor.interface.hasChildInterfaces():
             decorators = ""
             # We need a public virtual destructor our subclasses can use
             destructor = ClassDestructor(virtual=True, visibility="public")
         else:
             decorators = "MOZ_FINAL"
             destructor = None
 
-        baseConstructors=["mImpl(new %s(aJSImplObject))" % jsImplName(descriptor.name),
+        baseConstructors=["mImpl(new %s(aJSImplObject, /* aIncumbentGlobal = */ nullptr))" % jsImplName(descriptor.name),
                           "mParent(aParent)"]
         parentInterface = descriptor.interface.parent
         while parentInterface:
             if parentInterface.isJSImplemented():
                 baseConstructors.insert(
                     0, "%s(aJSImplObject, aParent)" % parentClass )
                 break
             parentInterface = parentInterface.parent
@@ -10671,22 +10677,22 @@ class CGCallback(CGClass):
                 realMethods.extend(self.getMethodImpls(method))
         CGClass.__init__(self, name,
                          bases=[ClassBase(baseName)],
                          constructors=self.getConstructors(),
                          methods=realMethods+getters+setters)
 
     def getConstructors(self):
         return [ClassConstructor(
-            [Argument("JSObject*", "aCallback")],
+            [Argument("JS::Handle<JSObject*>", "aCallback"), Argument("nsIGlobalObject*", "aIncumbentGlobal")],
             bodyInHeader=True,
             visibility="public",
             explicit=True,
             baseConstructors=[
-                "%s(aCallback)" % self.baseName
+                "%s(aCallback, aIncumbentGlobal)" % self.baseName,
                 ])]
 
     def getMethodImpls(self, method):
         assert method.needThisHandling
         args = list(method.args)
         # Strip out the JSContext*/JSObject* args
         # that got added.
         assert args[0].name == "cx" and args[0].argType == "JSContext*"
@@ -10701,17 +10707,17 @@ class CGCallback(CGClass):
         # method, insert our optional argument for deciding whether the
         # CallSetup should re-throw exceptions on aRv.
         args.append(Argument("ExceptionHandling", "aExceptionHandling",
                              "eReportExceptions"))
         # And now insert our template argument.
         argsWithoutThis = list(args)
         args.insert(0, Argument("const T&",  "thisObj"))
 
-        setupCall = ("CallSetup s(CallbackPreserveColor(), aRv, aExceptionHandling);\n"
+        setupCall = ("CallSetup s(this, aRv, aExceptionHandling);\n"
                      "if (!s.GetContext()) {\n"
                      "  aRv.Throw(NS_ERROR_UNEXPECTED);\n"
                      "  return${errorReturn};\n"
                      "}\n")
 
         bodyWithThis = string.Template(
             setupCall+
             "JS::Rooted<JSObject*> thisObjJS(s.GetContext(),\n"
@@ -10991,17 +10997,17 @@ class CallbackMember(CGNativeMember):
         # well as a JSContext.
         return [Argument("JSContext*", "cx"),
                 Argument("JS::Handle<JSObject*>", "aThisObj")] + args
 
     def getCallSetup(self):
         if self.needThisHandling:
             # It's been done for us already
             return ""
-        callSetup = "CallSetup s(CallbackPreserveColor(), aRv"
+        callSetup = "CallSetup s(this, aRv"
         if self.rethrowContentException:
             # getArgs doesn't add the aExceptionHandling argument but does add
             # aCompartment for us.
             callSetup += ", eRethrowContentExceptions, aCompartment"
         else:
             callSetup += ", aExceptionHandling"
         callSetup += ");"
         return string.Template(
--- a/dom/bluetooth/ObexBase.cpp
+++ b/dom/bluetooth/ObexBase.cpp
@@ -4,39 +4,43 @@
  * 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/. */
 
 #include "ObexBase.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 int
-AppendHeaderName(uint8_t* aRetBuf, const char* aName, int aLength)
+AppendHeaderName(uint8_t* aRetBuf, int aBufferSize, const char* aName,
+                 int aLength)
 {
   int headerLength = aLength + 3;
 
   aRetBuf[0] = ObexHeaderId::Name;
   aRetBuf[1] = (headerLength & 0xFF00) >> 8;
   aRetBuf[2] = headerLength & 0x00FF;
 
-  memcpy(&aRetBuf[3], aName, aLength);
+  memcpy(&aRetBuf[3], aName, (aLength < aBufferSize - 3)? aLength
+                                                        : aBufferSize - 3);
 
   return headerLength;
 }
 
 int
-AppendHeaderBody(uint8_t* aRetBuf, uint8_t* aData, int aLength)
+AppendHeaderBody(uint8_t* aRetBuf, int aBufferSize, const uint8_t* aData,
+                 int aLength)
 {
   int headerLength = aLength + 3;
 
   aRetBuf[0] = ObexHeaderId::Body;
   aRetBuf[1] = (headerLength & 0xFF00) >> 8;
   aRetBuf[2] = headerLength & 0x00FF;
 
-  memcpy(&aRetBuf[3], aData, aLength);
+  memcpy(&aRetBuf[3], aData, (aLength < aBufferSize - 3)? aLength
+                                                        : aBufferSize - 3);
 
   return headerLength;
 }
 
 int
 AppendHeaderEndOfBody(uint8_t* aRetBuf)
 {
   aRetBuf[0] = ObexHeaderId::EndOfBody;
--- a/dom/bluetooth/ObexBase.h
+++ b/dom/bluetooth/ObexBase.h
@@ -246,18 +246,20 @@ public:
     mHeaders.Clear();
   }
 
 private:
   uint8_t mOpcode;
   nsTArray<nsAutoPtr<ObexHeader> > mHeaders;
 };
 
-int AppendHeaderName(uint8_t* aRetBuf, const char* aName, int aLength);
-int AppendHeaderBody(uint8_t* aRetBuf, uint8_t* aData, int aLength);
+int AppendHeaderName(uint8_t* aRetBuf, int aBufferSize, const char* aName,
+                     int aLength);
+int AppendHeaderBody(uint8_t* aRetBuf, int aBufferSize, const uint8_t* aData,
+                     int aLength);
 int AppendHeaderEndOfBody(uint8_t* aRetBuf);
 int AppendHeaderLength(uint8_t* aRetBuf, int aObjectLength);
 int AppendHeaderConnectionId(uint8_t* aRetBuf, int aConnectionId);
 void SetObexPacketInfo(uint8_t* aRetBuf, uint8_t aOpcode, int aPacketLength);
 
 /**
  * @return true when the message was parsed without any error, false otherwise.
  */
--- a/dom/bluetooth/bluedroid/BluetoothOppManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp
@@ -41,16 +41,23 @@ namespace {
 static const uint32_t kUpdateProgressBase = 50 * 1024;
 
 /*
  * The format of the header of an PUT request is
  * [opcode:1][packet length:2][headerId:1][header length:2]
  */
 static const uint32_t kPutRequestHeaderSize = 6;
 
+/*
+ * The format of the appended header of an PUT request is
+ * [headerId:1][header length:4]
+ * P.S. Length of name header is 4 since unicode is 2 bytes per char.
+ */
+static const uint32_t kPutRequestAppendHeaderSize = 5;
+
 StaticRefPtr<BluetoothOppManager> sBluetoothOppManager;
 static bool sInShutdown = false;
 }
 
 class mozilla::dom::bluetooth::SendFileBatch {
 public:
   SendFileBatch(const nsAString& aDeviceAddress, BlobParent* aActor)
     : mDeviceAddress(aDeviceAddress)
@@ -965,16 +972,25 @@ BluetoothOppManager::ClientDataHandler(U
     AfterOppConnected();
 
     // Keep remote information
     mRemoteObexVersion = aMessage->mData[3];
     mRemoteConnectionFlags = aMessage->mData[4];
     mRemoteMaxPacketLength =
       (((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]);
 
+    // The length of file name exceeds maximum length.
+    int fileNameByteLen = (mFileName.Length() + 1) * 2;
+    int headerLen = kPutRequestHeaderSize + kPutRequestAppendHeaderSize;
+    if (fileNameByteLen > mRemoteMaxPacketLength - headerLen) {
+      BT_WARNING("The length of file name is aberrant.");
+      SendDisconnectRequest();
+      return;
+    }
+
     SendPutHeaderRequest(mFileName, mFileLength);
   } else if (mLastCommand == ObexRequestCode::Put) {
     if (mWaitingToSendPutFinal) {
       SendPutFinalRequest();
       return;
     }
 
     if (kUpdateProgressBase * mUpdateProgressCounter < mSentFileLength) {
@@ -1051,43 +1067,44 @@ BluetoothOppManager::SendPutHeaderReques
     fileName[i * 2] = (uint8_t)(fileNamePtr[i] >> 8);
     fileName[i * 2 + 1] = (uint8_t)fileNamePtr[i];
   }
 
   fileName[len * 2] = 0x00;
   fileName[len * 2 + 1] = 0x00;
 
   int index = 3;
-  index += AppendHeaderName(&req[index], (char*)fileName, (len + 1) * 2);
+  index += AppendHeaderName(&req[index], mRemoteMaxPacketLength - index,
+                            (char*)fileName, (len + 1) * 2);
   index += AppendHeaderLength(&req[index], aFileSize);
 
   SendObexData(req, ObexRequestCode::Put, index);
 
   delete [] fileName;
   delete [] req;
 }
 
 void
 BluetoothOppManager::SendPutRequest(uint8_t* aFileBody,
                                     int aFileBodyLength)
 {
+  if (!mConnected) return;
   int packetLeftSpace = mRemoteMaxPacketLength - kPutRequestHeaderSize;
-
-  if (!mConnected) return;
   if (aFileBodyLength > packetLeftSpace) {
     BT_WARNING("Not allowed such a small MaxPacketLength value");
     return;
   }
 
   // Section 3.3.3 "Put", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
 
   int index = 3;
-  index += AppendHeaderBody(&req[index], aFileBody, aFileBodyLength);
+  index += AppendHeaderBody(&req[index], mRemoteMaxPacketLength - index,
+                            aFileBody, aFileBodyLength);
 
   SendObexData(req, ObexRequestCode::Put, index);
   delete [] req;
 
   mSentFileLength += aFileBodyLength;
 }
 
 void
--- a/dom/bluetooth/bluez/BluetoothOppManager.cpp
+++ b/dom/bluetooth/bluez/BluetoothOppManager.cpp
@@ -41,16 +41,23 @@ namespace {
 static const uint32_t kUpdateProgressBase = 50 * 1024;
 
 /*
  * The format of the header of an PUT request is
  * [opcode:1][packet length:2][headerId:1][header length:2]
  */
 static const uint32_t kPutRequestHeaderSize = 6;
 
+/*
+ * The format of the appended header of an PUT request is
+ * [headerId:1][header length:4]
+ * P.S. Length of name header is 4 since unicode is 2 bytes per char.
+ */
+static const uint32_t kPutRequestAppendHeaderSize = 5;
+
 StaticRefPtr<BluetoothOppManager> sBluetoothOppManager;
 static bool sInShutdown = false;
 }
 
 class mozilla::dom::bluetooth::SendFileBatch {
 public:
   SendFileBatch(const nsAString& aDeviceAddress, BlobParent* aActor)
     : mDeviceAddress(aDeviceAddress)
@@ -979,16 +986,25 @@ BluetoothOppManager::ClientDataHandler(U
     AfterOppConnected();
 
     // Keep remote information
     mRemoteObexVersion = aMessage->mData[3];
     mRemoteConnectionFlags = aMessage->mData[4];
     mRemoteMaxPacketLength =
       (((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]);
 
+    // The length of file name exceeds maximum length.
+    int fileNameByteLen = (mFileName.Length() + 1) * 2;
+    int headerLen = kPutRequestHeaderSize + kPutRequestAppendHeaderSize;
+    if (fileNameByteLen > mRemoteMaxPacketLength - headerLen) {
+      BT_WARNING("The length of file name is aberrant.");
+      SendDisconnectRequest();
+      return;
+    }
+
     SendPutHeaderRequest(mFileName, mFileLength);
   } else if (mLastCommand == ObexRequestCode::Put) {
     if (mWaitingToSendPutFinal) {
       SendPutFinalRequest();
       return;
     }
 
     if (kUpdateProgressBase * mUpdateProgressCounter < mSentFileLength) {
@@ -1065,47 +1081,47 @@ BluetoothOppManager::SendPutHeaderReques
     fileName[i * 2] = (uint8_t)(fileNamePtr[i] >> 8);
     fileName[i * 2 + 1] = (uint8_t)fileNamePtr[i];
   }
 
   fileName[len * 2] = 0x00;
   fileName[len * 2 + 1] = 0x00;
 
   int index = 3;
-  index += AppendHeaderName(&req[index], (char*)fileName, (len + 1) * 2);
+  index += AppendHeaderName(&req[index], mRemoteMaxPacketLength - index,
+                            (char*)fileName, (len + 1) * 2);
   index += AppendHeaderLength(&req[index], aFileSize);
 
   SendObexData(req, ObexRequestCode::Put, index);
 
   delete [] fileName;
   delete [] req;
 }
 
 void
 BluetoothOppManager::SendPutRequest(uint8_t* aFileBody,
                                     int aFileBodyLength)
 {
+  if (!mConnected) return;
   int packetLeftSpace = mRemoteMaxPacketLength - kPutRequestHeaderSize;
-
-  if (!mConnected) return;
   if (aFileBodyLength > packetLeftSpace) {
     BT_WARNING("Not allowed such a small MaxPacketLength value");
     return;
   }
 
   // Section 3.3.3 "Put", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
 
   int index = 3;
-  index += AppendHeaderBody(&req[index], aFileBody, aFileBodyLength);
+  index += AppendHeaderBody(&req[index], mRemoteMaxPacketLength - index,
+                            aFileBody, aFileBodyLength);
 
   SendObexData(req, ObexRequestCode::Put, index);
   delete [] req;
-
   mSentFileLength += aFileBodyLength;
 }
 
 void
 BluetoothOppManager::SendPutFinalRequest()
 {
   if (!mConnected) return;
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -342,16 +342,25 @@ child:
     /**
      * Requests handling of a long tap. |point| is in CSS pixels, relative to
      * the scroll offset. This message is expected to send a "contextmenu"
      * events at this point.
      */
     HandleLongTap(CSSIntPoint point);
 
     /**
+     * Requests handling of releasing a long tap. |aPoint| is in CSS pixels,
+     * relative to the current scroll offset. In the case the "contextmenu"
+     * event generated by the preceding HandleLongTap call was not handled,
+     * this message is expected to generate a "mousedown" and "mouseup"
+     * series of events
+     */
+    HandleLongTapUp(CSSIntPoint point);
+
+    /**
      * Notifies the child that the parent has begun or finished transforming
      * the visible child content area. Useful for showing/hiding scrollbars.
      */
     NotifyTransformBegin(ViewID aViewId);
     NotifyTransformEnd(ViewID aViewId);
 
     /**
      * Sending an activate message moves focus to the child.
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -280,16 +280,17 @@ TabChild::TabChild(ContentChild* aManage
   , mOldViewportWidth(0.0f)
   , mLastBackgroundColor(NS_RGB(255, 255, 255))
   , mDidFakeShow(false)
   , mNotified(false)
   , mContentDocumentIsDisplayed(false)
   , mTriedBrowserInit(false)
   , mOrientation(eScreenOrientation_PortraitPrimary)
   , mUpdateHitRegion(false)
+  , mContextMenuHandled(false)
 {
 }
 
 NS_IMETHODIMP
 TabChild::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsAutoString eventType;
   aEvent->GetType(eventType);
@@ -1656,16 +1657,28 @@ TabChild::RecvHandleLongTap(const CSSInt
 
   DispatchMouseEvent(NS_LITERAL_STRING("contextmenu"), aPoint, 2, 1, 0, false,
                      nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
 
   return true;
 }
 
 bool
+TabChild::RecvHandleLongTapUp(const CSSIntPoint& aPoint)
+{
+  if (mContextMenuHandled) {
+    mContextMenuHandled = false;
+    return true;
+  }
+
+  RecvHandleSingleTap(aPoint);
+  return true;
+}
+
+bool
 TabChild::RecvNotifyTransformBegin(const ViewID& aViewId)
 {
   nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
   if (sf) {
     nsIScrollbarOwner* scrollbarOwner = do_QueryFrame(sf);
     if (scrollbarOwner) {
       scrollbarOwner->ScrollbarActivityStarted();
     }
@@ -2513,16 +2526,17 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED_1(Tab
                                      mMessageManager)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TabChildGlobal)
   NS_INTERFACE_MAP_ENTRY(nsIMessageListenerManager)
   NS_INTERFACE_MAP_ENTRY(nsIMessageSender)
   NS_INTERFACE_MAP_ENTRY(nsISyncMessageSender)
   NS_INTERFACE_MAP_ENTRY(nsIContentFrameMessageManager)
   NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
+  NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(ContentFrameMessageManager)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(TabChildGlobal, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(TabChildGlobal, nsDOMEventTargetHelper)
 
 /* [notxpcom] boolean markForCC (); */
 // This method isn't automatically forwarded safely because it's notxpcom, so
@@ -2577,15 +2591,25 @@ TabChildGlobal::Atob(const nsAString& aA
 }
 
 JSContext*
 TabChildGlobal::GetJSContextForEventHandlers()
 {
   return nsContentUtils::GetSafeJSContext();
 }
 
-nsIPrincipal* 
+nsIPrincipal*
 TabChildGlobal::GetPrincipal()
 {
   if (!mTabChild)
     return nullptr;
   return mTabChild->GetPrincipal();
 }
+
+JSObject*
+TabChildGlobal::GetGlobalJSObject()
+{
+  NS_ENSURE_TRUE(mTabChild, nullptr);
+  nsCOMPtr<nsIXPConnectJSObjectHolder> ref = mTabChild->GetGlobal();
+  NS_ENSURE_TRUE(ref, nullptr);
+  return ref->GetJSObject();
+}
+
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -46,17 +46,18 @@ class RenderFrameChild;
 namespace dom {
 
 class TabChild;
 class PContentDialogChild;
 class ClonedMessageData;
 
 class TabChildGlobal : public nsDOMEventTargetHelper,
                        public nsIContentFrameMessageManager,
-                       public nsIScriptObjectPrincipal
+                       public nsIScriptObjectPrincipal,
+                       public nsIGlobalObject
 {
 public:
   TabChildGlobal(TabChild* aTabChild);
   void Init();
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TabChildGlobal, nsDOMEventTargetHelper)
   NS_FORWARD_SAFE_NSIMESSAGELISTENERMANAGER(mMessageManager)
   NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
@@ -122,16 +123,17 @@ public:
   PreHandleEvent(nsEventChainPreVisitor& aVisitor)
   {
     aVisitor.mForceContentDispatch = true;
     return NS_OK;
   }
 
   virtual JSContext* GetJSContextForEventHandlers() MOZ_OVERRIDE;
   virtual nsIPrincipal* GetPrincipal() MOZ_OVERRIDE;
+  virtual JSObject* GetGlobalJSObject() MOZ_OVERRIDE;
 
   nsCOMPtr<nsIContentFrameMessageManager> mMessageManager;
   TabChild* mTabChild;
 };
 
 class ContentListener MOZ_FINAL : public nsIDOMEventListener
 {
 public:
@@ -212,16 +214,17 @@ public:
                                          const FileDescriptor& aFileDescriptor)
                                          MOZ_OVERRIDE;
     virtual bool RecvShow(const nsIntSize& size);
     virtual bool RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size, const ScreenOrientation& orientation);
     virtual bool RecvUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics);
     virtual bool RecvHandleDoubleTap(const CSSIntPoint& aPoint);
     virtual bool RecvHandleSingleTap(const CSSIntPoint& aPoint);
     virtual bool RecvHandleLongTap(const CSSIntPoint& aPoint);
+    virtual bool RecvHandleLongTapUp(const CSSIntPoint& aPoint);
     virtual bool RecvNotifyTransformBegin(const ViewID& aViewId);
     virtual bool RecvNotifyTransformEnd(const ViewID& aViewId);
     virtual bool RecvActivate();
     virtual bool RecvDeactivate();
     virtual bool RecvMouseEvent(const nsString& aType,
                                 const float&    aX,
                                 const float&    aY,
                                 const int32_t&  aButton,
@@ -491,16 +494,17 @@ private:
     nscolor mLastBackgroundColor;
     ScrollingBehavior mScrolling;
     bool mDidFakeShow;
     bool mNotified;
     bool mContentDocumentIsDisplayed;
     bool mTriedBrowserInit;
     ScreenOrientation mOrientation;
     bool mUpdateHitRegion;
+    bool mContextMenuHandled;
 
     DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 }
 }
 
 #endif // mozilla_dom_TabChild_h
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -516,16 +516,23 @@ void TabParent::HandleSingleTap(const CS
 
 void TabParent::HandleLongTap(const CSSIntPoint& aPoint, int32_t aModifiers)
 {
   if (!mIsDestroyed) {
     unused << SendHandleLongTap(aPoint);
   }
 }
 
+void TabParent::HandleLongTapUp(const CSSIntPoint& aPoint, int32_t aModifiers)
+{
+  if (!mIsDestroyed) {
+    unused << SendHandleLongTapUp(aPoint);
+  }
+}
+
 void TabParent::NotifyTransformBegin(ViewID aViewId)
 {
   if (!mIsDestroyed) {
     unused << SendNotifyTransformBegin(aViewId);
   }
 }
 
 void TabParent::NotifyTransformEnd(ViewID aViewId)
@@ -708,16 +715,25 @@ bool TabParent::SendHandleLongTap(const 
 {
   if (mIsDestroyed) {
     return false;
   }
 
   return PBrowserParent::SendHandleLongTap(AdjustTapToChildWidget(aPoint));
 }
 
+bool TabParent::SendHandleLongTapUp(const CSSIntPoint& aPoint)
+{
+  if (mIsDestroyed) {
+    return false;
+  }
+
+  return PBrowserParent::SendHandleLongTapUp(AdjustTapToChildWidget(aPoint));
+}
+
 bool TabParent::SendHandleDoubleTap(const CSSIntPoint& aPoint)
 {
   if (mIsDestroyed) {
     return false;
   }
 
   return PBrowserParent::SendHandleDoubleTap(AdjustTapToChildWidget(aPoint));
 }
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -191,16 +191,17 @@ public:
     // message-sending functions under a layer of indirection and
     // eating the return values
     void Show(const nsIntSize& size);
     void UpdateDimensions(const nsRect& rect, const nsIntSize& size);
     void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
     void HandleDoubleTap(const CSSIntPoint& aPoint, int32_t aModifiers);
     void HandleSingleTap(const CSSIntPoint& aPoint, int32_t aModifiers);
     void HandleLongTap(const CSSIntPoint& aPoint, int32_t aModifiers);
+    void HandleLongTapUp(const CSSIntPoint& aPoint, int32_t aModifiers);
     void NotifyTransformBegin(ViewID aViewId);
     void NotifyTransformEnd(ViewID aViewId);
     void Activate();
     void Deactivate();
 
     bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent);
     void MapEventCoordinatesForChildProcess(const LayoutDeviceIntPoint& aOffset,
                                             mozilla::WidgetEvent* aEvent);
@@ -212,16 +213,17 @@ public:
                       int32_t aCharCode, int32_t aModifiers,
                       bool aPreventDefault);
     bool SendRealMouseEvent(mozilla::WidgetMouseEvent& event);
     bool SendMouseWheelEvent(mozilla::WidgetWheelEvent& event);
     bool SendRealKeyEvent(mozilla::WidgetKeyboardEvent& event);
     bool SendRealTouchEvent(WidgetTouchEvent& event);
     bool SendHandleSingleTap(const CSSIntPoint& aPoint);
     bool SendHandleLongTap(const CSSIntPoint& aPoint);
+    bool SendHandleLongTapUp(const CSSIntPoint& aPoint);
     bool SendHandleDoubleTap(const CSSIntPoint& aPoint);
 
     virtual PDocumentRendererParent*
     AllocPDocumentRendererParent(const nsRect& documentRect, const gfxMatrix& transform,
                                  const nsString& bgcolor,
                                  const uint32_t& renderFlags, const bool& flushLayout,
                                  const nsIntSize& renderSize);
     virtual bool DeallocPDocumentRendererParent(PDocumentRendererParent* actor);
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -139,8 +139,19 @@ PESupportsConditionExpectedCloseParen=Ex
 PESupportsConditionExpectedStart2=Expected 'not', '(', or function while parsing supports condition but found '%1$S'.
 PESupportsConditionExpectedNot=Expected 'not' while parsing supports condition but found '%1$S'.
 PESupportsGroupRuleStart=Expected '{' to begin @supports rule but found '%1$S'.
 PEFilterEOF=filter
 PEExpectedNoneOrURL=Expected 'none' or URL but found '%1$S'.
 PEExpectedNoneOrURLOrFilterFunction=Expected 'none', URL, or filter function but found '%1$S'.
 PEExpectedNonnegativeNP=Expected non-negative number or percentage.
 PEFilterFunctionArgumentsParsingError=Error in parsing arguments for filter function.
+PEVariableEOF=variable
+PEVariableEmpty=Expected variable value but found '%1$S'.
+PEValueWithVariablesParsingError=Error in parsing value for '%1$S' after substituting variables.
+PEValueWithVariablesFallbackInherit=Falling back to 'inherit'.
+PEValueWithVariablesFallbackInitial=Falling back to 'initial'.
+PEInvalidVariableReference=Property contained reference to invalid variable.
+PEInvalidVariableTokenFallback=Found invalid token '%1$S' at top level of variable reference fallback.
+PEExpectedVariableNameEOF=identifier for variable name
+PEExpectedVariableName=Expected identifier for variable name but found '%1$S'.
+PEExpectedVariableFallback=Expected variable reference fallback after ','.
+PEExpectedVariableCommaOrCloseParen=Expected ',' or ')' after variable name in variable reference but found '%1$S'.
copy from dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
copy to dom/mobilemessage/src/gonk/MobileMessageDB.jsm
--- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
@@ -9,30 +9,25 @@ const {classes: Cc, interfaces: Ci, util
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
 Cu.importGlobalProperties(["indexedDB"]);
 
 var RIL = {};
 Cu.import("resource://gre/modules/ril_consts.js", RIL);
 
-const RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID =
-  "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1";
-const RIL_MOBILEMESSAGEDATABASESERVICE_CID =
-  Components.ID("{29785f90-6b5b-11e2-9201-3b280170b2ec}");
 const RIL_GETMESSAGESCURSOR_CID =
   Components.ID("{484d1ad8-840e-4782-9dc4-9ebc4d914937}");
 const RIL_GETTHREADSCURSOR_CID =
   Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}");
 
 const DEBUG = false;
 const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
 
 
-const DB_NAME = "sms";
 const DB_VERSION = 20;
 const MESSAGE_STORE_NAME = "sms";
 const THREAD_STORE_NAME = "thread";
 const PARTICIPANT_STORE_NAME = "participant";
 const MOST_RECENT_STORE_NAME = "most-recent";
 
 const DELIVERY_SENDING = "sending";
 const DELIVERY_SENT = "sent";
@@ -76,75 +71,34 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 XPCOMUtils.defineLazyGetter(this, "MMS", function () {
   let MMS = {};
   Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
   return MMS;
 });
 
 /**
- * MobileMessageDatabaseService
+ * MobileMessageDB
  */
-function MobileMessageDatabaseService() {
-  // Prime the directory service's cache to ensure that the ProfD entry exists
-  // by the time IndexedDB queries for it off the main thread. (See bug 743635.)
-  Services.dirsvc.get("ProfD", Ci.nsIFile);
-
-  let that = this;
-  this.newTxn(READ_ONLY, function(error, txn, messageStore){
-    if (error) {
-      return;
-    }
-    // In order to get the highest key value, we open a key cursor in reverse
-    // order and get only the first pointed value.
-    let request = messageStore.openCursor(null, PREV);
-    request.onsuccess = function onsuccess(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        if (DEBUG) {
-          debug("Could not get the last key from mobile message database. " +
-                "Probably empty database");
-        }
-        return;
-      }
-      that.lastMessageId = cursor.key || 0;
-      if (DEBUG) debug("Last assigned message ID was " + that.lastMessageId);
-    };
-    request.onerror = function onerror(event) {
-      if (DEBUG) {
-        debug("Could not get the last key from mobile message database " +
-              event.target.errorCode);
-      }
-    };
-  });
-  this.updatePendingTransactionToError();
-}
-MobileMessageDatabaseService.prototype = {
-
-  classID: RIL_MOBILEMESSAGEDATABASESERVICE_CID,
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRilMobileMessageDatabaseService,
-                                         Ci.nsIMobileMessageDatabaseService,
-                                         Ci.nsIObserver]),
+this.MobileMessageDB = function() {};
+MobileMessageDB.prototype = {
+  dbName: null,
+  dbVersion: null,
 
   /**
    * Cache the DB here.
    */
   db: null,
 
   /**
    * Last sms/mms object store key value in the database.
    */
   lastMessageId: 0,
 
   /**
-   * nsIObserver
-   */
-  observe: function observe() {},
-
-  /**
    * Prepare the database. This may include opening the database and upgrading
    * it to the latest schema version.
    *
    * @param callback
    *        Function that takes an error and db argument. It is called when
    *        the database is ready to use or if an error occurs while preparing
    *        the database.
    *
@@ -158,26 +112,26 @@ MobileMessageDatabaseService.prototype =
     }
 
     let self = this;
     function gotDB(db) {
       self.db = db;
       callback(null, db);
     }
 
-    let request = indexedDB.open(DB_NAME, DB_VERSION);
+    let request = indexedDB.open(this.dbName, this.dbVersion);
     request.onsuccess = function (event) {
-      if (DEBUG) debug("Opened database:", DB_NAME, DB_VERSION);
+      if (DEBUG) debug("Opened database:", self.dbName, self.dbVersion);
       gotDB(event.target.result);
     };
     request.onupgradeneeded = function (event) {
       if (DEBUG) {
-        debug("Database needs upgrade:", DB_NAME,
+        debug("Database needs upgrade:", self.dbName,
               event.oldVersion, event.newVersion);
-        debug("Correct new database version:", event.newVersion == DB_VERSION);
+        debug("Correct new database version:", event.newVersion == self.dbVersion);
       }
 
       let db = event.target.result;
 
       let currentVersion = event.oldVersion;
 
       function update(currentVersion) {
         let next = update.bind(self, currentVersion + 1);
@@ -330,25 +284,93 @@ MobileMessageDatabaseService.prototype =
           stores.push(txn.objectStore(storeName));
         }
       }
       callback(null, txn, stores);
     });
   },
 
   /**
+   * Initialize this MobileMessageDB.
+   *
+   * @param aDbName
+   *        A string name for that database.
+   * @param aDbVersion
+   *        The version that mmdb should upgrade to. 0 for the lastest version.
+   * @param aCallback
+   *        A function when either the initialization transaction is completed
+   *        or any error occurs.  Should take only one argument -- null when
+   *        initialized with success or the error object otherwise.
+   */
+  init: function init(aDbName, aDbVersion, aCallback) {
+    this.dbName = aDbName;
+    this.dbVersion = aDbVersion || DB_VERSION;
+
+    let self = this;
+    this.newTxn(READ_ONLY, function(error, txn, messageStore){
+      if (error) {
+        if (aCallback) {
+          aCallback(error);
+        }
+        return;
+      }
+
+      if (aCallback) {
+        txn.oncomplete = function() {
+          aCallback(null);
+        };
+      }
+
+      // In order to get the highest key value, we open a key cursor in reverse
+      // order and get only the first pointed value.
+      let request = messageStore.openCursor(null, PREV);
+      request.onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (!cursor) {
+          if (DEBUG) {
+            debug("Could not get the last key from mobile message database. " +
+                  "Probably empty database");
+          }
+          return;
+        }
+        self.lastMessageId = cursor.key || 0;
+        if (DEBUG) debug("Last assigned message ID was " + self.lastMessageId);
+      };
+      request.onerror = function onerror(event) {
+        if (DEBUG) {
+          debug("Could not get the last key from mobile message database " +
+                event.target.errorCode);
+        }
+      };
+    });
+  },
+
+  close: function close() {
+    if (!this.db) {
+      return;
+    }
+
+    this.db.close();
+    this.db = null;
+    this.lastMessageId = 0;
+  },
+
+  /**
    * Sometimes user might reboot or remove battery while sending/receiving
    * message. This is function set the status of message records to error.
    */
-  updatePendingTransactionToError: function updatePendingTransactionToError() {
+  updatePendingTransactionToError:
+    function updatePendingTransactionToError(aError) {
+    if (aError) {
+      return;
+    }
+
     this.newTxn(READ_WRITE, function (error, txn, messageStore) {
-      if (DEBUG) {
-        txn.onerror = function onerror(event) {
-          debug("updatePendingTransactionToError fail, event = " + event);
-        };
+      if (error) {
+        return;
       }
 
       let deliveryIndex = messageStore.index("delivery");
 
       // Set all 'delivery: sending' records to 'delivery: error' and 'deliveryStatus:
       // error'.
       let keyRange = IDBKeyRange.bound([DELIVERY_SENDING, 0], [DELIVERY_SENDING, ""]);
       let cursorRequestSending = deliveryIndex.openCursor(keyRange);
@@ -2592,32 +2614,32 @@ let FilterSearcherHelper = {
       range = IDBKeyRange.upperBound(endDate.getTime());
     }
     this.filterIndex("timestamp", range, direction, txn, collect);
   },
 
   /**
    * Instanciate a filtering transaction.
    *
-   * @param service
-   *        A MobileMessageDatabaseService. Used to create
+   * @param mmdb
+   *        A MobileMessageDB.
    * @param txn
    *        Ongoing IDBTransaction context object.
    * @param error
    *        Previous error while creating the transaction.
    * @param filter
    *        A SmsFilter object.
    * @param reverse
    *        A boolean value indicating whether we should filter message in
    *        reversed order.
    * @param collect
    *        Result colletor function. It takes three parameters -- txn, message
    *        id, and message timestamp.
    */
-  transact: function transact(service, txn, error, filter, reverse, collect) {
+  transact: function transact(mmdb, txn, error, filter, reverse, collect) {
     if (error) {
       //TODO look at event.target.errorCode, pick appropriate error constant.
       if (DEBUG) debug("IDBRequest error " + error.target.errorCode);
       collect(txn, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED);
       return;
     }
 
     let direction = reverse ? PREV : NEXT;
@@ -2697,19 +2719,19 @@ let FilterSearcherHelper = {
     if (filter.numbers) {
       if (DEBUG) debug("filter.numbers " + filter.numbers.join(", "));
 
       if (!single) {
         collect = intersectionCollector.newContext();
       }
 
       let participantStore = txn.objectStore(PARTICIPANT_STORE_NAME);
-      service.findParticipantIdsByAddresses(participantStore, filter.numbers,
-                                            false, true,
-                                            (function (participantIds) {
+      mmdb.findParticipantIdsByAddresses(participantStore, filter.numbers,
+                                         false, true,
+                                         (function (participantIds) {
         if (!participantIds || !participantIds.length) {
           // Oops! No such participant at all.
 
           collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
           return;
         }
 
         if (participantIds.length == 1) {
@@ -3022,42 +3044,42 @@ UnionResultsCollector.prototype = {
   },
 
   newContext: function newContext() {
     this.contexts[1].processing++;
     return this.collect.bind(this, 1);
   }
 };
 
-function GetMessagesCursor(service, callback) {
-  this.service = service;
+function GetMessagesCursor(mmdb, callback) {
+  this.mmdb = mmdb;
   this.callback = callback;
   this.collector = new ResultsCollector();
 
   this.handleContinue(); // Trigger first run.
 }
 GetMessagesCursor.prototype = {
   classID: RIL_GETMESSAGESCURSOR_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]),
 
-  service: null,
+  mmdb: null,
   callback: null,
   collector: null,
 
   getMessageTxn: function getMessageTxn(messageStore, messageId) {
     if (DEBUG) debug ("Fetching message " + messageId);
 
     let getRequest = messageStore.get(messageId);
     let self = this;
     getRequest.onsuccess = function onsuccess(event) {
       if (DEBUG) {
         debug("notifyNextMessageInListGot - messageId: " + messageId);
       }
       let domMessage =
-        self.service.createDomMessageFromRecord(event.target.result);
+        self.mmdb.createDomMessageFromRecord(event.target.result);
       self.callback.notifyCursorResult(domMessage);
     };
     getRequest.onerror = function onerror(event) {
       if (DEBUG) {
         debug("notifyCursorError - messageId: " + messageId);
       }
       self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
     };
@@ -3079,45 +3101,45 @@ GetMessagesCursor.prototype = {
     if (txn) {
       let messageStore = txn.objectStore(MESSAGE_STORE_NAME);
       this.getMessageTxn(messageStore, messageId);
       return;
     }
 
     // Or, we have to open another transaction ourselves.
     let self = this;
-    this.service.newTxn(READ_ONLY, function (error, txn, messageStore) {
+    this.mmdb.newTxn(READ_ONLY, function (error, txn, messageStore) {
       if (error) {
         self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
         return;
       }
       self.getMessageTxn(messageStore, messageId);
     }, [MESSAGE_STORE_NAME]);
   },
 
   // nsICursorContinueCallback
 
   handleContinue: function handleContinue() {
     if (DEBUG) debug("Getting next message in list");
     this.collector.squeeze(this.notify.bind(this));
   }
 };
 
-function GetThreadsCursor(service, callback) {
-  this.service = service;
+function GetThreadsCursor(mmdb, callback) {
+  this.mmdb = mmdb;
   this.callback = callback;
   this.collector = new ResultsCollector();
 
   this.handleContinue(); // Trigger first run.
 }
 GetThreadsCursor.prototype = {
   classID: RIL_GETTHREADSCURSOR_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]),
 
-  service: null,
+  mmdb: null,
   callback: null,
   collector: null,
 
   getThreadTxn: function getThreadTxn(threadStore, threadId) {
     if (DEBUG) debug ("Fetching thread " + threadId);
 
     let getRequest = threadStore.get(threadId);
     let self = this;
@@ -3160,30 +3182,32 @@ GetThreadsCursor.prototype = {
     if (txn) {
       let threadStore = txn.objectStore(THREAD_STORE_NAME);
       this.getThreadTxn(threadStore, threadId);
       return;
     }
 
     // Or, we have to open another transaction ourselves.
     let self = this;
-    this.service.newTxn(READ_ONLY, function (error, txn, threadStore) {
+    this.mmdb.newTxn(READ_ONLY, function (error, txn, threadStore) {
       if (error) {
         self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
         return;
       }
       self.getThreadTxn(threadStore, threadId);
     }, [THREAD_STORE_NAME]);
   },
 
   // nsICursorContinueCallback
 
   handleContinue: function handleContinue() {
     if (DEBUG) debug("Getting next thread in list");
     this.collector.squeeze(this.notify.bind(this));
   }
 }
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MobileMessageDatabaseService]);
+this.EXPORTED_SYMBOLS = [
+  'MobileMessageDB'
+];
 
 function debug() {
-  dump("MobileMessageDatabaseService: " + Array.slice(arguments).join(" ") + "\n");
+  dump("MobileMessageDB: " + Array.slice(arguments).join(" ") + "\n");
 }
--- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
@@ -3,3187 +3,114 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
-Cu.importGlobalProperties(["indexedDB"]);
 
-var RIL = {};
-Cu.import("resource://gre/modules/ril_consts.js", RIL);
+let MMDB = {};
+Cu.import("resource://gre/modules/MobileMessageDB.jsm", MMDB);
 
 const RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID =
   "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1";
 const RIL_MOBILEMESSAGEDATABASESERVICE_CID =
   Components.ID("{29785f90-6b5b-11e2-9201-3b280170b2ec}");
-const RIL_GETMESSAGESCURSOR_CID =
-  Components.ID("{484d1ad8-840e-4782-9dc4-9ebc4d914937}");
-const RIL_GETTHREADSCURSOR_CID =
-  Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}");
-
-const DEBUG = false;
-const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
-
 
 const DB_NAME = "sms";
-const DB_VERSION = 20;
-const MESSAGE_STORE_NAME = "sms";
-const THREAD_STORE_NAME = "thread";
-const PARTICIPANT_STORE_NAME = "participant";
-const MOST_RECENT_STORE_NAME = "most-recent";
-
-const DELIVERY_SENDING = "sending";
-const DELIVERY_SENT = "sent";
-const DELIVERY_RECEIVED = "received";
-const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
-const DELIVERY_ERROR = "error";
-
-const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
-const DELIVERY_STATUS_SUCCESS = "success";
-const DELIVERY_STATUS_PENDING = "pending";
-const DELIVERY_STATUS_ERROR = "error";
-
-const MESSAGE_CLASS_NORMAL = "normal";
-
-const FILTER_TIMESTAMP = "timestamp";
-const FILTER_NUMBERS = "numbers";
-const FILTER_DELIVERY = "delivery";
-const FILTER_READ = "read";
-
-// We can´t create an IDBKeyCursor with a boolean, so we need to use numbers
-// instead.
-const FILTER_READ_UNREAD = 0;
-const FILTER_READ_READ = 1;
-
-const READ_ONLY = "readonly";
-const READ_WRITE = "readwrite";
-const PREV = "prev";
-const NEXT = "next";
-
-const COLLECT_ID_END = 0;
-const COLLECT_ID_ERROR = -1;
-const COLLECT_TIMESTAMP_UNUSED = 0;
-
-XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService",
-                                   "@mozilla.org/mobilemessage/mobilemessageservice;1",
-                                   "nsIMobileMessageService");
-
-XPCOMUtils.defineLazyServiceGetter(this, "gMMSService",
-                                   "@mozilla.org/mms/rilmmsservice;1",
-                                   "nsIMmsService");
-
-XPCOMUtils.defineLazyGetter(this, "MMS", function () {
-  let MMS = {};
-  Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
-  return MMS;
-});
 
 /**
  * MobileMessageDatabaseService
  */
 function MobileMessageDatabaseService() {
   // Prime the directory service's cache to ensure that the ProfD entry exists
   // by the time IndexedDB queries for it off the main thread. (See bug 743635.)
   Services.dirsvc.get("ProfD", Ci.nsIFile);
 
-  let that = this;
-  this.newTxn(READ_ONLY, function(error, txn, messageStore){
-    if (error) {
-      return;
-    }
-    // In order to get the highest key value, we open a key cursor in reverse
-    // order and get only the first pointed value.
-    let request = messageStore.openCursor(null, PREV);
-    request.onsuccess = function onsuccess(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        if (DEBUG) {
-          debug("Could not get the last key from mobile message database. " +
-                "Probably empty database");
-        }
-        return;
-      }
-      that.lastMessageId = cursor.key || 0;
-      if (DEBUG) debug("Last assigned message ID was " + that.lastMessageId);
-    };
-    request.onerror = function onerror(event) {
-      if (DEBUG) {
-        debug("Could not get the last key from mobile message database " +
-              event.target.errorCode);
-      }
-    };
-  });
-  this.updatePendingTransactionToError();
+  let mmdb = new MMDB.MobileMessageDB();
+  mmdb.init(DB_NAME, 0, mmdb.updatePendingTransactionToError.bind(mmdb));
+  this.mmdb = mmdb;
 }
 MobileMessageDatabaseService.prototype = {
 
   classID: RIL_MOBILEMESSAGEDATABASESERVICE_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRilMobileMessageDatabaseService,
                                          Ci.nsIMobileMessageDatabaseService,
                                          Ci.nsIObserver]),
 
   /**
-   * Cache the DB here.
+   * MobileMessageDB instance.
    */
-  db: null,
-
-  /**
-   * Last sms/mms object store key value in the database.
-   */
-  lastMessageId: 0,
+  mmdb: null,
 
   /**
    * nsIObserver
    */
   observe: function observe() {},
 
   /**
-   * Prepare the database. This may include opening the database and upgrading
-   * it to the latest schema version.
-   *
-   * @param callback
-   *        Function that takes an error and db argument. It is called when
-   *        the database is ready to use or if an error occurs while preparing
-   *        the database.
-   *
-   * @return (via callback) a database ready for use.
-   */
-  ensureDB: function ensureDB(callback) {
-    if (this.db) {
-      if (DEBUG) debug("ensureDB: already have a database, returning early.");
-      callback(null, this.db);
-      return;
-    }
-
-    let self = this;
-    function gotDB(db) {
-      self.db = db;
-      callback(null, db);
-    }
-
-    let request = indexedDB.open(DB_NAME, DB_VERSION);
-    request.onsuccess = function (event) {
-      if (DEBUG) debug("Opened database:", DB_NAME, DB_VERSION);
-      gotDB(event.target.result);
-    };
-    request.onupgradeneeded = function (event) {
-      if (DEBUG) {
-        debug("Database needs upgrade:", DB_NAME,
-              event.oldVersion, event.newVersion);
-        debug("Correct new database version:", event.newVersion == DB_VERSION);
-      }
-
-      let db = event.target.result;
-
-      let currentVersion = event.oldVersion;
-
-      function update(currentVersion) {
-        let next = update.bind(self, currentVersion + 1);
-
-        switch (currentVersion) {
-          case 0:
-            if (DEBUG) debug("New database");
-            self.createSchema(db, next);
-            break;
-          case 1:
-            if (DEBUG) debug("Upgrade to version 2. Including `read` index");
-            self.upgradeSchema(event.target.transaction, next);
-            break;
-          case 2:
-            if (DEBUG) debug("Upgrade to version 3. Fix existing entries.");
-            self.upgradeSchema2(event.target.transaction, next);
-            break;
-          case 3:
-            if (DEBUG) debug("Upgrade to version 4. Add quick threads view.");
-            self.upgradeSchema3(db, event.target.transaction, next);
-            break;
-          case 4:
-            if (DEBUG) debug("Upgrade to version 5. Populate quick threads view.");
-            self.upgradeSchema4(event.target.transaction, next);
-            break;
-          case 5:
-            if (DEBUG) debug("Upgrade to version 6. Use PhonenumberJS.");
-            self.upgradeSchema5(event.target.transaction, next);
-            break;
-          case 6:
-            if (DEBUG) debug("Upgrade to version 7. Use multiple entry indexes.");
-            self.upgradeSchema6(event.target.transaction, next);
-            break;
-          case 7:
-            if (DEBUG) debug("Upgrade to version 8. Add participant/thread stores.");
-            self.upgradeSchema7(db, event.target.transaction, next);
-            break;
-          case 8:
-            if (DEBUG) debug("Upgrade to version 9. Add transactionId index for incoming MMS.");
-            self.upgradeSchema8(event.target.transaction, next);
-            break;
-          case 9:
-            if (DEBUG) debug("Upgrade to version 10. Upgrade type if it's not existing.");
-            self.upgradeSchema9(event.target.transaction, next);
-            break;
-          case 10:
-            if (DEBUG) debug("Upgrade to version 11. Add last message type into threadRecord.");
-            self.upgradeSchema10(event.target.transaction, next);
-            break;
-          case 11:
-            if (DEBUG) debug("Upgrade to version 12. Add envelopeId index for outgoing MMS.");
-            self.upgradeSchema11(event.target.transaction, next);
-            break;
-          case 12:
-            if (DEBUG) debug("Upgrade to version 13. Replaced deliveryStatus by deliveryInfo.");
-            self.upgradeSchema12(event.target.transaction, next);
-            break;
-          case 13:
-            if (DEBUG) debug("Upgrade to version 14. Fix the wrong participants.");
-            self.upgradeSchema13(event.target.transaction, next);
-            break;
-          case 14:
-            if (DEBUG) debug("Upgrade to version 15. Add deliveryTimestamp.");
-            self.upgradeSchema14(event.target.transaction, next);
-            break;
-          case 15:
-            if (DEBUG) debug("Upgrade to version 16. Add ICC ID for each message.");
-            self.upgradeSchema15(event.target.transaction, next);
-            break;
-          case 16:
-            if (DEBUG) debug("Upgrade to version 17. Add isReadReportSent for incoming MMS.");
-            self.upgradeSchema16(event.target.transaction, next);
-            break;
-          case 17:
-            if (DEBUG) debug("Upgrade to version 18. Add last message subject into threadRecord.");
-            self.upgradeSchema17(event.target.transaction, next);
-            break;
-          case 18:
-            if (DEBUG) debug("Upgrade to version 19. Add pid for incoming SMS.");
-            self.upgradeSchema18(event.target.transaction, next);
-            break;
-          case 19:
-            if (DEBUG) debug("Upgrade to version 20. Add readStatus and readTimestamp.");
-            self.upgradeSchema19(event.target.transaction, next);
-            break;
-          case 20:
-            // This will need to be moved for each new version
-            if (DEBUG) debug("Upgrade finished.");
-            break;
-          default:
-            event.target.transaction.abort();
-            callback("Old database version: " + event.oldVersion, null);
-            break;
-        }
-      }
-
-      update(currentVersion);
-    };
-    request.onerror = function (event) {
-      //TODO look at event.target.Code and change error constant accordingly
-      callback("Error opening database!", null);
-    };
-    request.onblocked = function (event) {
-      callback("Opening database request is blocked.", null);
-    };
-  },
-
-  /**
-   * Start a new transaction.
-   *
-   * @param txn_type
-   *        Type of transaction (e.g. READ_WRITE)
-   * @param callback
-   *        Function to call when the transaction is available. It will
-   *        be invoked with the transaction and opened object stores.
-   * @param storeNames
-   *        Names of the stores to open.
-   */
-  newTxn: function newTxn(txn_type, callback, storeNames) {
-    if (!storeNames) {
-      storeNames = [MESSAGE_STORE_NAME];
-    }
-    if (DEBUG) debug("Opening transaction for object stores: " + storeNames);
-    this.ensureDB(function (error, db) {
-      if (error) {
-        if (DEBUG) debug("Could not open database: " + error);
-        callback(error);
-        return;
-      }
-      let txn = db.transaction(storeNames, txn_type);
-      if (DEBUG) debug("Started transaction " + txn + " of type " + txn_type);
-      if (DEBUG) {
-        txn.oncomplete = function oncomplete(event) {
-          debug("Transaction " + txn + " completed.");
-        };
-        txn.onerror = function onerror(event) {
-          //TODO check event.target.errorCode and show an appropiate error
-          //     message according to it.
-          debug("Error occurred during transaction: " + event.target.errorCode);
-        };
-      }
-      let stores;
-      if (storeNames.length == 1) {
-        if (DEBUG) debug("Retrieving object store " + storeNames[0]);
-        stores = txn.objectStore(storeNames[0]);
-      } else {
-        stores = [];
-        for each (let storeName in storeNames) {
-          if (DEBUG) debug("Retrieving object store " + storeName);
-          stores.push(txn.objectStore(storeName));
-        }
-      }
-      callback(null, txn, stores);
-    });
-  },
-
-  /**
-   * Sometimes user might reboot or remove battery while sending/receiving
-   * message. This is function set the status of message records to error.
-   */
-  updatePendingTransactionToError: function updatePendingTransactionToError() {
-    this.newTxn(READ_WRITE, function (error, txn, messageStore) {
-      if (DEBUG) {
-        txn.onerror = function onerror(event) {
-          debug("updatePendingTransactionToError fail, event = " + event);
-        };
-      }
-
-      let deliveryIndex = messageStore.index("delivery");
-
-      // Set all 'delivery: sending' records to 'delivery: error' and 'deliveryStatus:
-      // error'.
-      let keyRange = IDBKeyRange.bound([DELIVERY_SENDING, 0], [DELIVERY_SENDING, ""]);
-      let cursorRequestSending = deliveryIndex.openCursor(keyRange);
-      cursorRequestSending.onsuccess = function(event) {
-        let messageCursor = event.target.result;
-        if (!messageCursor) {
-          return;
-        }
-
-        let messageRecord = messageCursor.value;
-
-        // Set delivery to error.
-        messageRecord.delivery = DELIVERY_ERROR;
-        messageRecord.deliveryIndex = [DELIVERY_ERROR, messageRecord.timestamp];
-
-        if (messageRecord.type == "sms") {
-          messageRecord.deliveryStatus = DELIVERY_STATUS_ERROR;
-        } else {
-          // Set delivery status to error.
-          for (let i = 0; i < messageRecord.deliveryInfo.length; i++) {
-            messageRecord.deliveryInfo[i].deliveryStatus = DELIVERY_STATUS_ERROR;
-          }
-        }
-
-        messageCursor.update(messageRecord);
-        messageCursor.continue();
-      };
-
-      // Set all 'delivery: not-downloaded' and 'deliveryStatus: pending'
-      // records to 'delivery: not-downloaded' and 'deliveryStatus: error'.
-      keyRange = IDBKeyRange.bound([DELIVERY_NOT_DOWNLOADED, 0], [DELIVERY_NOT_DOWNLOADED, ""]);
-      let cursorRequestNotDownloaded = deliveryIndex.openCursor(keyRange);
-      cursorRequestNotDownloaded.onsuccess = function(event) {
-        let messageCursor = event.target.result;
-        if (!messageCursor) {
-          return;
-        }
-
-        let messageRecord = messageCursor.value;
-
-        // We have no "not-downloaded" SMS messages.
-        if (messageRecord.type == "sms") {
-          messageCursor.continue();
-          return;
-        }
-
-        // Set delivery status to error.
-        let deliveryInfo = messageRecord.deliveryInfo;
-        if (deliveryInfo.length == 1 &&
-            deliveryInfo[0].deliveryStatus == DELIVERY_STATUS_PENDING) {
-          deliveryInfo[0].deliveryStatus = DELIVERY_STATUS_ERROR;
-        }
-
-        messageCursor.update(messageRecord);
-        messageCursor.continue();
-      };
-    });
-  },
-
-  /**
-   * Create the initial database schema.
-   *
-   * TODO need to worry about number normalization somewhere...
-   * TODO full text search on body???
-   */
-  createSchema: function createSchema(db, next) {
-    // This messageStore holds the main mobile message data.
-    let messageStore = db.createObjectStore(MESSAGE_STORE_NAME, { keyPath: "id" });
-    messageStore.createIndex("timestamp", "timestamp", { unique: false });
-    if (DEBUG) debug("Created object stores and indexes");
-    next();
-  },
-
-  /**
-   * Upgrade to the corresponding database schema version.
-   */
-  upgradeSchema: function upgradeSchema(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.createIndex("read", "read", { unique: false });
-    next();
-  },
-
-  upgradeSchema2: function upgradeSchema2(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      messageRecord.messageClass = MESSAGE_CLASS_NORMAL;
-      messageRecord.deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema3: function upgradeSchema3(db, transaction, next) {
-    // Delete redundant "id" index.
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    if (messageStore.indexNames.contains("id")) {
-      messageStore.deleteIndex("id");
-    }
-
-    /**
-     * This mostRecentStore can be used to quickly construct a thread view of
-     * the mobile message database. Each entry looks like this:
-     *
-     * { senderOrReceiver: <String> (primary key),
-     *   id: <Number>,
-     *   timestamp: <Date>,
-     *   body: <String>,
-     *   unreadCount: <Number> }
-     *
-     */
-    let mostRecentStore = db.createObjectStore(MOST_RECENT_STORE_NAME,
-                                               { keyPath: "senderOrReceiver" });
-    mostRecentStore.createIndex("timestamp", "timestamp");
-    next();
-  },
-
-  upgradeSchema4: function upgradeSchema4(transaction, next) {
-    let threads = {};
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME);
-
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        for (let thread in threads) {
-          mostRecentStore.put(threads[thread]);
-        }
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      let contact = messageRecord.sender || messageRecord.receiver;
-
-      if (contact in threads) {
-        let thread = threads[contact];
-        if (!messageRecord.read) {
-          thread.unreadCount++;
-        }
-        if (messageRecord.timestamp > thread.timestamp) {
-          thread.id = messageRecord.id;
-          thread.body = messageRecord.body;
-          thread.timestamp = messageRecord.timestamp;
-        }
-      } else {
-        threads[contact] = {
-          senderOrReceiver: contact,
-          id: messageRecord.id,
-          timestamp: messageRecord.timestamp,
-          body: messageRecord.body,
-          unreadCount: messageRecord.read ? 0 : 1
-        };
-      }
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema5: function upgradeSchema5(transaction, next) {
-    // Don't perform any upgrade. See Bug 819560.
-    next();
-  },
-
-  upgradeSchema6: function upgradeSchema6(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Delete "delivery" index.
-    if (messageStore.indexNames.contains("delivery")) {
-      messageStore.deleteIndex("delivery");
-    }
-    // Delete "sender" index.
-    if (messageStore.indexNames.contains("sender")) {
-      messageStore.deleteIndex("sender");
-    }
-    // Delete "receiver" index.
-    if (messageStore.indexNames.contains("receiver")) {
-      messageStore.deleteIndex("receiver");
-    }
-    // Delete "read" index.
-    if (messageStore.indexNames.contains("read")) {
-      messageStore.deleteIndex("read");
-    }
-
-    // Create new "delivery", "number" and "read" indexes.
-    messageStore.createIndex("delivery", "deliveryIndex");
-    messageStore.createIndex("number", "numberIndex", { multiEntry: true });
-    messageStore.createIndex("read", "readIndex");
-
-    // Populate new "deliverIndex", "numberIndex" and "readIndex" attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      let timestamp = messageRecord.timestamp;
-      messageRecord.deliveryIndex = [messageRecord.delivery, timestamp];
-      messageRecord.numberIndex = [
-        [messageRecord.sender, timestamp],
-        [messageRecord.receiver, timestamp]
-      ];
-      messageRecord.readIndex = [messageRecord.read, timestamp];
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Add participant/thread stores.
-   *
-   * The message store now saves original phone numbers/addresses input from
-   * content to message records. No normalization is made.
-   *
-   * For filtering messages by phone numbers, it first looks up corresponding
-   * participant IDs from participant table and fetch message records with
-   * matching keys defined in per record "participantIds" field.
-   *
-   * For message threading, messages with the same participant ID array are put
-   * in the same thread. So updating "unreadCount", "lastMessageId" and
-   * "lastTimestamp" are through the "threadId" carried by per message record.
-   * Fetching threads list is now simply walking through the thread sotre. The
-   * "mostRecentStore" is dropped.
-   */
-  upgradeSchema7: function upgradeSchema7(db, transaction, next) {
-    /**
-     * This "participant" object store keeps mappings of multiple phone numbers
-     * of the same recipient to an integer participant id. Each entry looks
-     * like:
-     *
-     * { id: <Number> (primary key),
-     *   addresses: <Array of strings> }
-     */
-    let participantStore = db.createObjectStore(PARTICIPANT_STORE_NAME,
-                                                { keyPath: "id",
-                                                  autoIncrement: true });
-    participantStore.createIndex("addresses", "addresses", { multiEntry: true });
-
-    /**
-     * This "threads" object store keeps mappings from an integer thread id to
-     * ids of the participants of that message thread. Each entry looks like:
-     *
-     * { id: <Number> (primary key),
-     *   participantIds: <Array of participant IDs>,
-     *   participantAddresses: <Array of the first addresses of the participants>,
-     *   lastMessageId: <Number>,
-     *   lastTimestamp: <Date>,
-     *   subject: <String>,
-     *   unreadCount: <Number> }
-     *
-     */
-    let threadStore = db.createObjectStore(THREAD_STORE_NAME,
-                                           { keyPath: "id",
-                                             autoIncrement: true });
-    threadStore.createIndex("participantIds", "participantIds");
-    threadStore.createIndex("lastTimestamp", "lastTimestamp");
-
-    /**
-     * Replace "numberIndex" with "participantIdsIndex" and create an additional
-     * "threadId". "numberIndex" will be removed later.
-     */
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.createIndex("threadId", "threadIdIndex");
-    messageStore.createIndex("participantIds", "participantIdsIndex",
-                             { multiEntry: true });
-
-    // Now populate participantStore & threadStore.
-    let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME);
-    let self = this;
-    let mostRecentRequest = mostRecentStore.openCursor();
-    mostRecentRequest.onsuccess = function(event) {
-      let mostRecentCursor = event.target.result;
-      if (!mostRecentCursor) {
-        db.deleteObjectStore(MOST_RECENT_STORE_NAME);
-
-        // No longer need the "number" index in messageStore, use
-        // "participantIds" index instead.
-        messageStore.deleteIndex("number");
-        next();
-        return;
-      }
-
-      let mostRecentRecord = mostRecentCursor.value;
-
-      // Each entry in mostRecentStore is supposed to be a unique thread, so we
-      // retrieve the records out and insert its "senderOrReceiver" column as a
-      // new record in participantStore.
-      let number = mostRecentRecord.senderOrReceiver;
-      self.findParticipantRecordByAddress(participantStore, number, true,
-                                          function (participantRecord) {
-        // Also create a new record in threadStore.
-        let threadRecord = {
-          participantIds: [participantRecord.id],
-          participantAddresses: [number],
-          lastMessageId: mostRecentRecord.id,
-          lastTimestamp: mostRecentRecord.timestamp,
-          subject: mostRecentRecord.body,
-          unreadCount: mostRecentRecord.unreadCount,
-        };
-        let addThreadRequest = threadStore.add(threadRecord);
-        addThreadRequest.onsuccess = function (event) {
-          threadRecord.id = event.target.result;
-
-          let numberRange = IDBKeyRange.bound([number, 0], [number, ""]);
-          let messageRequest = messageStore.index("number")
-                                           .openCursor(numberRange, NEXT);
-          messageRequest.onsuccess = function (event) {
-            let messageCursor = event.target.result;
-            if (!messageCursor) {
-              // No more message records, check next most recent record.
-              mostRecentCursor.continue();
-              return;
-            }
-
-            let messageRecord = messageCursor.value;
-            // Check whether the message really belongs to this thread.
-            let matchSenderOrReceiver = false;
-            if (messageRecord.delivery == DELIVERY_RECEIVED) {
-              if (messageRecord.sender == number) {
-                matchSenderOrReceiver = true;
-              }
-            } else if (messageRecord.receiver == number) {
-              matchSenderOrReceiver = true;
-            }
-            if (!matchSenderOrReceiver) {
-              // Check next message record.
-              messageCursor.continue();
-              return;
-            }
-
-            messageRecord.threadId = threadRecord.id;
-            messageRecord.threadIdIndex = [threadRecord.id,
-                                           messageRecord.timestamp];
-            messageRecord.participantIdsIndex = [
-              [participantRecord.id, messageRecord.timestamp]
-            ];
-            messageCursor.update(messageRecord);
-            // Check next message record.
-            messageCursor.continue();
-          };
-          messageRequest.onerror = function () {
-            // Error in fetching message records, check next most recent record.
-            mostRecentCursor.continue();
-          };
-        };
-        addThreadRequest.onerror = function () {
-          // Error in fetching message records, check next most recent record.
-          mostRecentCursor.continue();
-        };
-      });
-    };
-  },
-
-  /**
-   * Add transactionId index for MMS.
-   */
-  upgradeSchema8: function upgradeSchema8(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Delete "transactionId" index.
-    if (messageStore.indexNames.contains("transactionId")) {
-      messageStore.deleteIndex("transactionId");
-    }
-
-    // Create new "transactionId" indexes.
-    messageStore.createIndex("transactionId", "transactionIdIndex", { unique: true });
-
-    // Populate new "transactionIdIndex" attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if ("mms" == messageRecord.type &&
-          (DELIVERY_NOT_DOWNLOADED == messageRecord.delivery ||
-           DELIVERY_RECEIVED == messageRecord.delivery)) {
-        messageRecord.transactionIdIndex =
-          messageRecord.headers["x-mms-transaction-id"];
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema9: function upgradeSchema9(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Update type attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == undefined) {
-        messageRecord.type = "sms";
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema10: function upgradeSchema10(transaction, next) {
-    let threadStore = transaction.objectStore(THREAD_STORE_NAME);
-
-    // Add 'lastMessageType' to each thread record.
-    threadStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let threadRecord = cursor.value;
-      let lastMessageId = threadRecord.lastMessageId;
-      let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-      let request = messageStore.mozGetAll(lastMessageId);
-
-      request.onsuccess = function onsuccess() {
-        let messageRecord = request.result[0];
-        if (!messageRecord) {
-          if (DEBUG) debug("Message ID " + lastMessageId + " not found");
-          return;
-        }
-        if (messageRecord.id != lastMessageId) {
-          if (DEBUG) {
-            debug("Requested message ID (" + lastMessageId + ") is different from" +
-                  " the one we got");
-          }
-          return;
-        }
-        threadRecord.lastMessageType = messageRecord.type;
-        cursor.update(threadRecord);
-        cursor.continue();
-      };
-
-      request.onerror = function onerror(event) {
-        if (DEBUG) {
-          if (event.target) {
-            debug("Caught error on transaction", event.target.errorCode);
-          }
-        }
-        cursor.continue();
-      };
-    };
-  },
-
-  /**
-   * Add envelopeId index for MMS.
-   */
-  upgradeSchema11: function upgradeSchema11(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Delete "envelopeId" index.
-    if (messageStore.indexNames.contains("envelopeId")) {
-      messageStore.deleteIndex("envelopeId");
-    }
-
-    // Create new "envelopeId" indexes.
-    messageStore.createIndex("envelopeId", "envelopeIdIndex", { unique: true });
-
-    // Populate new "envelopeIdIndex" attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "mms" &&
-          messageRecord.delivery == DELIVERY_SENT) {
-        messageRecord.envelopeIdIndex = messageRecord.headers["message-id"];
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Replace deliveryStatus by deliveryInfo.
-   */
-  upgradeSchema12: function upgradeSchema12(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "mms") {
-        messageRecord.deliveryInfo = [];
-
-        if (messageRecord.deliveryStatus.length == 1 &&
-            (messageRecord.delivery == DELIVERY_NOT_DOWNLOADED ||
-             messageRecord.delivery == DELIVERY_RECEIVED)) {
-          messageRecord.deliveryInfo.push({
-            receiver: null,
-            deliveryStatus: messageRecord.deliveryStatus[0] });
-        } else {
-          for (let i = 0; i < messageRecord.deliveryStatus.length; i++) {
-            messageRecord.deliveryInfo.push({
-              receiver: messageRecord.receivers[i],
-              deliveryStatus: messageRecord.deliveryStatus[i] });
-          }
-        }
-        delete messageRecord.deliveryStatus;
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Fix the wrong participants.
-   */
-  upgradeSchema13: function upgradeSchema13(transaction, next) {
-    let participantStore = transaction.objectStore(PARTICIPANT_STORE_NAME);
-    let threadStore = transaction.objectStore(THREAD_STORE_NAME);
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    let self = this;
-
-    let isInvalid = function (participantRecord) {
-      let entries = [];
-      for (let addr of participantRecord.addresses) {
-        entries.push({
-          normalized: addr,
-          parsed: PhoneNumberUtils.parseWithMCC(addr, null)
-        })
-      }
-      for (let ix = 0 ; ix < entries.length - 1; ix++) {
-        let entry1 = entries[ix];
-        for (let iy = ix + 1 ; iy < entries.length; iy ++) {
-          let entry2 = entries[iy];
-          if (!self.matchPhoneNumbers(entry1.normalized, entry1.parsed,
-                                      entry2.normalized, entry2.parsed)) {
-            return true;
-          }
-        }
-      }
-      return false;
-    };
-
-    let invalidParticipantIds = [];
-    participantStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (cursor) {
-        let participantRecord = cursor.value;
-        // Check if this participant record is valid
-        if (isInvalid(participantRecord)) {
-          invalidParticipantIds.push(participantRecord.id);
-          cursor.delete();
-        }
-        cursor.continue();
-        return;
-      }
-
-      // Participant store cursor iteration done.
-      if (!invalidParticipantIds.length) {
-        next();
-        return;
-      }
-
-      // Find affected thread.
-      let wrongThreads = [];
-      threadStore.openCursor().onsuccess = function(event) {
-        let threadCursor = event.target.result;
-        if (threadCursor) {
-          let threadRecord = threadCursor.value;
-          let participantIds = threadRecord.participantIds;
-          let foundInvalid = false;
-          for (let invalidParticipantId of invalidParticipantIds) {
-            if (participantIds.indexOf(invalidParticipantId) != -1) {
-              foundInvalid = true;
-              break;
-            }
-          }
-          if (foundInvalid) {
-            wrongThreads.push(threadRecord.id);
-            threadCursor.delete();
-          }
-          threadCursor.continue();
-          return;
-        }
-
-        if (!wrongThreads.length) {
-          next();
-          return;
-        }
-        // Use recursive function to avoid we add participant twice.
-        (function createUpdateThreadAndParticipant(ix) {
-          let threadId = wrongThreads[ix];
-          let range = IDBKeyRange.bound([threadId, 0], [threadId, ""]);
-          messageStore.index("threadId").openCursor(range).onsuccess = function(event) {
-            let messageCursor = event.target.result;
-            if (!messageCursor) {
-              ix++;
-              if (ix === wrongThreads.length) {
-                next();
-                return;
-              }
-              createUpdateThreadAndParticipant(ix);
-              return;
-            }
-
-            let messageRecord = messageCursor.value;
-            let timestamp = messageRecord.timestamp;
-            let threadParticipants = [];
-            // Recaculate the thread participants of received message.
-            if (messageRecord.delivery === DELIVERY_RECEIVED ||
-                messageRecord.delivery === DELIVERY_NOT_DOWNLOADED) {
-              threadParticipants.push(messageRecord.sender);
-              if (messageRecord.type == "mms") {
-                this.fillReceivedMmsThreadParticipants(messageRecord, threadParticipants);
-              }
-            }
-            // Recaculate the thread participants of sent messages and error
-            // messages. In error sms messages, we don't have error received sms.
-            // In received MMS, we don't update the error to deliver field but
-            // deliverStatus. So we only consider sent message in DELIVERY_ERROR.
-            else if (messageRecord.delivery === DELIVERY_SENT ||
-                messageRecord.delivery === DELIVERY_ERROR) {
-              if (messageRecord.type == "sms") {
-                threadParticipants = [messageRecord.receiver];
-              } else if (messageRecord.type == "mms") {
-                threadParticipants = messageRecord.receivers;
-              }
-            }
-            self.findThreadRecordByParticipants(threadStore, participantStore,
-                                                threadParticipants, true,
-                                                function (threadRecord,
-                                                          participantIds) {
-              if (!participantIds) {
-                debug("participantIds is empty!");
-                return;
-              }
-
-              let timestamp = messageRecord.timestamp;
-              // Setup participantIdsIndex.
-              messageRecord.participantIdsIndex = [];
-              for each (let id in participantIds) {
-                messageRecord.participantIdsIndex.push([id, timestamp]);
-              }
-              if (threadRecord) {
-                let needsUpdate = false;
-
-                if (threadRecord.lastTimestamp <= timestamp) {
-                  threadRecord.lastTimestamp = timestamp;
-                  threadRecord.subject = messageRecord.body;
-                  threadRecord.lastMessageId = messageRecord.id;
-                  threadRecord.lastMessageType = messageRecord.type;
-                  needsUpdate = true;
-                }
-
-                if (!messageRecord.read) {
-                  threadRecord.unreadCount++;
-                  needsUpdate = true;
-                }
-
-                if (needsUpdate) {
-                  threadStore.put(threadRecord);
-                }
-                messageRecord.threadId = threadRecord.id;
-                messageRecord.threadIdIndex = [threadRecord.id, timestamp];
-                messageCursor.update(messageRecord);
-                messageCursor.continue();
-                return;
-              }
-
-              let threadRecord = {
-                participantIds: participantIds,
-                participantAddresses: threadParticipants,
-                lastMessageId: messageRecord.id,
-                lastTimestamp: timestamp,
-                subject: messageRecord.body,
-                unreadCount: messageRecord.read ? 0 : 1,
-                lastMessageType: messageRecord.type
-              };
-              threadStore.add(threadRecord).onsuccess = function (event) {
-                let threadId = event.target.result;
-                // Setup threadId & threadIdIndex.
-                messageRecord.threadId = threadId;
-                messageRecord.threadIdIndex = [threadId, timestamp];
-                messageCursor.update(messageRecord);
-                messageCursor.continue();
-              };
-            });
-          };
-        })(0);
-      };
-    };
-  },
-
-  /**
-   * Add deliveryTimestamp.
-   */
-  upgradeSchema14: function upgradeSchema14(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "sms") {
-        messageRecord.deliveryTimestamp = 0;
-      } else if (messageRecord.type == "mms") {
-        let deliveryInfo = messageRecord.deliveryInfo;
-        for (let i = 0; i < deliveryInfo.length; i++) {
-          deliveryInfo[i].deliveryTimestamp = 0;
-        }
-      }
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Add ICC ID.
-   */
-  upgradeSchema15: function upgradeSchema15(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      messageRecord.iccId = null;
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Add isReadReportSent for incoming MMS.
-   */
-  upgradeSchema16: function upgradeSchema16(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Update type attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "mms") {
-        messageRecord.isReadReportSent = false;
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema17: function upgradeSchema17(transaction, next) {
-    let threadStore = transaction.objectStore(THREAD_STORE_NAME);
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Add 'lastMessageSubject' to each thread record.
-    threadStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let threadRecord = cursor.value;
-      // We have defined 'threadRecord.subject' in upgradeSchema7(), but it
-      // actually means 'threadRecord.body'.  Swap the two values first.
-      threadRecord.body = threadRecord.subject;
-      delete threadRecord.subject;
-
-      // Only MMS supports subject so assign null for non-MMS one.
-      if (threadRecord.lastMessageType != "mms") {
-        threadRecord.lastMessageSubject = null;
-        cursor.update(threadRecord);
-
-        cursor.continue();
-        return;
-      }
-
-      messageStore.get(threadRecord.lastMessageId).onsuccess = function(event) {
-        let messageRecord = event.target.result;
-        let subject = messageRecord.headers.subject;
-        threadRecord.lastMessageSubject = subject || null;
-        cursor.update(threadRecord);
-
-        cursor.continue();
-      };
-    };
-  },
-
-  /**
-   * Add pid for incoming SMS.
-   */
-  upgradeSchema18: function upgradeSchema18(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "sms") {
-        messageRecord.pid = RIL.PDU_PID_DEFAULT;
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Add readStatus and readTimestamp.
-   */
-  upgradeSchema19: function upgradeSchema19(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "sms") {
-        cursor.continue();
-        return;
-      }
-
-      // We can always retrieve transaction id from
-      // |messageRecord.headers["x-mms-transaction-id"]|.
-      if (messageRecord.hasOwnProperty("transactionId")) {
-        delete messageRecord.transactionId;
-      }
-
-      // xpconnect gives "undefined" for an unassigned argument of an interface
-      // method.
-      if (messageRecord.envelopeIdIndex === "undefined") {
-        delete messageRecord.envelopeIdIndex;
-      }
-
-      // Convert some header fields that were originally decoded as BooleanValue
-      // to numeric enums.
-      for (let field of ["x-mms-cancel-status",
-                         "x-mms-sender-visibility",
-                         "x-mms-read-status"]) {
-        let value = messageRecord.headers[field];
-        if (value !== undefined) {
-          messageRecord.headers[field] = value ? 128 : 129;
-        }
-      }
-
-      // For all sent and received MMS messages, we have to add their
-      // |readStatus| and |readTimestamp| attributes in |deliveryInfo| array.
-      let readReportRequested =
-        messageRecord.headers["x-mms-read-report"] || false;
-      for (let element of messageRecord.deliveryInfo) {
-        element.readStatus = readReportRequested
-                           ? MMS.DOM_READ_STATUS_PENDING
-                           : MMS.DOM_READ_STATUS_NOT_APPLICABLE;
-        element.readTimestamp = 0;
-      }
-
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  matchParsedPhoneNumbers: function matchParsedPhoneNumbers(addr1, parsedAddr1,
-                                                            addr2, parsedAddr2) {
-    if ((parsedAddr1.internationalNumber &&
-         parsedAddr1.internationalNumber === parsedAddr2.internationalNumber) ||
-        (parsedAddr1.nationalNumber &&
-         parsedAddr1.nationalNumber === parsedAddr2.nationalNumber)) {
-      return true;
-    }
-
-    if (parsedAddr1.countryName != parsedAddr2.countryName) {
-      return false;
-    }
-
-    let ssPref = "dom.phonenumber.substringmatching." + parsedAddr1.countryName;
-    if (Services.prefs.getPrefType(ssPref) != Ci.nsIPrefBranch.PREF_INT) {
-      return false;
-    }
-
-    let val = Services.prefs.getIntPref(ssPref);
-    return addr1.length > val &&
-           addr2.length > val &&
-           addr1.slice(-val) === addr2.slice(-val);
-  },
-
-  matchPhoneNumbers: function matchPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2) {
-    if (parsedAddr1 && parsedAddr2) {
-      return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2);
-    }
-
-    if (parsedAddr1) {
-      parsedAddr2 = PhoneNumberUtils.parseWithCountryName(addr2, parsedAddr1.countryName);
-      if (parsedAddr2) {
-        return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2);
-      }
-
-      return false;
-    }
-
-    if (parsedAddr2) {
-      parsedAddr1 = PhoneNumberUtils.parseWithCountryName(addr1, parsedAddr2.countryName);
-      if (parsedAddr1) {
-        return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2);
-      }
-    }
-
-    return false;
-  },
-
-  createDomMessageFromRecord: function createDomMessageFromRecord(aMessageRecord) {
-    if (DEBUG) {
-      debug("createDomMessageFromRecord: " + JSON.stringify(aMessageRecord));
-    }
-    if (aMessageRecord.type == "sms") {
-      return gMobileMessageService.createSmsMessage(aMessageRecord.id,
-                                                    aMessageRecord.threadId,
-                                                    aMessageRecord.iccId,
-                                                    aMessageRecord.delivery,
-                                                    aMessageRecord.deliveryStatus,
-                                                    aMessageRecord.sender,
-                                                    aMessageRecord.receiver,
-                                                    aMessageRecord.body,
-                                                    aMessageRecord.messageClass,
-                                                    aMessageRecord.timestamp,
-                                                    aMessageRecord.deliveryTimestamp,
-                                                    aMessageRecord.read);
-    } else if (aMessageRecord.type == "mms") {
-      let headers = aMessageRecord["headers"];
-      if (DEBUG) {
-        debug("MMS: headers: " + JSON.stringify(headers));
-      }
-
-      let subject = headers["subject"];
-      if (subject == undefined) {
-        subject = "";
-      }
-
-      let smil = "";
-      let attachments = [];
-      let parts = aMessageRecord.parts;
-      if (parts) {
-        for (let i = 0; i < parts.length; i++) {
-          let part = parts[i];
-          if (DEBUG) {
-            debug("MMS: part[" + i + "]: " + JSON.stringify(part));
-          }
-          // Sometimes the part is incomplete because the device reboots when
-          // downloading MMS. Don't need to expose this part to the content.
-          if (!part) {
-            continue;
-          }
-
-          let partHeaders = part["headers"];
-          let partContent = part["content"];
-          // Don't need to make the SMIL part if it's present.
-          if (partHeaders["content-type"]["media"] == "application/smil") {
-            smil = partContent;
-            continue;
-          }
-          attachments.push({
-            "id": partHeaders["content-id"],
-            "location": partHeaders["content-location"],
-            "content": partContent
-          });
-        }
-      }
-      let expiryDate = 0;
-      if (headers["x-mms-expiry"] != undefined) {
-        expiryDate = aMessageRecord.timestamp + headers["x-mms-expiry"] * 1000;
-      }
-      let readReportRequested = headers["x-mms-read-report"] || false;
-      return gMobileMessageService.createMmsMessage(aMessageRecord.id,
-                                                    aMessageRecord.threadId,
-                                                    aMessageRecord.iccId,
-                                                    aMessageRecord.delivery,
-                                                    aMessageRecord.deliveryInfo,
-                                                    aMessageRecord.sender,
-                                                    aMessageRecord.receivers,
-                                                    aMessageRecord.timestamp,
-                                                    aMessageRecord.read,
-                                                    subject,
-                                                    smil,
-                                                    attachments,
-                                                    expiryDate,
-                                                    readReportRequested);
-    }
-  },
-
-  findParticipantRecordByAddress: function findParticipantRecordByAddress(
-      aParticipantStore, aAddress, aCreate, aCallback) {
-    if (DEBUG) {
-      debug("findParticipantRecordByAddress("
-            + JSON.stringify(aAddress) + ", " + aCreate + ")");
-    }
-
-    // Two types of input number to match here, international(+886987654321),
-    // and local(0987654321) types. The "nationalNumber" parsed from
-    // phonenumberutils will be "987654321" in this case.
-
-    // Normalize address before searching for participant record.
-    let normalizedAddress = PhoneNumberUtils.normalize(aAddress, false);
-    let allPossibleAddresses = [normalizedAddress];
-    let parsedAddress = PhoneNumberUtils.parse(normalizedAddress);
-    if (parsedAddress && parsedAddress.internationalNumber &&
-        allPossibleAddresses.indexOf(parsedAddress.internationalNumber) < 0) {
-      // We only stores international numbers into participant store because
-      // the parsed national number doesn't contain country info and may
-      // duplicate in different country.
-      allPossibleAddresses.push(parsedAddress.internationalNumber);
-    }
-    if (DEBUG) {
-      debug("findParticipantRecordByAddress: allPossibleAddresses = " +
-            JSON.stringify(allPossibleAddresses));
-    }
-
-    // Make a copy here because we may need allPossibleAddresses again.
-    let needles = allPossibleAddresses.slice(0);
-    let request = aParticipantStore.index("addresses").get(needles.pop());
-    request.onsuccess = (function onsuccess(event) {
-      let participantRecord = event.target.result;
-      // 1) First try matching through "addresses" index of participant store.
-      //    If we're lucky, return the fetched participant record.
-      if (participantRecord) {
-        if (DEBUG) {
-          debug("findParticipantRecordByAddress: got "
-                + JSON.stringify(participantRecord));
-        }
-        aCallback(participantRecord);
-        return;
-      }
-
-      // Try next possible address again.
-      if (needles.length) {
-        let request = aParticipantStore.index("addresses").get(needles.pop());
-        request.onsuccess = onsuccess.bind(this);
-        return;
-      }
-
-      // 2) Traverse throught all participants and check all alias addresses.
-      aParticipantStore.openCursor().onsuccess = (function (event) {
-        let cursor = event.target.result;
-        if (!cursor) {
-          // Have traversed whole object store but still in vain.
-          if (!aCreate) {
-            aCallback(null);
-            return;
-          }
-
-          let participantRecord = { addresses: [normalizedAddress] };
-          let addRequest = aParticipantStore.add(participantRecord);
-          addRequest.onsuccess = function (event) {
-            participantRecord.id = event.target.result;
-            if (DEBUG) {
-              debug("findParticipantRecordByAddress: created "
-                    + JSON.stringify(participantRecord));
-            }
-            aCallback(participantRecord);
-          };
-          return;
-        }
-
-        let participantRecord = cursor.value;
-        for (let storedAddress of participantRecord.addresses) {
-          let parsedStoredAddress = PhoneNumberUtils.parseWithMCC(storedAddress, null);
-          let match = this.matchPhoneNumbers(normalizedAddress, parsedAddress,
-                                             storedAddress, parsedStoredAddress);
-          if (!match) {
-            // 3) Else we fail to match current stored participant record.
-            continue;
-          }
-          // Match!
-          if (aCreate) {
-            // In a READ-WRITE transaction, append one more possible address for
-            // this participant record.
-            participantRecord.addresses =
-              participantRecord.addresses.concat(allPossibleAddresses);
-            cursor.update(participantRecord);
-          }
-
-          if (DEBUG) {
-            debug("findParticipantRecordByAddress: match "
-                  + JSON.stringify(cursor.value));
-          }
-          aCallback(participantRecord);
-          return;
-        }
-
-        // Check next participant record if available.
-        cursor.continue();
-      }).bind(this);
-    }).bind(this);
-  },
-
-  findParticipantIdsByAddresses: function findParticipantIdsByAddresses(
-      aParticipantStore, aAddresses, aCreate, aSkipNonexistent, aCallback) {
-    if (DEBUG) {
-      debug("findParticipantIdsByAddresses("
-            + JSON.stringify(aAddresses) + ", "
-            + aCreate + ", " + aSkipNonexistent + ")");
-    }
-
-    if (!aAddresses || !aAddresses.length) {
-      if (DEBUG) debug("findParticipantIdsByAddresses: returning null");
-      aCallback(null);
-      return;
-    }
-
-    let self = this;
-    (function findParticipantId(index, result) {
-      if (index >= aAddresses.length) {
-        // Sort numerically.
-        result.sort(function (a, b) {
-          return a - b;
-        });
-        if (DEBUG) debug("findParticipantIdsByAddresses: returning " + result);
-        aCallback(result);
-        return;
-      }
-
-      self.findParticipantRecordByAddress(aParticipantStore,
-                                          aAddresses[index++], aCreate,
-                                          function (participantRecord) {
-        if (!participantRecord) {
-          if (!aSkipNonexistent) {
-            if (DEBUG) debug("findParticipantIdsByAddresses: returning null");
-            aCallback(null);
-            return;
-          }
-        } else if (result.indexOf(participantRecord.id) < 0) {
-          result.push(participantRecord.id);
-        }
-        findParticipantId(index, result);
-      });
-    }) (0, []);
-  },
-
-  findThreadRecordByParticipants: function findThreadRecordByParticipants(
-      aThreadStore, aParticipantStore, aAddresses,
-      aCreateParticipants, aCallback) {
-    if (DEBUG) {
-      debug("findThreadRecordByParticipants(" + JSON.stringify(aAddresses)
-            + ", " + aCreateParticipants + ")");
-    }
-    this.findParticipantIdsByAddresses(aParticipantStore, aAddresses,
-                                       aCreateParticipants, false,
-                                       function (participantIds) {
-      if (!participantIds) {
-        if (DEBUG) debug("findThreadRecordByParticipants: returning null");
-        aCallback(null, null);
-        return;
-      }
-      // Find record from thread store.
-      let request = aThreadStore.index("participantIds").get(participantIds);
-      request.onsuccess = function (event) {
-        let threadRecord = event.target.result;
-        if (DEBUG) {
-          debug("findThreadRecordByParticipants: return "
-                + JSON.stringify(threadRecord));
-        }
-        aCallback(threadRecord, participantIds);
-      };
-    });
-  },
-
-  newTxnWithCallback: function newTxnWithCallback(aCallback, aFunc, aStoreNames) {
-    let self = this;
-    this.newTxn(READ_WRITE, function(aError, aTransaction, aStores) {
-      let notifyResult = function(aRv, aMessageRecord) {
-        if (!aCallback) {
-          return;
-        }
-        let domMessage =
-          aMessageRecord && self.createDomMessageFromRecord(aMessageRecord);
-        aCallback.notify(aRv, domMessage);
-      };
-
-      if (aError) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE, null);
-        return;
-      }
-
-      let capture = {};
-      aTransaction.oncomplete = function(event) {
-        notifyResult(Cr.NS_OK, capture.messageRecord);
-      };
-      aTransaction.onabort = function(event) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE, null);
-      };
-
-      aFunc(capture, aStores);
-    }, aStoreNames);
-  },
-
-  saveRecord: function saveRecord(aMessageRecord, aAddresses, aCallback) {
-    if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord));
-
-    let self = this;
-    this.newTxn(READ_WRITE, function(error, txn, stores) {
-      let notifyResult = function(aRv, aMessageRecord) {
-        if (!aCallback) {
-          return;
-        }
-        let domMessage =
-          aMessageRecord && self.createDomMessageFromRecord(aMessageRecord);
-        aCallback.notify(aRv, domMessage);
-      };
-
-      if (error) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE, null);
-        return;
-      }
-
-      txn.oncomplete = function oncomplete(event) {
-        if (aMessageRecord.id > self.lastMessageId) {
-          self.lastMessageId = aMessageRecord.id;
-        }
-        notifyResult(Cr.NS_OK, aMessageRecord);
-      };
-      txn.onabort = function onabort(event) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE, null);
-      };
-
-      let messageStore = stores[0];
-      let participantStore = stores[1];
-      let threadStore = stores[2];
-      self.replaceShortMessageOnSave(txn, messageStore, participantStore,
-                                     threadStore, aMessageRecord, aAddresses);
-    }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
-  },
-
-  replaceShortMessageOnSave:
-    function replaceShortMessageOnSave(aTransaction, aMessageStore,
-                                       aParticipantStore, aThreadStore,
-                                       aMessageRecord, aAddresses) {
-    let isReplaceTypePid = (aMessageRecord.pid) &&
-                           ((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 &&
-                             aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) ||
-                            aMessageRecord.pid == RIL.PDU_PID_RETURN_CALL_MESSAGE);
-
-    if (aMessageRecord.type != "sms" ||
-        aMessageRecord.delivery != DELIVERY_RECEIVED ||
-        !isReplaceTypePid) {
-      this.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                          aThreadStore, aMessageRecord, aAddresses);
-      return;
-    }
-
-    // 3GPP TS 23.040 subclause 9.2.3.9 "TP-Protocol-Identifier (TP-PID)":
-    //
-    //   ... the MS shall check the originating address and replace any
-    //   existing stored message having the same Protocol Identifier code
-    //   and originating address with the new short message and other
-    //   parameter values. If there is no message to be replaced, the MS
-    //   shall store the message in the normal way. ... it is recommended
-    //   that the SC address should not be checked by the MS."
-    let self = this;
-    this.findParticipantRecordByAddress(aParticipantStore,
-                                        aMessageRecord.sender, false,
-                                        function(participantRecord) {
-      if (!participantRecord) {
-        self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                            aThreadStore, aMessageRecord, aAddresses);
-        return;
-      }
-
-      let participantId = participantRecord.id;
-      let range = IDBKeyRange.bound([participantId, 0], [participantId, ""]);
-      let request = aMessageStore.index("participantIds").openCursor(range);
-      request.onsuccess = function onsuccess(event) {
-        let cursor = event.target.result;
-        if (!cursor) {
-          self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                              aThreadStore, aMessageRecord, aAddresses);
-          return;
-        }
-
-        // A message record with same participantId found.
-        // Verify matching criteria.
-        let foundMessageRecord = cursor.value;
-        if (foundMessageRecord.type != "sms" ||
-            foundMessageRecord.sender != aMessageRecord.sender ||
-            foundMessageRecord.pid != aMessageRecord.pid) {
-          cursor.continue();
-          return;
-        }
-
-        // Match! Now replace that found message record with current one.
-        aMessageRecord.id = foundMessageRecord.id;
-        self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                            aThreadStore, aMessageRecord, aAddresses);
-      };
-    });
-  },
-
-  realSaveRecord: function realSaveRecord(aTransaction, aMessageStore,
-                                          aParticipantStore, aThreadStore,
-                                          aMessageRecord, aAddresses) {
-    let self = this;
-    this.findThreadRecordByParticipants(aThreadStore, aParticipantStore,
-                                        aAddresses, true,
-                                        function(threadRecord, participantIds) {
-      if (!participantIds) {
-        aTransaction.abort();
-        return;
-      }
-
-      let isOverriding = (aMessageRecord.id !== undefined);
-      if (!isOverriding) {
-        // |self.lastMessageId| is only updated in |txn.oncomplete|.
-        aMessageRecord.id = self.lastMessageId + 1;
-      }
-
-      let timestamp = aMessageRecord.timestamp;
-      let insertMessageRecord = function(threadId) {
-        // Setup threadId & threadIdIndex.
-        aMessageRecord.threadId = threadId;
-        aMessageRecord.threadIdIndex = [threadId, timestamp];
-        // Setup participantIdsIndex.
-        aMessageRecord.participantIdsIndex = [];
-        for each (let id in participantIds) {
-          aMessageRecord.participantIdsIndex.push([id, timestamp]);
-        }
-
-        if (!isOverriding) {
-          // Really add to message store.
-          aMessageStore.put(aMessageRecord);
-          return;
-        }
-
-        // If we're going to override an old message, we need to update the
-        // info of the original thread containing the overridden message.
-        // To get the original thread ID and read status of the overridden
-        // message record, we need to retrieve it before overriding it.
-        aMessageStore.get(aMessageRecord.id).onsuccess = function(event) {
-          let oldMessageRecord = event.target.result;
-          aMessageStore.put(aMessageRecord);
-          if (oldMessageRecord) {
-            self.updateThreadByMessageChange(aMessageStore,
-                                             aThreadStore,
-                                             oldMessageRecord.threadId,
-                                             aMessageRecord.id,
-                                             oldMessageRecord.read);
-          }
-        };
-      };
-
-      if (threadRecord) {
-        let needsUpdate = false;
-
-        if (threadRecord.lastTimestamp <= timestamp) {
-          let lastMessageSubject;
-          if (aMessageRecord.type == "mms") {
-            lastMessageSubject = aMessageRecord.headers.subject;
-          }
-          threadRecord.lastMessageSubject = lastMessageSubject || null;
-          threadRecord.lastTimestamp = timestamp;
-          threadRecord.body = aMessageRecord.body;
-          threadRecord.lastMessageId = aMessageRecord.id;
-          threadRecord.lastMessageType = aMessageRecord.type;
-          needsUpdate = true;
-        }
-
-        if (!aMessageRecord.read) {
-          threadRecord.unreadCount++;
-          needsUpdate = true;
-        }
-
-        if (needsUpdate) {
-          aThreadStore.put(threadRecord);
-        }
-
-        insertMessageRecord(threadRecord.id);
-        return;
-      }
-
-      let lastMessageSubject;
-      if (aMessageRecord.type == "mms") {
-        lastMessageSubject = aMessageRecord.headers.subject;
-      }
-
-      threadRecord = {
-        participantIds: participantIds,
-        participantAddresses: aAddresses,
-        lastMessageId: aMessageRecord.id,
-        lastTimestamp: timestamp,
-        lastMessageSubject: lastMessageSubject || null,
-        body: aMessageRecord.body,
-        unreadCount: aMessageRecord.read ? 0 : 1,
-        lastMessageType: aMessageRecord.type,
-      };
-      aThreadStore.add(threadRecord).onsuccess = function(event) {
-        let threadId = event.target.result;
-        insertMessageRecord(threadId);
-      };
-    });
-  },
-
-  forEachMatchedMmsDeliveryInfo:
-    function forEachMatchedMmsDeliveryInfo(aDeliveryInfo, aNeedle, aCallback) {
-
-    let typedAddress = {
-      type: MMS.Address.resolveType(aNeedle),
-      address: aNeedle
-    };
-    let normalizedAddress, parsedAddress;
-    if (typedAddress.type === "PLMN") {
-      normalizedAddress = PhoneNumberUtils.normalize(aNeedle, false);
-      parsedAddress = PhoneNumberUtils.parse(normalizedAddress);
-    }
-
-    for (let element of aDeliveryInfo) {
-      let typedStoredAddress = {
-        type: MMS.Address.resolveType(element.receiver),
-        address: element.receiver
-      };
-      if (typedAddress.type !== typedStoredAddress.type) {
-        // Not even my type.  Skip.
-        continue;
-      }
-
-      if (typedAddress.address == typedStoredAddress.address) {
-        // Have a direct match.
-        aCallback(element);
-        continue;
-      }
-
-      if (typedAddress.type !== "PLMN") {
-        // Address type other than "PLMN" must have direct match.  Or, skip.
-        continue;
-      }
-
-      // Both are of "PLMN" type.
-      let normalizedStoredAddress =
-        PhoneNumberUtils.normalize(element.receiver, false);
-      let parsedStoredAddress =
-        PhoneNumberUtils.parseWithMCC(normalizedStoredAddress, null);
-      if (this.matchPhoneNumbers(normalizedAddress, parsedAddress,
-                                 normalizedStoredAddress, parsedStoredAddress)) {
-        aCallback(element);
-      }
-    }
-  },
-
-  updateMessageDeliveryById: function updateMessageDeliveryById(
-      id, type, receiver, delivery, deliveryStatus, envelopeId, callback) {
-    if (DEBUG) {
-      debug("Setting message's delivery by " + type + " = "+ id
-            + " receiver: " + receiver
-            + " delivery: " + delivery
-            + " deliveryStatus: " + deliveryStatus
-            + " envelopeId: " + envelopeId);
-    }
-
-    let self = this;
-    this.newTxnWithCallback(callback, function(aCapture, aMessageStore) {
-      let getRequest;
-      if (type === "messageId") {
-        getRequest = aMessageStore.get(id);
-      } else if (type === "envelopeId") {
-        getRequest = aMessageStore.index("envelopeId").get(id);
-      }
-
-      getRequest.onsuccess = function onsuccess(event) {
-        let messageRecord = event.target.result;
-        if (!messageRecord) {
-          if (DEBUG) debug("type = " + id + " is not found");
-          throw Cr.NS_ERROR_FAILURE;
-        }
-
-        let isRecordUpdated = false;
-
-        // Update |messageRecord.delivery| if needed.
-        if (delivery && messageRecord.delivery != delivery) {
-          messageRecord.delivery = delivery;
-          messageRecord.deliveryIndex = [delivery, messageRecord.timestamp];
-          isRecordUpdated = true;
-        }
-
-        // Attempt to update |deliveryStatus| and |deliveryTimestamp| of:
-        // - the |messageRecord| for SMS.
-        // - the element(s) in |messageRecord.deliveryInfo| for MMS.
-        if (deliveryStatus) {
-          // A callback for updating the deliveyStatus/deliveryTimestamp of
-          // each target.
-          let updateFunc = function(aTarget) {
-            if (aTarget.deliveryStatus == deliveryStatus) {
-              return;
-            }
-
-            aTarget.deliveryStatus = deliveryStatus;
-
-            // Update |deliveryTimestamp| if it's successfully delivered.
-            if (deliveryStatus == DELIVERY_STATUS_SUCCESS) {
-              aTarget.deliveryTimestamp = Date.now();
-            }
-
-            isRecordUpdated = true;
-          };
-
-          if (messageRecord.type == "sms") {
-            updateFunc(messageRecord);
-          } else if (messageRecord.type == "mms") {
-            if (!receiver) {
-              // If the receiver is specified, we only need to update the
-              // element(s) in deliveryInfo that match the same receiver.
-              messageRecord.deliveryInfo.forEach(updateFunc);
-            } else {
-              self.forEachMatchedMmsDeliveryInfo(messageRecord.deliveryInfo,
-                                                 receiver, updateFunc);
-            }
-          }
-        }
-
-        // Update |messageRecord.envelopeIdIndex| if needed.
-        if (envelopeId) {
-          if (messageRecord.envelopeIdIndex != envelopeId) {
-            messageRecord.envelopeIdIndex = envelopeId;
-            isRecordUpdated = true;
-          }
-        }
-
-        aCapture.messageRecord = messageRecord;
-        if (!isRecordUpdated) {
-          if (DEBUG) {
-            debug("The values of delivery, deliveryStatus and envelopeId " +
-                  "don't need to be updated.");
-          }
-          return;
-        }
-
-        if (DEBUG) {
-          debug("The delivery, deliveryStatus or envelopeId are updated.");
-        }
-        aMessageStore.put(messageRecord);
-      };
-    });
-  },
-
-  fillReceivedMmsThreadParticipants: function fillReceivedMmsThreadParticipants(aMessage, threadParticipants) {
-    let receivers = aMessage.receivers;
-    // If we don't want to disable the MMS grouping for receiving, we need to
-    // add the receivers (excluding the user's own number) to the participants
-    // for creating the thread. Some cases might be investigated as below:
-    //
-    // 1. receivers.length == 0
-    //    This usually happens when receiving an MMS notification indication
-    //    which doesn't carry any receivers.
-    // 2. receivers.length == 1
-    //    If the receivers contain single phone number, we don't need to
-    //    add it into participants because we know that number is our own.
-    // 3. receivers.length >= 2
-    //    If the receivers contain multiple phone numbers, we need to add all
-    //    of them but not the user's own number into participants.
-    if (DISABLE_MMS_GROUPING_FOR_RECEIVING || receivers.length < 2) {
-      return;
-    }
-    let isSuccess = false;
-    let slicedReceivers = receivers.slice();
-    if (aMessage.msisdn) {
-      let found = slicedReceivers.indexOf(aMessage.msisdn);
-      if (found !== -1) {
-        isSuccess = true;
-        slicedReceivers.splice(found, 1);
-      }
-    }
-
-    if (!isSuccess) {
-      // For some SIMs we cannot retrieve the vaild MSISDN (i.e. the user's
-      // own phone number), so we cannot correcly exclude the user's own
-      // number from the receivers, thus wrongly building the thread index.
-      if (DEBUG) debug("Error! Cannot strip out user's own phone number!");
-    }
-
-    threadParticipants = threadParticipants.concat(slicedReceivers);
-  },
-
-  updateThreadByMessageChange: function updateThreadByMessageChange(messageStore,
-                                                                    threadStore,
-                                                                    threadId,
-                                                                    messageId,
-                                                                    messageRead) {
-    threadStore.get(threadId).onsuccess = function(event) {
-      // This must exist.
-      let threadRecord = event.target.result;
-      if (DEBUG) debug("Updating thread record " + JSON.stringify(threadRecord));
-
-      if (!messageRead) {
-        threadRecord.unreadCount--;
-      }
-
-      if (threadRecord.lastMessageId == messageId) {
-        // Check most recent sender/receiver.
-        let range = IDBKeyRange.bound([threadId, 0], [threadId, ""]);
-        let request = messageStore.index("threadId")
-                                  .openCursor(range, PREV);
-        request.onsuccess = function(event) {
-          let cursor = event.target.result;
-          if (!cursor) {
-            if (DEBUG) {
-              debug("Deleting mru entry for thread id " + threadId);
-            }
-            threadStore.delete(threadId);
-            return;
-          }
-
-          let nextMsg = cursor.value;
-          let lastMessageSubject;
-          if (nextMsg.type == "mms") {
-            lastMessageSubject = nextMsg.headers.subject;
-          }
-          threadRecord.lastMessageSubject = lastMessageSubject || null;
-          threadRecord.lastMessageId = nextMsg.id;
-          threadRecord.lastTimestamp = nextMsg.timestamp;
-          threadRecord.body = nextMsg.body;
-          threadRecord.lastMessageType = nextMsg.type;
-          if (DEBUG) {
-            debug("Updating mru entry: " +
-                  JSON.stringify(threadRecord));
-          }
-          threadStore.put(threadRecord);
-        };
-      } else if (!messageRead) {
-        // Shortcut, just update the unread count.
-        if (DEBUG) {
-          debug("Updating unread count for thread id " + threadId + ": " +
-                (threadRecord.unreadCount + 1) + " -> " +
-                threadRecord.unreadCount);
-        }
-        threadStore.put(threadRecord);
-      }
-    };
-  },
-
-  /**
    * nsIRilMobileMessageDatabaseService API
    */
 
-  saveReceivedMessage: function saveReceivedMessage(aMessage, aCallback) {
-    if ((aMessage.type != "sms" && aMessage.type != "mms") ||
-        (aMessage.type == "sms" && (aMessage.messageClass == undefined ||
-                                    aMessage.sender == undefined)) ||
-        (aMessage.type == "mms" && (aMessage.delivery == undefined ||
-                                    aMessage.deliveryStatus == undefined ||
-                                    !Array.isArray(aMessage.receivers))) ||
-        aMessage.timestamp == undefined) {
-      if (aCallback) {
-        aCallback.notify(Cr.NS_ERROR_FAILURE, null);
-      }
-      return;
-    }
-
-    let threadParticipants;
-    if (aMessage.type == "mms") {
-      if (aMessage.headers.from) {
-        aMessage.sender = aMessage.headers.from.address;
-      } else {
-        aMessage.sender = "anonymous";
-      }
-
-      threadParticipants = [aMessage.sender];
-      this.fillReceivedMmsThreadParticipants(aMessage, threadParticipants);
-    } else { // SMS
-      threadParticipants = [aMessage.sender];
-    }
-
-    let timestamp = aMessage.timestamp;
-
-    // Adding needed indexes and extra attributes for internal use.
-    // threadIdIndex & participantIdsIndex are filled in saveRecord().
-    aMessage.readIndex = [FILTER_READ_UNREAD, timestamp];
-    aMessage.read = FILTER_READ_UNREAD;
-
-    if (aMessage.type == "mms") {
-      aMessage.transactionIdIndex = aMessage.headers["x-mms-transaction-id"];
-      aMessage.isReadReportSent = false;
-
-      // As a receiver, we don't need to care about the delivery status of
-      // others, so we put a single element with self's phone number in the
-      // |deliveryInfo| array.
-      aMessage.deliveryInfo = [{
-        receiver: aMessage.phoneNumber,
-        deliveryStatus: aMessage.deliveryStatus,
-        deliveryTimestamp: 0,
-        readStatus: MMS.DOM_READ_STATUS_NOT_APPLICABLE,
-        readTimestamp: 0,
-      }];
-
-      delete aMessage.deliveryStatus;
-    }
-
-    if (aMessage.type == "sms") {
-      aMessage.delivery = DELIVERY_RECEIVED;
-      aMessage.deliveryStatus = DELIVERY_STATUS_SUCCESS;
-      aMessage.deliveryTimestamp = 0;
-
-      if (aMessage.pid == undefined) {
-        aMessage.pid = RIL.PDU_PID_DEFAULT;
-      }
-    }
-    aMessage.deliveryIndex = [aMessage.delivery, timestamp];
-
-    this.saveRecord(aMessage, threadParticipants, aCallback);
+  saveReceivedMessage: function(aMessage, aCallback) {
+    this.mmdb.saveReceivedMessage(aMessage, aCallback);
   },
 
-  saveSendingMessage: function saveSendingMessage(aMessage, aCallback) {
-    if ((aMessage.type != "sms" && aMessage.type != "mms") ||
-        (aMessage.type == "sms" && aMessage.receiver == undefined) ||
-        (aMessage.type == "mms" && !Array.isArray(aMessage.receivers)) ||
-        aMessage.deliveryStatusRequested == undefined ||
-        aMessage.timestamp == undefined) {
-      if (aCallback) {
-        aCallback.notify(Cr.NS_ERROR_FAILURE, null);
-      }
-      return;
-    }
+  saveSendingMessage: function(aMessage, aCallback) {
+    this.mmdb.saveSendingMessage(aMessage, aCallback);
+  },
 
-    // Set |aMessage.deliveryStatus|. Note that for MMS record
-    // it must be an array of strings; For SMS, it's a string.
-    let deliveryStatus = aMessage.deliveryStatusRequested
-                       ? DELIVERY_STATUS_PENDING
-                       : DELIVERY_STATUS_NOT_APPLICABLE;
-    if (aMessage.type == "sms") {
-      aMessage.deliveryStatus = deliveryStatus;
-      // If |deliveryTimestamp| is not specified, use 0 as default.
-      if (aMessage.deliveryTimestamp == undefined) {
-        aMessage.deliveryTimestamp = 0;
-      }
-    } else if (aMessage.type == "mms") {
-      let receivers = aMessage.receivers
-      if (!Array.isArray(receivers)) {
-        if (DEBUG) {
-          debug("Need receivers for MMS. Fail to save the sending message.");
-        }
-        if (aCallback) {
-          aCallback.notify(Cr.NS_ERROR_FAILURE, null);
-        }
-        return;
-      }
-      let readStatus = aMessage.headers["x-mms-read-report"]
-                     ? MMS.DOM_READ_STATUS_PENDING
-                     : MMS.DOM_READ_STATUS_NOT_APPLICABLE;
-      aMessage.deliveryInfo = [];
-      for (let i = 0; i < receivers.length; i++) {
-        aMessage.deliveryInfo.push({
-          receiver: receivers[i],
-          deliveryStatus: deliveryStatus,
-          deliveryTimestamp: 0,
-          readStatus: readStatus,
-          readTimestamp: 0,
-        });
-      }
-    }
-
-    let timestamp = aMessage.timestamp;
-
-    // Adding needed indexes and extra attributes for internal use.
-    // threadIdIndex & participantIdsIndex are filled in saveRecord().
-    aMessage.deliveryIndex = [DELIVERY_SENDING, timestamp];
-    aMessage.readIndex = [FILTER_READ_READ, timestamp];
-    aMessage.delivery = DELIVERY_SENDING;
-    aMessage.messageClass = MESSAGE_CLASS_NORMAL;
-    aMessage.read = FILTER_READ_READ;
-
-    let addresses;
-    if (aMessage.type == "sms") {
-      addresses = [aMessage.receiver];
-    } else if (aMessage.type == "mms") {
-      addresses = aMessage.receivers;
-    }
-    this.saveRecord(aMessage, addresses, aCallback);
+  setMessageDeliveryByMessageId: function(aMessageId, aReceiver, aDelivery,
+                                          aDeliveryStatus, aEnvelopeId,
+                                          aCallback) {
+    this.mmdb.updateMessageDeliveryById(aMessageId, "messageId", aReceiver,
+                                        aDelivery, aDeliveryStatus,
+                                        aEnvelopeId, aCallback);
   },
 
-  setMessageDeliveryByMessageId: function setMessageDeliveryByMessageId(
-      messageId, receiver, delivery, deliveryStatus, envelopeId, callback) {
-    this.updateMessageDeliveryById(messageId, "messageId",
-                                   receiver, delivery, deliveryStatus,
-                                   envelopeId, callback);
-
-  },
-
-  setMessageDeliveryStatusByEnvelopeId:
-    function setMessageDeliveryStatusByEnvelopeId(aEnvelopeId, aReceiver,
-                                                  aDeliveryStatus, aCallback) {
-    this.updateMessageDeliveryById(aEnvelopeId, "envelopeId", aReceiver, null,
-                                   aDeliveryStatus, null, aCallback);
-  },
-
-  setMessageReadStatusByEnvelopeId:
-    function setMessageReadStatusByEnvelopeId(aEnvelopeId, aReceiver,
-                                              aReadStatus, aCallback) {
-    if (DEBUG) {
-      debug("Setting message's read status by envelopeId = " + aEnvelopeId +
-            ", receiver: " + aReceiver + ", readStatus: " + aReadStatus);
-    }
-
-    let self = this;
-    this.newTxnWithCallback(aCallback, function(aCapture, aMessageStore) {
-      let getRequest = aMessageStore.index("envelopeId").get(aEnvelopeId);
-      getRequest.onsuccess = function onsuccess(event) {
-        let messageRecord = event.target.result;
-        if (!messageRecord) {
-          if (DEBUG) debug("envelopeId '" + aEnvelopeId + "' not found");
-          throw Cr.NS_ERROR_FAILURE;
-        }
-
-        aCapture.messageRecord = messageRecord;
-
-        let isRecordUpdated = false;
-        self.forEachMatchedMmsDeliveryInfo(messageRecord.deliveryInfo,
-                                           aReceiver, function(aEntry) {
-          if (aEntry.readStatus == aReadStatus) {
-            return;
-          }
-
-          aEntry.readStatus = aReadStatus;
-          if (aReadStatus == MMS.DOM_READ_STATUS_SUCCESS) {
-            aEntry.readTimestamp = Date.now();
-          } else {
-            aEntry.readTimestamp = 0;
-          }
-          isRecordUpdated = true;
-        });
-
-        if (!isRecordUpdated) {
-          if (DEBUG) {
-            debug("The values of readStatus don't need to be updated.");
-          }
-          return;
-        }
-
-        if (DEBUG) {
-          debug("The readStatus is updated.");
-        }
-        aMessageStore.put(messageRecord);
-      };
-    });
+  setMessageDeliveryStatusByEnvelopeId: function(aEnvelopeId, aReceiver,
+                                                 aDeliveryStatus, aCallback) {
+    this.mmdb.updateMessageDeliveryById(aEnvelopeId, "envelopeId", aReceiver,
+                                        null, aDeliveryStatus, null, aCallback);
   },
 
-  getMessageRecordByTransactionId: function getMessageRecordByTransactionId(aTransactionId, aCallback) {
-    if (DEBUG) debug("Retrieving message with transaction ID " + aTransactionId);
-    let self = this;
-    this.newTxn(READ_ONLY, function (error, txn, messageStore) {
-      if (error) {
-        if (DEBUG) debug(error);
-        aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null);
-        return;
-      }
-      let request = messageStore.index("transactionId").get(aTransactionId);
-
-      txn.oncomplete = function oncomplete(event) {
-        if (DEBUG) debug("Transaction " + txn + " completed.");
-        let messageRecord = request.result;
-        if (!messageRecord) {
-          if (DEBUG) debug("Transaction ID " + aTransactionId + " not found");
-          aCallback.notify(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR, null, null);
-          return;
-        }
-        // In this case, we don't need a dom message. Just pass null to the
-        // third argument.
-        aCallback.notify(Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR,
-                         messageRecord, null);
-      };
-
-      txn.onerror = function onerror(event) {
-        if (DEBUG) {
-          if (event.target) {
-            debug("Caught error on transaction", event.target.errorCode);
-          }
-        }
-        aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null);
-      };
-    });
+  setMessageReadStatusByEnvelopeId: function(aEnvelopeId, aReceiver,
+                                             aReadStatus, aCallback) {
+    this.mmdb.setMessageReadStatusByEnvelopeId(aEnvelopeId, aReceiver,
+                                               aReadStatus, aCallback);
   },
 
-  getMessageRecordById: function getMessageRecordById(aMessageId, aCallback) {
-    if (DEBUG) debug("Retrieving message with ID " + aMessageId);
-    let self = this;
-    this.newTxn(READ_ONLY, function (error, txn, messageStore) {
-      if (error) {
-        if (DEBUG) debug(error);
-        aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null);
-        return;
-      }
-      let request = messageStore.mozGetAll(aMessageId);
+  getMessageRecordByTransactionId: function(aTransactionId, aCallback) {
+    this.mmdb.getMessageRecordByTransactionId(aTransactionId, aCallback);
+  },
 
-      txn.oncomplete = function oncomplete() {
-        if (DEBUG) debug("Transaction " + txn + " completed.");
-        if (request.result.length > 1) {
-          if (DEBUG) debug("Got too many results for id " + aMessageId);
-          aCallback.notify(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR, null, null);
-          return;
-        }
-        let messageRecord = request.result[0];
-        if (!messageRecord) {
-          if (DEBUG) debug("Message ID " + aMessageId + " not found");
-          aCallback.notify(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR, null, null);
-          return;
-        }
-        if (messageRecord.id != aMessageId) {
-          if (DEBUG) {
-            debug("Requested message ID (" + aMessageId + ") is " +
-                  "different from the one we got");
-          }
-          aCallback.notify(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR, null, null);
-          return;
-        }
-        let domMessage = self.createDomMessageFromRecord(messageRecord);
-        aCallback.notify(Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR,
-                         messageRecord, domMessage);
-      };
-
-      txn.onerror = function onerror(event) {
-        if (DEBUG) {
-          if (event.target) {
-            debug("Caught error on transaction", event.target.errorCode);
-          }
-        }
-        aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null);