Merge m-c to elm
authorNick Alexander <nalexander@mozilla.com>
Thu, 12 Dec 2013 08:21:15 -0800
changeset 161582 645dd937f6a68b9aaaceacaf514e59c7332a0f4f
parent 161581 460a43f82d0c4a6de0aa70e5f8a77df6d988f280 (current diff)
parent 160145 07e7a99841a64c59faf7ba1bd007e791c0c07b83 (diff)
child 161583 1b9829ecb1ce7222394b962d97383aec2b4a1e2f
push id37948
push userkwierso@gmail.com
push dateSat, 21 Dec 2013 03:01:11 +0000
treeherdermozilla-inbound@7bc1fb6a21ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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,
+                                             aRea