Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 20 Aug 2014 16:21:52 -0400
changeset 200667 e7806c9c83f3edc29787133cfaef14ab3bd635c9
parent 200666 a53cafed66b9ba0e4b49c911ad5c50f79d92c2cf (current diff)
parent 200656 6ab867edb95a0e7957d6c707405b4787a2808600 (diff)
child 200674 dac8b4a0bd7c67737e2e670a2e2b0d4d69d53dfc
child 200737 5b6dc6fbc556e6f3133368b8a7df99706ac29229
child 200762 4c24b0282198db337215c7782a213913e6d4d805
push id27351
push userkwierso@gmail.com
push dateWed, 20 Aug 2014 22:56:16 +0000
treeherdermozilla-central@e7806c9c83f3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone34.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 b2g-inbound. a=merge
editor/composer/res/text_selection_handle.png
editor/composer/res/text_selection_handle@1.5.png
editor/composer/res/text_selection_handle@2.png
editor/libeditor/InsertElementTxn.cpp
editor/libeditor/InsertElementTxn.h
js/src/jit/CompilerRoot.h
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -291,16 +291,17 @@
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
+@BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 #ifdef MOZ_USE_NATIVE_UCONV
 @BINPATH@/components/ucnative.xpt
@@ -535,16 +536,18 @@
 @BINPATH@/components/HealthReportService.js
 #endif
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/CaptivePortalDetectComponents.manifest
 @BINPATH@/components/captivedetect.js
 #endif
 @BINPATH@/components/TelemetryStartup.js
 @BINPATH@/components/TelemetryStartup.manifest
+@BINPATH@/components/XULStore.js
+@BINPATH@/components/XULStore.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 @BINPATH@/components/PushServiceLauncher.js
 
@@ -687,19 +690,16 @@
 @BINPATH@/res/text_caret_tilt_left.png
 @BINPATH@/res/text_caret_tilt_left@1.5x.png
 @BINPATH@/res/text_caret_tilt_left@2.25x.png
 @BINPATH@/res/text_caret_tilt_left@2x.png
 @BINPATH@/res/text_caret_tilt_right.png
 @BINPATH@/res/text_caret_tilt_right@1.5x.png
 @BINPATH@/res/text_caret_tilt_right@2.25x.png
 @BINPATH@/res/text_caret_tilt_right@2x.png
-@BINPATH@/res/text_selection_handle.png
-@BINPATH@/res/text_selection_handle@1.5.png
-@BINPATH@/res/text_selection_handle@2.png
 @BINPATH@/res/grabber.gif
 #ifdef XP_MACOSX
 @BINPATH@/res/cursors/*
 #endif
 @BINPATH@/res/fonts/*
 @BINPATH@/res/dtd/*
 @BINPATH@/res/html/*
 @BINPATH@/res/langGroups.properties
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -4,16 +4,31 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 var FullScreen = {
   _XULNS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
   get _fullScrToggler() {
     delete this._fullScrToggler;
     return this._fullScrToggler = document.getElementById("fullscr-toggler");
   },
+
+  init: function() {
+    // called when we go into full screen, even if initiated by a web page script
+    window.addEventListener("fullscreen", this, true);
+    window.messageManager.addMessageListener("MozEnteredDomFullscreen", this);
+
+    if (window.fullScreen)
+      this.toggle();
+  },
+
+  uninit: function() {
+    window.messageManager.removeMessageListener("MozEnteredDomFullscreen", this);
+    this.cleanup();
+  },
+
   toggle: function (event) {
     var enterFS = window.fullScreen;
 
     // We get the fullscreen event _before_ the window transitions into or out of FS mode.
     if (event && event.type == "fullscreen")
       enterFS = !enterFS;
 
     // Toggle the View:FullScreen command, which controls elements like the
@@ -90,38 +105,56 @@ var FullScreen = {
   exitDomFullScreen : function() {
     document.mozCancelFullScreen();
   },
 
   handleEvent: function (event) {
     switch (event.type) {
       case "activate":
         if (document.mozFullScreen) {
-          this.showWarning(this.fullscreenDoc);
+          this.showWarning(this.fullscreenOrigin);
         }
         break;
+      case "fullscreen":
+        this.toggle(event);
+        break;
       case "transitionend":
         if (event.propertyName == "opacity")
           this.cancelWarning();
         break;
     }
   },
 
-  enterDomFullscreen : function(event) {
+  receiveMessage: function(aMessage) {
+    if (aMessage.name == "MozEnteredDomFullscreen") {
+      // If we're a multiprocess browser, then the request to enter fullscreen
+      // did not bubble up to the root browser document - it stopped at the root
+      // of the content document. That means we have to kick off the switch to
+      // fullscreen here at the operating system level in the parent process
+      // ourselves.
+      let data = aMessage.data;
+      let browser = aMessage.target;
+      if (gMultiProcessBrowser && browser.getAttribute("remote") == "true") {
+        let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIDOMWindowUtils);
+        windowUtils.remoteFrameFullscreenChanged(browser, data.origin);
+      }
+      this.enterDomFullscreen(browser, data.origin);
+    }
+  },
+
+  enterDomFullscreen : function(aBrowser, aOrigin) {
     if (!document.mozFullScreen)
       return;
 
-    // However, if we receive a "MozEnteredDomFullScreen" event for a document
-    // which is not a subdocument of a currently active (ie. visible) browser
-    // or iframe, we know that we've switched to a different frame since the
-    // request to enter full-screen was made, so we should exit full-screen
-    // since the "full-screen document" isn't acutally visible.
-    if (!event.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
-                                 .getInterface(Ci.nsIWebNavigation)
-                                 .QueryInterface(Ci.nsIDocShell).isActive) {
+    // If we've received a fullscreen notification, we have to ensure that the
+    // element that's requesting fullscreen belongs to the browser that's currently
+    // active. If not, we exit fullscreen since the "full-screen document" isn't
+    // actually visible now.
+    if (gBrowser.selectedBrowser != aBrowser) {
       document.mozCancelFullScreen();
       return;
     }
 
     let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
     if (focusManager.activeWindow != window) {
       // The top-level window has lost focus since the request to enter
       // full-screen was made. Cancel full-screen.
@@ -131,17 +164,17 @@ var FullScreen = {
 
     // Ensure the sidebar is hidden.
     if (!document.getElementById("sidebar-box").hidden)
       toggleSidebar();
 
     if (gFindBarInitialized)
       gFindBar.close();
 
-    this.showWarning(event.target);
+    this.showWarning(aOrigin);
 
     // Exit DOM full-screen mode upon open, close, or change tab.
     gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
     gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
     gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
 
     // Add listener to detect when the fullscreen window is re-focused.
     // If a fullscreen window loses focus, we show a warning when the
@@ -173,17 +206,19 @@ var FullScreen = {
       this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
       this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
       this.cancelWarning();
       gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
       gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
       gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
       if (!this.useLionFullScreen)
         window.removeEventListener("activate", this);
-      this.fullscreenDoc = null;
+
+      window.messageManager
+            .broadcastAsyncMessage("DOMFullscreen:Cleanup");
     }
   },
 
   observe: function(aSubject, aTopic, aData)
   {
     if (aData == "browser.fullscreen.autohide") {
       if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
         gBrowser.mPanelContainer.addEventListener("mousemove",
@@ -332,17 +367,17 @@ var FullScreen = {
     this.warningBox = null;
   },
 
   setFullscreenAllowed: function(isApproved) {
     // The "remember decision" checkbox is hidden when showing for documents that
     // the permission manager can't handle (documents with URIs without a host).
     // We simply require those to be approved every time instead.
     let rememberCheckbox = document.getElementById("full-screen-remember-decision");
-    let uri = this.fullscreenDoc.nodePrincipal.URI;
+    let uri = BrowserUtils.makeURI(this.fullscreenOrigin);
     if (!rememberCheckbox.hidden) {
       if (rememberCheckbox.checked)
         Services.perms.add(uri,
                            "fullscreen",
                            isApproved ? Services.perms.ALLOW_ACTION : Services.perms.DENY_ACTION,
                            Services.perms.EXPIRE_NEVER);
       else if (isApproved) {
         // The user has only temporarily approved fullscren for this fullscreen
@@ -365,37 +400,39 @@ var FullScreen = {
         document.addEventListener("mozfullscreenchange", onFullscreenchange);
       }
     }
     if (this.warningBox)
       this.warningBox.setAttribute("fade-warning-out", "true");
     // If the document has been granted fullscreen, notify Gecko so it can resume
     // any pending pointer lock requests, otherwise exit fullscreen; the user denied
     // the fullscreen request.
-    if (isApproved)
-      Services.obs.notifyObservers(this.fullscreenDoc, "fullscreen-approved", "");
-    else
+    if (isApproved) {
+      gBrowser.selectedBrowser
+              .messageManager
+              .sendAsyncMessage("DOMFullscreen:Approved");
+    } else {
       document.mozCancelFullScreen();
+    }
   },
 
   warningBox: null,
   warningFadeOutTimeout: null,
-  fullscreenDoc: null,
 
   // Shows the fullscreen approval UI, or if the domain has already been approved
   // for fullscreen, shows a warning that the site has entered fullscreen for a short
   // duration.
-  showWarning: function(targetDoc) {
+  showWarning: function(aOrigin) {
     if (!document.mozFullScreen ||
         !gPrefService.getBoolPref("full-screen-api.approval-required"))
       return;
 
     // Set the strings on the fullscreen approval UI.
-    this.fullscreenDoc = targetDoc;
-    let uri = this.fullscreenDoc.nodePrincipal.URI;
+    this.fullscreenOrigin = aOrigin;
+    let uri = BrowserUtils.makeURI(aOrigin);
     let host = null;
     try {
       host = uri.host;
     } catch (e) { }
     let hostLabel = document.getElementById("full-screen-domain-text");
     let rememberCheckbox = document.getElementById("full-screen-remember-decision");
     let isApproved = false;
     if (host) {
--- a/browser/base/content/browser-fullZoom.js
+++ b/browser/base/content/browser-fullZoom.js
@@ -14,20 +14,16 @@ var FullZoom = {
   name: "browser.content.full-zoom",
 
   // browser.zoom.siteSpecific preference cache
   _siteSpecificPref: undefined,
 
   // browser.zoom.updateBackgroundTabs preference cache
   updateBackgroundTabs: undefined,
 
-  // One of the possible values for the mousewheel.* preferences.
-  // From EventStateManager.h.
-  ACTION_ZOOM: 3,
-
   // This maps the browser to monotonically increasing integer
   // tokens. _browserTokenMap[browser] is increased each time the zoom is
   // changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses.
   _browserTokenMap: new WeakMap(),
 
   // Stores initial locations if we receive onLocationChange
   // events before we're initialized.
   _initialLocations: new WeakMap(),
@@ -44,18 +40,17 @@ var FullZoom = {
                                          Ci.nsIContentPrefObserver,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsISupports]),
 
   //**************************************************************************//
   // Initialization & Destruction
 
   init: function FullZoom_init() {
-    // Listen for scrollwheel events so we can save scrollwheel-based changes.
-    window.addEventListener("DOMMouseScroll", this, false);
+    gBrowser.addEventListener("ZoomChangeUsingMouseWheel", this);
 
     // Register ourselves with the service so we know when our pref changes.
     this._cps2 = Cc["@mozilla.org/content-pref/service;1"].
                  getService(Ci.nsIContentPrefService2);
     this._cps2.addObserverForName(this.name, this);
 
     this._siteSpecificPref =
       gPrefService.getBoolPref("browser.zoom.siteSpecific");
@@ -76,79 +71,35 @@ var FullZoom = {
     // This should be nulled after initialization.
     this._initialLocations.clear();
     this._initialLocations = null;
   },
 
   destroy: function FullZoom_destroy() {
     gPrefService.removeObserver("browser.zoom.", this);
     this._cps2.removeObserverForName(this.name, this);
-    window.removeEventListener("DOMMouseScroll", this, false);
+    gBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this);
   },
 
 
   //**************************************************************************//
   // Event Handlers
 
   // nsIDOMEventListener
 
   handleEvent: function FullZoom_handleEvent(event) {
     switch (event.type) {
-      case "DOMMouseScroll":
-        this._handleMouseScrolled(event);
+      case "ZoomChangeUsingMouseWheel":
+        let browser = this._getTargetedBrowser(event);
+        this._ignorePendingZoomAccesses(browser);
+        this._applyZoomToPref(browser);
         break;
     }
   },
 
-  _handleMouseScrolled: function FullZoom__handleMouseScrolled(event) {
-    // Construct the "mousewheel action" pref key corresponding to this event.
-    // Based on EventStateManager::WheelPrefs::GetBasePrefName().
-    var pref = "mousewheel.";
-
-    var pressedModifierCount = event.shiftKey + event.ctrlKey + event.altKey +
-                                 event.metaKey + event.getModifierState("OS");
-    if (pressedModifierCount != 1) {
-      pref += "default.";
-    } else if (event.shiftKey) {
-      pref += "with_shift.";
-    } else if (event.ctrlKey) {
-      pref += "with_control.";
-    } else if (event.altKey) {
-      pref += "with_alt.";
-    } else if (event.metaKey) {
-      pref += "with_meta.";
-    } else {
-      pref += "with_win.";
-    }
-
-    pref += "action";
-
-    // Don't do anything if this isn't a "zoom" scroll event.
-    var isZoomEvent = false;
-    try {
-      isZoomEvent = (gPrefService.getIntPref(pref) == this.ACTION_ZOOM);
-    } catch (e) {}
-    if (!isZoomEvent)
-      return;
-
-    // XXX Lazily cache all the possible action prefs so we don't have to get
-    // them anew from the pref service for every scroll event?  We'd have to
-    // make sure to observe them so we can update the cache when they change.
-
-    // We have to call _applyZoomToPref in a timeout because we handle the
-    // event before the event state manager has a chance to apply the zoom
-    // during EventStateManager::PostHandleEvent.
-    let browser = gBrowser.selectedBrowser;
-    let token = this._getBrowserToken(browser);
-    window.setTimeout(function () {
-      if (token.isCurrent)
-        this._applyZoomToPref(browser);
-    }.bind(this), 0);
-  },
-
   // nsIObserver
 
   observe: function (aSubject, aTopic, aData) {
     switch (aTopic) {
       case "nsPref:changed":
         switch (aData) {
           case "browser.zoom.siteSpecific":
             this._siteSpecificPref =
@@ -465,16 +416,40 @@ var FullZoom = {
         // has no properties, so return false.  Check for this case by getting a
         // property, say, docShell.
         return map.get(browser) === this.token && browser.parentNode;
       },
     };
   },
 
   /**
+   * Returns the browser that the supplied zoom event is associated with.
+   * @param event  The ZoomChangeUsingMouseWheel event.
+   * @return  The associated browser element, if one exists, otherwise null.
+   */
+  _getTargetedBrowser: function FullZoom__getTargetedBrowser(event) {
+    let target = event.originalTarget;
+
+    // With remote content browsers, the event's target is the browser
+    // we're looking for.
+    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+    if (target instanceof window.XULElement &&
+        target.localName == "browser" &&
+        target.namespaceURI == XUL_NS)
+      return target;
+
+    // With in-process content browsers, the event's target is the content
+    // document.
+    if (target.nodeType == Node.DOCUMENT_NODE)
+      return gBrowser.getBrowserForDocument(target);
+
+    throw new Error("Unexpected ZoomChangeUsingMouseWheel event source");
+  },
+
+  /**
    * Increments the zoom change token for the given browser so that pending
    * async operations know that it may be unsafe to access they zoom when they
    * finish.
    *
    * @param browser  Pending accesses in this browser will be ignored.
    */
   _ignorePendingZoomAccesses: function FullZoom__ignorePendingZoomAccesses(browser) {
     let map = this._browserTokenMap;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -18,16 +18,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
                                   "resource://gre/modules/CharsetMenu.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
                                   "resource://gre/modules/ShortcutUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
                                   "resource://gre/modules/GMPInstallManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
+                                  "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
                                   "resource:///modules/ContentSearch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
                                   "resource:///modules/AboutHome.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 
@@ -1289,27 +1291,17 @@ var gBrowserInit = {
 
     gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
     gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
     gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
 
     if (Win7Features)
       Win7Features.onOpenWindow();
 
-   // called when we go into full screen, even if initiated by a web page script
-    window.addEventListener("fullscreen", onFullScreen, true);
-
-    // Called when we enter DOM full-screen mode. Note we can already be in browser
-    // full-screen mode when we enter DOM full-screen mode.
-    window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true);
-
-    if (window.fullScreen)
-      onFullScreen();
-    if (document.mozFullScreen)
-      onMozEnteredDomFullscreen();
+    FullScreen.init();
 
 #ifdef MOZ_SERVICES_SYNC
     // initialize the sync UI
     gSyncUI.init();
     gFxAccounts.init();
 #endif
 
 #ifdef MOZ_DATA_REPORTING
@@ -1430,17 +1422,17 @@ var gBrowserInit = {
     // uninit methods don't depend on the services having been initialized).
 
     CombinedStopReload.uninit();
 
     gGestureSupport.init(false);
 
     gHistorySwipeAnimation.uninit();
 
-    FullScreen.cleanup();
+    FullScreen.uninit();
 
 #ifdef MOZ_SERVICES_SYNC
     gFxAccounts.uninit();
 #endif
 
     Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
 
     try {
@@ -2757,24 +2749,16 @@ function SwitchToMetro() {
 
   let intervalID = window.setInterval(this._checkDefaultAndSwitchToMetro, 1000);
   window.setTimeout(function() { window.clearInterval(intervalID); }, 10000);
 #endif
 #endif
 #endif
 }
 
-function onFullScreen(event) {
-  FullScreen.toggle(event);
-}
-
-function onMozEnteredDomFullscreen(event) {
-  FullScreen.enterDomFullscreen(event);
-}
-
 function getWebNavigation()
 {
   return gBrowser.webNavigation;
 }
 
 function BrowserReloadWithFlags(reloadFlags) {
   let url = gBrowser.currentURI.spec;
   if (gBrowser.updateBrowserRemotenessByURL(gBrowser.selectedBrowser, url)) {
@@ -3104,17 +3088,17 @@ const BrowserSearch = {
     }
 #endif
     let openSearchPageIfFieldIsNotActive = function(aSearchBar) {
       if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
         let url = gBrowser.currentURI.spec.toLowerCase();
         let mm = gBrowser.selectedBrowser.messageManager;
         if (url === "about:home") {
           AboutHome.focusInput(mm);
-        } else if (url === "about:newtab") {
+        } else if (url === "about:newtab" && NewTabUtils.allPages.enabled) {
           ContentSearch.focusInput(mm);
         } else {
           openUILinkIn("about:home", "current");
         }
       }
     };
 
     let searchBar = this.searchBar;
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -573,8 +573,45 @@ let PageStyleHandler = {
 PageStyleHandler.init();
 
 // Keep a reference to the translation content handler to avoid it it being GC'ed.
 let trHandler = null;
 if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
   Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
   trHandler = new TranslationContentHandler(global, docShell);
 }
+
+let DOMFullscreenHandler = {
+  _fullscreenDoc: null,
+
+  init: function() {
+    addMessageListener("DOMFullscreen:Approved", this);
+    addMessageListener("DOMFullscreen:CleanUp", this);
+    addEventListener("MozEnteredDomFullscreen", this);
+  },
+
+  receiveMessage: function(aMessage) {
+    switch(aMessage.name) {
+      case "DOMFullscreen:Approved": {
+        if (this._fullscreenDoc) {
+          Services.obs.notifyObservers(this._fullscreenDoc,
+                                       "fullscreen-approved",
+                                       "");
+        }
+        break;
+      }
+      case "DOMFullscreen:CleanUp": {
+        this._fullscreenDoc = null;
+        break;
+      }
+    }
+  },
+
+  handleEvent: function(aEvent) {
+    if (aEvent.type == "MozEnteredDomFullscreen") {
+      this._fullscreenDoc = aEvent.target;
+      sendAsyncMessage("MozEnteredDomFullscreen", {
+        origin: this._fullscreenDoc.nodePrincipal.origin,
+      });
+    }
+  }
+};
+DOMFullscreenHandler.init();
\ No newline at end of file
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1220,16 +1220,25 @@
           return;
         }
 
         // Otherwise, focus the content area. If we're not using remote tabs, we
         // can focus the content area right away, since tab switching is synchronous.
         // If we're using remote tabs, we have to wait until after we've finalized
         // switching the tabs.
 
+        if (newTab._skipContentFocus) {
+          // It's possible the tab we're switching to is ready to focus asynchronously,
+          // when we've already focused something else. In that case, this
+          // _skipContentFocus property can be set so that we skip focusing the
+          // content after we switch tabs.
+          delete newTab._skipContentFocus;
+          return;
+        }
+
         let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
         let focusFlags = fm.FLAG_NOSCROLL;
 
         if (!gMultiProcessBrowser) {
           let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
 
           // for anchors, use FLAG_SHOWRING so that it is clear what link was
           // last clicked when switching back to that tab
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -271,16 +271,18 @@ skip-if = e10s # Bug 921959 - reload wit
 [browser_bug880101.js]
 [browser_bug882977.js]
 [browser_bug902156.js]
 skip-if = e10s # Bug 921959 - reload with LOAD_FLAGS_ALLOW_MIXED_CONTENT fails in e10s
 [browser_bug906190.js]
 skip-if = buildapp == "mulet" || e10s # Bug ?????? - test directly manipulates content (strange - gets an element from a child which it tries to treat as a string, but that fails)
 [browser_bug970746.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (directly gets elements from the content)
+[browser_bug1015721.js]
+skip-if = os == 'win' || e10s # Bug 1056146 - FullZoomHelper uses promiseTabLoadEvent() which isn't e10s friendly
 [browser_canonizeURL.js]
 skip-if = e10s # Bug ?????? - [JavaScript Error: "Error in AboutHome.sendAboutHomeData TypeError: target.messageManager is undefined" {file: "resource:///modules/AboutHome.jsm" line: 208}]
 [browser_contentAreaClick.js]
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" # bug 967013, bug 926729
 [browser_ctrlTab.js]
 skip-if = e10s # Bug ????? - thumbnail captures need e10s love (tabPreviews_capture fails with Argument 1 of CanvasRenderingContext2D.drawWindow does not implement interface Window.)
 [browser_customize_popupNotification.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1015721.js
@@ -0,0 +1,55 @@
+/* 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";
+
+const TEST_PAGE = "http://example.org/browser/browser/base/content/test/general/zoom_test.html";
+
+var gTab1, gTab2, gLevel1;
+
+function test() {
+  waitForExplicitFinish();
+
+  Task.spawn(function () {
+    gTab1 = gBrowser.addTab();
+    gTab2 = gBrowser.addTab();
+
+    yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+    yield FullZoomHelper.load(gTab1, TEST_PAGE);
+    yield FullZoomHelper.load(gTab2, TEST_PAGE);
+  }).then(zoomTab1, FullZoomHelper.failAndContinue(finish));
+}
+
+function dispatchZoomEventToBrowser(browser) {
+  EventUtils.synthesizeWheel(browser.contentDocument.documentElement, 10, 10, {
+    ctrlKey: true, deltaY: -1, deltaMode: WheelEvent.DOM_DELTA_LINE
+  }, browser.contentWindow);
+}
+
+function zoomTab1() {
+  Task.spawn(function () {
+    is(gBrowser.selectedTab, gTab1, "Tab 1 is selected");
+    FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
+    FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
+
+    let browser1 = gBrowser.getBrowserForTab(gTab1);
+    dispatchZoomEventToBrowser(browser1);
+
+    gLevel1 = ZoomManager.getZoomForBrowser(browser1);
+    ok(gLevel1 > 1, "New zoom for tab 1 should be greater than 1");
+
+    yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+    FullZoomHelper.zoomTest(gTab2, gLevel1, "Tab 2 should have zoomed along with tab 1");
+  }).then(finishTest, FullZoomHelper.failAndContinue(finish));
+}
+
+function finishTest() {
+  Task.spawn(function () {
+    yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+    FullZoom.reset();
+    yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
+    yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+    FullZoom.reset();
+    yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
+  }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
--- a/browser/base/content/test/newtab/browser_newtab_search.js
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -200,16 +200,34 @@ function runTests() {
   // Reset changes made to toolbar
   CustomizableUI.reset();
 
   // Test that Ctrl/Cmd + K will focus the search bar from toolbar.
   let searchBar = gWindow.document.getElementById("searchbar");
   EventUtils.synthesizeKey("k", { accelKey: true });
   is(searchBar.textbox.inputField, gWindow.document.activeElement, "Toolbar's search bar should be focused");
 
+  // Test that Ctrl/Cmd + K will focus the search bar from a new about:home page if
+  // the newtab is disabled from `NewTabUtils.allPages.enabled`.
+  yield addNewTabPageTab();
+  // Remove the search bar from toolbar
+  CustomizableUI.removeWidgetFromArea("search-container");
+  NewTabUtils.allPages.enabled = false;
+  EventUtils.synthesizeKey("k", { accelKey: true });
+  let waitEvent = "AboutHomeLoadSnippetsCompleted";
+  yield promiseTabLoadEvent(gWindow.gBrowser.selectedTab, "about:home", waitEvent).then(TestRunner.next);
+
+  is(getContentDocument().documentURI.toLowerCase(), "about:home", "New tab's uri should be about:home");
+  let searchInput = getContentDocument().getElementById("searchText");
+  is(searchInput, getContentDocument().activeElement, "Search input must be the selected element");
+
+  NewTabUtils.allPages.enabled = true;
+  CustomizableUI.reset();
+  gBrowser.removeCurrentTab();
+
   // Done.  Revert the current engine and remove the new engines.
   Services.search.currentEngine = oldCurrentEngine;
   yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
 
   let events = [];
   for (let engine of gNewEngines) {
     Services.search.removeEngine(engine);
     events.push("CurrentState");
@@ -409,8 +427,51 @@ function searchPanel() {
 
 function logoImg() {
   return $("logo");
 }
 
 function gSearch() {
   return getContentWindow().gSearch;
 }
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ *        The tab to load into.
+ * @param [optional] url
+ *        The url to load, or the current url.
+ * @param [optional] event
+ *        The load event type to wait for.  Defaults to "load".
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url, eventType="load") {
+  let deferred = Promise.defer();
+  info("Wait tab event: " + eventType);
+
+  function handle(event) {
+    if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+        event.target.location.href == "about:blank" ||
+        (url && event.target.location.href != url)) {
+      info("Skipping spurious '" + eventType + "'' event" +
+           " for " + event.target.location.href);
+      return;
+    }
+    clearTimeout(timeout);
+    tab.linkedBrowser.removeEventListener(eventType, handle, true);
+    info("Tab event received: " + eventType);
+    deferred.resolve(event);
+  }
+
+  let timeout = setTimeout(() => {
+    tab.linkedBrowser.removeEventListener(eventType, handle, true);
+    deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
+  }, 20000);
+
+  tab.linkedBrowser.addEventListener(eventType, handle, true, true);
+  if (url)
+    tab.linkedBrowser.loadURI(url);
+  return deferred.promise;
+}
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -9,17 +9,17 @@ Services.prefs.setBoolPref(PREF_NEWTAB_E
 let tmp = {};
 Cu.import("resource://gre/modules/Promise.jsm", tmp);
 Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
 Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm", tmp);
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("chrome://browser/content/sanitize.js", tmp);
 Cu.import("resource://gre/modules/Timer.jsm", tmp);
-let {Promise, NewTabUtils, Sanitizer, clearTimeout, DirectoryLinksProvider} = tmp;
+let {Promise, NewTabUtils, Sanitizer, clearTimeout, setTimeout, DirectoryLinksProvider} = tmp;
 
 let uri = Services.io.newURI("about:newtab", null, null);
 let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
 
 let isMac = ("nsILocalFileMac" in Ci);
 let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
 let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
 let gWindow = window;
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -304,16 +304,18 @@ function openLinkIn(url, where, params) 
       loadInBackground = false;
     }
   }
 
   // Raise the target window before loading the URI, since loading it may
   // result in a new frontmost window (e.g. "javascript:window.open('');").
   w.focus();
 
+  let newTab;
+
   switch (where) {
   case "current":
     let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
 
     if (aAllowThirdPartyFixup) {
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
     }
@@ -326,33 +328,40 @@ function openLinkIn(url, where, params) 
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
 
     w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData);
     break;
   case "tabshifted":
     loadInBackground = !loadInBackground;
     // fall through
   case "tab":
-    let browser = w.gBrowser;
-    browser.loadOneTab(url, {
-                       referrerURI: aReferrerURI,
-                       charset: aCharset,
-                       postData: aPostData,
-                       inBackground: loadInBackground,
-                       allowThirdPartyFixup: aAllowThirdPartyFixup,
-                       relatedToCurrent: aRelatedToCurrent,
-                       skipAnimation: aSkipTabAnimation,
-                       allowMixedContent: aAllowMixedContent });
+    newTab = w.gBrowser.loadOneTab(url, {
+      referrerURI: aReferrerURI,
+      charset: aCharset,
+      postData: aPostData,
+      inBackground: loadInBackground,
+      allowThirdPartyFixup: aAllowThirdPartyFixup,
+      relatedToCurrent: aRelatedToCurrent,
+      skipAnimation: aSkipTabAnimation,
+      allowMixedContent: aAllowMixedContent
+    });
     break;
   }
 
   w.gBrowser.selectedBrowser.focus();
 
-  if (!loadInBackground && w.isBlankPageURL(url))
+  if (!loadInBackground && w.isBlankPageURL(url)) {
+    if (newTab && gMultiProcessBrowser) {
+      // Remote browsers are switched to asynchronously, and we need to
+      // ensure that the location bar remains focused in that case rather
+      // than the content area being focused.
+      newTab._skipContentFocus = true;
+    }
     w.focusAndSelectUrlBar();
+  }
 }
 
 // Used as an onclick handler for UI elements with link-like behavior.
 // e.g. onclick="checkForMiddleClick(this, event);"
 function checkForMiddleClick(node, event) {
   // We should be using the disabled property here instead of the attribute,
   // but some elements that this function is used with don't support it (e.g.
   // menuitem).
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1315,118 +1315,105 @@ BrowserGlue.prototype = {
     var notification = notifyBox.appendNotification(text, title, null,
                                                     notifyBox.PRIORITY_CRITICAL_MEDIUM,
                                                     buttons);
     notification.persistence = -1; // Until user closes it
   },
 
   _migrateUI: function BG__migrateUI() {
     const UI_VERSION = 23;
-    const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
+    const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
     let currentUIVersion = 0;
     try {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } catch(ex) {}
     if (currentUIVersion >= UI_VERSION)
       return;
 
-    this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
-    this._dataSource = this._rdf.GetDataSource("rdf:local-store");
-    this._dirty = false;
+    let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
 
     if (currentUIVersion < 2) {
       // This code adds the customizable bookmarks button.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized and the element is not found.
       if (currentset &&
           currentset.indexOf("bookmarks-menu-button-container") == -1) {
         currentset += ",bookmarks-menu-button-container";
-        this._setPersist(toolbarResource, currentsetResource, currentset);
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
       }
     }
 
     if (currentUIVersion < 3) {
       // This code merges the reload/stop/go button into the url bar.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized and all 3 elements are found.
       if (currentset &&
           currentset.indexOf("reload-button") != -1 &&
           currentset.indexOf("stop-button") != -1 &&
           currentset.indexOf("urlbar-container") != -1 &&
           currentset.indexOf("urlbar-container,reload-button,stop-button") == -1) {
         currentset = currentset.replace(/(^|,)reload-button($|,)/, "$1$2")
                                .replace(/(^|,)stop-button($|,)/, "$1$2")
                                .replace(/(^|,)urlbar-container($|,)/,
                                         "$1urlbar-container,reload-button,stop-button$2");
-        this._setPersist(toolbarResource, currentsetResource, currentset);
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
       }
     }
 
     if (currentUIVersion < 4) {
       // This code moves the home button to the immediate left of the bookmarks menu button.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized and the elements are found.
       if (currentset &&
           currentset.indexOf("home-button") != -1 &&
           currentset.indexOf("bookmarks-menu-button-container") != -1) {
         currentset = currentset.replace(/(^|,)home-button($|,)/, "$1$2")
                                .replace(/(^|,)bookmarks-menu-button-container($|,)/,
                                         "$1home-button,bookmarks-menu-button-container$2");
-        this._setPersist(toolbarResource, currentsetResource, currentset);
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
       }
     }
 
     if (currentUIVersion < 5) {
       // This code uncollapses PersonalToolbar if its collapsed status is not
       // persisted, and user customized it or changed default bookmarks.
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "PersonalToolbar");
-      let collapsedResource = this._rdf.GetResource("collapsed");
-      let collapsed = this._getPersist(toolbarResource, collapsedResource);
+      //
       // If the user does not have a persisted value for the toolbar's
       // "collapsed" attribute, try to determine whether it's customized.
-      if (collapsed === null) {
+      if (!xulStore.hasValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed")) {
         // We consider the toolbar customized if it has more than
         // 3 children, or if it has a persisted currentset value.
-        let currentsetResource = this._rdf.GetResource("currentset");
-        let toolbarIsCustomized = !!this._getPersist(toolbarResource,
-                                                     currentsetResource);
+        let toolbarIsCustomized = xulStore.hasValue(BROWSER_DOCURL,
+                                                    "PersonalToolbar", "currentset");
         let getToolbarFolderCount = function () {
           let toolbarFolder =
             PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
           let toolbarChildCount = toolbarFolder.childCount;
           toolbarFolder.containerOpen = false;
           return toolbarChildCount;
         };
 
         if (toolbarIsCustomized || getToolbarFolderCount() > 3) {
-          this._setPersist(toolbarResource, collapsedResource, "false");
+          xulStore.setValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed", "false");
         }
       }
     }
 
     if (currentUIVersion < 8) {
       // Reset homepage pref for users who have it set to google.com/firefox
       let uri = Services.prefs.getComplexValue("browser.startup.homepage",
                                                Ci.nsIPrefLocalizedString).data;
       if (uri && /^https?:\/\/(www\.)?google(\.\w{2,3}){1,2}\/firefox\/?$/.test(uri)) {
         Services.prefs.clearUserPref("browser.startup.homepage");
       }
     }
 
     if (currentUIVersion < 9) {
       // This code adds the customizable downloads buttons.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
 
       // Since the Downloads button is located in the navigation bar by default,
       // migration needs to happen only if the toolbar was customized using a
       // previous UI version, and the button was not already placed on the
       // toolbar manually.
       if (currentset &&
           currentset.indexOf("downloads-button") == -1) {
         // The element is added either after the search bar or before the home
@@ -1437,17 +1424,17 @@ BrowserGlue.prototype = {
                                           "$1search-container,downloads-button$2")
         } else if (currentset.indexOf("home-button") != -1) {
           currentset = currentset.replace(/(^|,)home-button($|,)/,
                                           "$1downloads-button,home-button$2")
         } else {
           currentset = currentset.replace(/(^|,)window-controls($|,)/,
                                           "$1downloads-button,window-controls$2")
         }
-        this._setPersist(toolbarResource, currentsetResource, currentset);
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
       }
     }
 
 #ifdef XP_WIN
     if (currentUIVersion < 10) {
       // For Windows systems with display set to > 96dpi (i.e. systemDefaultScale
       // will return a value > 1.0), we want to discard any saved full-zoom settings,
       // as we'll now be scaling the content according to the system resolution
@@ -1467,25 +1454,23 @@ BrowserGlue.prototype = {
       Services.prefs.clearUserPref("dom.event.contextmenu.enabled");
       Services.prefs.clearUserPref("javascript.enabled");
       Services.prefs.clearUserPref("permissions.default.image");
     }
 
     if (currentUIVersion < 12) {
       // Remove bookmarks-menu-button-container, then place
       // bookmarks-menu-button into its position.
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized.
       if (currentset) {
         if (currentset.contains("bookmarks-menu-button-container")) {
           currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/,
                                           "$1bookmarks-menu-button$2");
-          this._setPersist(toolbarResource, currentsetResource, currentset);
+          xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
         }
       }
     }
 
     if (currentUIVersion < 13) {
       try {
         if (Services.prefs.getBoolPref("plugins.hide_infobar_for_missing_plugin"))
           Services.prefs.setBoolPref("plugins.notifyMissingFlash", false);
@@ -1496,62 +1481,54 @@ BrowserGlue.prototype = {
     if (currentUIVersion < 14) {
       // DOM Storage doesn't specially handle about: pages anymore.
       let path = OS.Path.join(OS.Constants.Path.profileDir,
                               "chromeappsstore.sqlite");
       OS.File.remove(path);
     }
 
     if (currentUIVersion < 16) {
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let collapsedResource = this._rdf.GetResource("collapsed");
-      let isCollapsed = this._getPersist(toolbarResource, collapsedResource);
+      let isCollapsed = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "collapsed");
       if (isCollapsed == "true") {
-        this._setPersist(toolbarResource, collapsedResource, "false");
+        xulStore.setValue(BROWSER_DOCURL, "nav-bar", "collapsed", "false");
       }
     }
 
     // Insert the bookmarks-menu-button into the nav-bar if it isn't already
     // there.
     if (currentUIVersion < 17) {
-      let currentsetResource = this._rdf.GetResource("currentset");
-      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
-      let currentset = this._getPersist(toolbarResource, currentsetResource);
+      let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
       // Need to migrate only if toolbar is customized.
       if (currentset) {
         if (!currentset.contains("bookmarks-menu-button")) {
           // The button isn't in the nav-bar, so let's look for an appropriate
           // place to put it.
           if (currentset.contains("downloads-button")) {
             currentset = currentset.replace(/(^|,)downloads-button($|,)/,
                                             "$1bookmarks-menu-button,downloads-button$2");
           } else if (currentset.contains("home-button")) {
             currentset = currentset.replace(/(^|,)home-button($|,)/,
                                             "$1bookmarks-menu-button,home-button$2");
           } else {
             // Just append.
             currentset = currentset.replace(/(^|,)window-controls($|,)/,
                                             "$1bookmarks-menu-button,window-controls$2")
           }
-          this._setPersist(toolbarResource, currentsetResource, currentset);
+          xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
         }
       }
     }
 
     if (currentUIVersion < 18) {
       // Remove iconsize and mode from all the toolbars
       let toolbars = ["navigator-toolbox", "nav-bar", "PersonalToolbar",
                       "addon-bar", "TabsToolbar", "toolbar-menubar"];
       for (let resourceName of ["mode", "iconsize"]) {
-        let resource = this._rdf.GetResource(resourceName);
         for (let toolbarId of toolbars) {
-          let toolbar = this._rdf.GetResource(BROWSER_DOCURL + toolbarId);
-          if (this._getPersist(toolbar, resource)) {
-            this._setPersist(toolbar, resource);
-          }
+          xulStore.removeValue(BROWSER_DOCURL, toolbarId, resourceName);
         }
       }
     }
 
     if (currentUIVersion < 19) {
       let detector = null;    
       try {
         detector = Services.prefs.getComplexValue("intl.charset.detector",
@@ -1564,31 +1541,23 @@ BrowserGlue.prototype = {
         // If the encoding detector pref value is not reachable from the UI,
         // reset to default (varies by localization).
         Services.prefs.clearUserPref("intl.charset.detector");
       }
     }
 
     if (currentUIVersion < 20) {
       // Remove persisted collapsed state from TabsToolbar.
-      let resource = this._rdf.GetResource("collapsed");
-      let toolbar = this._rdf.GetResource(BROWSER_DOCURL + "TabsToolbar");
-      if (this._getPersist(toolbar, resource)) {
-        this._setPersist(toolbar, resource);
-      }
+      xulStore.removeValue(BROWSER_DOCURL, "TabsToolbar", "collapsed");
     }
 
     if (currentUIVersion < 21) {
       // Make sure the 'toolbarbutton-1' class will always be present from here
       // on out.
-      let button = this._rdf.GetResource(BROWSER_DOCURL + "bookmarks-menu-button");
-      let classResource = this._rdf.GetResource("class");
-      if (this._getPersist(button, classResource)) {
-        this._setPersist(button, classResource);
-      }
+      xulStore.removeValue(BROWSER_DOCURL, "bookmarks-menu-button", "class");
     }
 
     if (currentUIVersion < 22) {
       // Reset the Sync promobox count to promote the new FxAccount-based Sync.
       Services.prefs.clearUserPref("browser.syncPromoViewsLeft");
       Services.prefs.clearUserPref("browser.syncPromoViewsLeftMap");
     }
 
@@ -1598,59 +1567,20 @@ BrowserGlue.prototype = {
         try {
           let name = Services.prefs.getComplexValue(kSelectedEnginePref,
                                                     Ci.nsIPrefLocalizedString).data;
           Services.search.currentEngine = Services.search.getEngineByName(name);
         } catch (ex) {}
       }
     }
 
-    if (this._dirty)
-      this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
-
-    delete this._rdf;
-    delete this._dataSource;
-
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
-  _getPersist: function BG__getPersist(aSource, aProperty) {
-    var target = this._dataSource.GetTarget(aSource, aProperty, true);
-    if (target instanceof Ci.nsIRDFLiteral)
-      return target.Value;
-    return null;
-  },
-
-  _setPersist: function BG__setPersist(aSource, aProperty, aTarget) {
-    this._dirty = true;
-    try {
-      var oldTarget = this._dataSource.GetTarget(aSource, aProperty, true);
-      if (oldTarget) {
-        if (aTarget)
-          this._dataSource.Change(aSource, aProperty, oldTarget, this._rdf.GetLiteral(aTarget));
-        else
-          this._dataSource.Unassert(aSource, aProperty, oldTarget);
-      }
-      else {
-        this._dataSource.Assert(aSource, aProperty, this._rdf.GetLiteral(aTarget), true);
-      }
-
-      // Add the entry to the persisted set for this document if it's not there.
-      // This code is mostly borrowed from XULDocument::Persist.
-      let docURL = aSource.ValueUTF8.split("#")[0];
-      let docResource = this._rdf.GetResource(docURL);
-      let persistResource = this._rdf.GetResource("http://home.netscape.com/NC-rdf#persist");
-      if (!this._dataSource.HasAssertion(docResource, persistResource, aSource, true)) {
-        this._dataSource.Assert(docResource, persistResource, aSource, true);
-      }
-    }
-    catch(ex) {}
-  },
-
   // ------------------------------
   // public nsIBrowserGlue members
   // ------------------------------
 
   sanitize: function BG_sanitize(aParentWindow) {
     this._sanitizer.sanitize(aParentWindow);
   },
 
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -1015,20 +1015,16 @@ this.PlacesUIUtils = {
            Weave.Service.engineManager.get("tabs").enabled;
   },
 };
 
 XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
                                    "@mozilla.org/rdf/rdf-service;1",
                                    "nsIRDFService");
 
-XPCOMUtils.defineLazyGetter(PlacesUIUtils, "localStore", function() {
-  return PlacesUIUtils.RDF.GetDataSource("rdf:local-store");
-});
-
 XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() {
   return Services.prefs.getComplexValue("intl.ellipsis",
                                         Ci.nsIPrefLocalizedString).data;
 });
 
 XPCOMUtils.defineLazyGetter(PlacesUIUtils, "useAsyncTransactions", function() {
   try {
     return Services.prefs.getBoolPref("browser.places.useAsyncTransactions");
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -18,16 +18,24 @@ function PlacesTreeView(aFlatList, aOnOp
   this._flatList = aFlatList;
   this._openContainerCallback = aOnOpenFlatContainer;
   this._controller = aController;
 }
 
 PlacesTreeView.prototype = {
   get wrappedJSObject() this,
 
+  __xulStore: null,
+  get _xulStore() {
+    if (!this.__xulStore) {
+      this.__xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+    }
+    return this.__xulStore;
+  },
+
   __dateService: null,
   get _dateService() {
     if (!this.__dateService) {
       this.__dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
                            getService(Ci.nsIScriptableDateFormat);
     }
     return this.__dateService;
   },
@@ -302,21 +310,25 @@ PlacesTreeView.prototype = {
 
       this._rows[row] = curChild;
       rowsInserted++;
 
       // Recursively do containers.
       if (!this._flatList &&
           curChild instanceof Ci.nsINavHistoryContainerResultNode &&
           !this._controller.hasCachedLivemarkInfo(curChild)) {
-        let resource = this._getResourceForNode(curChild);
-        let isopen = resource != null &&
-                     PlacesUIUtils.localStore.HasAssertion(resource,
-                                                           openLiteral,
-                                                           trueLiteral, true);
+        let uri = curChild.uri;
+        let isopen = false;
+
+        if (uri) {
+          let docURI = this._getDocumentURI();
+          let val = this._xulStore.getValue(docURI, uri, "open");
+          isopen = (val == "true");
+        }
+
         if (isopen != curChild.containerOpen)
           aToOpen.push(curChild);
         else if (curChild.containerOpen && curChild.childCount > 0)
           rowsInserted += this._buildVisibleSection(curChild, row + 1, aToOpen);
       }
     }
 
     return rowsInserted;
@@ -1104,21 +1116,26 @@ PlacesTreeView.prototype = {
     try {
       return this._getRowForNode(aNode, true);
     }
     catch(ex) { }
 
     return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
   },
 
-  _getResourceForNode: function PTV_getResourceForNode(aNode)
+  // Retrieves an nsIURI for the document
+  _documentURI: null,
+  _getDocumentURI: function()
   {
-    let uri = aNode.uri;
-    NS_ASSERT(uri, "if there is no uri, we can't persist the open state");
-    return uri ? PlacesUIUtils.RDF.GetResource(uri) : null;
+    if (!this._documentURI) {
+      let ioService = Cc["@mozilla.org/network/io-service;1"].
+                      getService(Ci.nsIIOService);
+      this._documentURI = ioService.newURI(document.URL, null, null);
+    }
+    return this._documentURI;
   },
 
   // nsITreeView
   get rowCount() this._rows.length,
   get selection() this._selection,
   set selection(val) this._selection = val,
 
   getRowProperties: function() { return ""; },
@@ -1492,25 +1509,26 @@ PlacesTreeView.prototype = {
     let node = this._rows[aRow];
     if (this._flatList && this._openContainerCallback) {
       this._openContainerCallback(node);
       return;
     }
 
     // Persist containers open status, but never persist livemarks.
     if (!this._controller.hasCachedLivemarkInfo(node)) {
-      let resource = this._getResourceForNode(node);
-      if (resource) {
-        const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
-        const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
+      let uri = node.uri;
+
+      if (uri) {
+        let docURI = this._getDocumentURI();
 
-        if (node.containerOpen)
-          PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
-        else
-          PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
+        if (node.containerOpen) {
+          this._xulStore.removeValue(docURI, uri, "open");
+        } else {
+          this._xulStore.setValue(docURI, uri, "open", "true");
+        }
       }
     }
 
     node.containerOpen = !node.containerOpen;
   },
 
   cycleHeader: function PTV_cycleHeader(aColumn) {
     if (!this._result)
--- a/browser/components/places/tests/browser/browser_toolbar_migration.js
+++ b/browser/components/places/tests/browser/browser_toolbar_migration.js
@@ -1,90 +1,53 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 /**
  * Tests PersonalToolbar migration path.
  */
-
 let bg = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
 let gOriginalMigrationVersion;
 const BROWSER_URL = getBrowserURL();
 
 let localStore = {
-  get RDF() Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService),
-  get store() this.RDF.GetDataSource("rdf:local-store"),
+  get xulStore() Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore),
 
-  get toolbar()
+  getValue: function getValue(aProperty)
   {
-    delete this.toolbar;
-    let toolbar = this.RDF.GetResource(BROWSER_URL + "#PersonalToolbar");
-    // Add the entry to the persisted set for this document if it's not there.
-    // See XULDocument::Persist.
-    let doc = this.RDF.GetResource(BROWSER_URL);
-    let persist = this.RDF.GetResource("http://home.netscape.com/NC-rdf#persist");
-    if (!this.store.HasAssertion(doc, persist, toolbar, true)) {
-      this.store.Assert(doc, persist, toolbar, true);
-    }
-    return this.toolbar = toolbar;
+    return this.xulStore.getValue(BROWSER_URL, "PersonalToolbar", aProperty);
   },
 
-  getPersist: function getPersist(aProperty)
-  {
-    let property = this.RDF.GetResource(aProperty);
-    let target = this.store.GetTarget(this.toolbar, property, true);
-    if (target instanceof Ci.nsIRDFLiteral)
-      return target.Value;
-    return null;
-  },
-
-  setPersist: function setPersist(aProperty, aValue)
+  setValue: function setValue(aProperty, aValue)
   {
-    let property = this.RDF.GetResource(aProperty);
-    let value = aValue ? this.RDF.GetLiteral(aValue) : null;
-
-    try {
-      let oldTarget = this.store.GetTarget(this.toolbar, property, true);
-      if (oldTarget && value) {
-        this.store.Change(this.toolbar, property, oldTarget, value);
-      }
-      else if (value) {
-        this.store.Assert(this.toolbar, property, value, true);  
-      }
-      else if (oldTarget) {
-        this.store.Unassert(this.toolbar, property, oldTarget);
-      }
-      else {
-        return;
-      }
+    if (aValue) {
+      this.xulStore.setValue(BROWSER_URL, "PersonalToolbar", aProperty, aValue);
+    } else {
+      this.xulStore.removeValue(BROWSER_URL, "PersonalToolbar", aProperty);
     }
-    catch(ex) {
-      return;
-    }
-    this.store.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
   }
 };
 
 let gTests = [
 
 function test_explicitly_collapsed_toolbar()
 {
   info("An explicitly collapsed toolbar should not be uncollapsed.");
-  localStore.setPersist("collapsed", "true");
+  localStore.setValue("collapsed", "true");
   bg.observe(null, "browser-glue-test", "force-ui-migration");
-  is(localStore.getPersist("collapsed"), "true", "Toolbar is collapsed");
+  is(localStore.getValue("collapsed"), "true", "Toolbar is collapsed");
 },
 
 function test_customized_toolbar()
 {
   info("A customized toolbar should be uncollapsed.");
-  localStore.setPersist("currentset", "splitter");
+  localStore.setValue("currentset", "splitter");
   bg.observe(null, "browser-glue-test", "force-ui-migration");
-  is(localStore.getPersist("collapsed"), "false", "Toolbar has been uncollapsed");
+  is(localStore.getValue("collapsed"), "false", "Toolbar has been uncollapsed");
 },
 
 function test_many_bookmarks_toolbar()
 {
   info("A toolbar with added bookmarks should be uncollapsed.");
   let ids = [];
   ids.push(
     PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
@@ -93,43 +56,47 @@ function test_many_bookmarks_toolbar()
   ids.push(
     PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
                                           PlacesUtils.bookmarks.DEFAULT_INDEX)
   );
   ids.push(
     PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
                                           PlacesUtils.bookmarks.DEFAULT_INDEX)
   );
+  ids.push(
+    PlacesUtils.bookmarks.insertSeparator(PlacesUtils.toolbarFolderId,
+                                          PlacesUtils.bookmarks.DEFAULT_INDEX)
+  );
   bg.observe(null, "browser-glue-test", "force-ui-migration");
-  is(localStore.getPersist("collapsed"), "false", "Toolbar has been uncollapsed");
+  is(localStore.getValue("collapsed"), "false", "Toolbar has been uncollapsed");
 },
 
 ];
 
 function test()
 {
   gOriginalMigrationVersion = Services.prefs.getIntPref("browser.migration.version");
   registerCleanupFunction(clean);
 
-  if (localStore.getPersist("currentset") !== null) {
+  if (localStore.getValue("currentset") !== null) {
     info("Toolbar currentset was persisted by a previous test, fixing it.");
-    localStore.setPersist("currentset", null);
+    localStore.setValue("currentset", null);
   }
 
-  if (localStore.getPersist("collapsed") !== null) {
+  if (localStore.getValue("collapsed") !== null) {
     info("Toolbar collapsed status was persisted by a previous test, fixing it.");
-    localStore.setPersist("collapsed", null);
+    localStore.setValue("collapsed", null);
   }
 
   while (gTests.length) {
     clean();
     Services.prefs.setIntPref("browser.migration.version", 4);
     gTests.shift().call();
   }
 }
 
 function clean()
 {
   Services.prefs.setIntPref("browser.migration.version", gOriginalMigrationVersion);
-  localStore.setPersist("currentset", null);
-  localStore.setPersist("collapsed", null);
+  localStore.setValue("currentset", null);
+  localStore.setValue("collapsed", null);
 }
 
--- a/browser/components/search/test/browser_426329.js
+++ b/browser/components/search/test/browser_426329.js
@@ -2,298 +2,304 @@
 // we only need ChromeUtils.js for a few files which is why we are using loadSubScript.
 var ChromeUtils = {};
 this._scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                      getService(Ci.mozIJSSubScriptLoader);
 this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js", ChromeUtils);
 
 XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
   "resource://gre/modules/FormHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/Promise.jsm");
 
-function test() {
-  waitForExplicitFinish();
+function expectedURL(aSearchTerms) {
+  const ENGINE_HTML_BASE = "http://mochi.test:8888/browser/browser/components/search/test/test.html";
+  var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
+                     getService(Ci.nsITextToSubURI);
+  var searchArg = textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
+  return ENGINE_HTML_BASE + "?test=" + searchArg;
+}
 
-  const ENGINE_HTML_BASE = "http://mochi.test:8888/browser/browser/components/search/test/test.html";
+function simulateClick(aEvent, aTarget) {
+  var event = document.createEvent("MouseEvent");
+  var ctrlKeyArg  = aEvent.ctrlKey  || false;
+  var altKeyArg   = aEvent.altKey   || false;
+  var shiftKeyArg = aEvent.shiftKey || false;
+  var metaKeyArg  = aEvent.metaKey  || false;
+  var buttonArg   = aEvent.button   || 0;
+  event.initMouseEvent("click", true, true, window,
+                        0, 0, 0, 0, 0,
+                        ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
+                        buttonArg, null);
+  aTarget.dispatchEvent(event);
+}
+
+// modified from toolkit/components/satchel/test/test_form_autocomplete.html
+function checkMenuEntries(expectedValues) {
+  var actualValues = getMenuEntries();
+  is(actualValues.length, expectedValues.length, "Checking length of expected menu");
+  for (var i = 0; i < expectedValues.length; i++)
+    is(actualValues[i], expectedValues[i], "Checking menu entry #" + i);
+}
 
-  var searchEntries = ["test", "More Text", "Some Text"];
-  var searchBar = BrowserSearch.searchBar;
-  var searchButton = document.getAnonymousElementByAttribute(searchBar,
-                     "anonid", "search-go-button");
-  ok(searchButton, "got search-go-button");
+function getMenuEntries() {
+  var entries = [];
+  var autocompleteMenu = searchBar.textbox.popup;
+  // Could perhaps pull values directly from the controller, but it seems
+  // more reliable to test the values that are actually in the tree?
+  var column = autocompleteMenu.tree.columns[0];
+  var numRows = autocompleteMenu.tree.view.rowCount;
+  for (var i = 0; i < numRows; i++) {
+    entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
+  }
+  return entries;
+}
 
-  searchBar.value = "test";
+function* countEntries(name, value) {
+  let deferred = Promise.defer();
+  let count = 0;
+  let obj = name && value ? {fieldname: name, value: value} : {};
+  FormHistory.count(obj,
+                    { handleResult: function(result) { count = result; },
+                      handleError: function(error) { throw error; },
+                      handleCompletion: function(reason) {
+                        if (!reason) {
+                          deferred.resolve(count);
+                        }
+                      }
+                    });
+  return deferred.promise;
+}
 
+var searchBar;
+var searchButton;
+var searchEntries = ["test", "More Text", "Some Text"];
+function* promiseSetEngine() {
+  let deferred = Promise.defer();
   var ss = Services.search;
 
-  let testIterator;
-
   function observer(aSub, aTopic, aData) {
     switch (aData) {
       case "engine-added":
         var engine = ss.getEngineByName("Bug 426329");
         ok(engine, "Engine was added.");
         ss.currentEngine = engine;
         break;
       case "engine-current":
         ok(ss.currentEngine.name == "Bug 426329", "currentEngine set");
-        testReturn();
-        break;
-      case "engine-removed":
+        searchBar = BrowserSearch.searchBar;
+        searchButton = document.getAnonymousElementByAttribute(searchBar,
+                           "anonid", "search-go-button");
+        ok(searchButton, "got search-go-button");
+        searchBar.value = "test";
+
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
-        finish();
+        deferred.resolve();
         break;
     }
-  }
+  };
 
   Services.obs.addObserver(observer, "browser-search-engine-modified", false);
   ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/426329.xml",
                Ci.nsISearchEngine.DATA_XML, "data:image/x-icon,%00",
                false);
 
-  var preSelectedBrowser, preTabNo;
-  function init() {
-    preSelectedBrowser = gBrowser.selectedBrowser;
-    preTabNo = gBrowser.tabs.length;
-    searchBar.focus();
-  }
-
-  function testReturn() {
-    init();
-    EventUtils.synthesizeKey("VK_RETURN", {});
-    doOnloadOnce(function(event) {
+  return deferred.promise;
+}
 
-      is(gBrowser.tabs.length, preTabNo, "Return key did not open new tab");
-      is(event.originalTarget, preSelectedBrowser.contentDocument,
-         "Return key loaded results in current tab");
-      is(event.originalTarget.URL, expectedURL(searchBar.value), "testReturn opened correct search page");
-
-      testAltReturn();
-    });
-  }
-
-  function testAltReturn() {
-    init();
-    EventUtils.synthesizeKey("VK_RETURN", { altKey: true });
-    doOnloadOnce(function(event) {
+function* promiseRemoveEngine() {
+  let deferred = Promise.defer();
+  var ss = Services.search;
 
-      is(gBrowser.tabs.length, preTabNo + 1, "Alt+Return key added new tab");
-      isnot(event.originalTarget, preSelectedBrowser.contentDocument,
-            "Alt+Return key loaded results in new tab");
-      is(event.originalTarget, gBrowser.contentDocument,
-         "Alt+Return key loaded results in foreground tab");
-      is(event.originalTarget.URL, expectedURL(searchBar.value), "testAltReturn opened correct search page");
-
-      //Shift key has no effect for now, so skip it
-      //testShiftAltReturn();
-      testLeftClick();
-    });
-  }
+  function observer(aSub, aTopic, aData) {
+    if (aData == "engine-removed") {
+      Services.obs.removeObserver(observer, "browser-search-engine-modified");
+      deferred.resolve();
+    }
+  };
 
-  function testShiftAltReturn() {
-    init();
-    EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true, altKey: true });
-    doOnloadOnce(function(event) {
+  Services.obs.addObserver(observer, "browser-search-engine-modified", false);
+  var engine = ss.getEngineByName("Bug 426329");
+  ss.removeEngine(engine);
 
-      is(gBrowser.tabs.length, preTabNo + 1, "Shift+Alt+Return key added new tab");
-      isnot(event.originalTarget, preSelectedBrowser.contentDocument,
-            "Shift+Alt+Return key loaded results in new tab");
-      isnot(event.originalTarget, gBrowser.contentDocument,
-            "Shift+Alt+Return key loaded results in background tab");
-      is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftAltReturn opened correct search page");
-
-      testLeftClick();
-    });
-  }
-
-  function testLeftClick() {
-    init();
-    simulateClick({ button: 0 }, searchButton);
-    doOnloadOnce(function(event) {
+  return deferred.promise;
+}
 
-      is(gBrowser.tabs.length, preTabNo, "LeftClick did not open new tab");
-      is(event.originalTarget, preSelectedBrowser.contentDocument,
-         "LeftClick loaded results in current tab");
-      is(event.originalTarget.URL, expectedURL(searchBar.value), "testLeftClick opened correct search page");
 
-      testMiddleClick();
-    });
-  }
-
-  function testMiddleClick() {
-    init();
-    simulateClick({ button: 1 }, searchButton);
-    doOnloadOnce(function(event) {
-
-      is(gBrowser.tabs.length, preTabNo + 1, "MiddleClick added new tab");
-      isnot(event.originalTarget, preSelectedBrowser.contentDocument,
-            "MiddleClick loaded results in new tab");
-      is(event.originalTarget, gBrowser.contentDocument,
-         "MiddleClick loaded results in foreground tab");
-      is(event.originalTarget.URL, expectedURL(searchBar.value), "testMiddleClick opened correct search page");
-
-      testShiftMiddleClick();
-    });
-  }
-
-  function testShiftMiddleClick() {
-    init();
-    simulateClick({ button: 1, shiftKey: true }, searchButton);
-    doOnloadOnce(function(event) {
+var preSelectedBrowser;
+var preTabNo;
+function* prepareTest() {
+  preSelectedBrowser = gBrowser.selectedBrowser;
+  preTabNo = gBrowser.tabs.length;
+  searchBar = BrowserSearch.searchBar;
 
-      is(gBrowser.tabs.length, preTabNo + 1, "Shift+MiddleClick added new tab");
-      isnot(event.originalTarget, preSelectedBrowser.contentDocument,
-            "Shift+MiddleClick loaded results in new tab");
-      isnot(event.originalTarget, gBrowser.contentDocument,
-            "Shift+MiddleClick loaded results in background tab");
-      is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftMiddleClick opened correct search page");
-
-      testDropText();
-     });
-   }
- 
-  // prevent the search buttonmenu from opening during the drag tests
-  function stopPopup(event) { event.preventDefault(); }
+  let windowFocused = Promise.defer();
+  SimpleTest.waitForFocus(windowFocused.resolve, window);
+  yield windowFocused.promise;
 
-  function testDropText() {
-    init();
-    searchBar.addEventListener("popupshowing", stopPopup, true);
-    // drop on the search button so that we don't need to worry about the
-    // default handlers for textboxes.
-    ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/plain", data: "Some Text" } ]], "copy", window);
-    doOnloadOnce(function(event) {
-      is(searchBar.value, "Some Text", "drop text/plain on searchbar");
-      testDropInternalText();
+  let deferred = Promise.defer();
+  if (document.activeElement != searchBar) {
+    searchBar.addEventListener("focus", function onFocus() {
+      searchBar.removeEventListener("focus", onFocus);
+      deferred.resolve();
     });
-  }
-
-  function testDropInternalText() {
-    init();
-    ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/x-moz-text-internal", data: "More Text" } ]], "copy", window);
-    doOnloadOnce(function(event) {
-      is(searchBar.value, "More Text", "drop text/x-moz-text-internal on searchbar");
-      testDropLink();
-    });
+    searchBar.focus();
+  } else {
+    deferred.resolve();
   }
-
-  function testDropLink() {
-    init();
-    ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/uri-list", data: "http://www.mozilla.org" } ]], "copy", window);
-    is(searchBar.value, "More Text", "drop text/uri-list on searchbar");
-    SimpleTest.executeSoon(testRightClick);
-  }
-
-  function testRightClick() {
-    init();
-    searchBar.removeEventListener("popupshowing", stopPopup, true);
-    content.location.href = "about:blank";
-    simulateClick({ button: 2 }, searchButton);
-    setTimeout(function() {
-      is(gBrowser.tabs.length, preTabNo, "RightClick did not open new tab");
-      is(gBrowser.currentURI.spec, "about:blank", "RightClick did nothing");
-
-      testIterator = testSearchHistory();
-      testIterator.next();
-    }, 5000);
-  }
-
-  function countEntries(name, value, message) {
-    let count = 0;
-    FormHistory.count({ fieldname: name, value: value },
-                      { handleResult: function(result) { count = result; },
-                        handleError: function(error) { throw error; },
-                        handleCompletion: function(reason) {
-                          if (!reason) {
-                            ok(count > 0, message);
-                            testIterator.next();
-                          }
-                        }
-                      });
-  }
-
-  function testSearchHistory() {
-    var textbox = searchBar._textbox;
-    for (var i = 0; i < searchEntries.length; i++) {
-      yield countEntries(textbox.getAttribute("autocompletesearchparam"), searchEntries[i],
-                         "form history entry '" + searchEntries[i] + "' should exist");
-    }
-    testAutocomplete();
-  }
-
-  function testAutocomplete() {
-    var popup = searchBar.textbox.popup;
-    popup.addEventListener("popupshown", function testACPopupShowing() {
-      popup.removeEventListener("popupshown", testACPopupShowing);
-      checkMenuEntries(searchEntries);
-      testClearHistory();
-    });
-    searchBar.textbox.showHistoryPopup();
-  }
-
-  function testClearHistory() {
-    let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory")
-    ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
-    controller.doCommand("cmd_clearhistory");
-    let count = 0;
-    FormHistory.count({ },
-                      { handleResult: function(result) { count = result; },
-                        handleError: function(error) { throw error; },
-                        handleCompletion: function(reason) {
-                          if (!reason) {
-                            ok(count == 0, "History cleared");
-                            finalize();
-                          }
-                        }
-                      });
-  }
-
-  function finalize() {
-    searchBar.value = "";
-    while (gBrowser.tabs.length != 1) {
-      gBrowser.removeTab(gBrowser.tabs[0]);
-    }
-    content.location.href = "about:blank";
-    var engine = ss.getEngineByName("Bug 426329");
-    ss.removeEngine(engine);
-  }
-
-  function simulateClick(aEvent, aTarget) {
-    var event = document.createEvent("MouseEvent");
-    var ctrlKeyArg  = aEvent.ctrlKey  || false;
-    var altKeyArg   = aEvent.altKey   || false;
-    var shiftKeyArg = aEvent.shiftKey || false;
-    var metaKeyArg  = aEvent.metaKey  || false;
-    var buttonArg   = aEvent.button   || 0;
-    event.initMouseEvent("click", true, true, window,
-                          0, 0, 0, 0, 0,
-                          ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
-                          buttonArg, null);
-    aTarget.dispatchEvent(event);
-  }
-
-  function expectedURL(aSearchTerms) {
-    var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
-                       getService(Ci.nsITextToSubURI);
-    var searchArg = textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
-    return ENGINE_HTML_BASE + "?test=" + searchArg;
-  }
-
-  // modified from toolkit/components/satchel/test/test_form_autocomplete.html
-  function checkMenuEntries(expectedValues) {
-    var actualValues = getMenuEntries();
-    is(actualValues.length, expectedValues.length, "Checking length of expected menu");
-    for (var i = 0; i < expectedValues.length; i++)
-      is(actualValues[i], expectedValues[i], "Checking menu entry #" + i);
-  }
-
-  function getMenuEntries() {
-    var entries = [];
-    var autocompleteMenu = searchBar.textbox.popup;
-    // Could perhaps pull values directly from the controller, but it seems
-    // more reliable to test the values that are actually in the tree?
-    var column = autocompleteMenu.tree.columns[0];
-    var numRows = autocompleteMenu.tree.view.rowCount;
-    for (var i = 0; i < numRows; i++) {
-      entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
-    }
-    return entries;
-  }
+  return deferred.promise;
 }
 
+add_task(function testSetupEngine() {
+  yield promiseSetEngine();
+});
+
+add_task(function testReturn() {
+  yield prepareTest();
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  let event = yield promiseOnLoad();
+
+  is(gBrowser.tabs.length, preTabNo, "Return key did not open new tab");
+  is(event.originalTarget, preSelectedBrowser.contentDocument,
+     "Return key loaded results in current tab");
+  is(event.originalTarget.URL, expectedURL(searchBar.value), "testReturn opened correct search page");
+});
+
+add_task(function testAltReturn() {
+  yield prepareTest();
+  EventUtils.synthesizeKey("VK_RETURN", { altKey: true });
+  let event = yield promiseOnLoad();
+
+  is(gBrowser.tabs.length, preTabNo + 1, "Alt+Return key added new tab");
+  isnot(event.originalTarget, preSelectedBrowser.contentDocument,
+        "Alt+Return key loaded results in new tab");
+  is(event.originalTarget, gBrowser.contentDocument,
+     "Alt+Return key loaded results in foreground tab");
+  is(event.originalTarget.URL, expectedURL(searchBar.value), "testAltReturn opened correct search page");
+});
+
+//Shift key has no effect for now, so skip it
+add_task(function testShiftAltReturn() {
+  return;
+
+  yield prepareTest();
+  EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true, altKey: true });
+  let event = yield promiseOnLoad();
+
+  is(gBrowser.tabs.length, preTabNo + 1, "Shift+Alt+Return key added new tab");
+  isnot(event.originalTarget, preSelectedBrowser.contentDocument,
+        "Shift+Alt+Return key loaded results in new tab");
+  isnot(event.originalTarget, gBrowser.contentDocument,
+        "Shift+Alt+Return key loaded results in background tab");
+  is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftAltReturn opened correct search page");
+});
+
+add_task(function testLeftClick() {
+  yield prepareTest();
+  simulateClick({ button: 0 }, searchButton);
+  let event = yield promiseOnLoad();
+  is(gBrowser.tabs.length, preTabNo, "LeftClick did not open new tab");
+  is(event.originalTarget, preSelectedBrowser.contentDocument,
+     "LeftClick loaded results in current tab");
+  is(event.originalTarget.URL, expectedURL(searchBar.value), "testLeftClick opened correct search page");
+});
+
+add_task(function testMiddleClick() {
+  yield prepareTest();
+  simulateClick({ button: 1 }, searchButton);
+  let event = yield promiseOnLoad();
+  is(gBrowser.tabs.length, preTabNo + 1, "MiddleClick added new tab");
+  isnot(event.originalTarget, preSelectedBrowser.contentDocument,
+        "MiddleClick loaded results in new tab");
+  is(event.originalTarget, gBrowser.contentDocument,
+     "MiddleClick loaded results in foreground tab");
+  is(event.originalTarget.URL, expectedURL(searchBar.value), "testMiddleClick opened correct search page");
+});
+
+add_task(function testShiftMiddleClick() {
+  yield prepareTest();
+  simulateClick({ button: 1, shiftKey: true }, searchButton);
+  let event = yield promiseOnLoad();
+  is(gBrowser.tabs.length, preTabNo + 1, "Shift+MiddleClick added new tab");
+  isnot(event.originalTarget, preSelectedBrowser.contentDocument,
+        "Shift+MiddleClick loaded results in new tab");
+  isnot(event.originalTarget, gBrowser.contentDocument,
+        "Shift+MiddleClick loaded results in background tab");
+  is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftMiddleClick opened correct search page");
+});
+
+add_task(function testDropText() {
+  yield prepareTest();
+  let promisePreventPopup = promiseEvent(searchBar, "popupshowing", true);
+  // drop on the search button so that we don't need to worry about the
+  // default handlers for textboxes.
+  ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/plain", data: "Some Text" } ]], "copy", window);
+  yield promisePreventPopup;
+  let event = yield promiseOnLoad();
+  is(event.originalTarget.URL, expectedURL(searchBar.value), "testDropText opened correct search page");
+  is(searchBar.value, "Some Text", "drop text/plain on searchbar");
+});
+
+add_task(function testDropInternalText() {
+  yield prepareTest();
+  let promisePreventPopup = promiseEvent(searchBar, "popupshowing", true);
+  ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/x-moz-text-internal", data: "More Text" } ]], "copy", window);
+  yield promisePreventPopup;
+  let event = yield promiseOnLoad();
+  is(event.originalTarget.URL, expectedURL(searchBar.value), "testDropInternalText opened correct search page");
+  is(searchBar.value, "More Text", "drop text/x-moz-text-internal on searchbar");
+
+  // testDropLink implicitly depended on testDropInternalText, so these two tests
+  // were merged so that if testDropInternalText failed it wouldn't cause testDropLink
+  // to fail unexplainably.
+  yield prepareTest();
+  let promisePreventPopup = promiseEvent(searchBar, "popupshowing", true);
+  ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/uri-list", data: "http://www.mozilla.org" } ]], "copy", window);
+  yield promisePreventPopup;
+  is(searchBar.value, "More Text", "drop text/uri-list on searchbar shouldn't change anything");
+});
+
+add_task(function testRightClick() {
+  preTabNo = gBrowser.tabs.length;
+  content.location.href = "about:blank";
+  simulateClick({ button: 2 }, searchButton);
+  let deferred = Promise.defer();
+  setTimeout(function() {
+    is(gBrowser.tabs.length, preTabNo, "RightClick did not open new tab");
+    is(gBrowser.currentURI.spec, "about:blank", "RightClick did nothing");
+    deferred.resolve();
+  }, 5000);
+  yield deferred.promise;
+});
+
+add_task(function testSearchHistory() {
+  var textbox = searchBar._textbox;
+  for (var i = 0; i < searchEntries.length; i++) {
+    let count = yield countEntries(textbox.getAttribute("autocompletesearchparam"), searchEntries[i]);
+    ok(count > 0, "form history entry '" + searchEntries[i] + "' should exist");
+  }
+});
+
+add_task(function testAutocomplete() {
+  var popup = searchBar.textbox.popup;
+  let popupShownPromise = promiseEvent(popup, "popupshown");
+  searchBar.textbox.showHistoryPopup();
+  yield popupShownPromise;
+  checkMenuEntries(searchEntries);
+});
+
+add_task(function testClearHistory() {
+  let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory")
+  ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
+  controller.doCommand("cmd_clearhistory");
+  let count = yield countEntries();
+  ok(count == 0, "History cleared");
+});
+
+add_task(function asyncCleanup() {
+  searchBar.value = "";
+  while (gBrowser.tabs.length != 1) {
+    gBrowser.removeTab(gBrowser.tabs[0], {animate: false});
+  }
+  content.location.href = "about:blank";
+  yield promiseRemoveEngine();
+});
+
--- a/browser/components/search/test/head.js
+++ b/browser/components/search/test/head.js
@@ -1,11 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/Promise.jsm");
+
 function whenNewWindowLoaded(aOptions, aCallback) {
   let win = OpenBrowserWindow(aOptions);
   let gotLoad = false;
   let gotActivate = Services.focus.activeWindow == win;
 
   function maybeRunCallback() {
     if (gotLoad && gotActivate) {
       executeSoon(function() { aCallback(win); });
@@ -83,16 +86,28 @@ function waitForPopupShown(aPopupId, aCa
   }
   function removePopupShownListener() {
     popup.removeEventListener("popupshown", onPopupShown);
   }
   popup.addEventListener("popupshown", onPopupShown);
   registerCleanupFunction(removePopupShownListener);
 }
 
+function* promiseEvent(aTarget, aEventName, aPreventDefault) {
+  let deferred = Promise.defer();
+  aTarget.addEventListener(aEventName, function onEvent(aEvent) {
+    aTarget.removeEventListener(aEventName, onEvent, true);
+    if (aPreventDefault) {
+      aEvent.preventDefault();
+    }
+    deferred.resolve();
+  }, true);
+  return deferred.promise;
+}
+
 function waitForBrowserContextMenu(aCallback) {
   waitForPopupShown(gBrowser.selectedBrowser.contextMenu, aCallback);
 }
 
 function doOnloadOnce(aCallback) {
   function doOnloadOnceListener(aEvent) {
     info("doOnloadOnce: " + aEvent.originalTarget.location);
     removeDoOnloadOnceListener();
@@ -101,8 +116,21 @@ function doOnloadOnce(aCallback) {
     });
   }
   function removeDoOnloadOnceListener() {
     gBrowser.removeEventListener("load", doOnloadOnceListener, true);
   }
   gBrowser.addEventListener("load", doOnloadOnceListener, true);
   registerCleanupFunction(removeDoOnloadOnceListener);
 }
+
+function* promiseOnLoad() {
+  let deferred = Promise.defer();
+
+  gBrowser.addEventListener("load", function onLoadListener(aEvent) {
+    info("onLoadListener: " + aEvent.originalTarget.location);
+    gBrowser.removeEventListener("load", onLoadListener, true);
+    deferred.resolve(aEvent);
+  }, true);
+
+  return deferred.promise;
+}
+
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -17,23 +17,23 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource://gre/modules/devtools/Console.jsm");
 
 const EventEmitter = devtools.require("devtools/toolkit/event-emitter");
 const FORBIDDEN_IDS = new Set(["toolbox", ""]);
 const MAX_ORDINAL = 99;
 
-
 /**
  * DevTools is a class that represents a set of developer tools, it holds a
  * set of tools and keeps track of open toolboxes in the browser.
  */
 this.DevTools = function DevTools() {
   this._tools = new Map();     // Map<toolId, tool>
+  this._themes = new Map();    // Map<themeId, theme>
   this._toolboxes = new Map(); // Map<target, toolbox>
 
   // destroy() is an observer's handler so we need to preserve context.
   this.destroy = this.destroy.bind(this);
   this._teardown = this._teardown.bind(this);
 
   this._testing = false;
 
@@ -225,16 +225,146 @@ DevTools.prototype = {
         definitions.push(definition);
       }
     }
 
     return definitions.sort(this.ordinalSort);
   },
 
   /**
+   * Register a new theme for developer tools toolbox.
+   *
+   * A definition is a light object that holds various information about a
+   * theme.
+   *
+   * Each themeDefinition has the following properties:
+   * - id: Unique identifier for this theme (string|required)
+   * - label: Localized name for the theme to be displayed to the user
+   *          (string|required)
+   * - stylesheets: Array of URLs pointing to a CSS document(s) containing
+   *                the theme style rules (array|required)
+   * - classList: Array of class names identifying the theme within a document.
+   *              These names are set to document element when applying
+   *              the theme (array|required)
+   * - onApply: Function that is executed by the framework when the theme
+   *            is applied. The function takes the current iframe window
+   *            and the previous theme id as arguments (function)
+   * - onUnapply: Function that is executed by the framework when the theme
+   *            is unapplied. The function takes the current iframe window
+   *            and the new theme id as arguments (function)
+   */
+  registerTheme: function DT_registerTheme(themeDefinition) {
+    let themeId = themeDefinition.id;
+
+    if (!themeId) {
+      throw new Error("Invalid theme id");
+    }
+
+    if (this._themes.get(themeId)) {
+      throw new Error("Theme with the same id is already registered");
+    }
+
+    this._themes.set(themeId, themeDefinition);
+
+    this.emit("theme-registered", themeId);
+  },
+
+  /**
+   * Removes an existing theme from the list of registered themes.
+   * Needed so that add-ons can remove themselves when they are deactivated
+   *
+   * @param {string|object} theme
+   *        Definition or the id of the theme to unregister.
+   */
+  unregisterTheme: function DT_unregisterTheme(theme) {
+    let themeId = null;
+    if (typeof theme == "string") {
+      themeId = theme;
+      theme = this._themes.get(theme);
+    }
+    else {
+      themeId = theme.id;
+    }
+
+    let currTheme = Services.prefs.getCharPref("devtools.theme");
+
+    // Change the current theme if it's being dynamically removed together
+    // with the owner (bootstrapped) extension.
+    // But, do not change it if the application is just shutting down.
+    if (!Services.startup.shuttingDown && theme.id == currTheme) {
+      Services.prefs.setCharPref("devtools.theme", "light");
+
+      let data = {
+        pref: "devtools.theme",
+        newValue: "light",
+        oldValue: currTheme
+      };
+
+      gDevTools.emit("pref-changed", data);
+
+      this.emit("theme-unregistered", theme);
+    }
+
+    this._themes.delete(themeId);
+  },
+
+  /**
+   * Get a theme definition if it exists.
+   *
+   * @param {string} themeId
+   *        The id of the theme
+   *
+   * @return {ThemeDefinition|null} theme
+   *         The ThemeDefinition for the id or null.
+   */
+  getThemeDefinition: function DT_getThemeDefinition(themeId) {
+    let theme = this._themes.get(themeId);
+    if (!theme) {
+      return null;
+    }
+    return theme;
+  },
+
+  /**
+   * Get map of registered themes.
+   *
+   * @return {Map} themes
+   *         A map of the the theme definitions registered in this instance
+   */
+  getThemeDefinitionMap: function DT_getThemeDefinitionMap() {
+    let themes = new Map();
+
+    for (let [id, definition] of this._themes) {
+      if (this.getThemeDefinition(id)) {
+        themes.set(id, definition);
+      }
+    }
+
+    return themes;
+  },
+
+  /**
+   * Get registered themes definitions sorted by ordinal value.
+   *
+   * @return {Array} themes
+   *         A sorted array of the theme definitions registered in this instance
+   */
+  getThemeDefinitionArray: function DT_getThemeDefinitionArray() {
+    let definitions = [];
+
+    for (let [id, definition] of this._themes) {
+      if (this.getThemeDefinition(id)) {
+        definitions.push(definition);
+      }
+    }
+
+    return definitions.sort(this.ordinalSort);
+  },
+
+  /**
    * Show a Toolbox for a target (either by creating a new one, or if a toolbox
    * already exists for the target, by bring to the front the existing one)
    * If |toolId| is specified then the displayed toolbox will have the
    * specified tool selected.
    * If |hostType| is specified then the toolbox will be displayed using the
    * specified HostType.
    *
    * @param {Target} target
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 subsuite = devtools
 support-files =
   browser_toolbox_options_disable_js.html
   browser_toolbox_options_disable_js_iframe.html
   browser_toolbox_options_disable_cache.sjs
   head.js
+  doc_theme.css
 
 [browser_devtools_api.js]
 [browser_dynamic_tool_enabling.js]
 [browser_keybindings.js]
 [browser_new_activation_workflow.js]
 [browser_target_events.js]
 [browser_target_remote.js]
 [browser_toolbox_dynamic_registration.js]
@@ -27,12 +28,13 @@ skip-if = e10s # Bug 1030318
 [browser_toolbox_sidebar.js]
 [browser_toolbox_tabsswitch_shortcuts.js]
 [browser_toolbox_tool_ready.js]
 [browser_toolbox_window_reload_target.js]
 [browser_toolbox_window_shortcuts.js]
 [browser_toolbox_window_title_changes.js]
 [browser_toolbox_zoom.js]
 [browser_toolbox_custom_host.js]
+[browser_toolbox_theme_registration.js]
 
 # We want this test to run for mochitest-dt as well, so we include it here:
 [../../../base/content/test/general/browser_parsable_css.js]
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_theme_registration.js
@@ -0,0 +1,113 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const CHROME_URL = "chrome://mochitests/content/browser/browser/devtools/framework/test/";
+
+let toolbox;
+
+function test()
+{
+  gBrowser.selectedTab = gBrowser.addTab();
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
+    gDevTools.showToolbox(target).then(testRegister);
+  }, true);
+
+  content.location = "data:text/html,test for dynamically registering and unregistering themes";
+}
+
+function testRegister(aToolbox)
+{
+  toolbox = aToolbox
+  gDevTools.once("theme-registered", themeRegistered);
+
+  gDevTools.registerTheme({
+    id: "test-theme",
+    label: "Test theme",
+    stylesheets: [CHROME_URL + "doc_theme.css"],
+    classList: ["theme-test"],
+  });
+}
+
+function themeRegistered(event, themeId)
+{
+  is(themeId, "test-theme", "theme-registered event handler sent theme id");
+
+  ok(gDevTools.getThemeDefinitionMap().has(themeId), "theme added to map");
+
+  // Test that new theme appears in the Options panel
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  gDevTools.showToolbox(target, "options").then(() => {
+    let panel = toolbox.getCurrentPanel();
+    let doc = panel.panelWin.frameElement.contentDocument;
+    let themeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
+
+    ok(themeOption, "new theme exists in the Options panel");
+
+    // Apply the new theme.
+    applyTheme();
+  });
+}
+
+function applyTheme()
+{
+  let panelWin = toolbox.getCurrentPanel().panelWin;
+  let doc = panelWin.frameElement.contentDocument;
+  let testThemeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
+  let lightThemeOption = doc.querySelector("#devtools-theme-box > radio[value=light]");
+
+  let color = panelWin.getComputedStyle(testThemeOption).color;
+  isnot(color, "rgb(255, 0, 0)", "style unapplied");
+
+  // Select test theme.
+  testThemeOption.click();
+
+  let color = panelWin.getComputedStyle(testThemeOption).color;
+  is(color, "rgb(255, 0, 0)", "style applied");
+
+  // Select light theme
+  lightThemeOption.click();
+
+  let color = panelWin.getComputedStyle(testThemeOption).color;
+  isnot(color, "rgb(255, 0, 0)", "style unapplied");
+
+  // Select test theme again.
+  testThemeOption.click();
+
+  // Then unregister the test theme.
+  testUnregister();
+}
+
+function testUnregister()
+{
+  gDevTools.unregisterTheme("test-theme");
+
+  ok(!gDevTools.getThemeDefinitionMap().has("test-theme"), "theme removed from map");
+
+  let panelWin = toolbox.getCurrentPanel().panelWin;
+  let doc = panelWin.frameElement.contentDocument;
+  let themeBox = doc.querySelector("#devtools-theme-box");
+
+  // The default light theme must be selected now.
+  is(themeBox.selectedItem, themeBox.querySelector("[value=light]"),
+    "theme light must be selected");
+
+  // Make sure the tab-attaching process is done before we destroy the toolbox.
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  let actor = target.activeTab.actor;
+  target.client.attachTab(actor, (response) => {
+    cleanup();
+  });
+}
+
+function cleanup()
+{
+  toolbox.destroy().then(function() {
+    toolbox = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/doc_theme.css
@@ -0,0 +1,3 @@
+.theme-test #devtools-theme-box radio {
+  color: red !important;
+}
--- a/browser/devtools/framework/toolbox-options.js
+++ b/browser/devtools/framework/toolbox-options.js
@@ -70,16 +70,18 @@ function InfallibleGetBoolPref(key) {
  */
 function OptionsPanel(iframeWindow, toolbox) {
   this.panelDoc = iframeWindow.document;
   this.panelWin = iframeWindow;
   this.toolbox = toolbox;
   this.isReady = false;
 
   this._prefChanged = this._prefChanged.bind(this);
+  this._themeRegistered = this._themeRegistered.bind(this);
+  this._themeUnregistered = this._themeUnregistered.bind(this);
 
   this._addListeners();
 
   const EventEmitter = require("devtools/toolkit/event-emitter");
   EventEmitter.decorate(this);
 }
 
 OptionsPanel.prototype = {
@@ -96,17 +98,19 @@ OptionsPanel.prototype = {
       targetPromise = this.target.makeRemote();
     } else {
       targetPromise = promise.resolve(this.target);
     }
 
     return targetPromise.then(() => {
       this.setupToolsList();
       this.setupToolbarButtonsList();
+      this.setupThemeList();
       this.populatePreferences();
+      this.updateDefaultTheme();
 
       this._disableJSClicked = this._disableJSClicked.bind(this);
 
       let disableJSNode = this.panelDoc.getElementById("devtools-disable-javascript");
       disableJSNode.addEventListener("click", this._disableJSClicked, false);
     }).then(() => {
       this.isReady = true;
       this.emit("ready");
@@ -114,29 +118,49 @@ OptionsPanel.prototype = {
     }).then(null, function onError(aReason) {
       Cu.reportError("OptionsPanel open failed. " +
                      aReason.error + ": " + aReason.message);
     });
   },
 
   _addListeners: function() {
     gDevTools.on("pref-changed", this._prefChanged);
+    gDevTools.on("theme-registered", this._themeRegistered);
+    gDevTools.on("theme-unregistered", this._themeUnregistered);
   },
 
   _removeListeners: function() {
     gDevTools.off("pref-changed", this._prefChanged);
+    gDevTools.off("theme-registered", this._themeRegistered);
+    gDevTools.off("theme-unregistered", this._themeUnregistered);
   },
 
   _prefChanged: function(event, data) {
     if (data.pref === "devtools.cache.disabled") {
       let cacheDisabled = data.newValue;
       let cbx = this.panelDoc.getElementById("devtools-disable-cache");
 
       cbx.checked = cacheDisabled;
     }
+    else if (data.pref === "devtools.theme") {
+      this.updateCurrentTheme();
+    }
+  },
+
+  _themeRegistered: function(event, themeId) {
+    this.setupThemeList();
+  },
+
+  _themeUnregistered: function(event, theme) {
+    let themeBox = this.panelDoc.getElementById("devtools-theme-box");
+    let themeOption = themeBox.querySelector("[value=" + theme.id + "]");
+
+    if (themeOption) {
+      themeBox.removeChild(themeOption);
+    }
   },
 
   setupToolbarButtonsList: function() {
     let enabledToolbarButtonsBox = this.panelDoc.getElementById("enabled-toolbox-buttons-box");
     enabledToolbarButtonsBox.textContent = "";
 
     let toggleableButtons = this.toolbox.toolboxButtons;
     let setToolboxButtonsVisibility =
@@ -224,16 +248,36 @@ OptionsPanel.prototype = {
 
     if (!atleastOneToolNotSupported) {
       toolsNotSupportedLabel.style.display = "none";
     }
 
     this.panelWin.focus();
   },
 
+  setupThemeList: function() {
+    let themeBox = this.panelDoc.getElementById("devtools-theme-box");
+    themeBox.textContent = "";
+
+    let createThemeOption = theme => {
+      let radio = this.panelDoc.createElement("radio");
+      radio.setAttribute("value", theme.id);
+      radio.setAttribute("label", theme.label);
+      return radio;
+    };
+
+    // Populating the default theme list
+    let themes = gDevTools.getThemeDefinitionArray();
+    for (let theme of themes) {
+      themeBox.appendChild(createThemeOption(theme));
+    }
+
+    this.updateCurrentTheme();
+  },
+
   populatePreferences: function() {
     let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]");
     for (let checkbox of prefCheckboxes) {
       checkbox.checked = GetPref(checkbox.getAttribute("data-pref"));
       checkbox.addEventListener("command", function() {
         let data = {
           pref: this.getAttribute("data-pref"),
           newValue: this.checked
@@ -253,19 +297,23 @@ OptionsPanel.prototype = {
           break;
         }
       }
       radiogroup.addEventListener("select", function() {
         let data = {
           pref: this.getAttribute("data-pref"),
           newValue: this.selectedItem.getAttribute("value")
         };
+
         data.oldValue = GetPref(data.pref);
         SetPref(data.pref, data.newValue);
-        gDevTools.emit("pref-changed", data);
+
+        if (data.newValue != data.oldValue) {
+          gDevTools.emit("pref-changed", data);
+        }
       }.bind(radiogroup));
     }
     let prefMenulists = this.panelDoc.querySelectorAll("menulist[data-pref]");
     for (let menulist of prefMenulists) {
       let pref = GetPref(menulist.getAttribute("data-pref"));
       let menuitems = menulist.querySelectorAll("menuitem");
       for (let menuitem of menuitems) {
         let value = menuitem.value;
@@ -287,16 +335,35 @@ OptionsPanel.prototype = {
 
     this.target.client.attachTab(this.target.activeTab._actor, (response) => {
       this._origJavascriptEnabled = response.javascriptEnabled;
 
       this._populateDisableJSCheckbox();
     });
   },
 
+  updateDefaultTheme: function() {
+    // Make sure a theme is set in case the previous one coming from
+    // an extension isn't available anymore.
+    let themeBox = this.panelDoc.getElementById("devtools-theme-box");
+    if (themeBox.selectedIndex == -1) {
+      themeBox.selectedItem = themeBox.querySelector("[value=light]");
+    }
+  },
+
+  updateCurrentTheme: function() {
+    let currentTheme = GetPref("devtools.theme");
+    let themeBox = this.panelDoc.getElementById("devtools-theme-box");
+    let themeOption = themeBox.querySelector("[value=" + currentTheme + "]");
+
+    if (themeOption) {
+      themeBox.selectedItem = themeOption;
+    }
+  },
+
   _populateDisableJSCheckbox: function() {
     let cbx = this.panelDoc.getElementById("devtools-disable-javascript");
     cbx.checked = !this._origJavascriptEnabled;
   },
 
   /**
    * Disables JavaScript for the currently loaded tab. We force a page refresh
    * here because setting docShell.allowJavascript to true fails to block JS
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -28,18 +28,16 @@
 
       </vbox>
       <vbox class="options-vertical-pane" flex="1">
         <label>&options.selectDevToolsTheme.label;</label>
         <radiogroup id="devtools-theme-box"
                     class="options-groupbox"
                     data-pref="devtools.theme"
                     orient="horizontal">
-          <radio value="light" label="&options.lightTheme.label;"/>
-          <radio value="dark" label="&options.darkTheme.label;"/>
         </radiogroup>
         <label>&options.commonPrefs.label;</label>
         <vbox id="commonprefs-options" class="options-groupbox">
           <checkbox label="&options.enablePersistentLogs.label;"
                     tooltiptext="&options.enablePersistentLogs.tooltip;"
                     data-pref="devtools.webconsole.persistlog"/>
         </vbox>
         <label>&options.context.inspector;</label>
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -352,23 +352,51 @@ let defaultTools = [
 ];
 
 exports.defaultTools = defaultTools;
 
 for (let definition of defaultTools) {
   gDevTools.registerTool(definition);
 }
 
+Tools.darkTheme = {
+  id: "dark",
+  label: l10n("options.darkTheme.label", toolboxStrings),
+  ordinal: 1,
+  stylesheets: ["chrome://browser/skin/devtools/dark-theme.css"],
+  classList: ["theme-dark"],
+};
+
+Tools.lightTheme = {
+  id: "light",
+  label: l10n("options.lightTheme.label", toolboxStrings),
+  ordinal: 2,
+  stylesheets: ["chrome://browser/skin/devtools/light-theme.css"],
+  classList: ["theme-light"],
+};
+
+let defaultThemes = [
+  Tools.darkTheme,
+  Tools.lightTheme,
+];
+
+for (let definition of defaultThemes) {
+  gDevTools.registerTheme(definition);
+}
+
 var unloadObserver = {
   observe: function(subject, topic, data) {
     if (subject.wrappedJSObject === require("@loader/unload")) {
       Services.obs.removeObserver(unloadObserver, "sdk:loader:destroy");
       for (let definition of gDevTools.getToolDefinitionArray()) {
         gDevTools.unregisterTool(definition.id);
       }
+      for (let definition of gDevTools.getThemeDefinitionArray()) {
+        gDevTools.unregisterTheme(definition.id);
+      }
     }
   }
 };
 Services.obs.addObserver(unloadObserver, "sdk:loader:destroy", false);
 
 events.emit("devtools-loaded", {});
 
 /**
--- a/browser/devtools/shared/frame-script-utils.js
+++ b/browser/devtools/shared/frame-script-utils.js
@@ -1,18 +1,28 @@
 /* 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";
 
+let { utils: Cu, interfaces: Ci } = Components;
+
 addMessageListener("devtools:test:history", function ({ data }) {
   content.history[data.direction]();
 });
 
 addMessageListener("devtools:test:navigate", function ({ data }) {
   content.location = data.location;
 });
 
 addMessageListener("devtools:test:reload", function ({ data }) {
   data = data || {};
   content.location.reload(data.forceget);
 });
+
+addMessageListener("devtools:test:forceCC", function () {
+  let DOMWindowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+    .getInterface(Ci.nsIDOMWindowUtils)
+  DOMWindowUtils.cycleCollect();
+  DOMWindowUtils.garbageCollect();
+  DOMWindowUtils.garbageCollect();
+});
--- a/browser/devtools/shared/theme-switching.js
+++ b/browser/devtools/shared/theme-switching.js
@@ -19,34 +19,45 @@
     documentElement.style.display = display; // Restore
   }
 
   function switchTheme(newTheme, oldTheme) {
     if (newTheme === oldTheme) {
       return;
     }
 
-    if (oldTheme && newTheme != oldTheme) {
-      StylesheetUtils.removeSheet(
-        window,
-        DEVTOOLS_SKIN_URL + oldTheme + "-theme.css",
-        "author"
-      );
+    let oldThemeDef = gDevTools.getThemeDefinition(oldTheme);
+    let newThemeDef = gDevTools.getThemeDefinition(newTheme);
+
+    // Unload all theme stylesheets related to the old theme.
+    if (oldThemeDef) {
+      for (let url of oldThemeDef.stylesheets) {
+        StylesheetUtils.removeSheet(window, url, "author");
+      }
     }
 
-    StylesheetUtils.loadSheet(
-      window,
-      DEVTOOLS_SKIN_URL + newTheme + "-theme.css",
-      "author"
-    );
+    // Load all stylesheets associated with the new theme.
+    let newThemeDef = gDevTools.getThemeDefinition(newTheme);
 
-    // Floating scrollbars à la osx
+    // The theme might not be available anymore (e.g. uninstalled)
+    // Use the default one.
+    if (!newThemeDef) {
+      newThemeDef = gDevTools.getThemeDefinition("light");
+    }
+
+    for (let url of newThemeDef.stylesheets) {
+      StylesheetUtils.loadSheet(window, url, "author");
+    }
+
+    // Floating scroll-bars like in OSX
     let hiddenDOMWindow = Cc["@mozilla.org/appshell/appShellService;1"]
                  .getService(Ci.nsIAppShellService)
                  .hiddenDOMWindow;
+
+    // TODO: extensions might want to customize scrollbar styles too.
     if (!hiddenDOMWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
       let scrollbarsUrl = Services.io.newURI(
         DEVTOOLS_SKIN_URL + "floating-scrollbars-light.css", null, null);
 
       if (newTheme == "dark") {
         StylesheetUtils.loadSheet(
           window,
           scrollbarsUrl,
@@ -57,18 +68,36 @@
           window,
           scrollbarsUrl,
           "agent"
         );
       }
       forceStyle();
     }
 
-    documentElement.classList.remove("theme-" + oldTheme);
-    documentElement.classList.add("theme-" + newTheme);
+    if (oldThemeDef) {
+      for (let name of oldThemeDef.classList) {
+        documentElement.classList.remove(name);
+      }
+
+      if (oldThemeDef.onUnapply) {
+        oldThemeDef.onUnapply(window, newTheme);
+      }
+    }
+
+    for (let name of newThemeDef.classList) {
+      documentElement.classList.add(name);
+    }
+
+    if (newThemeDef.onApply) {
+      newThemeDef.onApply(window, oldTheme);
+    }
+
+    // Final notification for further theme-switching related logic.
+    gDevTools.emit("theme-switched", window, newTheme, oldTheme);
   }
 
   function handlePrefChange(event, data) {
     if (data.pref == "devtools.theme") {
       switchTheme(data.newValue, data.oldValue);
     }
   }
 
--- a/browser/devtools/webaudioeditor/test/browser.ini
+++ b/browser/devtools/webaudioeditor/test/browser.ini
@@ -5,28 +5,33 @@ support-files =
   doc_complex-context.html
   doc_simple-node-creation.html
   doc_buffer-and-array.html
   doc_media-node-creation.html
   doc_destroy-nodes.html
   doc_connect-toggle.html
   doc_connect-param.html
   doc_connect-multi-param.html
+  doc_change-param.html
   440hz_sine.ogg
   head.js
 
-[browser_audionode-actor-get-set-param.js]
-[browser_audionode-actor-get-type.js]
+[browser_audionode-actor-get-param-flags.js]
 [browser_audionode-actor-get-params-01.js]
 [browser_audionode-actor-get-params-02.js]
-[browser_audionode-actor-get-param-flags.js]
+[browser_audionode-actor-get-set-param.js]
+[browser_audionode-actor-get-type.js]
 [browser_audionode-actor-is-source.js]
+
+[browser_webaudio-actor-change-params-01.js]
+[browser_webaudio-actor-change-params-02.js]
+[browser_webaudio-actor-change-params-03.js]
+[browser_webaudio-actor-connect-param.js]
+[browser_webaudio-actor-destroy-node.js]
 [browser_webaudio-actor-simple.js]
-[browser_webaudio-actor-destroy-node.js]
-[browser_webaudio-actor-connect-param.js]
 
 [browser_wa_destroy-node-01.js]
 
 [browser_wa_first-run.js]
 [browser_wa_reset-01.js]
 [browser_wa_reset-02.js]
 [browser_wa_reset-03.js]
 
@@ -43,9 +48,10 @@ support-files =
 [browser_wa_inspector-toggle.js]
 
 [browser_wa_properties-view.js]
 [browser_wa_properties-view-media-nodes.js]
 # [browser_wa_properties-view-edit-01.js]
 # [browser_wa_properties-view-edit-02.js]
 # Disabled for too many intermittents bug 1010423
 [browser_wa_properties-view-params.js]
+[browser_wa_properties-view-change-params.js]
 [browser_wa_properties-view-params-objects.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/browser_wa_properties-view-change-params.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that params view correctly updates changed parameters
+ * when source code updates them, as well as CHANGE_PARAM events.
+ */
+
+function spawnTest() {
+  let [target, debuggee, panel] = yield initWebAudioEditor(CHANGE_PARAM_URL);
+  let { panelWin } = panel;
+  let { gFront, $, $$, EVENTS, WebAudioInspectorView } = panelWin;
+  let gVars = WebAudioInspectorView._propsView;
+
+  // Set parameter polling to 20ms for tests
+  panelWin.PARAM_POLLING_FREQUENCY = 20;
+
+  let started = once(gFront, "start-context");
+
+  reload(target);
+
+  let [actors] = yield Promise.all([
+    getN(gFront, "create-node", 3),
+    waitForGraphRendered(panelWin, 3, 0)
+  ]);
+
+  let oscId = actors[1].actorID;
+
+  click(panelWin, findGraphNode(panelWin, oscId));
+  yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
+
+  // Yield twice so we get a diff
+  yield once(panelWin, EVENTS.CHANGE_PARAM);
+  let [[_, args]] = yield getSpread(panelWin, EVENTS.CHANGE_PARAM);
+  is(args.actorID, oscId, "EVENTS.CHANGE_PARAM has correct `actorID`");
+  ok(args.oldValue < args.newValue, "EVENTS.CHANGE_PARAM has correct `newValue` and `oldValue`");
+  is(args.param, "detune", "EVENTS.CHANGE_PARAM has correct `param`");
+
+  let [[_, args]] = yield getSpread(panelWin, EVENTS.CHANGE_PARAM);
+  checkVariableView(gVars, 0, { "detune": args.newValue }, "`detune` parameter updated.");
+  let [[_, args]] = yield getSpread(panelWin, EVENTS.CHANGE_PARAM);
+  checkVariableView(gVars, 0, { "detune": args.newValue }, "`detune` parameter updated.");
+
+  yield teardown(panel);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/browser_webaudio-actor-change-params-01.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test WebAudioActor `change-param` events and front.[en|dis]ableChangeParamEvents
+ */
+
+function spawnTest () {
+  let [target, debuggee, front] = yield initBackend(CHANGE_PARAM_URL);
+  let [_, nodes] = yield Promise.all([
+    front.setup({ reload: true }),
+    getN(front, "create-node", 3)
+  ]);
+
+  let osc = nodes[1];
+  let eventCount = 0;
+
+  yield front.enableChangeParamEvents(osc, 20);
+
+  front.on("change-param", onChangeParam);
+
+  yield getN(front, "change-param", 3);
+  yield front.disableChangeParamEvents();
+
+  let currEventCount = eventCount;
+
+  // Be flexible here incase we get an extra counter before the listener is turned off
+  ok(eventCount >= 3, "Calling `enableChangeParamEvents` should allow front to emit `change-param`.");
+
+  yield wait(100);
+
+  ok((eventCount - currEventCount) <= 2, "Calling `disableChangeParamEvents` should turn off the listener.");
+
+  front.off("change-param", onChangeParam);
+
+  yield removeTab(target.tab);
+  finish();
+
+  function onChangeParam ({ newValue, oldValue, param, actorID }) {
+    is(actorID, osc.actorID, "correct `actorID` in `change-param`.");
+    is(param, "detune", "correct `param` property in `change-param`.");
+    ok(newValue > oldValue,
+      "correct `newValue` (" + newValue + ") and `oldValue` (" + oldValue + ") in `change-param`");
+    eventCount++;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/browser_webaudio-actor-change-params-02.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that listening to param change polling does not break when the AudioNode is collected.
+ */
+
+function spawnTest () {
+  let [target, debuggee, front] = yield initBackend(DESTROY_NODES_URL);
+  let waitUntilDestroyed = getN(front, "destroy-node", 10);
+  let [_, nodes] = yield Promise.all([
+    front.setup({ reload: true }),
+    getN(front, "create-node", 13)
+  ]);
+
+  let bufferNode = nodes[6];
+
+  yield front.enableChangeParamEvents(bufferNode, 20);
+
+  front.on("change-param", onChangeParam);
+
+  forceCC();
+
+  yield waitUntilDestroyed;
+  yield wait(50);
+
+  front.off("change-param", onChangeParam);
+
+  ok(true, "listening to `change-param` on a dead node doesn't throw.");
+  yield removeTab(target.tab);
+  finish();
+
+  function onChangeParam (args) {
+    ok(false, "`change-param` should not be emitted on a node that hasn't changed params or is dead.");
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/browser_webaudio-actor-change-params-03.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test WebAudioActor `change-param` events on special types.
+ */
+
+function spawnTest () {
+  let [target, debuggee, front] = yield initBackend(CHANGE_PARAM_URL);
+  let [_, nodes] = yield Promise.all([
+    front.setup({ reload: true }),
+    getN(front, "create-node", 3)
+  ]);
+
+  let shaper = nodes[2];
+  let eventCount = 0;
+
+  yield front.enableChangeParamEvents(shaper, 20);
+
+  let onChange = once(front, "change-param");
+
+  shaper.setParam("curve", null);
+
+  let { newValue, oldValue } = yield onChange;
+
+  is(oldValue.type, "object", "`oldValue` should be an object.");
+  is(oldValue.class, "Float32Array", "`oldValue` should be of class Float32Array.");
+  is(newValue.type, "null", "`newValue` should be null.");
+
+  yield removeTab(target.tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/doc_change-param.html
@@ -0,0 +1,25 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Web Audio Editor test page</title>
+  </head>
+
+  <body>
+
+    <script type="text/javascript;version=1.8">
+      "use strict";
+
+      let ctx = new AudioContext();
+      let osc = ctx.createOscillator();
+      let shaperNode = ctx.createWaveShaper();
+      let detuneVal = 0;
+      shaperNode.curve = new Float32Array(65536);
+      setInterval(() => osc.detune.value = ++detuneVal, 10);
+    </script>
+  </body>
+
+</html>
--- a/browser/devtools/webaudioeditor/test/head.js
+++ b/browser/devtools/webaudioeditor/test/head.js
@@ -14,27 +14,30 @@ Services.prefs.setBoolPref("devtools.deb
 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 
 let { WebAudioFront } = devtools.require("devtools/server/actors/webaudio");
 let TargetFactory = devtools.TargetFactory;
+let mm = null;
 
+const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js";
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/";
 const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
 const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
 const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html";
 const MEDIA_NODES_URL = EXAMPLE_URL + "doc_media-node-creation.html";
 const BUFFER_AND_ARRAY_URL = EXAMPLE_URL + "doc_buffer-and-array.html";
 const DESTROY_NODES_URL = EXAMPLE_URL + "doc_destroy-nodes.html";
 const CONNECT_TOGGLE_URL = EXAMPLE_URL + "doc_connect-toggle.html";
 const CONNECT_PARAM_URL = EXAMPLE_URL + "doc_connect-param.html";
 const CONNECT_MULTI_PARAM_URL = EXAMPLE_URL + "doc_connect-multi-param.html";
+const CHANGE_PARAM_URL = EXAMPLE_URL + "doc_change-param.html";
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 let gToolEnabled = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled");
 
 registerCleanupFunction(() => {
   info("finish() was called, cleaning up...");
@@ -128,16 +131,18 @@ function initBackend(aUrl) {
   return Task.spawn(function*() {
     let tab = yield addTab(aUrl);
     let target = TargetFactory.forTab(tab);
     let debuggee = target.window.wrappedJSObject;
 
     yield target.makeRemote();
 
     let front = new WebAudioFront(target.client, target.form);
+
+    loadFrameScripts();
     return [target, debuggee, front];
   });
 }
 
 function initWebAudioEditor(aUrl) {
   info("Initializing a web audio editor pane.");
 
   return Task.spawn(function*() {
@@ -145,16 +150,18 @@ function initWebAudioEditor(aUrl) {
     let target = TargetFactory.forTab(tab);
     let debuggee = target.window.wrappedJSObject;
 
     yield target.makeRemote();
 
     Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true);
     let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor");
     let panel = toolbox.getCurrentPanel();
+
+    loadFrameScripts();
     return [target, debuggee, panel];
   });
 }
 
 function teardown(aPanel) {
   info("Destroying the web audio editor.");
 
   return Promise.all([
@@ -382,19 +389,22 @@ function countGraphObjects (win) {
     edges: win.document.querySelectorAll(".edgePaths > .edgePath").length
   }
 }
 
 /**
 * Forces cycle collection and GC, used in AudioNode destruction tests.
 */
 function forceCC () {
-  SpecialPowers.DOMWindowUtils.cycleCollect();
-  SpecialPowers.DOMWindowUtils.garbageCollect();
-  SpecialPowers.DOMWindowUtils.garbageCollect();
+  mm.sendAsyncMessage("devtools:test:forceCC");
+}
+
+function loadFrameScripts () {
+  mm = gBrowser.selectedBrowser.messageManager;
+  mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
 }
 
 /**
  * List of audio node properties to test against expectations of the AudioNode actor
  */
 
 const NODE_DEFAULT_VALUES = {
   "AudioDestinationNode": {},
--- a/browser/devtools/webaudioeditor/webaudioeditor-controller.js
+++ b/browser/devtools/webaudioeditor/webaudioeditor-controller.js
@@ -15,18 +15,19 @@ const { defer, all } = Cu.import("resour
 
 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const STRINGS_URI = "chrome://browser/locale/devtools/webaudioeditor.properties"
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 const Telemetry = require("devtools/shared/telemetry");
 const telemetry = new Telemetry();
+let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 
-let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
+let PARAM_POLLING_FREQUENCY = 1000;
 
 // The panel's window global is an EventEmitter firing the following events:
 const EVENTS = {
   // Fired when the first AudioNode has been created, signifying
   // that the AudioContext is being used and should be tracked via the editor.
   START_CONTEXT: "WebAudioEditor:StartContext",
 
   // On node creation, connect and disconnect.
@@ -168,16 +169,18 @@ function shutdownWebAudioEditor() {
 let WebAudioEditorController = {
   /**
    * Listen for events emitted by the current tab target.
    */
   initialize: function() {
     telemetry.toolOpened("webaudioeditor");
     this._onTabNavigated = this._onTabNavigated.bind(this);
     this._onThemeChange = this._onThemeChange.bind(this);
+    this._onSelectNode = this._onSelectNode.bind(this);
+    this._onChangeParam = this._onChangeParam.bind(this);
     gTarget.on("will-navigate", this._onTabNavigated);
     gTarget.on("navigate", this._onTabNavigated);
     gFront.on("start-context", this._onStartContext);
     gFront.on("create-node", this._onCreateNode);
     gFront.on("connect-node", this._onConnectNode);
     gFront.on("connect-param", this._onConnectParam);
     gFront.on("disconnect-node", this._onDisconnectNode);
     gFront.on("change-param", this._onChangeParam);
@@ -189,39 +192,45 @@ let WebAudioEditorController = {
     gDevTools.on("pref-changed", this._onThemeChange);
 
     // Set up events to refresh the Graph view
     window.on(EVENTS.CREATE_NODE, this._onUpdatedContext);
     window.on(EVENTS.CONNECT_NODE, this._onUpdatedContext);
     window.on(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
     window.on(EVENTS.DESTROY_NODE, this._onUpdatedContext);
     window.on(EVENTS.CONNECT_PARAM, this._onUpdatedContext);
+
+    // Set up a controller for managing parameter changes per audio node
+    window.on(EVENTS.UI_SELECT_NODE, this._onSelectNode);
   },
 
   /**
    * Remove events emitted by the current tab target.
    */
-  destroy: function() {
+  destroy: Task.async(function* () {
     telemetry.toolClosed("webaudioeditor");
     gTarget.off("will-navigate", this._onTabNavigated);
     gTarget.off("navigate", this._onTabNavigated);
     gFront.off("start-context", this._onStartContext);
     gFront.off("create-node", this._onCreateNode);
     gFront.off("connect-node", this._onConnectNode);
     gFront.off("connect-param", this._onConnectParam);
     gFront.off("disconnect-node", this._onDisconnectNode);
     gFront.off("change-param", this._onChangeParam);
     gFront.off("destroy-node", this._onDestroyNode);
     window.off(EVENTS.CREATE_NODE, this._onUpdatedContext);
     window.off(EVENTS.CONNECT_NODE, this._onUpdatedContext);
     window.off(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
     window.off(EVENTS.DESTROY_NODE, this._onUpdatedContext);
     window.off(EVENTS.CONNECT_PARAM, this._onUpdatedContext);
+    window.off(EVENTS.UI_SELECT_NODE, this._onSelectNode);
     gDevTools.off("pref-changed", this._onThemeChange);
-  },
+
+    yield gFront.disableChangeParamEvents();
+  }),
 
   /**
    * Called when page is reloaded to show the reload notice and waiting
    * for an audio context notice.
    */
   reset: function () {
     $("#reload-notice").hidden = true;
     $("#waiting-notice").hidden = false;
@@ -340,19 +349,31 @@ let WebAudioEditorController = {
     let node = getViewNodeByActor(nodeActor);
     node.disconnect();
     window.emit(EVENTS.DISCONNECT_NODE, node.id);
   },
 
   /**
    * Called when a node param is changed.
    */
-  _onChangeParam: function({ actor, param, value }) {
-    window.emit(EVENTS.CHANGE_PARAM, getViewNodeByActor(actor), param, value);
-  }
+  _onChangeParam: function (args) {
+    window.emit(EVENTS.CHANGE_PARAM, args);
+  },
+
+  /**
+   * Called on UI_SELECT_NODE, used to manage
+   * `change-param` events on that node.
+   */
+  _onSelectNode: function (_, id) {
+    let node = getViewNodeById(id);
+
+    if (node && node.actor) {
+      gFront.enableChangeParamEvents(node.actor, PARAM_POLLING_FREQUENCY);
+    }
+  },
 };
 
 /**
  * Convenient way of emitting events from the panel window.
  */
 EventEmitter.decorate(this);
 
 /**
--- a/browser/devtools/webaudioeditor/webaudioeditor-view.js
+++ b/browser/devtools/webaudioeditor/webaudioeditor-view.js
@@ -372,32 +372,35 @@ let WebAudioInspectorView = {
     // Hide inspector view on startup
     this._inspectorPane.setAttribute("width", INSPECTOR_WIDTH);
     this.toggleInspector({ visible: false, delayed: false, animated: false });
 
     this._onEval = this._onEval.bind(this);
     this._onNodeSelect = this._onNodeSelect.bind(this);
     this._onTogglePaneClick = this._onTogglePaneClick.bind(this);
     this._onDestroyNode = this._onDestroyNode.bind(this);
+    this._onChangeParam = this._onChangeParam.bind(this);
 
     this._inspectorPaneToggleButton.addEventListener("mousedown", this._onTogglePaneClick, false);
     this._propsView = new VariablesView($("#properties-tabpanel-content"), GENERIC_VARIABLES_VIEW_SETTINGS);
     this._propsView.eval = this._onEval;
 
     window.on(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
     window.on(EVENTS.DESTROY_NODE, this._onDestroyNode);
+    window.on(EVENTS.CHANGE_PARAM, this._onChangeParam);
   },
 
   /**
    * Destruction function called when the tool cleans up.
    */
   destroy: function () {
     this._inspectorPaneToggleButton.removeEventListener("mousedown", this._onTogglePaneClick);
     window.off(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
     window.off(EVENTS.DESTROY_NODE, this._onDestroyNode);
+    window.off(EVENTS.CHANGE_PARAM, this._onChangeParam);
 
     this._inspectorPane = null;
     this._inspectorPaneToggleButton = null;
     this._tabsPane = null;
   },
 
   /**
    * Toggles the visibility of the AudioNode Inspector.
@@ -607,17 +610,32 @@ let WebAudioInspectorView = {
   /**
    * Called when `DESTROY_NODE` is fired to remove the node from props view if
    * it's currently selected.
    */
   _onDestroyNode: function (_, id) {
     if (this._currentNode && this._currentNode.id === id) {
       this.setCurrentAudioNode(null);
     }
-  }
+  },
+
+  /**
+   * Called when `CHANGE_PARAM` is fired. We should ensure that this event is
+   * for the same node that is currently selected. We check the existence
+   * of each part of the scope to make sure that if this event was fired
+   * during a VariablesView rebuild, then we just ignore it.
+   */
+  _onChangeParam: function (_, { param, newValue, oldValue, actorID }) {
+    if (!this._currentNode || this._currentNode.actor.actorID !== actorID) return;
+    let scope = this._getAudioPropertiesScope();
+    if (!scope) return;
+    let property = scope.get(param);
+    if (!property) return;
+    property.setGrip(newValue);
+  },
 };
 
 /**
  * Takes an element in an SVG graph and iterates over
  * ancestors until it finds the graph node container. If not found,
  * returns null.
  */
 
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -108,17 +108,20 @@ support-files =
   test-bug_939783_console_trace_duplicates.html
   test-bug-952277-highlight-nodes-in-vview.html
   test-bug-609872-cd-iframe-parent.html
   test-bug-609872-cd-iframe-child.html
   test-bug-989025-iframe-parent.html
   test-console-api-stackframe.html
   test_bug_1010953_cspro.html^headers^
   test_bug_1010953_cspro.html
+  test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^
+  test_bug1045902_console_csp_ignore_reflected_xss_message.html
 
+[browser_bug1045902_console_csp_ignore_reflected_xss_message.js]
 [browser_bug664688_sandbox_update_after_navigation.js]
 [browser_bug_638949_copy_link_location.js]
 [browser_bug_862916_console_dir_and_filter_off.js]
 [browser_bug_865288_repeat_different_objects.js]
 [browser_bug_865871_variables_view_close_on_esc_key.js]
 [browser_bug_869003_inspect_cross_domain_object.js]
 [browser_bug_871156_ctrlw_close_tab.js]
 [browser_cached_messages.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug1045902_console_csp_ignore_reflected_xss_message.js
@@ -0,0 +1,53 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Description of the test:
+ * We are loading a file with the following CSP:
+ *     'reflected-xss filter'
+ * This directive is not supported, hence we confirm that
+ * the according message is displayed in the web console.
+ */
+
+const EXPECTED_RESULT = "Not supporting directive 'reflected-xss'. Directive and values will be ignored.";
+const TEST_FILE = "http://example.com/browser/browser/devtools/webconsole/test/" +
+                  "test_bug1045902_console_csp_ignore_reflected_xss_message.html";
+
+let hud = undefined;
+
+function test() {
+  addTab("data:text/html;charset=utf8,Web Console CSP ignoring reflected XSS (bug 1045902)");
+  browser.addEventListener("load", function _onLoad() {
+    browser.removeEventListener("load", _onLoad, true);
+    openConsole(null, loadDocument);
+  }, true);
+}
+
+function loadDocument(theHud) {
+  hud = theHud;
+  hud.jsterm.clearOutput()
+  browser.addEventListener("load", onLoad, true);
+  content.location = TEST_FILE;
+}
+
+function onLoad(aEvent) {
+  browser.removeEventListener("load", onLoad, true);
+  testViolationMessage();
+}
+
+function testViolationMessage() {
+  let aOutputNode = hud.outputNode;
+
+  waitForSuccess({
+      name: "Confirming that CSP logs messages to the console when 'reflected-xss' directive is used!",
+      validatorFn: function() {
+        console.log(hud.outputNode.textContent);
+        let success = false;
+        success = hud.outputNode.textContent.indexOf(EXPECTED_RESULT) > -1;
+        return success;
+      },
+      successFn: finishTest,
+      failureFn: finishTest,
+    });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Bug 1045902 - CSP: Log console message for 'reflected-xss'</title>
+</head>
+<body>
+Bug 1045902 - CSP: Log console message for 'reflected-xss'
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: reflected-xss filter;
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -54,17 +54,17 @@
   </commandset>
 
   <menubar id="main-menubar">
     <menu id="menu-project" label="&projectMenu_label;" accesskey="&projectMenu_accesskey;">
       <menupopup id="menu-project-popup">
         <menuitem command="cmd_newApp" accesskey="&projectMenu_newApp_accesskey;"/>
         <menuitem command="cmd_importPackagedApp" accesskey="&projectMenu_importPackagedApp_accesskey;"/>
         <menuitem command="cmd_importHostedApp" accesskey="&projectMenu_importHostedApp_accesskey;"/>
-        <menuitem command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accessley;"/>
+        <menuitem command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/>
         <menuitem command="cmd_stop" accesskey="&projectMenu_stop_accesskey;"/>
         <menuitem command="cmd_toggleToolbox" key="key_toggleToolbox" label="&projectMenu_debug_label;" accesskey="&projectMenu_debug_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_removeProject" accesskey="&projectMenu_remove_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_showPrefs" label="&projectMenu_showPrefs_label;" accesskey="&projectMenu_showPrefs_accesskey;"/>
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -354,17 +354,17 @@ Experiments.Experiments = function (poli
   let log = Log.repository.getLoggerWithMessagePrefix(
       "Browser.Experiments.Experiments",
       "Experiments #" + gExperimentsCounter++ + "::");
 
   // At the time of this writing, Experiments.jsm has severe
   // crashes. For forensics purposes, keep the last few log
   // messages in memory and upload them in case of crash.
   this._forensicsLogs = [];
-  this._forensicsLogs.length = 3;
+  this._forensicsLogs.length = 10;
   this._log = Object.create(log);
   this._log.log = (level, string, params) => {
     this._forensicsLogs.shift();
     this._forensicsLogs.push(level + ": " + string);
     log.log(level, string, params);
   };
 
   this._log.trace("constructor");
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -298,16 +298,17 @@
 #endif
 @BINPATH@/browser/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
+@BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
 @BINPATH@/components/unicharutil.xpt
@@ -498,16 +499,18 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/CaptivePortalDetectComponents.manifest
 @BINPATH@/components/captivedetect.js
 #endif
 @BINPATH@/components/servicesComponents.manifest
 @BINPATH@/components/cryptoComponents.manifest
 @BINPATH@/components/TelemetryStartup.js
 @BINPATH@/components/TelemetryStartup.manifest
+@BINPATH@/components/XULStore.js
+@BINPATH@/components/XULStore.manifest
 @BINPATH@/components/messageWakeupService.js
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -109,18 +109,16 @@
   -  the explanation of the * marker on a tool which is currently not supported
   -  for the target of the toolbox. -->
 <!ENTITY options.toolNotSupported.label  "* Not supported for current toolbox target">
 
 <!-- LOCALIZATION NOTE (options.selectDevToolsTheme.label): This is the label for
   -  the heading of the radiobox corresponding to the theme of the developer
   -  tools. -->
 <!ENTITY options.selectDevToolsTheme.label   "Choose DevTools theme:">
-<!ENTITY options.darkTheme.label             "Dark theme">
-<!ENTITY options.lightTheme.label            "Light theme">
 
 <!-- LOCALIZATION NOTE (options.webconsole.label): This is the label for the
   -  heading of the group of Web Console preferences in the options panel. -->
 <!ENTITY options.webconsole.label            "Web Console">
 
 <!-- LOCALIZATION NOTE (options.timestampMessages.label): This is the
    - label for the checkbox that toggles timestamps in the Web Console -->
 <!ENTITY options.timestampMessages.label      "Enable timestamps">
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
@@ -65,8 +65,16 @@ scratchpad.keycode=VK_F4
 # LOCALIZATION NOTE (browserConsoleCmd.commandkey)
 # Used for toggling the browser console from the detached toolbox window
 # Needs to match browserConsoleCmd.commandkey from browser.dtd
 browserConsoleCmd.commandkey=j
 
 # LOCALIZATION NOTE (pickButton.tooltip)
 # This is the tooltip of the pick button in the toolbox toolbar
 pickButton.tooltip=Pick an element from the page
+
+# LOCALIZATION NOTE (options.darkTheme.label)
+# Used as a label for dark theme
+options.darkTheme.label=Dark theme
+
+# LOCALIZATION NOTE (options.lightTheme.label)
+# Used as a label for light theme
+options.lightTheme.label=Light theme
--- a/browser/locales/en-US/chrome/browser/devtools/webide.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.dtd
@@ -8,17 +8,17 @@
 <!ENTITY projectMenu_accesskey "P">
 <!ENTITY projectMenu_newApp_label "New App…">
 <!ENTITY projectMenu_newApp_accesskey "N">
 <!ENTITY projectMenu_importPackagedApp_label "Open Packaged App…">
 <!ENTITY projectMenu_importPackagedApp_accesskey "P">
 <!ENTITY projectMenu_importHostedApp_label "Open Hosted App…">
 <!ENTITY projectMenu_importHostedApp_accesskey "H">
 <!ENTITY projectMenu_selectApp_label "Open App…">
-<!ENTITY projectMenu_selectApp_accessley "S">
+<!ENTITY projectMenu_selectApp_accesskey "O">
 <!ENTITY projectMenu_play_label "Install and Run">
 <!ENTITY projectMenu_play_accesskey "I">
 <!ENTITY projectMenu_stop_label "Stop App">
 <!ENTITY projectMenu_stop_accesskey "S">
 <!ENTITY projectMenu_debug_label "Debug App">
 <!ENTITY projectMenu_debug_accesskey "D">
 <!ENTITY projectMenu_remove_label "Remove Project">
 <!ENTITY projectMenu_remove_accesskey "R">
--- a/browser/locales/en-US/chrome/browser/devtools/webide.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.properties
@@ -35,16 +35,20 @@ error_operationFail=Operation failed: %1
 # Variable: app name
 error_cantConnectToApp=Can't connect to app: %1$S
 
 # Variable: error message (in english)
 error_cantFetchAddonsJSON=Can't fetch the add-on list: %S
 
 addons_stable=stable
 addons_unstable=unstable
+# LOCALIZATION NOTE (addons_simulator_label): This label is shown as the name of
+# a given simulator version in the "Manage Simulators" pane.  %1$S: Firefox OS
+# version in the simulator, ex. 1.3.  %2$S: Simulator stability label, ex.
+# "stable" or "unstable".
 addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
 addons_install_button=install
 addons_uninstall_button=uninstall
 addons_adb_label=ADB Helper Add-on
 addons_adb_warning=USB devices won't be detected without this add-on
 addons_status_unknown=?
 addons_status_installed=Installed
 addons_status_uninstalled=Not Installed
--- a/browser/themes/linux/customizableui/panelUIOverlay.css
+++ b/browser/themes/linux/customizableui/panelUIOverlay.css
@@ -72,16 +72,20 @@ menu.subviewbutton > .menu-right {
   width: 16px;
   height: 16px;
 }
 
 menu[disabled="true"].subviewbutton > .menu-right {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
+menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
+  transform: scaleX(-1);
+}
+
 .subviewbutton > .toolbarbutton-icon {
   -moz-margin-end: 5px !important;
 }
 
 .subviewbutton > .menu-right,
 .subviewbutton > .menu-iconic-left {
   padding-top: 1px;
   /* These need !important to override menu.css */
--- a/browser/themes/windows/customizableui/panelUIOverlay.css
+++ b/browser/themes/windows/customizableui/panelUIOverlay.css
@@ -121,16 +121,20 @@ menu.subviewbutton > .menu-right {
   list-style-image: url(chrome://browser/skin/places/bookmarks-menu-arrow.png);
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
 menu[disabled="true"].subviewbutton > .menu-right {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
+menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
+  transform: scaleX(-1);
+}
+
 %ifdef WINDOWS_AERO
 /* Win8 and beyond. */
 @media not all and (-moz-os-version: windows-vista) {
   @media not all and (-moz-os-version: windows-win7) {
     panelview .toolbarbutton-1,
     .subviewbutton,
     .widget-overflow-list .toolbarbutton-1,
     .panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button,
--- a/build/sanitizers/lsan_suppressions.txt
+++ b/build/sanitizers/lsan_suppressions.txt
@@ -56,16 +56,20 @@ leak:fsmdef_ev_createanswer
 leak:vcmRxAllocICE_s
 leak:vcmRxStartICE_m
 # About 50KB of leaks under this stack.
 leak:ccsnap_EscapeStrToLocaleStr
 leak:gsmsdp_add_default_audio_formats_to_local_sdp
 leak:gsmsdp_add_default_video_formats_to_local_sdp
 leak:CCAPI_CallInfo_getMediaStreams
 
+# Intermittent Mochitest 3 WebRTC leaks, as seen in bug 1055910.
+leak:sdp_build_attr_ice_attr
+leak:VcmSIPCCBinding::CandidateReady
+
 
 ###
 ### Many leaks only affect some test suites.  The suite annotations are not checked.
 ###
 
 # Bug 981195 - Small leak in the parser. m4
 leak:TypeCompartment::fixObjectType
 
--- a/content/base/src/nsCSPParser.cpp
+++ b/content/base/src/nsCSPParser.cpp
@@ -779,16 +779,27 @@ nsCSPParser::directiveName()
   // Check if it is a valid directive
   if (!CSP_IsValidDirective(mCurToken)) {
     const char16_t* params[] = { mCurToken.get() };
     logWarningErrorToConsole(nsIScriptError::warningFlag, "couldNotProcessUnknownDirective",
                              params, ArrayLength(params));
     return nullptr;
   }
 
+  // The directive 'reflected-xss' is part of CSP 1.1, see:
+  // http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss
+  // Currently we are not supporting that directive, hence we log a
+  // warning to the console and ignore the directive including its values.
+  if (CSP_IsDirective(mCurToken, CSP_REFLECTED_XSS)) {
+    const char16_t* params[] = { mCurToken.get() };
+    logWarningErrorToConsole(nsIScriptError::warningFlag, "notSupportingDirective",
+                             params, ArrayLength(params));
+    return nullptr;
+  }
+
   // Make sure the directive does not already exist
   // (see http://www.w3.org/TR/CSP11/#parsing)
   if (mPolicy->directiveExists(CSP_DirectiveToEnum(mCurToken))) {
     const char16_t* params[] = { mCurToken.get() };
     logWarningErrorToConsole(nsIScriptError::warningFlag, "duplicateDirective",
                              params, ArrayLength(params));
     return nullptr;
   }
--- a/content/base/src/nsCSPUtils.h
+++ b/content/base/src/nsCSPUtils.h
@@ -65,33 +65,35 @@ enum CSPDirective {
   CSP_STYLE_SRC,
   CSP_IMG_SRC,
   CSP_MEDIA_SRC,
   CSP_FRAME_SRC,
   CSP_FONT_SRC,
   CSP_CONNECT_SRC,
   CSP_REPORT_URI,
   CSP_FRAME_ANCESTORS,
+  CSP_REFLECTED_XSS,
   // CSP_LAST_DIRECTIVE_VALUE always needs to be the last element in the enum
   // because we use it to calculate the size for the char* array.
   CSP_LAST_DIRECTIVE_VALUE
 };
 
 static const char* CSPStrDirectives[] = {
-  "default-src",    // CSP_DEFAULT_SRC = 0
-  "script-src",     // CSP_SCRIPT_SRC
-  "object-src",     // CSP_OBJECT_SRC
-  "style-src",      // CSP_STYLE_SRC
-  "img-src",        // CSP_IMG_SRC
-  "media-src",      // CSP_MEDIA_SRC
-  "frame-src",      // CSP_FRAME_SRC
-  "font-src",       // CSP_FONT_SRC
-  "connect-src",    // CSP_CONNECT_SRC
-  "report-uri",     // CSP_REPORT_URI
-  "frame-ancestors" // CSP_FRAME_ANCESTORS
+  "default-src",     // CSP_DEFAULT_SRC = 0
+  "script-src",      // CSP_SCRIPT_SRC
+  "object-src",      // CSP_OBJECT_SRC
+  "style-src",       // CSP_STYLE_SRC
+  "img-src",         // CSP_IMG_SRC
+  "media-src",       // CSP_MEDIA_SRC
+  "frame-src",       // CSP_FRAME_SRC
+  "font-src",        // CSP_FONT_SRC
+  "connect-src",     // CSP_CONNECT_SRC
+  "report-uri",      // CSP_REPORT_URI
+  "frame-ancestors", // CSP_FRAME_ANCESTORS
+  "reflected-xss"    // CSP_REFLECTED_XSS
 };
 
 inline const char* CSP_EnumToDirective(enum CSPDirective aDir)
 {
   // Make sure all elements in enum CSPDirective got added to CSPStrDirectives.
   static_assert((sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]) ==
                 static_cast<uint32_t>(CSP_LAST_DIRECTIVE_VALUE)),
                 "CSP_LAST_DIRECTIVE_VALUE does not match length of CSPStrDirectives");
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -193,16 +193,17 @@
 #include "mozilla/dom/AnimationTimeline.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/NodeFilterBinding.h"
 #include "mozilla/dom/OwningNonNull.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/UndoManager.h"
 #include "mozilla/dom/WebComponentsBinding.h"
 #include "nsFrame.h"
 #include "nsDOMCaretPosition.h"
 #include "nsIDOMHTMLTextAreaElement.h"
 #include "nsViewportInfo.h"
 #include "nsIContentPermissionPrompt.h"
 #include "mozilla/StaticPtr.h"
@@ -10538,18 +10539,19 @@ nsIDocument::ExitFullscreen(nsIDocument*
   if (aRunAsync) {
     NS_DispatchToCurrentThread(new nsCallExitFullscreen(aDoc));
     return;
   }
   nsDocument::ExitFullscreen(aDoc);
 }
 
 // Returns true if the document is a direct child of a cross process parent
-// mozbrowser iframe. This is the case when the document has a null parent,
-// and its DocShell reports that it is a browser frame.
+// mozbrowser iframe or TabParent. This is the case when the document has
+// a null parent and its DocShell reports that it is a browser frame, or
+// we can get a TabChild from it.
 static bool
 HasCrossProcessParent(nsIDocument* aDocument)
 {
   if (XRE_GetProcessType() != GeckoProcessType_Content) {
     return false;
   }
   if (aDocument->GetParentDocument() != nullptr) {
     return false;
@@ -10557,17 +10559,22 @@ HasCrossProcessParent(nsIDocument* aDocu
   nsPIDOMWindow* win = aDocument->GetWindow();
   if (!win) {
     return false;
   }
   nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
   if (!docShell) {
     return false;
   }
-  return docShell->GetIsBrowserOrApp();
+  TabChild* tabChild(TabChild::GetFrom(docShell));
+  if (!tabChild) {
+    return false;
+  }
+
+  return true;
 }
 
 static bool
 CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData)
 {
   if (aDoc->IsFullScreenDoc()) {
     uint32_t* count = static_cast<uint32_t*>(aData);
     (*count)++;
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -30,51 +30,58 @@
 #include "nsContentUtils.h"
 #include "MediaShutdownManager.h"
 #include "SharedThreadPool.h"
 #include "MediaTaskQueue.h"
 #include "nsIEventTarget.h"
 #include "prenv.h"
 #include "mozilla/Preferences.h"
 #include "gfx2DGlue.h"
+#include "nsPrintfCString.h"
 
 #include <algorithm>
 
 namespace mozilla {
 
 using namespace mozilla::layers;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 // avoid redefined macro in unified build
 #undef DECODER_LOG
 #undef VERBOSE_LOG
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaDecoderLog;
-#define DECODER_LOG(type, msg, ...) \
-  PR_LOG(gMediaDecoderLog, type, ("Decoder=%p " msg, mDecoder.get(), ##__VA_ARGS__))
-#define VERBOSE_LOG(msg, ...)                          \
+#define DECODER_LOG(x, ...) \
+  PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, ("Decoder=%p " x, mDecoder.get(), ##__VA_ARGS__))
+#define VERBOSE_LOG(x, ...)                            \
     PR_BEGIN_MACRO                                     \
       if (!PR_GetEnv("MOZ_QUIET")) {                   \
-        DECODER_LOG(PR_LOG_DEBUG, msg, ##__VA_ARGS__); \
+        DECODER_LOG(x, ##__VA_ARGS__);                 \
       }                                                \
     PR_END_MACRO
-#define SAMPLE_LOG(msg, ...)                          \
+#define SAMPLE_LOG(x, ...)                             \
     PR_BEGIN_MACRO                                     \
       if (PR_GetEnv("MEDIA_LOG_SAMPLES")) {            \
-        DECODER_LOG(PR_LOG_DEBUG, msg, ##__VA_ARGS__); \
+        DECODER_LOG(x, ##__VA_ARGS__);                 \
       }                                                \
     PR_END_MACRO
 #else
-#define DECODER_LOG(type, msg, ...)
-#define VERBOSE_LOG(msg, ...)
-#define SAMPLE_LOG(msg, ...)
+#define DECODER_LOG(x, ...)
+#define VERBOSE_LOG(x, ...)
+#define SAMPLE_LOG(x, ...)
 #endif
 
+// Somehow MSVC doesn't correctly delete the comma before ##__VA_ARGS__
+// when __VA_ARGS__ expands to nothing. This is a workaround for it.
+#define DECODER_WARN_HELPER(a, b) NS_WARNING b
+#define DECODER_WARN(x, ...) \
+  DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoder.get(), ##__VA_ARGS__).get()))
+
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
 // implementation.  With unified builds, putting this in headers is not enough.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
 // Wait this number of seconds when buffering, then leave and play
@@ -595,17 +602,17 @@ MediaDecoderStateMachine::DecodeVideo()
            // don't skip frame when |clock time| <= |mVideoFrameEndTime| for
            // we are still in the safe range without underrunning video frames
            GetClock() > mVideoFrameEndTime &&
           (static_cast<uint32_t>(VideoQueue().GetSize())
             < LOW_VIDEO_FRAMES * mPlaybackRate))) &&
         !HasLowUndecodedData())
     {
       skipToNextKeyFrame = true;
-      DECODER_LOG(PR_LOG_DEBUG, "Skipping video decode to the next keyframe");
+      DECODER_LOG("Skipping video decode to the next keyframe");
     }
     currentTime = mState == DECODER_STATE_SEEKING ? 0 : GetMediaTime();
 
     // Time the video decode, so that if it's slow, we can increase our low
     // audio threshold to reduce the chance of an audio underrun while we're
     // waiting for a video decode to complete.
     mVideoDecodeStartTime = TimeStamp::Now();
   }
@@ -918,17 +925,17 @@ MediaDecoderStateMachine::OnVideoDecoded
       TimeDuration decodeTime = TimeStamp::Now() - mVideoDecodeStartTime;
       if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
           !HasLowUndecodedData())
       {
         mLowAudioThresholdUsecs =
           std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS);
         mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs,
                                               mAmpleAudioThresholdUsecs);
-        DECODER_LOG(PR_LOG_DEBUG, "Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
+        DECODER_LOG("Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
                     mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs);
       }
       return;
     }
     case DECODER_STATE_SEEKING: {
       if (!mCurrentSeekTarget.IsValid()) {
         // We've received a sample from a previous decode. Discard it.
         return;
@@ -978,26 +985,26 @@ void
 MediaDecoderStateMachine::CheckIfSeekComplete()
 {
   AssertCurrentThreadInMonitor();
 
   const bool videoSeekComplete = IsVideoSeekComplete();
   if (HasVideo() && !videoSeekComplete) {
     // We haven't reached the target. Ensure we have requested another sample.
     if (NS_FAILED(EnsureVideoDecodeTaskQueued())) {
-      NS_WARNING("Failed to request video during seek");
+      DECODER_WARN("Failed to request video during seek");
       DecodeError();
     }
   }
 
   const bool audioSeekComplete = IsAudioSeekComplete();
   if (HasAudio() && !audioSeekComplete) {
     // We haven't reached the target. Ensure we have requested another sample.
     if (NS_FAILED(EnsureAudioDecodeTaskQueued())) {
-      NS_WARNING("Failed to request audio during seek");
+      DECODER_WARN("Failed to request audio during seek");
       DecodeError();
     }
   }
 
   SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d",
              audioSeekComplete, videoSeekComplete);
 
   if (audioSeekComplete && videoSeekComplete) {
@@ -1038,17 +1045,17 @@ MediaDecoderStateMachine::CheckIfDecodeC
   }
   if (!IsVideoDecoding() && !IsAudioDecoding()) {
     // We've finished decoding all active streams,
     // so move to COMPLETED state.
     mState = DECODER_STATE_COMPLETED;
     DispatchDecodeTasksIfNeeded();
     ScheduleStateMachine();
   }
-  DECODER_LOG(PR_LOG_DEBUG, "CheckIfDecodeComplete %scompleted",
+  DECODER_LOG("CheckIfDecodeComplete %scompleted",
               ((mState == DECODER_STATE_COMPLETED) ? "" : "NOT "));
 }
 
 bool MediaDecoderStateMachine::IsPlaying()
 {
   AssertCurrentThreadInMonitor();
 
   return !mPlayStartTime.IsNull();
@@ -1082,17 +1089,17 @@ nsresult MediaDecoderStateMachine::Init(
   rv = mReader->Init(cloneReader);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::StopPlayback()
 {
-  DECODER_LOG(PR_LOG_DEBUG, "StopPlayback()");
+  DECODER_LOG("StopPlayback()");
 
   AssertCurrentThreadInMonitor();
 
   mDecoder->NotifyPlaybackStopped();
 
   if (IsPlaying()) {
     mPlayDuration = GetClock() - mStartTime;
     SetPlayStartTime(TimeStamp());
@@ -1125,32 +1132,32 @@ int64_t MediaDecoderStateMachine::GetCur
   NS_ASSERTION(mSyncPointInDecodedStream >= 0, "Should have set up sync point");
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
   int64_t streamDelta = stream->GetLastOutputTime() - mSyncPointInMediaStream;
   return mSyncPointInDecodedStream + streamDelta;
 }
 
 void MediaDecoderStateMachine::StartPlayback()
 {
-  DECODER_LOG(PR_LOG_DEBUG, "StartPlayback()");
+  DECODER_LOG("StartPlayback()");
 
   NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
   AssertCurrentThreadInMonitor();
 
   if (mDecoder->CheckDecoderCanOffloadAudio()) {
-    DECODER_LOG(PR_LOG_DEBUG, "Offloading playback");
+    DECODER_LOG("Offloading playback");
     return;
   }
 
   mDecoder->NotifyPlaybackStarted();
   SetPlayStartTime(TimeStamp::Now());
 
   NS_ASSERTION(IsPlaying(), "Should report playing by end of StartPlayback()");
   if (NS_FAILED(StartAudioThread())) {
-    NS_WARNING("Failed to create audio thread");
+    DECODER_WARN("Failed to create audio thread");
   }
   mDecoder->GetReentrantMonitor().NotifyAll();
   mDecoder->UpdateStreamBlockingForStateMachinePlaying();
   DispatchDecodeTasksIfNeeded();
 }
 
 void MediaDecoderStateMachine::UpdatePlaybackPositionInternal(int64_t aTime)
 {
@@ -1308,16 +1315,18 @@ void MediaDecoderStateMachine::SetDorman
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   AssertCurrentThreadInMonitor();
 
   if (!mReader) {
     return;
   }
 
+  DECODER_LOG("SetDormant=%d", aDormant);
+
   if (aDormant) {
     ScheduleStateMachine();
     mState = DECODER_STATE_DORMANT;
     mDecoder->GetReentrantMonitor().NotifyAll();
   } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
     ScheduleStateMachine();
     mStartTime = 0;
     mCurrentFrameTime = 0;
@@ -1330,17 +1339,17 @@ void MediaDecoderStateMachine::Shutdown(
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Once we've entered the shutdown state here there's no going back.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // Change state before issuing shutdown request to threads so those
   // threads can start exiting cleanly during the Shutdown call.
-  DECODER_LOG(PR_LOG_DEBUG, "Changed state to SHUTDOWN");
+  DECODER_LOG("Changed state to SHUTDOWN");
   mState = DECODER_STATE_SHUTDOWN;
   mScheduler->ScheduleAndShutdown();
   if (mAudioSink) {
     mAudioSink->PrepareToShutdown();
   }
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
@@ -1372,40 +1381,42 @@ void MediaDecoderStateMachine::StartDeco
 }
 
 void MediaDecoderStateMachine::StartWaitForResources()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
   AssertCurrentThreadInMonitor();
   mState = DECODER_STATE_WAIT_FOR_RESOURCES;
+  DECODER_LOG("StartWaitForResources");
 }
 
 void MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged()
 {
   AssertCurrentThreadInMonitor();
   if (mState != DECODER_STATE_WAIT_FOR_RESOURCES ||
       mReader->IsWaitingMediaResources()) {
     return;
   }
+  DECODER_LOG("NotifyWaitingForResourcesStatusChanged");
   // The reader is no longer waiting for resources (say a hardware decoder),
   // we can now proceed to decode metadata.
   mState = DECODER_STATE_DECODING_METADATA;
   EnqueueDecodeMetadataTask();
 }
 
 void MediaDecoderStateMachine::Play()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   // When asked to play, switch to decoding state only if
   // we are currently buffering. In other cases, we'll start playing anyway
   // when the state machine notices the decoder's state change to PLAYING.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mState == DECODER_STATE_BUFFERING) {
-    DECODER_LOG(PR_LOG_DEBUG, "Changed state from BUFFERING to DECODING");
+    DECODER_LOG("Changed state from BUFFERING to DECODING");
     mState = DECODER_STATE_DECODING;
     mDecodeStartTime = TimeStamp::Now();
   }
   // Once we start playing, we don't want to minimize our prerolling, as we
   // assume the user is likely to want to keep playing in future.
   mMinimizePreroll = false;
   ScheduleStateMachine();
 }
@@ -1456,17 +1467,17 @@ void MediaDecoderStateMachine::NotifyDat
 void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // We need to be able to seek both at a transport level and at a media level
   // to seek.
   if (!mDecoder->IsMediaSeekable()) {
-    NS_WARNING("Seek() function should not be called on a non-seekable state machine");
+    DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
     return;
   }
   // MediaDecoder::mPlayState should be SEEKING while we seek, and
   // in that case MediaDecoder shouldn't be calling us.
   NS_ASSERTION(mState != DECODER_STATE_SEEKING,
                "We shouldn't already be seeking");
   NS_ASSERTION(mState >= DECODER_STATE_DECODING,
                "We should have loaded metadata");
@@ -1476,17 +1487,17 @@ void MediaDecoderStateMachine::Seek(cons
   NS_ASSERTION(mEndTime != -1, "Should know end time by now");
   int64_t seekTime = aTarget.mTime + mStartTime;
   seekTime = std::min(seekTime, mEndTime);
   seekTime = std::max(mStartTime, seekTime);
   NS_ASSERTION(seekTime >= mStartTime && seekTime <= mEndTime,
                "Can only seek in range [0,duration]");
   mSeekTarget = SeekTarget(seekTime, aTarget.mType);
 
-  DECODER_LOG(PR_LOG_DEBUG, "Changed state to SEEKING (to %lld)", mSeekTarget.mTime);
+  DECODER_LOG("Changed state to SEEKING (to %lld)", mSeekTarget.mTime);
   mState = DECODER_STATE_SEEKING;
   if (mDecoder->GetDecodedStream()) {
     mDecoder->RecreateDecodedStream(seekTime - mStartTime);
   }
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::StopAudioThread()
@@ -1498,17 +1509,17 @@ void MediaDecoderStateMachine::StopAudio
   if (mStopAudioThread) {
     // Nothing to do, since the thread is already stopping
     return;
   }
 
   mStopAudioThread = true;
   mDecoder->GetReentrantMonitor().NotifyAll();
   if (mAudioSink) {
-    DECODER_LOG(PR_LOG_DEBUG, "Shutdown audio thread");
+    DECODER_LOG("Shutdown audio thread");
     mAudioSink->PrepareToShutdown();
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       mAudioSink->Shutdown();
     }
     mAudioSink = nullptr;
     // Now that the audio sink is dead, try sending data to our MediaStream(s).
     // That may have been waiting for the audio thread to stop.
@@ -1526,29 +1537,29 @@ MediaDecoderStateMachine::EnqueueDecodeM
     return NS_OK;
   }
 
   mDispatchedDecodeMetadataTask = true;
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeMetadata));
   nsresult rv = mDecodeTaskQueue->Dispatch(task);
   if (NS_FAILED(rv)) {
-    NS_WARNING("Dispatch ReadMetadata task failed.");
+    DECODER_WARN("Dispatch ReadMetadata task failed.");
     mDispatchedDecodeMetadataTask = false;
   }
   return rv;
 }
 
 void
 MediaDecoderStateMachine::SetReaderIdle()
 {
 #ifdef PR_LOGGING
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    DECODER_LOG(PR_LOG_DEBUG, "SetReaderIdle() audioQueue=%lld videoQueue=%lld",
+    DECODER_LOG("SetReaderIdle() audioQueue=%lld videoQueue=%lld",
                 GetDecodedAudioDuration(),
                 VideoQueue().Duration());
   }
 #endif
   MOZ_ASSERT(OnDecodeThread());
   mReader->SetIdle();
 }
 
@@ -1601,17 +1612,17 @@ MediaDecoderStateMachine::DispatchDecode
              needToDecodeVideo, mVideoRequestPending,
              needIdle);
 
   if (needIdle) {
     RefPtr<nsIRunnable> event = NS_NewRunnableMethod(
         this, &MediaDecoderStateMachine::SetReaderIdle);
     nsresult rv = mDecodeTaskQueue->Dispatch(event.forget());
     if (NS_FAILED(rv) && mState != DECODER_STATE_SHUTDOWN) {
-      NS_WARNING("Failed to dispatch event to set decoder idle state");
+      DECODER_WARN("Failed to dispatch event to set decoder idle state");
     }
   }
 }
 
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeSeekTask()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
@@ -1627,17 +1638,17 @@ MediaDecoderStateMachine::EnqueueDecodeS
   mSeekTarget.Reset();
   mDropAudioUntilNextDiscontinuity = HasAudio();
   mDropVideoUntilNextDiscontinuity = HasVideo();
 
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeSeek));
   nsresult rv = mDecodeTaskQueue->Dispatch(task);
   if (NS_FAILED(rv)) {
-    NS_WARNING("Dispatch DecodeSeek task failed.");
+    DECODER_WARN("Dispatch DecodeSeek task failed.");
     mCurrentSeekTarget.Reset();
     DecodeError();
   }
   return rv;
 }
 
 nsresult
 MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
@@ -1671,17 +1682,17 @@ MediaDecoderStateMachine::EnsureAudioDec
 
   if (IsAudioDecoding() && !mAudioRequestPending) {
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeAudio));
     nsresult rv = mDecodeTaskQueue->Dispatch(task);
     if (NS_SUCCEEDED(rv)) {
       mAudioRequestPending = true;
     } else {
-      NS_WARNING("Failed to dispatch task to decode audio");
+      DECODER_WARN("Failed to dispatch task to decode audio");
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
@@ -1716,17 +1727,17 @@ MediaDecoderStateMachine::EnsureVideoDec
 
   if (IsVideoDecoding() && !mVideoRequestPending) {
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeVideo));
     nsresult rv = mDecodeTaskQueue->Dispatch(task);
     if (NS_SUCCEEDED(rv)) {
       mVideoRequestPending = true;
     } else {
-      NS_WARNING("Failed to dispatch task to decode video");
+      DECODER_WARN("Failed to dispatch task to decode video");
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::StartAudioThread()
@@ -1741,17 +1752,17 @@ MediaDecoderStateMachine::StartAudioThre
 
   mStopAudioThread = false;
   if (HasAudio() && !mAudioSink) {
     mAudioCompleted = false;
     mAudioSink = new AudioSink(this,
                                mAudioStartTime, mInfo.mAudio, mDecoder->GetAudioChannel());
     nsresult rv = mAudioSink->Init();
     if (NS_FAILED(rv)) {
-      DECODER_LOG(PR_LOG_WARNING, "Changed state to SHUTDOWN because audio sink initialization failed");
+      DECODER_WARN("Changed state to SHUTDOWN because audio sink initialization failed");
       mState = DECODER_STATE_SHUTDOWN;
       mScheduler->ScheduleAndShutdown();
       return rv;
     }
 
     mAudioSink->SetVolume(mVolume);
     mAudioSink->SetPlaybackRate(mPlaybackRate);
     mAudioSink->SetPreservesPitch(mPreservesPitch);
@@ -1820,17 +1831,17 @@ MediaDecoderStateMachine::DecodeError()
   if (mState == DECODER_STATE_SHUTDOWN) {
     // Already shutdown.
     return;
   }
 
   // Change state to shutdown before sending error report to MediaDecoder
   // and the HTMLMediaElement, so that our pipeline can start exiting
   // cleanly during the sync dispatch below.
-  DECODER_LOG(PR_LOG_WARNING, "Decode error, changed state to SHUTDOWN due to error");
+  DECODER_WARN("Decode error, changed state to SHUTDOWN due to error");
   mState = DECODER_STATE_SHUTDOWN;
   mScheduler->ScheduleAndShutdown();
   mDecoder->GetReentrantMonitor().NotifyAll();
 
   // Dispatch the event to call DecodeError synchronously. This ensures
   // we're in shutdown state by the time we exit the decode thread.
   // If we just moved to shutdown state here on the decode thread, we may
   // cause the state machine to shutdown/free memory without closing its
@@ -1847,26 +1858,26 @@ MediaDecoderStateMachine::DecodeError()
 void
 MediaDecoderStateMachine::CallDecodeMetadata()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mState != DECODER_STATE_DECODING_METADATA) {
     return;
   }
   if (NS_FAILED(DecodeMetadata())) {
-    DECODER_LOG(PR_LOG_WARNING, "Decode metadata failed, shutting down decoder");
+    DECODER_WARN("Decode metadata failed, shutting down decoder");
     DecodeError();
   }
 }
 
 nsresult MediaDecoderStateMachine::DecodeMetadata()
 {
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  DECODER_LOG(PR_LOG_DEBUG, "Decoding Media Headers");
+  DECODER_LOG("Decoding Media Headers");
   if (mState != DECODER_STATE_DECODING_METADATA) {
     return NS_ERROR_FAILURE;
   }
 
   nsresult res;
   MediaInfo info;
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@@ -1886,16 +1897,17 @@ nsresult MediaDecoderStateMachine::Decod
 
   if (NS_SUCCEEDED(res)) {
     mDecoder->SetMediaSeekable(mReader->IsMediaSeekable());
   }
 
   mInfo = info;
 
   if (NS_FAILED(res) || (!info.HasValidMedia())) {
+    DECODER_WARN("ReadMetadata failed, res=%x HasValidMedia=%d", res, info.HasValidMedia());
     return NS_ERROR_FAILURE;
   }
   mDecoder->StartProgressUpdates();
   mGotDurationFromMetaData = (GetDuration() != -1);
 
   if (HasAudio()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded));
@@ -1925,53 +1937,51 @@ nsresult MediaDecoderStateMachine::Decod
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::FinishDecodeMetadata()
 {
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  DECODER_LOG(PR_LOG_DEBUG, "Decoding Media Headers");
+  DECODER_LOG("FinishDecodeMetadata");
 
   if (mState == DECODER_STATE_SHUTDOWN) {
     return NS_ERROR_FAILURE;
   }
 
   if (!mScheduler->IsRealTime()) {
 
     const VideoData* v = VideoQueue().PeekFront();
     const AudioData* a = AudioQueue().PeekFront();
 
     int64_t startTime = std::min<int64_t>(a ? a->mTime : INT64_MAX,
                                           v ? v->mTime : INT64_MAX);
     if (startTime == INT64_MAX) {
       startTime = 0;
     }
-    DECODER_LOG(PR_LOG_DEBUG, "DecodeMetadata first video frame start %lld",
-                              v ? v->mTime : -1);
-    DECODER_LOG(PR_LOG_DEBUG, "DecodeMetadata first audio frame start %lld",
-                              a ? a->mTime : -1);
+    DECODER_LOG("DecodeMetadata first video frame start %lld", v ? v->mTime : -1);
+    DECODER_LOG("DecodeMetadata first audio frame start %lld", a ? a->mTime : -1);
     SetStartTime(startTime);
     if (VideoQueue().GetSize()) {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       RenderVideoFrame(VideoQueue().PeekFront(), TimeStamp::Now());
     }
   }
 
   NS_ASSERTION(mStartTime != -1, "Must have start time");
   MOZ_ASSERT((!HasVideo() && !HasAudio()) ||
                !(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) ||
                mEndTime != -1,
              "Active seekable media should have end time");
   MOZ_ASSERT(!(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) ||
                GetDuration() != -1,
              "Seekable media should have duration");
-  DECODER_LOG(PR_LOG_DEBUG, "Media goes from %lld to %lld (duration %lld) "
-                            "transportSeekable=%d, mediaSeekable=%d",
+  DECODER_LOG("Media goes from %lld to %lld (duration %lld) "
+              "transportSeekable=%d, mediaSeekable=%d",
               mStartTime, mEndTime, GetDuration(),
               mDecoder->IsTransportSeekable(), mDecoder->IsMediaSeekable());
 
   if (HasAudio() && !HasVideo()) {
     // We're playing audio only. We don't need to worry about slow video
     // decodes causing audio underruns, so don't buffer so much audio in
     // order to reduce memory usage.
     mAmpleAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
@@ -1981,17 +1991,17 @@ MediaDecoderStateMachine::FinishDecodeMe
   // Inform the element that we've loaded the metadata and the first frame.
   nsAutoPtr<MediaInfo> info(new MediaInfo());
   *info = mInfo;
   nsCOMPtr<nsIRunnable> metadataLoadedEvent =
     new MetadataEventRunner(mDecoder, info.forget(), mMetadataTags.forget());
   NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
 
   if (mState == DECODER_STATE_DECODING_METADATA) {
-    DECODER_LOG(PR_LOG_DEBUG, "Changed state from DECODING_METADATA to DECODING");
+    DECODER_LOG("Changed state from DECODING_METADATA to DECODING");
     StartDecoding();
   }
 
   // For very short media the metadata decode can decode the entire media.
   // So we need to check if this has occurred, else our decode pipeline won't
   // run (since it doesn't need to) and we won't detect end of stream.
   CheckIfDecodeComplete();
 
@@ -2046,17 +2056,17 @@ void MediaDecoderStateMachine::DecodeSee
     NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC);
   }
   if (mState != DECODER_STATE_SEEKING) {
     // May have shutdown while we released the monitor.
     return;
   }
 
   if (!currentTimeChanged) {
-    DECODER_LOG(PR_LOG_DEBUG, "Seek !currentTimeChanged...");
+    DECODER_LOG("Seek !currentTimeChanged...");
     mDecodeToSeekTarget = false;
     nsresult rv = mDecodeTaskQueue->Dispatch(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
     if (NS_FAILED(rv)) {
       DecodeError();
     }
   } else {
     // The seek target is different than the current playback position,
@@ -2145,36 +2155,36 @@ MediaDecoderStateMachine::SeekCompleted(
   // if we need to seek again.
 
   nsCOMPtr<nsIRunnable> stopEvent;
   bool isLiveStream = mDecoder->GetResource()->GetLength() == -1;
   if (GetMediaTime() == mEndTime && !isLiveStream) {
     // Seeked to end of media, move to COMPLETED state. Note we don't do
     // this if we're playing a live stream, since the end of media will advance
     // once we download more data!
-    DECODER_LOG(PR_LOG_DEBUG, "Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
+    DECODER_LOG("Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
     stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStoppedAtEnd);
     // Explicitly set our state so we don't decode further, and so
     // we report playback ended to the media element.
     mState = DECODER_STATE_COMPLETED;
     DispatchDecodeTasksIfNeeded();
   } else {
-    DECODER_LOG(PR_LOG_DEBUG, "Changed state from SEEKING (to %lld) to DECODING", seekTime);
+    DECODER_LOG("Changed state from SEEKING (to %lld) to DECODING", seekTime);
     stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStopped);
     StartDecoding();
   }
 
   // Ensure timestamps are up to date.
   UpdatePlaybackPositionInternal(newCurrentTime);
   if (mDecoder->GetDecodedStream()) {
     SetSyncPointForMediaStream();
   }
 
   // Try to decode another frame to detect if we're at the end...
-  DECODER_LOG(PR_LOG_DEBUG, "Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime);
+  DECODER_LOG("Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime);
 
   // Prevent changes in playback position before 'seeked' is fired for we
   // expect currentTime equals seek target in 'seeked' callback.
   mScheduler->FreezeScheduling();
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
   }
@@ -2288,16 +2298,17 @@ nsresult MediaDecoderStateMachine::RunSt
       // state machine) needs to finish and be released in order to allow
       // that. So we dispatch an event to run after this event runner has
       // finished and released its monitor/references. That event then will
       // dispatch an event to the main thread to release the decoder and
       // state machine.
       GetStateMachineThread()->Dispatch(
         new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL);
 
+      DECODER_LOG("SHUTDOWN OK");
       return NS_OK;
     }
 
     case DECODER_STATE_DORMANT: {
       if (IsPlaying()) {
         StopPlayback();
       }
       FlushDecoding();
@@ -2357,24 +2368,24 @@ nsresult MediaDecoderStateMachine::RunSt
       bool isLiveStream = resource->GetLength() == -1;
       if ((isLiveStream || !mDecoder->CanPlayThrough()) &&
             elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
             (mQuickBuffering ? HasLowDecodedData(QUICK_BUFFERING_LOW_DATA_USECS)
                             : HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
             !mDecoder->IsDataCachedToEndOfResource() &&
             !resource->IsSuspended())
       {
-        DECODER_LOG(PR_LOG_DEBUG, "Buffering: wait %ds, timeout in %.3lfs %s",
+        DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
                     mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
                     (mQuickBuffering ? "(quick exit)" : ""));
         ScheduleStateMachine(USECS_PER_S);
         return NS_OK;
       } else {
-        DECODER_LOG(PR_LOG_DEBUG, "Changed state from BUFFERING to DECODING");
-        DECODER_LOG(PR_LOG_DEBUG, "Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
+        DECODER_LOG("Changed state from BUFFERING to DECODING");
+        DECODER_LOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
         StartDecoding();
       }
 
       // Notify to allow blocked decoder thread to continue
       mDecoder->GetReentrantMonitor().NotifyAll();
       UpdateReadyState();
       if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
           !IsPlaying())
@@ -2693,18 +2704,17 @@ void MediaDecoderStateMachine::AdvanceFr
   ScheduleStateMachine(remainingTime);
 }
 
 nsresult
 MediaDecoderStateMachine::DropVideoUpToSeekTarget(VideoData* aSample)
 {
   nsAutoPtr<VideoData> video(aSample);
   MOZ_ASSERT(video);
-  DECODER_LOG(PR_LOG_DEBUG,
-              "DropVideoUpToSeekTarget() frame [%lld, %lld] dup=%d",
+  DECODER_LOG("DropVideoUpToSeekTarget() frame [%lld, %lld] dup=%d",
               video->mTime, video->GetEndTime(), video->mDuplicate);
   const int64_t target = mCurrentSeekTarget.mTime;
 
   // Duplicate handling: if we're dropping frames up the seek target, we must
   // be wary of Theora duplicate frames. They don't have an image, so if the
   // target frame is in a run of duplicates, we won't have an image to draw
   // after the seek. So store the last frame encountered while dropping, and
   // copy its Image forward onto duplicate frames, so that every frame has
@@ -2717,32 +2727,30 @@ MediaDecoderStateMachine::DropVideoUpToS
                                                        video->mTime,
                                                        video->mDuration);
     video = temp;
   }
 
   // If the frame end time is less than the seek target, we won't want
   // to display this frame after the seek, so discard it.
   if (target >= video->GetEndTime()) {
-    DECODER_LOG(PR_LOG_DEBUG,
-                "DropVideoUpToSeekTarget() pop video frame [%lld, %lld] target=%lld",
+    DECODER_LOG("DropVideoUpToSeekTarget() pop video frame [%lld, %lld] target=%lld",
                 video->mTime, video->GetEndTime(), target);
     mFirstVideoFrameAfterSeek = video;
   } else {
     if (target >= video->mTime && video->GetEndTime() >= target) {
       // The seek target lies inside this frame's time slice. Adjust the frame's
       // start time to match the seek target. We do this by replacing the
       // first frame with a shallow copy which has the new timestamp.
       VideoData* temp = VideoData::ShallowCopyUpdateTimestamp(video, target);
       video = temp;
     }
     mFirstVideoFrameAfterSeek = nullptr;
 
-    DECODER_LOG(PR_LOG_DEBUG,
-                "DropVideoUpToSeekTarget() found video frame [%lld, %lld] containing target=%lld",
+    DECODER_LOG("DropVideoUpToSeekTarget() found video frame [%lld, %lld] containing target=%lld",
                 video->mTime, video->GetEndTime(), target);
 
     VideoQueue().PushFront(video.forget());
 
   }
   return NS_OK;
 }
 
@@ -2769,34 +2777,34 @@ MediaDecoderStateMachine::DropAudioUpToS
   if (startFrame.value() > targetFrame.value()) {
     // The seek target doesn't lie in the audio block just after the last
     // audio frames we've seen which were before the seek target. This
     // could have been the first audio data we've seen after seek, i.e. the
     // seek terminated after the seek target in the audio stream. Just
     // abort the audio decode-to-target, the state machine will play
     // silence to cover the gap. Typically this happens in poorly muxed
     // files.
-    NS_WARNING("Audio not synced after seek, maybe a poorly muxed file?");
+    DECODER_WARN("Audio not synced after seek, maybe a poorly muxed file?");
     AudioQueue().Push(audio.forget());
     return NS_OK;
   }
 
   // The seek target lies somewhere in this AudioData's frames, strip off
   // any frames which lie before the seek target, so we'll begin playback
   // exactly at the seek target.
   NS_ASSERTION(targetFrame.value() >= startFrame.value(),
                "Target must at or be after data start.");
   NS_ASSERTION(targetFrame.value() < startFrame.value() + audio->mFrames,
                "Data must end after target.");
 
   int64_t framesToPrune = targetFrame.value() - startFrame.value();
   if (framesToPrune > audio->mFrames) {
     // We've messed up somehow. Don't try to trim frames, the |frames|
     // variable below will overflow.
-    NS_WARNING("Can't prune more frames that we have!");
+    DECODER_WARN("Can't prune more frames that we have!");
     return NS_ERROR_FAILURE;
   }
   uint32_t frames = audio->mFrames - static_cast<uint32_t>(framesToPrune);
   uint32_t channels = audio->mChannels;
   nsAutoArrayPtr<AudioDataValue> audioData(new AudioDataValue[frames * channels]);
   memcpy(audioData.get(),
          audio->mAudioData.get() + (framesToPrune * channels),
          frames * channels * sizeof(AudioDataValue));
@@ -2814,34 +2822,34 @@ MediaDecoderStateMachine::DropAudioUpToS
   AudioQueue().PushFront(data.forget());
 
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::SetStartTime(int64_t aStartTimeUsecs)
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  DECODER_LOG(PR_LOG_DEBUG, "SetStartTime(%lld)", aStartTimeUsecs);
+  DECODER_LOG("SetStartTime(%lld)", aStartTimeUsecs);
   mStartTime = 0;
   if (aStartTimeUsecs != 0) {
     mStartTime = aStartTimeUsecs;
     if (mGotDurationFromMetaData) {
       NS_ASSERTION(mEndTime != -1,
                    "We should have mEndTime as supplied duration here");
       // We were specified a duration from a Content-Duration HTTP header.
       // Adjust mEndTime so that mEndTime-mStartTime matches the specified
       // duration.
       mEndTime = mStartTime + mEndTime;
     }
   }
   // Set the audio start time to be start of media. If this lies before the
   // first actual audio frame we have, we'll inject silence during playback
   // to ensure the audio starts at the correct time.
   mAudioStartTime = mStartTime;
-  DECODER_LOG(PR_LOG_DEBUG, "Set media start time to %lld", mStartTime);
+  DECODER_LOG("Set media start time to %lld", mStartTime);
 }
 
 void MediaDecoderStateMachine::UpdateReadyState() {
   AssertCurrentThreadInMonitor();
 
   MediaDecoderOwner::NextFrameStatus nextFrameStatus = GetNextFrameStatus();
   if (nextFrameStatus == mLastFrameStatus) {
     return;
@@ -2900,21 +2908,21 @@ void MediaDecoderStateMachine::StartBuff
   // there might be pending main-thread events, such as "data
   // received" notifications, that mean we're not actually still
   // buffering by the time this runnable executes. So instead
   // we just trigger UpdateReadyStateForData; when it runs, it
   // will check the current state and decide whether to tell
   // the element we're buffering or not.
   UpdateReadyState();
   mState = DECODER_STATE_BUFFERING;
-  DECODER_LOG(PR_LOG_DEBUG, "Changed state from DECODING to BUFFERING, decoded for %.3lfs",
+  DECODER_LOG("Changed state from DECODING to BUFFERING, decoded for %.3lfs",
               decodeDuration.ToSeconds());
 #ifdef PR_LOGGING
   MediaDecoder::Statistics stats = mDecoder->GetStatistics();
-  DECODER_LOG(PR_LOG_DEBUG, "Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
+  DECODER_LOG("Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
               stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)",
               stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)");
 #endif
 }
 
 nsresult MediaDecoderStateMachine::GetBuffered(dom::TimeRanges* aBuffered) {
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource, NS_ERROR_FAILURE);
@@ -3075,8 +3083,10 @@ void MediaDecoderStateMachine::OnAudioSi
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 } // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef DECODER_LOG
 #undef VERBOSE_LOG
+#undef DECODER_WARN
+#undef DECODER_WARN_HELPER
--- a/content/media/fmp4/MP4Reader.cpp
+++ b/content/media/fmp4/MP4Reader.cpp
@@ -565,17 +565,17 @@ MP4Reader::Decode(TrackType aTrack)
       data.mMonitor.Wait();
     }
     if (data.mError ||
         (data.mEOS && data.mDrainComplete)) {
       break;
     }
   }
   data.mMonitor.AssertCurrentThreadOwns();
-  bool rv = !(data.mEOS || data.mError);
+  bool rv = !(data.mDrainComplete || data.mError);
   data.mMonitor.Unlock();
   return rv;
 }
 
 nsresult
 MP4Reader::ResetDecode()
 {
   Flush(kAudio);
--- a/content/media/gmp/GMPChild.cpp
+++ b/content/media/gmp/GMPChild.cpp
@@ -44,49 +44,60 @@ GMPChild::GMPChild()
 {
 }
 
 GMPChild::~GMPChild()
 {
 }
 
 static bool
-GetPluginBinaryPath(const std::string& aPluginPath,
-                    nsCString &aPluginBinaryPath)
+GetPluginBinaryFile(const std::string& aPluginPath,
+                    nsCOMPtr<nsIFile>& aLibFile)
 {
   nsDependentCString pluginPath(aPluginPath.c_str());
 
-  nsCOMPtr<nsIFile> libFile;
-  nsresult rv = NS_NewNativeLocalFile(pluginPath, true, getter_AddRefs(libFile));
+  nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(pluginPath),
+                                true, getter_AddRefs(aLibFile));
   if (NS_FAILED(rv)) {
     return false;
   }
 
   nsAutoString leafName;
-  if (NS_FAILED(libFile->GetLeafName(leafName))) {
+  if (NS_FAILED(aLibFile->GetLeafName(leafName))) {
     return false;
   }
   nsAutoString baseName(Substring(leafName, 4, leafName.Length() - 1));
 
 #if defined(XP_MACOSX)
   nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".dylib");
 #elif defined(OS_POSIX)
   nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".so");
 #elif defined(XP_WIN)
   nsAutoString binaryName =                            baseName + NS_LITERAL_STRING(".dll");
 #else
 #error not defined
 #endif
-  libFile->AppendRelativePath(binaryName);
+  aLibFile->AppendRelativePath(binaryName);
+  return true;
+}
+
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
+static bool
+GetPluginBinaryPath(const std::string& aPluginPath,
+                    nsCString &aPluginBinaryPath)
+{
+  nsCOMPtr<nsIFile> libFile;
+  if (!GetPluginBinaryFile(aPluginPath, libFile)) {
+    return false;
+  }
 
   libFile->GetNativePath(aPluginBinaryPath);
   return true;
 }
 
-#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
 void
 GMPChild::OnChannelConnected(int32_t aPid)
 {
   MacSandboxInfo info;
   info.type = MacSandboxType_Plugin;
   info.pluginInfo.type = MacSandboxPluginType_GMPlugin_Default;
   info.pluginInfo.pluginPath.Assign(mPluginPath.c_str());
 
@@ -142,33 +153,39 @@ GMPChild::Init(const std::string& aPlugi
 #endif
 
   return LoadPluginLibrary(aPluginPath);
 }
 
 bool
 GMPChild::LoadPluginLibrary(const std::string& aPluginPath)
 {
+#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
   nsAutoCString nativePath;
-#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
   nativePath.Assign(mPluginBinaryPath);
+
+  mLib = PR_LoadLibrary(nativePath.get());
 #else
-  if (!GetPluginBinaryPath(aPluginPath, nativePath)) {
+  nsCOMPtr<nsIFile> libFile;
+  if (!GetPluginBinaryFile(aPluginPath, libFile)) {
     return false;
   }
-#endif
+#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
+  nsAutoCString nativePath;
+  libFile->GetNativePath(nativePath);
 
-#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
   // Enable sandboxing here -- we know the plugin file's path, but
   // this process's execution hasn't been affected by its content yet.
   MOZ_ASSERT(mozilla::CanSandboxMediaPlugin());
   mozilla::SetMediaPluginSandbox(nativePath.get());
-#endif
+#endif // XP_LINUX && MOZ_GMP_SANDBOX
 
-  mLib = PR_LoadLibrary(nativePath.get());
+  libFile->Load(&mLib);
+#endif // XP_MACOSX && MOZ_GMP_SANDBOX
+
   if (!mLib) {
     return false;
   }
 
   GMPInitFunc initFunc = reinterpret_cast<GMPInitFunc>(PR_FindFunctionSymbol(mLib, "GMPInit"));
   if (!initFunc) {
     return false;
   }
--- a/content/media/gmp/GMPParent.cpp
+++ b/content/media/gmp/GMPParent.cpp
@@ -114,24 +114,24 @@ GMPParent::Crash()
 
 nsresult
 GMPParent::LoadProcess()
 {
   MOZ_ASSERT(mDirectory, "Plugin directory cannot be NULL!");
   MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
   MOZ_ASSERT(mState == GMPStateNotLoaded);
 
-  nsAutoCString path;
-  if (NS_FAILED(mDirectory->GetNativePath(path))) {
+  nsAutoString path;
+  if (NS_FAILED(mDirectory->GetPath(path))) {
     return NS_ERROR_FAILURE;
   }
   LOGD(("%s::%s: %p for %s", __CLASS__, __FUNCTION__, this, path.get()));
 
   if (!mProcess) {
-    mProcess = new GMPProcessParent(path.get());
+    mProcess = new GMPProcessParent(NS_ConvertUTF16toUTF8(path).get());
     if (!mProcess->Launch(30 * 1000)) {
       mProcess->Delete();
       mProcess = nullptr;
       return NS_ERROR_FAILURE;
     }
 
     bool opened = Open(mProcess->GetChannel(), mProcess->GetChildProcessHandle());
     if (!opened) {
--- a/content/media/omx/MediaCodecReader.cpp
+++ b/content/media/omx/MediaCodecReader.cpp
@@ -23,138 +23,153 @@
 #include <stagefright/MetaData.h>
 #include <stagefright/Utils.h>
 
 #include "mozilla/TimeStamp.h"
 
 #include "gfx2DGlue.h"
 
 #include "MediaStreamSource.h"
+#include "MediaTaskQueue.h"
+#include "nsThreadUtils.h"
 #include "ImageContainer.h"
+#include "SharedThreadPool.h"
 #include "VideoFrameContainer.h"
 
 using namespace android;
 
 namespace mozilla {
 
 enum {
   kNotifyCodecReserved = 'core',
   kNotifyCodecCanceled = 'coca',
 };
 
 static const int64_t sInvalidDurationUs = INT64_C(-1);
 static const int64_t sInvalidTimestampUs = INT64_C(-1);
 
-// Try not to spend more than this much time (in seconds) in a single call to DecodeAudioData.
+// Try not to spend more than this much time (in seconds) in a single call
+// to GetCodecOutputData.
 static const double sMaxAudioDecodeDurationS = 0.1;
-// Try not to spend more than this much time (in seconds) in a single call to DecodeVideoFrame.
 static const double sMaxVideoDecodeDurationS = 0.1;
 
 static CheckedUint32 sInvalidInputIndex = INT32_C(-1);
 
 inline bool
 IsValidDurationUs(int64_t aDuration)
 {
   return aDuration >= INT64_C(0);
 }
 
 inline bool
 IsValidTimestampUs(int64_t aTimestamp)
 {
   return aTimestamp >= INT64_C(0);
 }
 
-MediaCodecReader::MessageHandler::MessageHandler(MediaCodecReader *aReader)
+MediaCodecReader::MessageHandler::MessageHandler(MediaCodecReader* aReader)
   : mReader(aReader)
 {
 }
 
 MediaCodecReader::MessageHandler::~MessageHandler()
 {
   mReader = nullptr;
 }
 
 void
-MediaCodecReader::MessageHandler::onMessageReceived(const android::sp<android::AMessage> &aMessage)
+MediaCodecReader::MessageHandler::onMessageReceived(
+  const sp<AMessage>& aMessage)
 {
-  if (mReader != nullptr) {
+  if (mReader) {
     mReader->onMessageReceived(aMessage);
   }
 }
 
-MediaCodecReader::VideoResourceListener::VideoResourceListener(MediaCodecReader *aReader)
+MediaCodecReader::VideoResourceListener::VideoResourceListener(
+  MediaCodecReader* aReader)
   : mReader(aReader)
 {
 }
 
 MediaCodecReader::VideoResourceListener::~VideoResourceListener()
 {
   mReader = nullptr;
 }
 
 void
 MediaCodecReader::VideoResourceListener::codecReserved()
 {
-  if (mReader != nullptr) {
+  if (mReader) {
     mReader->codecReserved(mReader->mVideoTrack);
   }
 }
 
 void
 MediaCodecReader::VideoResourceListener::codecCanceled()
 {
-  if (mReader != nullptr) {
+  if (mReader) {
     mReader->codecCanceled(mReader->mVideoTrack);
   }
 }
 
 bool
-MediaCodecReader::TrackInputCopier::Copy(MediaBuffer* aSourceBuffer, sp<ABuffer> aCodecBuffer)
+MediaCodecReader::TrackInputCopier::Copy(MediaBuffer* aSourceBuffer,
+                                         sp<ABuffer> aCodecBuffer)
 {
   if (aSourceBuffer == nullptr ||
       aCodecBuffer == nullptr ||
       aSourceBuffer->range_length() > aCodecBuffer->capacity()) {
     return false;
   }
 
   aCodecBuffer->setRange(0, aSourceBuffer->range_length());
-  memcpy(aCodecBuffer->data(), aSourceBuffer->data() + aSourceBuffer->range_offset(), aSourceBuffer->range_length());
+  memcpy(aCodecBuffer->data(),
+         (uint8_t*)aSourceBuffer->data() + aSourceBuffer->range_offset(),
+         aSourceBuffer->range_length());
 
   return true;
 }
 
 MediaCodecReader::Track::Track()
   : mDurationUs(INT64_C(0))
   , mInputIndex(sInvalidInputIndex)
   , mInputEndOfStream(false)
+  , mOutputEndOfStream(false)
   , mSeekTimeUs(sInvalidTimestampUs)
   , mFlushed(false)
+  , mDiscontinuity(false)
+  , mTaskQueue(nullptr)
 {
 }
 
 // Append the value of |kKeyValidSamples| to the end of each vorbis buffer.
 // https://github.com/mozilla-b2g/platform_frameworks_av/blob/master/media/libstagefright/OMXCodec.cpp#L3128
 // https://github.com/mozilla-b2g/platform_frameworks_av/blob/master/media/libstagefright/NuMediaExtractor.cpp#L472
 bool
-MediaCodecReader::VorbisInputCopier::Copy(MediaBuffer* aSourceBuffer, sp<ABuffer> aCodecBuffer)
+MediaCodecReader::VorbisInputCopier::Copy(MediaBuffer* aSourceBuffer,
+                                          sp<ABuffer> aCodecBuffer)
 {
   if (aSourceBuffer == nullptr ||
       aCodecBuffer == nullptr ||
       aSourceBuffer->range_length() + sizeof(int32_t) > aCodecBuffer->capacity()) {
     return false;
   }
 
   int32_t numPageSamples = 0;
   if (!aSourceBuffer->meta_data()->findInt32(kKeyValidSamples, &numPageSamples)) {
     numPageSamples = -1;
   }
 
   aCodecBuffer->setRange(0, aSourceBuffer->range_length() + sizeof(int32_t));
-  memcpy(aCodecBuffer->data(), aSourceBuffer->data() + aSourceBuffer->range_offset(), aSourceBuffer->range_length());
-  memcpy(aCodecBuffer->data() + aSourceBuffer->range_length(), &numPageSamples, sizeof(numPageSamples));
+  memcpy(aCodecBuffer->data(),
+         (uint8_t*)aSourceBuffer->data() + aSourceBuffer->range_offset(),
+         aSourceBuffer->range_length());
+  memcpy(aCodecBuffer->data() + aSourceBuffer->range_length(),
+         &numPageSamples, sizeof(numPageSamples));
 
   return true;
 }
 
 MediaCodecReader::AudioTrack::AudioTrack()
 {
 }
 
@@ -215,200 +230,175 @@ MediaCodecReader::ReleaseMediaResources(
 }
 
 void
 MediaCodecReader::Shutdown()
 {
   ReleaseResources();
 }
 
-bool
-MediaCodecReader::DecodeAudioData()
+void
+MediaCodecReader::DispatchAudioTask()
+{
+  if (mAudioTrack.mTaskQueue && mAudioTrack.mTaskQueue->IsEmpty()) {
+    RefPtr<nsIRunnable> task =
+      NS_NewRunnableMethod(this,
+                           &MediaCodecReader::DecodeAudioDataTask);
+    mAudioTrack.mTaskQueue->Dispatch(task);
+  }
+}
+
+void
+MediaCodecReader::DispatchVideoTask(int64_t aTimeThreshold)
+{
+  if (mVideoTrack.mTaskQueue && mVideoTrack.mTaskQueue->IsEmpty()) {
+    RefPtr<nsIRunnable> task =
+      NS_NewRunnableMethodWithArg<int64_t>(this,
+                                           &MediaCodecReader::DecodeVideoFrameTask,
+                                           aTimeThreshold);
+    mVideoTrack.mTaskQueue->Dispatch(task);
+  }
+}
+
+void
+MediaCodecReader::RequestAudioData()
 {
-  MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
+  MOZ_ASSERT(HasAudio());
+  if (CheckAudioResources()) {
+    DispatchAudioTask();
+  }
+}
+
+void
+MediaCodecReader::RequestVideoData(bool aSkipToNextKeyframe,
+                                   int64_t aTimeThreshold)
+{
+  MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
+  MOZ_ASSERT(HasVideo());
 
-  if (mAudioTrack.mCodec == nullptr || !mAudioTrack.mCodec->allocated()) {
+  int64_t threshold = sInvalidTimestampUs;
+  if (aSkipToNextKeyframe && IsValidTimestampUs(aTimeThreshold)) {
+    mVideoTrack.mTaskQueue->Flush();
+    threshold = aTimeThreshold;
+  }
+  if (CheckVideoResources()) {
+    DispatchVideoTask(threshold);
+  }
+}
+
+bool
+MediaCodecReader::DecodeAudioDataSync()
+{
+  if (mAudioTrack.mCodec == nullptr || !mAudioTrack.mCodec->allocated() ||
+      mAudioTrack.mOutputEndOfStream) {
     return false;
   }
 
   // Get one audio output data from MediaCodec
   CodecBufferInfo bufferInfo;
   status_t status;
-  TimeStamp timeout = TimeStamp::Now() + TimeDuration::FromSeconds(sMaxAudioDecodeDurationS);
+  TimeStamp timeout = TimeStamp::Now() +
+                      TimeDuration::FromSeconds(sMaxAudioDecodeDurationS);
   while (true) {
-    if (timeout < TimeStamp::Now()) {
-      return true; // Try it again later.
-    }
-    status = GetCodecOutputData(mAudioTrack, bufferInfo, sInvalidTimestampUs, timeout);
+    // Try to fill more input buffers and then get one output buffer.
+    // FIXME: use callback from MediaCodec
+    FillCodecInputData(mAudioTrack);
+
+    status = GetCodecOutputData(mAudioTrack, bufferInfo, sInvalidTimestampUs,
+                                timeout);
     if (status == OK || status == ERROR_END_OF_STREAM) {
       break;
     } else if (status == -EAGAIN) {
-      return true; // Try it again later.
+      if (TimeStamp::Now() > timeout) {
+        // Don't let this loop run for too long. Try it again later.
+        if (CheckAudioResources()) {
+          DispatchAudioTask();
+        }
+        return true;
+      }
+      continue; // Try it again now.
     } else if (status == INFO_FORMAT_CHANGED) {
       if (UpdateAudioInfo()) {
         continue; // Try it again now.
       } else {
         return false;
       }
     } else {
       return false;
     }
   }
 
-  bool result = true;
-
-  if (bufferInfo.mBuffer != nullptr && bufferInfo.mSize > 0 && bufferInfo.mBuffer->data() != nullptr) {
+  bool result = false;
+  if (bufferInfo.mBuffer != nullptr && bufferInfo.mSize > 0 &&
+      bufferInfo.mBuffer->data() != nullptr) {
     // This is the approximate byte position in the stream.
     int64_t pos = mDecoder->GetResource()->Tell();
 
-    uint32_t frames = bufferInfo.mSize / (mInfo.mAudio.mChannels * sizeof(AudioDataValue));
+    uint32_t frames = bufferInfo.mSize /
+                      (mInfo.mAudio.mChannels * sizeof(AudioDataValue));
 
     result = mAudioCompactor.Push(
-        pos,
-        bufferInfo.mTimeUs,
-        mInfo.mAudio.mRate,
-        frames,
-        mInfo.mAudio.mChannels,
-        AudioCompactor::NativeCopy(
-            bufferInfo.mBuffer->data() + bufferInfo.mOffset,
-            bufferInfo.mSize,
-            mInfo.mAudio.mChannels));
+      pos,
+      bufferInfo.mTimeUs,
+      mInfo.mAudio.mRate,
+      frames,
+      mInfo.mAudio.mChannels,
+      AudioCompactor::NativeCopy(
+        bufferInfo.mBuffer->data() + bufferInfo.mOffset,
+        bufferInfo.mSize,
+        mInfo.mAudio.mChannels));
   }
 
+  if ((bufferInfo.mFlags & MediaCodec::BUFFER_FLAG_EOS) ||
+      (status == ERROR_END_OF_STREAM)) {
+    AudioQueue().Finish();
+  }
   mAudioTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex);
 
-  if (status == ERROR_END_OF_STREAM) {
-    return false;
-  }
-
   return result;
 }
 
 bool
-MediaCodecReader::DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold)
+MediaCodecReader::DecodeAudioDataTask()
 {
-  MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-
-  if (mVideoTrack.mCodec == nullptr || !mVideoTrack.mCodec->allocated()) {
-    return false;
-  }
-
-  int64_t threshold = sInvalidTimestampUs;
-  if (aKeyframeSkip && IsValidTimestampUs(aTimeThreshold)) {
-    threshold = aTimeThreshold;
-  }
-
-  // Get one video output data from MediaCodec
-  CodecBufferInfo bufferInfo;
-  status_t status;
-  TimeStamp timeout = TimeStamp::Now() + TimeDuration::FromSeconds(sMaxVideoDecodeDurationS);
-  while (true) {
-    if (timeout < TimeStamp::Now()) {
-      return true; // Try it again later.
-    }
-    status = GetCodecOutputData(mVideoTrack, bufferInfo, threshold, timeout);
-    if (status == OK || status == ERROR_END_OF_STREAM) {
-      break;
-    } else if (status == -EAGAIN) {
-      return true; // Try it again later.
-    } else if (status == INFO_FORMAT_CHANGED) {
-      if (UpdateVideoInfo()) {
-        continue; // Try it again now.
-      } else {
-        return false;
+  bool result = DecodeAudioDataSync();
+  if (AudioQueue().GetSize() > 0) {
+    AudioData* a = AudioQueue().PopFront();
+    if (a) {
+      if (mAudioTrack.mDiscontinuity) {
+        a->mDiscontinuity = true;
+        mAudioTrack.mDiscontinuity = false;
       }
-    } else {
-      return false;
+      GetCallback()->OnAudioDecoded(a);
     }
   }
-
-  bool result = true;
-
-  if (bufferInfo.mBuffer != nullptr && bufferInfo.mSize > 0 && bufferInfo.mBuffer->data() != nullptr) {
-    uint8_t *yuv420p_buffer = bufferInfo.mBuffer->data();
-    int32_t stride = mVideoTrack.mStride;
-    int32_t slice_height = mVideoTrack.mSliceHeight;
-
-    // Converts to OMX_COLOR_FormatYUV420Planar
-    if (mVideoTrack.mColorFormat != OMX_COLOR_FormatYUV420Planar) {
-      ARect crop;
-      crop.top = 0;
-      crop.bottom = mVideoTrack.mHeight;
-      crop.left = 0;
-      crop.right = mVideoTrack.mWidth;
-
-      yuv420p_buffer = GetColorConverterBuffer(mVideoTrack.mWidth, mVideoTrack.mHeight);
-      if (mColorConverter.convertDecoderOutputToI420(
-          bufferInfo.mBuffer->data(), mVideoTrack.mWidth, mVideoTrack.mHeight, crop, yuv420p_buffer) != OK) {
-        mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex);
-        NS_WARNING("Unable to convert color format");
-        return false;
-      }
-
-      stride = mVideoTrack.mWidth;
-      slice_height = mVideoTrack.mHeight;
-    }
-
-    size_t yuv420p_y_size = stride * slice_height;
-    size_t yuv420p_u_size = ((stride + 1) / 2) * ((slice_height + 1) / 2);
-    uint8_t *yuv420p_y = yuv420p_buffer;
-    uint8_t *yuv420p_u = yuv420p_y + yuv420p_y_size;
-    uint8_t *yuv420p_v = yuv420p_u + yuv420p_u_size;
-
-    // This is the approximate byte position in the stream.
-    int64_t pos = mDecoder->GetResource()->Tell();
+  if (AudioQueue().AtEndOfStream()) {
+    GetCallback()->OnAudioEOS();
+  }
+  return result;
+}
 
-    VideoData::YCbCrBuffer b;
-    b.mPlanes[0].mData = yuv420p_y;
-    b.mPlanes[0].mWidth = mVideoTrack.mWidth;
-    b.mPlanes[0].mHeight = mVideoTrack.mHeight;
-    b.mPlanes[0].mStride = stride;
-    b.mPlanes[0].mOffset = 0;
-    b.mPlanes[0].mSkip = 0;
-
-    b.mPlanes[1].mData = yuv420p_u;
-    b.mPlanes[1].mWidth = (mVideoTrack.mWidth + 1) / 2;
-    b.mPlanes[1].mHeight = (mVideoTrack.mHeight + 1) / 2;
-    b.mPlanes[1].mStride = (stride + 1) / 2;
-    b.mPlanes[1].mOffset = 0;
-    b.mPlanes[1].mSkip = 0;
-
-    b.mPlanes[2].mData = yuv420p_v;
-    b.mPlanes[2].mWidth =(mVideoTrack.mWidth + 1) / 2;
-    b.mPlanes[2].mHeight = (mVideoTrack.mHeight + 1) / 2;
-    b.mPlanes[2].mStride = (stride + 1) / 2;
-    b.mPlanes[2].mOffset = 0;
-    b.mPlanes[2].mSkip = 0;
-
-    VideoData *v = VideoData::Create(
-        mInfo.mVideo,
-        mDecoder->GetImageContainer(),
-        pos,
-        bufferInfo.mTimeUs,
-        1, // We don't know the duration.
-        b,
-        bufferInfo.mFlags & MediaCodec::BUFFER_FLAG_SYNCFRAME,
-        -1,
-        mVideoTrack.mRelativePictureRect);
-
-    if (v != nullptr) {
-      result = true;
-      mVideoQueue.Push(v);
-      aKeyframeSkip = false;
-    } else {
-      NS_WARNING("Unable to create VideoData");
+bool
+MediaCodecReader::DecodeVideoFrameTask(int64_t aTimeThreshold)
+{
+  bool result = DecodeVideoFrameSync(aTimeThreshold);
+  if (VideoQueue().GetSize() > 0) {
+    VideoData* v = VideoQueue().PopFront();
+    if (v) {
+      if (mVideoTrack.mDiscontinuity) {
+        v->mDiscontinuity = true;
+        mVideoTrack.mDiscontinuity = false;
+      }
+      GetCallback()->OnVideoDecoded(v);
     }
   }
-
-  mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex);
-
-  if (status == ERROR_END_OF_STREAM) {
-    return false;
+  if (VideoQueue().AtEndOfStream()) {
+    GetCallback()->OnVideoEOS();
   }
-
   return result;
 }
 
 bool
 MediaCodecReader::HasAudio()
 {
   return mInfo.mAudio.mHasAudio;
 }
@@ -447,137 +437,297 @@ MediaCodecReader::ReadMetadata(MediaInfo
     return NS_ERROR_FAILURE;
   }
 
   if (!UpdateVideoInfo()) {
     return NS_ERROR_FAILURE;
   }
 
   // Set the total duration (the max of the audio and video track).
-  int64_t duration = mAudioTrack.mDurationUs > mVideoTrack.mDurationUs
-      ? mAudioTrack.mDurationUs
-      : mVideoTrack.mDurationUs;
+  int64_t duration = mAudioTrack.mDurationUs > mVideoTrack.mDurationUs ?
+    mAudioTrack.mDurationUs : mVideoTrack.mDurationUs;
   if (duration >= 0LL) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mDecoder->SetMediaDuration(duration);
   }
 
   // Video track's frame sizes will not overflow. Activate the video track.
   VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
   if (container) {
     container->SetCurrentFrame(
-        gfxIntSize(mInfo.mVideo.mDisplay.width, mInfo.mVideo.mDisplay.height),
-        nullptr,
-        mozilla::TimeStamp::Now());
+      gfxIntSize(mInfo.mVideo.mDisplay.width, mInfo.mVideo.mDisplay.height),
+      nullptr,
+      mozilla::TimeStamp::Now());
   }
 
   *aInfo = mInfo;
   *aTags = nullptr;
 
   return NS_OK;
 }
 
 nsresult
+MediaCodecReader::ResetDecode()
+{
+  if (CheckAudioResources()) {
+    mAudioTrack.mTaskQueue->Flush();
+    FlushCodecData(mAudioTrack);
+    mAudioTrack.mDiscontinuity = true;
+  }
+  if (CheckVideoResources()) {
+    mVideoTrack.mTaskQueue->Flush();
+    FlushCodecData(mVideoTrack);
+    mVideoTrack.mDiscontinuity = true;
+  }
+
+  return MediaDecoderReader::ResetDecode();
+}
+
+bool
+MediaCodecReader::DecodeVideoFrameSync(int64_t aTimeThreshold)
+{
+  if (mVideoTrack.mCodec == nullptr || !mVideoTrack.mCodec->allocated() ||
+      mVideoTrack.mOutputEndOfStream) {
+    return false;
+  }
+
+  // Get one video output data from MediaCodec
+  CodecBufferInfo bufferInfo;
+  status_t status;
+  TimeStamp timeout = TimeStamp::Now() +
+                      TimeDuration::FromSeconds(sMaxVideoDecodeDurationS);
+  while (true) {
+    // Try to fill more input buffers and then get one output buffer.
+    // FIXME: use callback from MediaCodec
+    FillCodecInputData(mVideoTrack);
+
+    status = GetCodecOutputData(mVideoTrack, bufferInfo, aTimeThreshold,
+                                timeout);
+    if (status == OK || status == ERROR_END_OF_STREAM) {
+      break;
+    } else if (status == -EAGAIN) {
+      if (TimeStamp::Now() > timeout) {
+        // Don't let this loop run for too long. Try it again later.
+        if (CheckVideoResources()) {
+          DispatchVideoTask(aTimeThreshold);
+        }
+        return true;
+      }
+      continue; // Try it again now.
+    } else if (status == INFO_FORMAT_CHANGED) {
+      if (UpdateVideoInfo()) {
+        continue; // Try it again now.
+      } else {
+        return false;
+      }
+    } else {
+      return false;
+    }
+  }
+
+  bool result = false;
+  if (bufferInfo.mBuffer != nullptr && bufferInfo.mSize > 0 &&
+      bufferInfo.mBuffer->data() != nullptr) {
+    uint8_t* yuv420p_buffer = bufferInfo.mBuffer->data();
+    int32_t stride = mVideoTrack.mStride;
+    int32_t slice_height = mVideoTrack.mSliceHeight;
+
+    // Converts to OMX_COLOR_FormatYUV420Planar
+    if (mVideoTrack.mColorFormat != OMX_COLOR_FormatYUV420Planar) {
+      ARect crop;
+      crop.top = 0;
+      crop.bottom = mVideoTrack.mHeight;
+      crop.left = 0;
+      crop.right = mVideoTrack.mWidth;
+
+      yuv420p_buffer = GetColorConverterBuffer(mVideoTrack.mWidth,
+                                               mVideoTrack.mHeight);
+      if (mColorConverter.convertDecoderOutputToI420(
+            bufferInfo.mBuffer->data(), mVideoTrack.mWidth, mVideoTrack.mHeight,
+            crop, yuv420p_buffer) != OK) {
+        mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex);
+        NS_WARNING("Unable to convert color format");
+        return false;
+      }
+
+      stride = mVideoTrack.mWidth;
+      slice_height = mVideoTrack.mHeight;
+    }
+
+    size_t yuv420p_y_size = stride * slice_height;
+    size_t yuv420p_u_size = ((stride + 1) / 2) * ((slice_height + 1) / 2);
+    uint8_t* yuv420p_y = yuv420p_buffer;
+    uint8_t* yuv420p_u = yuv420p_y + yuv420p_y_size;
+    uint8_t* yuv420p_v = yuv420p_u + yuv420p_u_size;
+
+    // This is the approximate byte position in the stream.
+    int64_t pos = mDecoder->GetResource()->Tell();
+
+    VideoData::YCbCrBuffer b;
+    b.mPlanes[0].mData = yuv420p_y;
+    b.mPlanes[0].mWidth = mVideoTrack.mWidth;
+    b.mPlanes[0].mHeight = mVideoTrack.mHeight;
+    b.mPlanes[0].mStride = stride;
+    b.mPlanes[0].mOffset = 0;
+    b.mPlanes[0].mSkip = 0;
+
+    b.mPlanes[1].mData = yuv420p_u;
+    b.mPlanes[1].mWidth = (mVideoTrack.mWidth + 1) / 2;
+    b.mPlanes[1].mHeight = (mVideoTrack.mHeight + 1) / 2;
+    b.mPlanes[1].mStride = (stride + 1) / 2;
+    b.mPlanes[1].mOffset = 0;
+    b.mPlanes[1].mSkip = 0;
+
+    b.mPlanes[2].mData = yuv420p_v;
+    b.mPlanes[2].mWidth =(mVideoTrack.mWidth + 1) / 2;
+    b.mPlanes[2].mHeight = (mVideoTrack.mHeight + 1) / 2;
+    b.mPlanes[2].mStride = (stride + 1) / 2;
+    b.mPlanes[2].mOffset = 0;
+    b.mPlanes[2].mSkip = 0;
+
+    VideoData *v = VideoData::Create(
+      mInfo.mVideo,
+      mDecoder->GetImageContainer(),
+      pos,
+      bufferInfo.mTimeUs,
+      1, // We don't know the duration.
+      b,
+      bufferInfo.mFlags & MediaCodec::BUFFER_FLAG_SYNCFRAME,
+      -1,
+      mVideoTrack.mRelativePictureRect);
+
+    if (v) {
+      result = true;
+      VideoQueue().Push(v);
+    } else {
+      NS_WARNING("Unable to create VideoData");
+    }
+  }
+
+  if ((bufferInfo.mFlags & MediaCodec::BUFFER_FLAG_EOS) ||
+      (status == ERROR_END_OF_STREAM)) {
+    VideoQueue().Finish();
+  }
+  mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex);
+
+  return result;
+}
+
+nsresult
 MediaCodecReader::Seek(int64_t aTime,
                        int64_t aStartTime,
                        int64_t aEndTime,
                        int64_t aCurrentTime)
 {
   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
-  VideoFrameContainer* videoframe = mDecoder->GetVideoFrameContainer();
-  if (videoframe != nullptr) {
-    mozilla::layers::ImageContainer *image = videoframe->GetImageContainer();
-    if (image != nullptr) {
-      image->ClearAllImagesExceptFront();
-    }
-  }
-
+  mVideoTrack.mSeekTimeUs = aTime;
+  mAudioTrack.mSeekTimeUs = aTime;
+  mVideoTrack.mInputEndOfStream = false;
+  mVideoTrack.mOutputEndOfStream = false;
   mAudioTrack.mInputEndOfStream = false;
-  mVideoTrack.mInputEndOfStream = false;
-
-  mAudioTrack.mSeekTimeUs = aTime;
-  mVideoTrack.mSeekTimeUs = aTime;
-
+  mAudioTrack.mOutputEndOfStream = false;
   mAudioTrack.mFlushed = false;
   mVideoTrack.mFlushed = false;
 
-  // Regulate the seek time to the closest sync point of video data.
-  if (HasVideo() && mVideoTrack.mSource != nullptr) {
-    MediaBuffer *source_buffer = nullptr;
+  if (CheckVideoResources()) {
+    VideoFrameContainer* videoframe = mDecoder->GetVideoFrameContainer();
+    if (videoframe) {
+      layers::ImageContainer* image = videoframe->GetImageContainer();
+      if (image) {
+        image->ClearAllImagesExceptFront();
+      }
+    }
+
+    MediaBuffer* source_buffer = nullptr;
     MediaSource::ReadOptions options;
+    int64_t timestamp = sInvalidTimestampUs;
     options.setSeekTo(aTime, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
-    if (mVideoTrack.mSource->read(&source_buffer, &options) != OK || source_buffer == nullptr) {
+    if (mVideoTrack.mSource->read(&source_buffer, &options) != OK ||
+        source_buffer == nullptr) {
       return NS_ERROR_FAILURE;
     }
     sp<MetaData> format = source_buffer->meta_data();
     if (format != nullptr) {
-      int64_t timestamp = sInvalidTimestampUs;
-      if (format->findInt64(kKeyTime, &timestamp) && IsValidTimestampUs(timestamp)) {
+      if (format->findInt64(kKeyTime, &timestamp) &&
+          IsValidTimestampUs(timestamp)) {
+        mVideoTrack.mSeekTimeUs = timestamp;
         mAudioTrack.mSeekTimeUs = timestamp;
-        mVideoTrack.mSeekTimeUs = timestamp;
       }
       format = nullptr;
     }
     source_buffer->release();
+
+    MOZ_ASSERT(mVideoTrack.mTaskQueue->IsEmpty());
+    DispatchVideoTask(mVideoTrack.mSeekTimeUs);
+
+    if (CheckAudioResources()) {
+      MOZ_ASSERT(mAudioTrack.mTaskQueue->IsEmpty());
+      DispatchAudioTask();
+    }
+  } else if (CheckAudioResources()) {// Audio only
+    MOZ_ASSERT(mAudioTrack.mTaskQueue->IsEmpty());
+    DispatchAudioTask();
   }
-
   return NS_OK;
 }
 
 bool
 MediaCodecReader::IsMediaSeekable()
 {
   // Check the MediaExtract flag if the source is seekable.
-  return (mExtractor != nullptr) && (mExtractor->flags() & MediaExtractor::CAN_SEEK);
+  return (mExtractor != nullptr) &&
+         (mExtractor->flags() & MediaExtractor::CAN_SEEK);
 }
 
-android::sp<android::MediaSource>
+sp<MediaSource>
 MediaCodecReader::GetAudioOffloadTrack()
 {
   return mAudioOffloadTrack.mSource;
 }
 
 bool
 MediaCodecReader::ReallocateResources()
 {
   if (CreateLooper() &&
       CreateExtractor() &&
       CreateMediaSources() &&
-      CreateMediaCodecs()) {
+      CreateMediaCodecs() &&
+      CreateTaskQueues()) {
     return true;
   }
 
   ReleaseResources();
   return false;
 }
 
 void
 MediaCodecReader::ReleaseCriticalResources()
 {
   ResetDecode();
   // Before freeing a video codec, all video buffers needed to be released
   // even from graphics pipeline.
   VideoFrameContainer* videoframe = mDecoder->GetVideoFrameContainer();
-  if (videoframe != nullptr) {
+  if (videoframe) {
     videoframe->ClearCurrentFrame();
   }
 
   DestroyMediaCodecs();
 
   ClearColorConverterBuffer();
 }
 
 void
 MediaCodecReader::ReleaseResources()
 {
   ReleaseCriticalResources();
   DestroyMediaSources();
   DestroyExtractor();
   DestroyLooper();
+  ShutdownTaskQueues();
 }
 
 bool
 MediaCodecReader::CreateLooper()
 {
   if (mLooper != nullptr) {
     return true;
   }
@@ -657,17 +807,17 @@ MediaCodecReader::CreateMediaSources()
 
   const ssize_t invalidTrackIndex = -1;
   ssize_t audioTrackIndex = invalidTrackIndex;
   ssize_t videoTrackIndex = invalidTrackIndex;
 
   for (size_t i = 0; i < mExtractor->countTracks(); ++i) {
     sp<MetaData> trackFormat = mExtractor->getTrackMetaData(i);
 
-    const char *mime;
+    const char* mime;
     if (!trackFormat->findCString(kKeyMIMEType, &mime)) {
       continue;
     }
 
     if (audioTrackIndex == invalidTrackIndex &&
         !strncasecmp(mime, "audio/", 6)) {
       audioTrackIndex = i;
     } else if (videoTrackIndex == invalidTrackIndex &&
@@ -694,49 +844,81 @@ MediaCodecReader::CreateMediaSources()
   if (videoTrackIndex != invalidTrackIndex && mVideoTrack.mSource == nullptr) {
     sp<MediaSource> videoSource = mExtractor->getTrack(videoTrackIndex);
     if (videoSource != nullptr && videoSource->start() == OK) {
       mVideoTrack.mSource = videoSource;
     }
   }
 
   return
-      (audioTrackIndex == invalidTrackIndex || mAudioTrack.mSource != nullptr) &&
-      (videoTrackIndex == invalidTrackIndex || mVideoTrack.mSource != nullptr);
+    (audioTrackIndex == invalidTrackIndex || mAudioTrack.mSource != nullptr) &&
+    (videoTrackIndex == invalidTrackIndex || mVideoTrack.mSource != nullptr);
 }
 
 void
 MediaCodecReader::DestroyMediaSources()
 {
   mAudioTrack.mSource = nullptr;
   mVideoTrack.mSource = nullptr;
   mAudioOffloadTrack.mSource = nullptr;
 }
 
+void
+MediaCodecReader::ShutdownTaskQueues()
+{
+  if(mAudioTrack.mTaskQueue) {
+    mAudioTrack.mTaskQueue->Shutdown();
+    mAudioTrack.mTaskQueue = nullptr;
+  }
+  if(mVideoTrack.mTaskQueue) {
+    mVideoTrack.mTaskQueue->Shutdown();
+    mVideoTrack.mTaskQueue = nullptr;
+  }
+}
+
+bool
+MediaCodecReader::CreateTaskQueues()
+{
+  if (mAudioTrack.mSource != nullptr && mAudioTrack.mCodec != nullptr &&
+      !mAudioTrack.mTaskQueue) {
+    mAudioTrack.mTaskQueue = new MediaTaskQueue(
+      SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaCodecReader Audio"), 1));
+    NS_ENSURE_TRUE(mAudioTrack.mTaskQueue, false);
+  }
+ if (mVideoTrack.mSource != nullptr && mVideoTrack.mCodec != nullptr &&
+     !mVideoTrack.mTaskQueue) {
+    mVideoTrack.mTaskQueue = new MediaTaskQueue(
+      SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaCodecReader Video"), 1));
+    NS_ENSURE_TRUE(mVideoTrack.mTaskQueue, false);
+  }
+
+  return true;
+}
+
 bool
 MediaCodecReader::CreateMediaCodecs()
 {
   if (CreateMediaCodec(mLooper, mAudioTrack, false, nullptr) &&
       CreateMediaCodec(mLooper, mVideoTrack, true, mVideoListener)) {
     return true;
   }
 
   return false;
 }
 
 bool
-MediaCodecReader::CreateMediaCodec(sp<ALooper> &aLooper,
-                                   Track &aTrack,
+MediaCodecReader::CreateMediaCodec(sp<ALooper>& aLooper,
+                                   Track& aTrack,
                                    bool aAsync,
                                    wp<MediaCodecProxy::CodecResourceListener> aListener)
 {
   if (aTrack.mSource != nullptr && aTrack.mCodec == nullptr) {
     sp<MetaData> sourceFormat = aTrack.mSource->getFormat();
 
-    const char *mime;
+    const char* mime;
     if (sourceFormat->findCString(kKeyMIMEType, &mime)) {
       aTrack.mCodec = MediaCodecProxy::CreateByType(aLooper, mime, false, aAsync, aListener);
     }
 
     if (aTrack.mCodec == nullptr) {
       NS_WARNING("Couldn't create MediaCodecProxy");
       return false;
     }
@@ -757,17 +939,17 @@ MediaCodecReader::CreateMediaCodec(sp<AL
       }
     }
   }
 
   return true;
 }
 
 bool
-MediaCodecReader::ConfigureMediaCodec(Track &aTrack)
+MediaCodecReader::ConfigureMediaCodec(Track& aTrack)
 {
 
   if (aTrack.mSource != nullptr && aTrack.mCodec != nullptr) {
     if (!aTrack.mCodec->allocated()) {
       return false;
     }
 
     sp<MetaData> sourceFormat = aTrack.mSource->getFormat();
@@ -803,17 +985,17 @@ MediaCodecReader::ConfigureMediaCodec(Tr
 void
 MediaCodecReader::DestroyMediaCodecs()
 {
   DestroyMediaCodecs(mAudioTrack);
   DestroyMediaCodecs(mVideoTrack);
 }
 
 void
-MediaCodecReader::DestroyMediaCodecs(Track &aTrack)
+MediaCodecReader::DestroyMediaCodecs(Track& aTrack)
 {
   aTrack.mCodec = nullptr;
 }
 
 bool
 MediaCodecReader::UpdateDuration()
 {
   // read audio duration
@@ -847,17 +1029,18 @@ MediaCodecReader::UpdateDuration()
 bool
 MediaCodecReader::UpdateAudioInfo()
 {
   if (mAudioTrack.mSource == nullptr && mAudioTrack.mCodec == nullptr) {
     // No needs to update AudioInfo if no audio streams.
     return true;
   }
 
-  if (mAudioTrack.mSource == nullptr || mAudioTrack.mCodec == nullptr || !mAudioTrack.mCodec->allocated()) {
+  if (mAudioTrack.mSource == nullptr || mAudioTrack.mCodec == nullptr ||
+      !mAudioTrack.mCodec->allocated()) {
     // Something wrong.
     MOZ_ASSERT(mAudioTrack.mSource != nullptr, "mAudioTrack.mSource should not be nullptr");
     MOZ_ASSERT(mAudioTrack.mCodec != nullptr, "mAudioTrack.mCodec should not be nullptr");
     MOZ_ASSERT(mAudioTrack.mCodec->allocated(), "mAudioTrack.mCodec->allocated() should not be false");
     return false;
   }
 
   // read audio metadata from MediaSource
@@ -868,17 +1051,18 @@ MediaCodecReader::UpdateAudioInfo()
 
   // ensure audio metadata from MediaCodec has been parsed
   if (!EnsureCodecFormatParsed(mAudioTrack)){
     return false;
   }
 
   // read audio metadata from MediaCodec
   sp<AMessage> audioCodecFormat;
-  if (mAudioTrack.mCodec->getOutputFormat(&audioCodecFormat) != OK || audioCodecFormat == nullptr) {
+  if (mAudioTrack.mCodec->getOutputFormat(&audioCodecFormat) != OK ||
+      audioCodecFormat == nullptr) {
     return false;
   }
 
   AString codec_mime;
   int32_t codec_channel_count = 0;
   int32_t codec_sample_rate = 0;
   if (!audioCodecFormat->findString("mime", &codec_mime) ||
       !audioCodecFormat->findInt32("channel-count", &codec_channel_count) ||
@@ -897,17 +1081,18 @@ MediaCodecReader::UpdateAudioInfo()
 bool
 MediaCodecReader::UpdateVideoInfo()
 {
   if (mVideoTrack.mSource == nullptr && mVideoTrack.mCodec == nullptr) {
     // No needs to update VideoInfo if no video streams.
     return true;
   }
 
-  if (mVideoTrack.mSource == nullptr || mVideoTrack.mCodec == nullptr || !mVideoTrack.mCodec->allocated()) {
+  if (mVideoTrack.mSource == nullptr || mVideoTrack.mCodec == nullptr ||
+      !mVideoTrack.mCodec->allocated()) {
     // Something wrong.
     MOZ_ASSERT(mVideoTrack.mSource != nullptr, "mVideoTrack.mSource should not be nullptr");
     MOZ_ASSERT(mVideoTrack.mCodec != nullptr, "mVideoTrack.mCodec should not be nullptr");
     MOZ_ASSERT(mVideoTrack.mCodec->allocated(), "mVideoTrack.mCodec->allocated() should not be false");
     return false;
   }
 
   // read video metadata from MediaSource
@@ -929,17 +1114,18 @@ MediaCodecReader::UpdateVideoInfo()
 
   // ensure video metadata from MediaCodec has been parsed
   if (!EnsureCodecFormatParsed(mVideoTrack)){
     return false;
   }
 
   // read video metadata from MediaCodec
   sp<AMessage> videoCodecFormat;
-  if (mVideoTrack.mCodec->getOutputFormat(&videoCodecFormat) != OK || videoCodecFormat == nullptr) {
+  if (mVideoTrack.mCodec->getOutputFormat(&videoCodecFormat) != OK ||
+      videoCodecFormat == nullptr) {
     return false;
   }
   AString codec_mime;
   int32_t codec_width = 0;
   int32_t codec_height = 0;
   int32_t codec_stride = 0;
   int32_t codec_slice_height = 0;
   int32_t codec_color_format = 0;
@@ -948,17 +1134,18 @@ MediaCodecReader::UpdateVideoInfo()
   int32_t codec_crop_right = 0;
   int32_t codec_crop_bottom = 0;
   if (!videoCodecFormat->findString("mime", &codec_mime) ||
       !videoCodecFormat->findInt32("width", &codec_width) ||
       !videoCodecFormat->findInt32("height", &codec_height) ||
       !videoCodecFormat->findInt32("stride", &codec_stride) ||
       !videoCodecFormat->findInt32("slice-height", &codec_slice_height) ||
       !videoCodecFormat->findInt32("color-format", &codec_color_format) ||
-      !videoCodecFormat->findRect("crop", &codec_crop_left, &codec_crop_top, &codec_crop_right, &codec_crop_bottom)) {
+      !videoCodecFormat->findRect("crop", &codec_crop_left, &codec_crop_top,
+                                  &codec_crop_right, &codec_crop_bottom)) {
     return false;
   }
 
   mVideoTrack.mWidth = codec_width;
   mVideoTrack.mHeight = codec_height;
   mVideoTrack.mStride = codec_stride;
   mVideoTrack.mSliceHeight = codec_slice_height;
   mVideoTrack.mColorFormat = codec_color_format;
@@ -975,89 +1162,96 @@ MediaCodecReader::UpdateVideoInfo()
 
   // Relative picture size
   gfx::IntRect relative_picture_rect = gfx::ToIntRect(picture_rect);
   if (mVideoTrack.mWidth != mVideoTrack.mFrameSize.width ||
       mVideoTrack.mHeight != mVideoTrack.mFrameSize.height) {
     // Frame size is different from what the container reports. This is legal,
     // and we will preserve the ratio of the crop rectangle as it
     // was reported relative to the picture size reported by the container.
-    relative_picture_rect.x = (picture_rect.x * mVideoTrack.mWidth) / mVideoTrack.mFrameSize.width;
-    relative_picture_rect.y = (picture_rect.y * mVideoTrack.mHeight) / mVideoTrack.mFrameSize.height;
-    relative_picture_rect.width = (picture_rect.width * mVideoTrack.mWidth) / mVideoTrack.mFrameSize.width;
-    relative_picture_rect.height = (picture_rect.height * mVideoTrack.mHeight) / mVideoTrack.mFrameSize.height;
+    relative_picture_rect.x = (picture_rect.x * mVideoTrack.mWidth) /
+                              mVideoTrack.mFrameSize.width;
+    relative_picture_rect.y = (picture_rect.y * mVideoTrack.mHeight) /
+                              mVideoTrack.mFrameSize.height;
+    relative_picture_rect.width = (picture_rect.width * mVideoTrack.mWidth) /
+                                  mVideoTrack.mFrameSize.width;
+    relative_picture_rect.height = (picture_rect.height * mVideoTrack.mHeight) /
+                                   mVideoTrack.mFrameSize.height;
   }
 
   // Update VideoInfo
   mInfo.mVideo.mHasVideo = true;
   mVideoTrack.mPictureRect = picture_rect;
   mInfo.mVideo.mDisplay = display_size;
   mVideoTrack.mRelativePictureRect = relative_picture_rect;
 
   return true;
 }
 
 status_t
-MediaCodecReader::FlushCodecData(Track &aTrack)
+MediaCodecReader::FlushCodecData(Track& aTrack)
 {
-  if (aTrack.mSource == nullptr || aTrack.mCodec == nullptr || !aTrack.mCodec->allocated()) {
+  if (aTrack.mSource == nullptr || aTrack.mCodec == nullptr ||
+      !aTrack.mCodec->allocated()) {
     return UNKNOWN_ERROR;
   }
 
   status_t status = aTrack.mCodec->flush();
   aTrack.mFlushed = (status == OK);
   if (aTrack.mFlushed) {
     aTrack.mInputIndex = sInvalidInputIndex;
   }
 
   return status;
 }
 
 // Keep filling data if there are available buffers.
 // FIXME: change to non-blocking read
 status_t
-MediaCodecReader::FillCodecInputData(Track &aTrack)
+MediaCodecReader::FillCodecInputData(Track& aTrack)
 {
-  if (aTrack.mSource == nullptr || aTrack.mCodec == nullptr || !aTrack.mCodec->allocated()) {
+  if (aTrack.mSource == nullptr || aTrack.mCodec == nullptr ||
+      !aTrack.mCodec->allocated()) {
     return UNKNOWN_ERROR;
   }
 
   if (aTrack.mInputEndOfStream) {
     return ERROR_END_OF_STREAM;
   }
 
   if (IsValidTimestampUs(aTrack.mSeekTimeUs) && !aTrack.mFlushed) {
     FlushCodecData(aTrack);
   }
 
   size_t index = 0;
-  while (aTrack.mInputIndex.isValid() || aTrack.mCodec->dequeueInputBuffer(&index) == OK) {
+  while (aTrack.mInputIndex.isValid() ||
+         aTrack.mCodec->dequeueInputBuffer(&index) == OK) {
     if (!aTrack.mInputIndex.isValid()) {
       aTrack.mInputIndex = index;
     }
     MOZ_ASSERT(aTrack.mInputIndex.isValid(), "aElement.mInputIndex should be valid");
 
-    MediaBuffer *source_buffer = nullptr;
+    MediaBuffer* source_buffer = nullptr;
     status_t status = OK;
     if (IsValidTimestampUs(aTrack.mSeekTimeUs)) {
       MediaSource::ReadOptions options;
       options.setSeekTo(aTrack.mSeekTimeUs);
       status = aTrack.mSource->read(&source_buffer, &options);
     } else {
       status = aTrack.mSource->read(&source_buffer);
     }
 
     // read() fails
     if (status == INFO_FORMAT_CHANGED) {
       return INFO_FORMAT_CHANGED;
     } else if (status == ERROR_END_OF_STREAM) {
       aTrack.mInputEndOfStream = true;
-      status = aTrack.mCodec->queueInputBuffer(aTrack.mInputIndex.value(),
-                                               0, 0, 0,
-                                               MediaCodec::BUFFER_FLAG_EOS);
+      aTrack.mCodec->queueInputBuffer(aTrack.mInputIndex.value(),
+                                      0, 0, 0,
+                                      MediaCodec::BUFFER_FLAG_EOS);
       return ERROR_END_OF_STREAM;
     } else if (status == -ETIMEDOUT) {
       return OK; // try it later
     } else if (status != OK) {
       return status;
     } else if (source_buffer == nullptr) {
       return UNKNOWN_ERROR;
     }
@@ -1075,62 +1269,58 @@ MediaCodecReader::FillCodecInputData(Tra
         aTrack.mInputCopier->Copy(source_buffer, input_buffer)) {
       int64_t timestamp = sInvalidTimestampUs;
       sp<MetaData> codec_format = source_buffer->meta_data();
       if (codec_format != nullptr) {
         codec_format->findInt64(kKeyTime, &timestamp);
       }
 
       status = aTrack.mCodec->queueInputBuffer(
-          aTrack.mInputIndex.value(), input_buffer->offset(), input_buffer->size(), timestamp, 0);
+        aTrack.mInputIndex.value(), input_buffer->offset(),
+        input_buffer->size(), timestamp, 0);
       if (status == OK) {
         aTrack.mInputIndex = sInvalidInputIndex;
       }
     }
     source_buffer->release();
 
     if (status != OK) {
       return status;
     }
   }
 
   return OK;
 }
 
 status_t
-MediaCodecReader::GetCodecOutputData(Track &aTrack,
-                                     CodecBufferInfo &aBuffer,
+MediaCodecReader::GetCodecOutputData(Track& aTrack,
+                                     CodecBufferInfo& aBuffer,
                                      int64_t aThreshold,
-                                     const TimeStamp &aTimeout)
+                                     const TimeStamp& aTimeout)
 {
   // Read next frame.
   CodecBufferInfo info;
-
-  // Try to fill more input buffers and then get one output buffer.
-  // FIXME: use callback from MediaCodec
   status_t status = OK;
+  while (status == OK || status == INFO_OUTPUT_BUFFERS_CHANGED ||
+         status == -EAGAIN) {
 
-  while (status == OK || status == INFO_OUTPUT_BUFFERS_CHANGED ||
-         status == -EAGAIN || status == ERROR_END_OF_STREAM) {
-    // Try to fill more input buffers and then get one output buffer.
-    // FIXME: use callback from MediaCodec
-    status = FillCodecInputData(aTrack);
     int64_t duration = (int64_t)(aTimeout - TimeStamp::Now()).ToMicroseconds();
     if (!IsValidDurationUs(duration)) {
       return -EAGAIN;
     }
 
-    if (status == OK || status == ERROR_END_OF_STREAM) {
-      status = aTrack.mCodec->dequeueOutputBuffer(
-          &info.mIndex, &info.mOffset, &info.mSize, &info.mTimeUs, &info.mFlags, duration);
-      if (info.mFlags & MediaCodec::BUFFER_FLAG_EOS) {
-        aBuffer = info;
-        aBuffer.mBuffer = aTrack.mOutputBuffers[info.mIndex];
-        return ERROR_END_OF_STREAM;
-      }
+    status = aTrack.mCodec->dequeueOutputBuffer(&info.mIndex, &info.mOffset,
+      &info.mSize, &info.mTimeUs, &info.mFlags, duration);
+    // Check EOS first.
+    if (status == ERROR_END_OF_STREAM ||
+        info.mFlags & MediaCodec::BUFFER_FLAG_EOS) {
+      aBuffer = info;
+      aBuffer.mBuffer = aTrack.mOutputBuffers[info.mIndex];
+      aTrack.mOutputEndOfStream = true;
+      return ERROR_END_OF_STREAM;
     }
 
     if (status == OK) {
       if (!IsValidTimestampUs(aThreshold) || info.mTimeUs >= aThreshold) {
         // Get a valid output buffer.
         break;
       } else {
         aTrack.mCodec->releaseOutputBuffer(info.mIndex);
@@ -1163,48 +1353,50 @@ MediaCodecReader::GetCodecOutputData(Tra
 
   aBuffer = info;
   aBuffer.mBuffer = aTrack.mOutputBuffers[info.mIndex];
 
   return OK;
 }
 
 bool
-MediaCodecReader::EnsureCodecFormatParsed(Track &aTrack)
+MediaCodecReader::EnsureCodecFormatParsed(Track& aTrack)
 {
-  if (aTrack.mSource == nullptr || aTrack.mCodec == nullptr || !aTrack.mCodec->allocated()) {
+  if (aTrack.mSource == nullptr || aTrack.mCodec == nullptr ||
+      !aTrack.mCodec->allocated()) {
     return false;
   }
 
   sp<AMessage> format;
   if (aTrack.mCodec->getOutputFormat(&format) == OK) {
     return true;
   }
 
   status_t status = OK;
   size_t index = 0;
   size_t offset = 0;
   size_t size = 0;
   int64_t timeUs = 0LL;
   uint32_t flags = 0;
-  while ((status = aTrack.mCodec->dequeueOutputBuffer(&index, &offset, &size, &timeUs, &flags)) != INFO_FORMAT_CHANGED) {
+  while ((status = aTrack.mCodec->dequeueOutputBuffer(&index, &offset, &size,
+                     &timeUs, &flags)) != INFO_FORMAT_CHANGED) {
     if (status == OK) {
       aTrack.mCodec->releaseOutputBuffer(index);
     }
     status = FillCodecInputData(aTrack);
     if (status == INFO_FORMAT_CHANGED) {
       break;
     } else if (status != OK) {
       return false;
     }
   }
   return aTrack.mCodec->getOutputFormat(&format) == OK;
 }
 
-uint8_t *
+uint8_t*
 MediaCodecReader::GetColorConverterBuffer(int32_t aWidth, int32_t aHeight)
 {
   // Allocate a temporary YUV420Planer buffer.
   size_t yuv420p_y_size = aWidth * aHeight;
   size_t yuv420p_u_size = ((aWidth + 1) / 2) * ((aHeight + 1) / 2);
   size_t yuv420p_v_size = yuv420p_u_size;
   size_t yuv420p_size = yuv420p_y_size + yuv420p_u_size + yuv420p_v_size;
   if (mColorConverterBufferSize != yuv420p_size) {
@@ -1219,17 +1411,17 @@ void
 MediaCodecReader::ClearColorConverterBuffer()
 {
   mColorConverterBuffer = nullptr;
   mColorConverterBufferSize = 0;
 }
 
 // Called on MediaCodecReader::mLooper thread.
 void
-MediaCodecReader::onMessageReceived(const sp<AMessage> &aMessage)
+MediaCodecReader::onMessageReceived(const sp<AMessage>& aMessage)
 {
   switch (aMessage->what()) {
 
     case kNotifyCodecReserved:
     {
       // Our decode may have acquired the hardware resource that it needs
       // to start. Notify the state machine to resume loading metadata.
       mDecoder->NotifyWaitingForResourcesStatusChanged();
--- a/content/media/omx/MediaCodecReader.h
+++ b/content/media/omx/MediaCodecReader.h
@@ -24,16 +24,18 @@ struct AMessage;
 class MOZ_EXPORT MediaExtractor;
 class MOZ_EXPORT MediaBuffer;
 struct MOZ_EXPORT MediaSource;
 struct MediaCodec;
 } // namespace android
 
 namespace mozilla {
 
+class MediaTaskQueue;
+
 class MediaCodecReader : public MediaOmxCommonReader
 {
 public:
   MediaCodecReader(AbstractMediaDecoder* aDecoder);
   virtual ~MediaCodecReader();
 
   // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
   // on failure.
@@ -48,27 +50,25 @@ public:
   // Release media resources they should be released in dormant state
   virtual void ReleaseMediaResources();
 
   // Destroys the decoding state. The reader cannot be made usable again.
   // This is different from ReleaseMediaResources() as Shutdown() is
   // irreversible, whereas ReleaseMediaResources() is reversible.
   virtual void Shutdown();
 
-  // Decodes an unspecified amount of audio data, enqueuing the audio data
-  // in mAudioQueue. Returns true when there's more audio to decode,
-  // false if the audio is finished, end of file has been reached,
-  // or an un-recoverable read error has occured.
-  virtual bool DecodeAudioData();
+  // Flush the MediaTaskQueue, flush MediaCodec and raise the mDiscontinuity.
+  virtual nsresult ResetDecode() MOZ_OVERRIDE;
 
-  // Reads and decodes one video frame. Packets with a timestamp less
-  // than aTimeThreshold will be decoded (unless they're not keyframes
-  // and aKeyframeSkip is true), but will not be added to the queue.
-  virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
-                                int64_t aTimeThreshold);
+  // Disptach a DecodeVideoFrameTask to decode video data.
+  virtual void RequestVideoData(bool aSkipToNextKeyframe,
+                                int64_t aTimeThreshold) MOZ_OVERRIDE;
+
+  // Disptach a DecodeAduioDataTask to decode video data.
+  virtual void RequestAudioData() MOZ_OVERRIDE;
 
   virtual bool HasAudio();
   virtual bool HasVideo();
 
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
@@ -85,17 +85,18 @@ public:
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual android::sp<android::MediaSource> GetAudioOffloadTrack();
 
 protected:
   struct TrackInputCopier
   {
-    virtual bool Copy(android::MediaBuffer* aSourceBuffer, android::sp<android::ABuffer> aCodecBuffer);
+    virtual bool Copy(android::MediaBuffer* aSourceBuffer,
+                      android::sp<android::ABuffer> aCodecBuffer);
   };
 
   struct Track
   {
     Track();
 
     // pipeline parameters
     android::sp<android::MediaSource> mSource;
@@ -106,75 +107,82 @@ protected:
     // pipeline copier
     nsAutoPtr<TrackInputCopier> mInputCopier;
 
     // media parameters
     int64_t mDurationUs;
 
     // playback parameters
     CheckedUint32 mInputIndex;
+    // mDiscontinuity, mFlushed, mInputEndOfStream, mInputEndOfStream,
+    // mSeekTimeUs don't be protected by a lock because the
+    // mTaskQueue->Flush() will flush all tasks.
     bool mInputEndOfStream;
+    bool mOutputEndOfStream;
     int64_t mSeekTimeUs;
     bool mFlushed; // meaningless when mSeekTimeUs is invalid.
+    bool mDiscontinuity;
+    nsRefPtr<MediaTaskQueue> mTaskQueue;
   };
 
   // Receive a message from MessageHandler.
   // Called on MediaCodecReader::mLooper thread.
-  void onMessageReceived(const android::sp<android::AMessage> &aMessage);
+  void onMessageReceived(const android::sp<android::AMessage>& aMessage);
 
   // Receive a notify from ResourceListener.
   // Called on Binder thread.
   virtual void codecReserved(Track& aTrack);
   virtual void codecCanceled(Track& aTrack);
 
 private:
   // An intermediary class that can be managed by android::sp<T>.
   // Redirect onMessageReceived() to MediaCodecReader.
   class MessageHandler : public android::AHandler
   {
   public:
-    MessageHandler(MediaCodecReader *aReader);
+    MessageHandler(MediaCodecReader* aReader);
     ~MessageHandler();
 
-    virtual void onMessageReceived(const android::sp<android::AMessage> &aMessage);
+    virtual void onMessageReceived(const android::sp<android::AMessage>& aMessage);
 
   private:
     // Forbidden
     MessageHandler() MOZ_DELETE;
-    MessageHandler(const MessageHandler &rhs) MOZ_DELETE;
-    const MessageHandler &operator=(const MessageHandler &rhs) MOZ_DELETE;
+    MessageHandler(const MessageHandler& rhs) MOZ_DELETE;
+    const MessageHandler& operator=(const MessageHandler& rhs) MOZ_DELETE;
 
     MediaCodecReader *mReader;
   };
   friend class MessageHandler;
 
   // An intermediary class that can be managed by android::sp<T>.
   // Redirect codecReserved() and codecCanceled() to MediaCodecReader.
   class VideoResourceListener : public android::MediaCodecProxy::CodecResourceListener
   {
   public:
-    VideoResourceListener(MediaCodecReader *aReader);
+    VideoResourceListener(MediaCodecReader* aReader);
     ~VideoResourceListener();
 
     virtual void codecReserved();
     virtual void codecCanceled();
 
   private:
     // Forbidden
     VideoResourceListener() MOZ_DELETE;
-    VideoResourceListener(const VideoResourceListener &rhs) MOZ_DELETE;
-    const VideoResourceListener &operator=(const VideoResourceListener &rhs) MOZ_DELETE;
+    VideoResourceListener(const VideoResourceListener& rhs) MOZ_DELETE;
+    const VideoResourceListener& operator=(const VideoResourceListener& rhs) MOZ_DELETE;
 
-    MediaCodecReader *mReader;
+    MediaCodecReader* mReader;
   };
   friend class VideoResourceListener;
 
   class VorbisInputCopier : public TrackInputCopier
   {
-    virtual bool Copy(android::MediaBuffer* aSourceBuffer, android::sp<android::ABuffer> aCodecBuffer);
+    virtual bool Copy(android::MediaBuffer* aSourceBuffer,
+                      android::sp<android::ABuffer> aCodecBuffer);
   };
 
   struct AudioTrack : public Track
   {
     AudioTrack();
   };
 
   struct VideoTrack : public Track
@@ -201,53 +209,71 @@ private:
     size_t mOffset;
     size_t mSize;
     int64_t mTimeUs;
     uint32_t mFlags;
   };
 
   // Forbidden
   MediaCodecReader() MOZ_DELETE;
-  const MediaCodecReader &operator=(const MediaCodecReader &rhs) MOZ_DELETE;
+  const MediaCodecReader& operator=(const MediaCodecReader& rhs) MOZ_DELETE;
 
   bool ReallocateResources();
   void ReleaseCriticalResources();
   void ReleaseResources();
 
   bool CreateLooper();
   void DestroyLooper();
 
   bool CreateExtractor();
   void DestroyExtractor();
 
   bool CreateMediaSources();
   void DestroyMediaSources();
 
   bool CreateMediaCodecs();
-  static bool CreateMediaCodec(android::sp<android::ALooper> &aLooper,
-                               Track &aTrack,
+  static bool CreateMediaCodec(android::sp<android::ALooper>& aLooper,
+                               Track& aTrack,
                                bool aAsync,
                                android::wp<android::MediaCodecProxy::CodecResourceListener> aListener);
-  static bool ConfigureMediaCodec(Track &aTrack);
+  static bool ConfigureMediaCodec(Track& aTrack);
   void DestroyMediaCodecs();
-  static void DestroyMediaCodecs(Track &aTrack);
+  static void DestroyMediaCodecs(Track& aTrack);
+
+  bool CreateTaskQueues();
+  void ShutdownTaskQueues();
+  bool DecodeVideoFrameTask(int64_t aTimeThreshold);
+  bool DecodeVideoFrameSync(int64_t aTimeThreshold);
+  bool DecodeAudioDataTask();
+  bool DecodeAudioDataSync();
+  void DispatchVideoTask(int64_t aTimeThreshold);
+  void DispatchAudioTask();
+  inline bool CheckVideoResources() {
+    return (HasVideo() && mVideoTrack.mSource != nullptr &&
+            mVideoTrack.mTaskQueue);
+  }
+
+  inline bool CheckAudioResources() {
+    return (HasAudio() && mAudioTrack.mSource != nullptr &&
+            mAudioTrack.mTaskQueue);
+  }
 
   bool UpdateDuration();
   bool UpdateAudioInfo();
   bool UpdateVideoInfo();
 
-  static android::status_t FlushCodecData(Track &aTrack);
-  static android::status_t FillCodecInputData(Track &aTrack);
-  static android::status_t GetCodecOutputData(Track &aTrack,
-                                              CodecBufferInfo &aBuffer,
+  static android::status_t FlushCodecData(Track& aTrack);
+  static android::status_t FillCodecInputData(Track& aTrack);
+  static android::status_t GetCodecOutputData(Track& aTrack,
+                                              CodecBufferInfo& aBuffer,
                                               int64_t aThreshold,
-                                              const TimeStamp &aTimeout);
-  static bool EnsureCodecFormatParsed(Track &aTrack);
+                                              const TimeStamp& aTimeout);
+  static bool EnsureCodecFormatParsed(Track& aTrack);
 
-  uint8_t *GetColorConverterBuffer(int32_t aWidth, int32_t aHeight);
+  uint8_t* GetColorConverterBuffer(int32_t aWidth, int32_t aHeight);
   void ClearColorConverterBuffer();
 
   android::sp<MessageHandler> mHandler;
   android::sp<VideoResourceListener> mVideoListener;
 
   android::sp<android::ALooper> mLooper;
   android::sp<android::MediaExtractor> mExtractor;
 
--- a/content/xul/document/src/XULDocument.cpp
+++ b/content/xul/document/src/XULDocument.cpp
@@ -28,32 +28,28 @@
 
 #include "nsError.h"
 #include "nsIBoxObject.h"
 #include "nsIChromeRegistry.h"
 #include "nsView.h"
 #include "nsViewManager.h"
 #include "nsIContentViewer.h"
 #include "nsIDOMXULElement.h"
-#include "nsIRDFNode.h"
-#include "nsIRDFRemoteDataSource.h"
-#include "nsIRDFService.h"
 #include "nsIStreamListener.h"
 #include "nsITimer.h"
 #include "nsDocShell.h"
 #include "nsGkAtoms.h"
 #include "nsXMLContentSink.h"
 #include "nsXULContentSink.h"
 #include "nsXULContentUtils.h"
 #include "nsIXULOverlayProvider.h"
+#include "nsIStringEnumerator.h"
 #include "nsNetUtil.h"
 #include "nsParserCIID.h"
 #include "nsPIBoxObject.h"
-#include "nsRDFCID.h"
-#include "nsILocalStore.h"
 #include "nsXPIDLString.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsXULCommandDispatcher.h"
 #include "nsXULElement.h"
 #include "prlog.h"
 #include "rdf.h"
 #include "nsIFrame.h"
@@ -132,21 +128,16 @@ const uint32_t kMaxAttributeLength = 409
 
 //----------------------------------------------------------------------
 //
 // Statics
 //
 
 int32_t XULDocument::gRefCnt = 0;
 
-nsIRDFService* XULDocument::gRDFService;
-nsIRDFResource* XULDocument::kNC_persist;
-nsIRDFResource* XULDocument::kNC_attribute;
-nsIRDFResource* XULDocument::kNC_value;
-
 PRLogModuleInfo* XULDocument::gXULLog;
 
 //----------------------------------------------------------------------
 
 struct BroadcasterMapEntry : public PLDHashEntryHdr {
     Element*         mBroadcaster; // [WEAK]
     nsSmallVoidArray mListeners;   // [OWNING] of BroadcastListener objects
 };
@@ -225,36 +216,21 @@ XULDocument::~XULDocument()
     // look for persisted data:
     mPersistenceIds.Clear();
 
     // Destroy our broadcaster map.
     if (mBroadcasterMap) {
         PL_DHashTableDestroy(mBroadcasterMap);
     }
 
-    if (mLocalStore) {
-        nsCOMPtr<nsIRDFRemoteDataSource> remote =
-            do_QueryInterface(mLocalStore);
-        if (remote)
-            remote->Flush();
-    }
-
     delete mTemplateBuilderTable;
 
     Preferences::UnregisterCallback(XULDocument::DirectionChanged,
                                     "intl.uidirection.", this);
 
-    if (--gRefCnt == 0) {
-        NS_IF_RELEASE(gRDFService);
-
-        NS_IF_RELEASE(kNC_persist);
-        NS_IF_RELEASE(kNC_attribute);
-        NS_IF_RELEASE(kNC_value);
-    }
-
     if (mOffThreadCompileStringBuf) {
       js_free(mOffThreadCompileStringBuf);
     }
 }
 
 } // namespace dom
 } // namespace mozilla
 
@@ -344,16 +320,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
     delete tmp->mTemplateBuilderTable;
     tmp->mTemplateBuilderTable = nullptr;
 
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore)
     //XXX We should probably unlink all the objects we traverse.
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(XULDocument, XMLDocument)
 NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument)
 
 
 // QueryInterface implementation for XULDocument
@@ -1326,126 +1303,60 @@ XULDocument::Persist(const nsAString& aI
         }
 
         tag = do_GetAtom(aAttr);
         NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
 
         nameSpaceID = kNameSpaceID_None;
     }
 
-    rv = Persist(element, nameSpaceID, tag);
-    if (NS_FAILED(rv)) return rv;
-
-    return NS_OK;
+    return Persist(element, nameSpaceID, tag);
 }
 
 nsresult
 XULDocument::Persist(nsIContent* aElement, int32_t aNameSpaceID,
                      nsIAtom* aAttribute)
 {
     // For non-chrome documents, persistance is simply broken
     if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
         return NS_ERROR_NOT_AVAILABLE;
 
-    // First make sure we _have_ a local store to stuff the persisted
-    // information into. (We might not have one if profile information
-    // hasn't been loaded yet...)
-    if (!mLocalStore)
-        return NS_OK;
-
-    nsresult rv;
-
-    nsCOMPtr<nsIRDFResource> element;
-    rv = nsXULContentUtils::GetElementResource(aElement, getter_AddRefs(element));
-    if (NS_FAILED(rv)) return rv;
-
-    // No ID, so nothing to persist.
-    if (! element)
-        return NS_OK;
-
-    // Ick. Construct a property from the attribute. Punt on
-    // namespaces for now.
-    // Don't bother with unreasonable attributes. We clamp long values,
-    // but truncating attribute names turns it into a different attribute
-    // so there's no point in persisting anything at all
-    nsAtomCString attrstr(aAttribute);
-    if (attrstr.Length() > kMaxAttrNameLength) {
-        NS_WARNING("Can't persist, Attribute name too long");
-        return NS_ERROR_ILLEGAL_VALUE;
+    if (!mLocalStore) {
+        mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+        if (NS_WARN_IF(!mLocalStore)) {
+            return NS_ERROR_NOT_INITIALIZED;
+        }
     }
 
-    nsCOMPtr<nsIRDFResource> attr;
-    rv = gRDFService->GetResource(attrstr,
-                                  getter_AddRefs(attr));
-    if (NS_FAILED(rv)) return rv;
-
-    // Turn the value into a literal
+    nsAutoString id;
+
+    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+    nsAtomString attrstr(aAttribute);
+
     nsAutoString valuestr;
     aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
 
-    // prevent over-long attributes that choke the parser (bug 319846)
-    // (can't simply Truncate without testing, it's implemented
-    // using SetLength and will grow a short string)
-    if (valuestr.Length() > kMaxAttributeLength) {
-        NS_WARNING("Truncating persisted attribute value");
-        valuestr.Truncate(kMaxAttributeLength);
-    }
-
-    // See if there was an old value...
-    nsCOMPtr<nsIRDFNode> oldvalue;
-    rv = mLocalStore->GetTarget(element, attr, true, getter_AddRefs(oldvalue));
-    if (NS_FAILED(rv)) return rv;
-
-    if (oldvalue && valuestr.IsEmpty()) {
-        // ...there was an oldvalue, and they've removed it. XXXThis
-        // handling isn't quite right...
-        rv = mLocalStore->Unassert(element, attr, oldvalue);
+    nsAutoCString utf8uri;
+    nsresult rv = mDocumentURI->GetSpec(utf8uri);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
     }
-    else {
-        // Now either 'change' or 'assert' based on whether there was
-        // an old value.
-        nsCOMPtr<nsIRDFLiteral> newvalue;
-        rv = gRDFService->GetLiteral(valuestr.get(), getter_AddRefs(newvalue));
-        if (NS_FAILED(rv)) return rv;
-
-        if (oldvalue) {
-            if (oldvalue != newvalue)
-                rv = mLocalStore->Change(element, attr, oldvalue, newvalue);
-            else
-                rv = NS_OK;
-        }
-        else {
-            rv = mLocalStore->Assert(element, attr, newvalue, true);
-        }
+    NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+    bool hasAttr;
+    rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
     }
 
-    if (NS_FAILED(rv)) return rv;
-
-    // Add it to the persisted set for this document (if it's not
-    // there already).
-    {
-        nsAutoCString docurl;
-        rv = mDocumentURI->GetSpec(docurl);
-        if (NS_FAILED(rv)) return rv;
-
-        nsCOMPtr<nsIRDFResource> doc;
-        rv = gRDFService->GetResource(docurl, getter_AddRefs(doc));
-        if (NS_FAILED(rv)) return rv;
-
-        bool hasAssertion;
-        rv = mLocalStore->HasAssertion(doc, kNC_persist, element, true, &hasAssertion);
-        if (NS_FAILED(rv)) return rv;
-
-        if (! hasAssertion) {
-            rv = mLocalStore->Assert(doc, kNC_persist, element, true);
-            if (NS_FAILED(rv)) return rv;
-        }
+    if (hasAttr && valuestr.IsEmpty()) {
+        return mLocalStore->RemoveValue(uri, id, attrstr);
+    } else {
+        return mLocalStore->SetValue(uri, id, attrstr, valuestr);
     }
-
-    return NS_OK;
 }
 
 
 nsresult
 XULDocument::GetViewportSize(int32_t* aWidth,
                              int32_t* aHeight)
 {
     *aWidth = *aHeight = 0;
@@ -1988,35 +1899,17 @@ XULDocument::Init()
 {
     nsresult rv = XMLDocument::Init();
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Create our command dispatcher and hook it up.
     mCommandDispatcher = new nsXULCommandDispatcher(this);
     NS_ENSURE_TRUE(mCommandDispatcher, NS_ERROR_OUT_OF_MEMORY);
 
-    // this _could_ fail; e.g., if we've tried to grab the local store
-    // before profiles have initialized. If so, no big deal; nothing
-    // will persist.
-    mLocalStore = do_GetService(NS_LOCALSTORE_CONTRACTID);
-
     if (gRefCnt++ == 0) {
-        // Keep the RDF service cached in a member variable to make using
-        // it a bit less painful
-        rv = CallGetService("@mozilla.org/rdf/rdf-service;1", &gRDFService);
-        NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF Service");
-        if (NS_FAILED(rv)) return rv;
-
-        gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "persist"),
-                                 &kNC_persist);
-        gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "attribute"),
-                                 &kNC_attribute);
-        gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "value"),
-                                 &kNC_value);
-
         // ensure that the XUL prototype cache is instantiated successfully,
         // so that we can use nsXULPrototypeCache::GetInstance() without
         // null-checks in the rest of the class.
         nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
         if (!cache) {
           NS_ERROR("Could not instantiate nsXULPrototypeCache");
           return NS_ERROR_FAILURE;
         }
@@ -2170,154 +2063,133 @@ nsresult
 XULDocument::ApplyPersistentAttributes()
 {
     // For non-chrome documents, persistance is simply broken
     if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
         return NS_ERROR_NOT_AVAILABLE;
 
     // Add all of the 'persisted' attributes into the content
     // model.
-    if (!mLocalStore)
-        return NS_OK;
+    if (!mLocalStore) {
+        mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+        if (NS_WARN_IF(!mLocalStore)) {
+            return NS_ERROR_NOT_INITIALIZED;
+        }
+    }
 
     mApplyingPersistedAttrs = true;
     ApplyPersistentAttributesInternal();
     mApplyingPersistedAttrs = false;
 
     // After we've applied persistence once, we should only reapply
     // it to nodes created by overlays
     mRestrictPersistence = true;
     mPersistenceIds.Clear();
 
     return NS_OK;
 }
 
 
-nsresult 
+nsresult
 XULDocument::ApplyPersistentAttributesInternal()
 {
     nsCOMArray<nsIContent> elements;
 
-    nsAutoCString docurl;
-    mDocumentURI->GetSpec(docurl);
-
-    nsCOMPtr<nsIRDFResource> doc;
-    gRDFService->GetResource(docurl, getter_AddRefs(doc));
-
-    nsCOMPtr<nsISimpleEnumerator> persisted;
-    mLocalStore->GetTargets(doc, kNC_persist, true, getter_AddRefs(persisted));
+    nsAutoCString utf8uri;
+    nsresult rv = mDocumentURI->GetSpec(utf8uri);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+    NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+    // Get a list of element IDs for which persisted values are available
+    nsCOMPtr<nsIStringEnumerator> ids;
+    rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
 
     while (1) {
         bool hasmore = false;
-        persisted->HasMoreElements(&hasmore);
-        if (! hasmore)
+        ids->HasMore(&hasmore);
+        if (!hasmore) {
             break;
-
-        nsCOMPtr<nsISupports> isupports;
-        persisted->GetNext(getter_AddRefs(isupports));
-
-        nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(isupports);
-        if (! resource) {
-            NS_WARNING("expected element to be a resource");
+        }
+
+        nsAutoString id;
+        ids->GetNext(id);
+
+        if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
             continue;
         }
 
-        const char *uri;
-        resource->GetValueConst(&uri);
-        if (! uri)
-            continue;
-
-        nsAutoString id;
-        nsXULContentUtils::MakeElementID(this, nsDependentCString(uri), id);
-
-        if (id.IsEmpty())
-            continue;
-
-        if (mRestrictPersistence && !mPersistenceIds.Contains(id))
-            continue;
-
         // This will clear the array if there are no elements.
         GetElementsForID(id, elements);
-
-        if (!elements.Count())
+        if (!elements.Count()) {
             continue;
-
-        ApplyPersistentAttributesToElements(resource, elements);
+        }
+
+        rv = ApplyPersistentAttributesToElements(id, elements);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+        }
     }
 
     return NS_OK;
 }
 
 
 nsresult
-XULDocument::ApplyPersistentAttributesToElements(nsIRDFResource* aResource,
+XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID,
                                                  nsCOMArray<nsIContent>& aElements)
 {
-    nsresult rv;
-
-    nsCOMPtr<nsISimpleEnumerator> attrs;
-    rv = mLocalStore->ArcLabelsOut(aResource, getter_AddRefs(attrs));
-    if (NS_FAILED(rv)) return rv;
+    nsAutoCString utf8uri;
+    nsresult rv = mDocumentURI->GetSpec(utf8uri);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+    NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+    // Get a list of attributes for which persisted values are available
+    nsCOMPtr<nsIStringEnumerator> attrs;
+    rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
 
     while (1) {
-        bool hasmore;
-        rv = attrs->HasMoreElements(&hasmore);
-        if (NS_FAILED(rv)) return rv;
-
-        if (! hasmore)
+        bool hasmore = PR_FALSE;
+        attrs->HasMore(&hasmore);
+        if (!hasmore) {
             break;
-
-        nsCOMPtr<nsISupports> isupports;
-        rv = attrs->GetNext(getter_AddRefs(isupports));
-        if (NS_FAILED(rv)) return rv;
-
-        nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
-        if (! property) {
-            NS_WARNING("expected a resource");
-            continue;
         }
 
-        const char* attrname;
-        rv = property->GetValueConst(&attrname);
-        if (NS_FAILED(rv)) return rv;
-
-        nsCOMPtr<nsIAtom> attr = do_GetAtom(attrname);
-        if (! attr)
+        nsAutoString attrstr;
+        attrs->GetNext(attrstr);
+
+        nsAutoString value;
+        rv = mLocalStore->GetValue(uri, aID, attrstr, value);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+        }
+
+        nsCOMPtr<nsIAtom> attr = do_GetAtom(attrstr);
+        if (NS_WARN_IF(!attr)) {
             return NS_ERROR_OUT_OF_MEMORY;
-
-        // XXX could hang namespace off here, as well...
-
-        nsCOMPtr<nsIRDFNode> node;
-        rv = mLocalStore->GetTarget(aResource, property, true,
-                                    getter_AddRefs(node));
-        if (NS_FAILED(rv)) return rv;
-
-        nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(node);
-        if (! literal) {
-            NS_WARNING("expected a literal");
-            continue;
         }
 
-        const char16_t* value;
-        rv = literal->GetValueConst(&value);
-        if (NS_FAILED(rv)) return rv;
-
-        nsDependentString wrapper(value);
-
         uint32_t cnt = aElements.Count();
 
         for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
             nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i);
-            if (!element)
-                continue;
-
-            rv = element->SetAttr(/* XXX */ kNameSpaceID_None,
-                                  attr,
-                                  wrapper,
-                                  true);
+            if (!element) {
+                 continue;
+            }
+
+            rv = element->SetAttr(kNameSpaceID_None, attr, value, PR_TRUE);
         }
     }
 
     return NS_OK;
 }
 
 void
 XULDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber)
--- a/content/xul/document/src/XULDocument.h
+++ b/content/xul/document/src/XULDocument.h
@@ -17,16 +17,17 @@
 #include "nsIDOMXULCommandDispatcher.h"
 #include "nsIDOMXULDocument.h"
 #include "nsCOMArray.h"
 #include "nsIURI.h"
 #include "nsIXULDocument.h"
 #include "nsScriptLoader.h"
 #include "nsIStreamListener.h"
 #include "nsICSSLoaderObserver.h"
+#include "nsIXULStore.h"
 
 #include "mozilla/Attributes.h"
 
 #include "js/TracingAPI.h"
 #include "js/TypeDecls.h"
 
 class nsIRDFResource;
 class nsIRDFService;
@@ -254,17 +255,17 @@ protected:
                            nsIParser** aResult);
 
     nsresult 
     LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic, bool* aShouldReturn,
                         bool* aFailureFromContent);
 
     nsresult ApplyPersistentAttributes();
     nsresult ApplyPersistentAttributesInternal();
-    nsresult ApplyPersistentAttributesToElements(nsIRDFResource* aResource,
+    nsresult ApplyPersistentAttributesToElements(const nsAString &aID,
                                                  nsCOMArray<nsIContent>& aElements);
 
     nsresult
     AddElementToDocumentPre(Element* aElement);
 
     nsresult
     AddElementToDocumentPost(Element* aElement);
 
@@ -309,20 +310,20 @@ protected:
     // NOTE, THIS IS STILL IN PROGRESS, TALK TO PINK OR SCC BEFORE
     // CHANGING
 
     XULDocument*             mNextSrcLoadWaiter;  // [OWNER] but not COMPtr
 
     // Tracks elements with a 'ref' attribute, or an 'id' attribute where
     // the element's namespace has no registered ID attribute name.
     nsTHashtable<nsRefMapEntry> mRefMap;
-    nsCOMPtr<nsIRDFDataSource> mLocalStore;
-    bool                       mApplyingPersistedAttrs;
-    bool                       mIsWritingFastLoad;
-    bool                       mDocumentLoaded;
+    nsCOMPtr<nsIXULStore>       mLocalStore;
+    bool                        mApplyingPersistedAttrs;
+    bool                        mIsWritingFastLoad;
+    bool                        mDocumentLoaded;
     /**
      * Since ResumeWalk is interruptible, it's possible that last
      * stylesheet finishes loading while the PD walk is still in
      * progress (waiting for an overlay to finish loading).
      * mStillWalking prevents DoneLoading (and StartLayout) from being
      * called in this situation.
      */
     bool                       mStillWalking;
--- a/content/xul/templates/src/nsXULContentUtils.cpp
+++ b/content/xul/templates/src/nsXULContentUtils.cpp
@@ -181,46 +181,16 @@ nsXULContentUtils::FindChildByTag(nsICon
         }
     }
 
     *aResult = nullptr;
     return NS_RDF_NO_VALUE; // not found
 }
 
 
-nsresult
-nsXULContentUtils::GetElementResource(nsIContent* aElement, nsIRDFResource** aResult)
-{
-    // Perform a reverse mapping from an element in the content model
-    // to an RDF resource.
-    nsresult rv;
-
-    char16_t buf[128];
-    nsFixedString id(buf, ArrayLength(buf), 0);
-
-    // Whoa.  Why the "id" attribute?  What if it's not even a XUL
-    // element?  This is totally bogus!
-    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
-    if (id.IsEmpty())
-        return NS_ERROR_FAILURE;
-
-    // Since the element will store its ID attribute as a document-relative value,
-    // we may need to qualify it first...
-    nsCOMPtr<nsIDocument> doc = aElement->GetDocument();
-    NS_ASSERTION(doc, "element is not in any document");
-    if (! doc)
-        return NS_ERROR_FAILURE;
-
-    rv = nsXULContentUtils::MakeElementResource(doc, id, aResult);
-    if (NS_FAILED(rv)) return rv;
-
-    return NS_OK;
-}
-
-
 /*
 	Note: this routine is similar, yet distinctly different from, nsBookmarksService::GetTextForNode
 */
 
 nsresult
 nsXULContentUtils::GetTextForNode(nsIRDFNode* aNode, nsAString& aResult)
 {
     if (! aNode) {
@@ -283,88 +253,16 @@ nsXULContentUtils::GetTextForNode(nsIRDF
         return NS_OK;
     }
 
     NS_ERROR("not a resource or a literal");
     return NS_ERROR_UNEXPECTED;
 }
 
 nsresult
-nsXULContentUtils::MakeElementURI(nsIDocument* aDocument,
-                                  const nsAString& aElementID,
-                                  nsCString& aURI)
-{
-    // Convert an element's ID to a URI that can be used to refer to
-    // the element in the XUL graph.
-
-    nsIURI *docURI = aDocument->GetDocumentURI();
-    NS_ENSURE_TRUE(docURI, NS_ERROR_UNEXPECTED);
-
-    nsRefPtr<nsIURI> docURIClone;
-    nsresult rv = docURI->Clone(getter_AddRefs(docURIClone));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = docURIClone->SetRef(NS_ConvertUTF16toUTF8(aElementID));
-    if (NS_SUCCEEDED(rv)) {
-        return docURIClone->GetSpec(aURI);
-    }
-
-    // docURIClone is apparently immutable. Fine - we can append ref manually.
-    rv = docURI->GetSpec(aURI);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsAutoCString ref;
-    NS_EscapeURL(NS_ConvertUTF16toUTF8(aElementID), esc_FilePath | esc_AlwaysCopy, ref);
-
-    aURI.Append('#');
-    aURI.Append(ref);
-
-    return NS_OK;
-}
-
-
-nsresult
-nsXULContentUtils::MakeElementResource(nsIDocument* aDocument, const nsAString& aID, nsIRDFResource** aResult)
-{
-    nsresult rv;
-
-    char buf[256];
-    nsFixedCString uri(buf, sizeof(buf), 0);
-    rv = MakeElementURI(aDocument, aID, uri);
-    if (NS_FAILED(rv)) return rv;
-
-    rv = gRDF->GetResource(uri, aResult);
-    NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create resource");
-    if (NS_FAILED(rv)) return rv;
-
-    return NS_OK;
-}
-
-
-
-nsresult
-nsXULContentUtils::MakeElementID(nsIDocument* aDocument,
-                                 const nsACString& aURI,
-                                 nsAString& aElementID)
-{
-    // Convert a URI into an element ID that can be accessed from the
-    // DOM APIs.
-    nsCOMPtr<nsIURI> uri;
-    nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI,
-                            aDocument->GetDocumentCharacterSet().get());
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsAutoCString ref;
-    uri->GetRef(ref);
-    CopyUTF8toUTF16(ref, aElementID);
-
-    return NS_OK;
-}
-
-nsresult
 nsXULContentUtils::GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult)
 {
     // construct a fully-qualified URI from the namespace/tag pair.
     NS_PRECONDITION(aAttribute != nullptr, "null ptr");
     if (! aAttribute)
         return NS_ERROR_NULL_POINTER;
 
     return GetResource(aNameSpaceID, nsDependentAtomString(aAttribute),
--- a/content/xul/templates/src/nsXULContentUtils.h
+++ b/content/xul/templates/src/nsXULContentUtils.h
@@ -111,40 +111,18 @@ public:
                    nsIContent **aResult);
 
     static nsresult
     FindChildByResource(nsIContent* aElement,
                         nsIRDFResource* aResource,
                         nsIContent** aResult);
 
     static nsresult
-    GetElementResource(nsIContent* aElement, nsIRDFResource** aResult);
-
-    static nsresult
     GetTextForNode(nsIRDFNode* aNode, nsAString& aResult);
 
-    /**
-     * Construct a URI from the element ID given.  This uses aElement as the
-     * ref and aDocument's document URI as the base.  If aDocument's document
-     * URI does not support refs, this will throw NS_ERROR_NOT_AVAILABLE.
-     */
-    static nsresult
-    MakeElementURI(nsIDocument* aDocument, const nsAString& aElementID, nsCString& aURI);
-
-    static nsresult
-    MakeElementResource(nsIDocument* aDocument, const nsAString& aElementID, nsIRDFResource** aResult);
-
-    /**
-     * Extract the element ID from aURI.  Note that aURI must be an absolute
-     * URI string in UTF8; the element ID is the ref from the URI.  If the
-     * scheme does not support refs, then the ID will be empty.
-     */
-    static nsresult
-    MakeElementID(nsIDocument* aDocument, const nsACString& aURI, nsAString& aElementID);
-
     static nsresult
     GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult);
 
     static nsresult
     GetResource(int32_t aNameSpaceID, const nsAString& aAttribute, nsIRDFResource** aResult);
 
     static nsresult
     SetCommandUpdater(nsIDocument* aDocument, nsIContent* aElement);
--- a/content/xul/templates/src/nsXULTreeBuilder.cpp
+++ b/content/xul/templates/src/nsXULTreeBuilder.cpp
@@ -3,17 +3,16 @@
  * 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 "nscore.h"
 #include "nsError.h"
 #include "nsIContent.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "nsIDOMElement.h"
-#include "nsILocalStore.h"
 #include "nsIBoxObject.h"
 #include "nsITreeBoxObject.h"
 #include "nsITreeSelection.h"
 #include "nsITreeColumns.h"
 #include "nsITreeView.h"
 #include "nsTreeUtils.h"
 #include "nsIServiceManager.h"
 #include "nsReadableUtils.h"
@@ -26,16 +25,17 @@
 #include "nsXULTemplateBuilder.h"
 #include "nsIXULSortService.h"
 #include "nsTArray.h"
 #include "nsUnicharUtils.h"
 #include "nsNameSpaceManager.h"
 #include "nsDOMClassInfoID.h"
 #include "nsWhitespaceTokenizer.h"
 #include "nsTreeContentView.h"
+#include "nsIXULStore.h"
 
 // For security check
 #include "nsIDocument.h"
 
 /**
  * A XUL template builder that serves as an tree view, allowing
  * (pretty much) arbitrary RDF to be presented in an tree.
  */
@@ -134,23 +134,20 @@ protected:
 
     /**
      * Remove the matches for the rows in a subtree
      */
     nsresult
     RemoveMatchesFor(nsTreeRows::Subtree& subtree);
 
     /**
-     * Helper methods that determine if the specified container is open.
+     * Helper method that determines if the specified container is open.
      */
-    nsresult
-    IsContainerOpen(nsIXULTemplateResult *aResult, bool* aOpen);
-
-    nsresult
-    IsContainerOpen(nsIRDFResource* aResource, bool* aOpen);
+    bool
+    IsContainerOpen(nsIXULTemplateResult* aResource);
 
     /**
      * A sorting callback for NS_QuickSort().
      */
     static int
     Compare(const void* aLeft, const void* aRight, void* aClosure);
 
     /**
@@ -237,16 +234,21 @@ protected:
      * Sort hints (compare case, etc)
      */
     uint32_t mSortHints;
 
     /** 
      * The builder observers.
      */
     nsCOMArray<nsIXULTreeBuilderObserver> mObservers;
+
+    /*
+     * XUL store for holding open container state
+     */
+    nsCOMPtr<nsIXULStore> mLocalStore;
 };
 
 //----------------------------------------------------------------------
 
 nsresult
 NS_NewXULTreeBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult)
 {
     *aResult = nullptr;
@@ -273,16 +275,17 @@ NS_NewXULTreeBuilder(nsISupports* aOuter
 
 NS_IMPL_ADDREF_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder)
 NS_IMPL_RELEASE_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder)
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder,
                                    mBoxObject,
                                    mSelection,
                                    mPersistStateStore,
+                                   mLocalStore,
                                    mObservers)
 
 DOMCI_DATA(XULTreeBuilder, nsXULTreeBuilder)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXULTreeBuilder)
     NS_INTERFACE_MAP_ENTRY(nsIXULTreeBuilder)
     NS_INTERFACE_MAP_ENTRY(nsITreeView)
     NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULTreeBuilder)
@@ -523,18 +526,17 @@ nsXULTreeBuilder::IsContainerOpen(int32_
 {
     NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row");
     if (aIndex < 0 || aIndex >= mRows.Count())
         return NS_ERROR_INVALID_ARG;
 
     nsTreeRows::iterator iter = mRows[aIndex];
 
     if (iter->mContainerState == nsTreeRows::eContainerState_Unknown) {
-        bool isOpen;
-        IsContainerOpen(iter->mMatch->mResult, &isOpen);
+        bool isOpen = IsContainerOpen(iter->mMatch->mResult);
 
         iter->mContainerState = isOpen
             ? nsTreeRows::eContainerState_Open
             : nsTreeRows::eContainerState_Closed;
     }
 
     *aOpen = (iter->mContainerState == nsTreeRows::eContainerState_Open);
     return NS_OK;
@@ -752,52 +754,26 @@ nsXULTreeBuilder::SetTree(nsITreeBoxObje
 
     // If this is teardown time, then we're done.
     if (!mBoxObject) {
         Uninit(false);
         return NS_OK;
     }
     NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
 
-    // Is our root's principal trusted?
+    // Only use the XUL store if the root's principal is trusted.
     bool isTrusted = false;
     nsresult rv = IsSystemPrincipal(mRoot->NodePrincipal(), &isTrusted);
     if (NS_SUCCEEDED(rv) && isTrusted) {
-        // Get the datasource we intend to use to remember open state.
-        nsAutoString datasourceStr;
-        mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::statedatasource, datasourceStr);
-
-        // since we are trusted, use the user specified datasource
-        // if non specified, use localstore, which gives us
-        // persistence across sessions
-        if (! datasourceStr.IsEmpty()) {
-            gRDFService->GetDataSource(NS_ConvertUTF16toUTF8(datasourceStr).get(),
-                                       getter_AddRefs(mPersistStateStore));
-        }
-        else {
-            gRDFService->GetDataSource("rdf:local-store",
-                                       getter_AddRefs(mPersistStateStore));
+        mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+        if(NS_WARN_IF(!mLocalStore)){
+            return NS_ERROR_NOT_INITIALIZED;
         }
     }
 
-    // Either no specific datasource was specified, or we failed
-    // to get one because we are not trusted.
-    //
-    // XXX if it were possible to ``write an arbitrary datasource
-    // back'', then we could also allow an untrusted document to
-    // use a statedatasource from the same codebase.
-    if (! mPersistStateStore) {
-        mPersistStateStore =
-            do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource");
-    }
-
-    NS_ASSERTION(mPersistStateStore, "failed to get a persistent state store");
-    if (! mPersistStateStore)
-        return NS_ERROR_FAILURE;
-
     Rebuild();
 
     EnsureSortVariables();
     if (mSortVariable)
         SortSubtree(mRows.GetRoot());
 
     return NS_OK;
 }
@@ -825,44 +801,46 @@ nsXULTreeBuilder::ToggleOpenState(int32_
 
     uint32_t count = mObservers.Count();
     for (uint32_t i = 0; i < count; ++i) {
         nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
         if (observer)
             observer->OnToggleOpenState(aIndex);
     }
 
-    if (mPersistStateStore) {
+    if (mLocalStore && mRoot) {
         bool isOpen;
         IsContainerOpen(aIndex, &isOpen);
 
-        nsCOMPtr<nsIRDFResource> container;
-        GetResourceFor(aIndex, getter_AddRefs(container));
-        if (! container)
+        nsIDocument* doc = mRoot->GetDocument();
+        if (!doc) {
             return NS_ERROR_FAILURE;
+        }
 
-        bool hasProperty;
-        IsContainerOpen(container, &hasProperty);
+        nsIURI* docURI = doc->GetDocumentURI();
+        nsTreeRows::Row& row = *(mRows[aIndex]);
+        nsAutoString nodeid;
+        nsresult rv = row.mMatch->mResult->GetId(nodeid);
+        if (NS_FAILED(rv)) {
+            return rv;
+        }
+
+        nsAutoCString utf8uri;
+        rv = docURI->GetSpec(utf8uri);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+        }
+        NS_ConvertUTF8toUTF16 uri(utf8uri);
 
         if (isOpen) {
-            if (hasProperty) {
-                mPersistStateStore->Unassert(container,
-                                             nsXULContentUtils::NC_open,
-                                             nsXULContentUtils::true_);
-            }
-
+            mLocalStore->RemoveValue(uri, nodeid, NS_LITERAL_STRING("open"));
             CloseContainer(aIndex);
-        }
-        else {
-            if (! hasProperty) {
-                mPersistStateStore->Assert(container,
-                                           nsXULContentUtils::NC_open,
-                                           nsXULContentUtils::true_,
-                                           true);
-            }
+        } else {
+            mLocalStore->SetValue(uri, nodeid, NS_LITERAL_STRING("open"),
+                NS_LITERAL_STRING("true"));
 
             OpenContainer(aIndex, result);
         }
     }
 
     return NS_OK;
 }
 
@@ -1220,20 +1198,19 @@ nsXULTreeBuilder::ReplaceMatch(nsIXULTem
 
             if (result != mRootResult) {
                 // don't open containers if child processing isn't allowed
                 bool mayProcessChildren;
                 nsresult rv = result->GetMayProcessChildren(&mayProcessChildren);
                 if (NS_FAILED(rv) || ! mayProcessChildren) return NS_OK;
             }
 
-            bool open;
-            IsContainerOpen(result, &open);
-            if (open)
+            if (IsContainerOpen(result)) {
                 OpenContainer(iter.GetRowIndex(), result);
+            }
         }
     }
 
     return NS_OK;
 }
 
 nsresult
 nsXULTreeBuilder::SynchronizeResult(nsIXULTemplateResult* aResult)
@@ -1631,19 +1608,17 @@ nsXULTreeBuilder::OpenSubtreeForQuerySet
                     return rv;
                 }
 
                 // Remember that this match applied to this row
                 mRows.InsertRowAt(newmatch, aSubtree, count);
 
                 // If this is open, then remember it so we can recursively add
                 // *its* rows to the tree.
-                bool isOpen = false;
-                IsContainerOpen(nextresult, &isOpen);
-                if (isOpen) {
+                if (IsContainerOpen(nextresult)) {
                     if (open.AppendElement(count) == nullptr)
                         return NS_ERROR_OUT_OF_MEMORY;
                 }
 
                 ++count;
             }
 
             if (mFlags & eLoggingEnabled)
@@ -1717,46 +1692,52 @@ nsXULTreeBuilder::RemoveMatchesFor(nsTre
 
         if ((row.mContainerState == nsTreeRows::eContainerState_Open) && row.mSubtree)
             RemoveMatchesFor(*(row.mSubtree));
     }
 
     return NS_OK;
 }
 
-nsresult
-nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult, bool* aOpen)
+
+bool
+nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult)
 {
-    // items are never open if recursion is disabled
-    if ((mFlags & eDontRecurse) && aResult != mRootResult) {
-        *aOpen = false;
-        return NS_OK;
-    }
+  // items are never open if recursion is disabled
+  if ((mFlags & eDontRecurse) && aResult != mRootResult) {
+    return false;
+  }
 
-    nsCOMPtr<nsIRDFResource> id;
-    nsresult rv = GetResultResource(aResult, getter_AddRefs(id));
-    if (NS_FAILED(rv))
-        return rv;
+  if (!mLocalStore) {
+    return false;
+  }
+
+  nsIDocument* doc = mRoot->GetDocument();
+  if (!doc) {
+    return false;
+  }
 
-    return IsContainerOpen(id, aOpen);
-}
+  nsIURI* docURI = doc->GetDocumentURI();
+
+  nsAutoString nodeid;
+  nsresult rv = aResult->GetId(nodeid);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
 
-nsresult
-nsXULTreeBuilder::IsContainerOpen(nsIRDFResource* aResource, bool* aOpen)
-{
-    if (mPersistStateStore)
-        mPersistStateStore->HasAssertion(aResource,
-                                         nsXULContentUtils::NC_open,
-                                         nsXULContentUtils::true_,
-                                         true,
-                                         aOpen);
-    else
-        *aOpen = false;
+  nsAutoCString utf8uri;
+  rv = docURI->GetSpec(utf8uri);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+      return false;
+  }
+  NS_ConvertUTF8toUTF16 uri(utf8uri);
 
-    return NS_OK;
+  nsAutoString val;
+  mLocalStore->GetValue(uri, nodeid, NS_LITERAL_STRING("open"), val);
+  return val.EqualsLiteral("true");
 }
 
 int
 nsXULTreeBuilder::Compare(const void* aLeft, const void* aRight, void* aClosure)
 {
     nsXULTreeBuilder* self = static_cast<nsXULTreeBuilder*>(aClosure);
 
     nsTreeRows::Row* left = static_cast<nsTreeRows::Row*>
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -10747,22 +10747,16 @@ class CGDescriptor(CGThing):
                 if m.isStatic():
                     assert descriptor.interface.hasInterfaceObject()
                     cgThings.append(CGStaticGetter(descriptor, m))
                 elif descriptor.interface.hasInterfacePrototypeObject():
                     cgThings.append(CGSpecializedGetter(descriptor, m))
                     if props.isCrossOriginGetter:
                         crossOriginGetters.add(m.identifier.name)
                 if not m.readonly:
-                    for extAttr in ["PutForwards", "Replaceable"]:
-                        if m.getExtendedAttribute(extAttr):
-                            raise TypeError("Writable attributes should not "
-                                            "have %s specified.\n"
-                                            "%s" %
-                                            (extAttr, m.location))
                     if m.isStatic():
                         assert descriptor.interface.hasInterfaceObject()
                         cgThings.append(CGStaticSetter(descriptor, m))
                     elif descriptor.interface.hasInterfacePrototypeObject():
                         cgThings.append(CGSpecializedSetter(descriptor, m))
                         if props.isCrossOriginSetter:
                             crossOriginSetters.add(m.identifier.name)
                 elif m.getExtendedAttribute("PutForwards"):
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -952,20 +952,25 @@ class IDLInterface(IDLObjectWithScope):
         # We also don't support inheriting from unforgeable interfaces.
         if self.getExtendedAttribute("Unforgeable") and self.hasChildInterfaces():
             raise WebIDLError("%s is an unforgeable ancestor interface" %
                 self.identifier.name,
                 [self.location] +
                 list(i.location for i in
                      self.interfacesBasedOnSelf if i.parent == self))
 
-
         for member in self.members:
             member.validate()
 
+            if self.isCallback() and member.getExtendedAttribute("Replaceable"):
+                raise WebIDLError("[Replaceable] used on an attribute on "
+                                  "interface %s which is a callback interface" %
+                                  self.identifier.name,
+                                  [self.location, member.location])
+
             # Check that PutForwards refers to another attribute and that no
             # cycles exist in forwarded assignments.
             if member.isAttr():
                 iface = self
                 attr = member
                 putForwards = attr.getExtendedAttribute("PutForwards")
                 if putForwards and self.isCallback():
                     raise WebIDLError("[PutForwards] used on an attribute "
@@ -3231,16 +3236,25 @@ class IDLAttribute(IDLInterfaceMember):
             if self.getExtendedAttribute("Replaceable") is not None:
                 raise WebIDLError("[PutForwards] and [Replaceable] can't both "
                                   "appear on the same attribute",
                                   [attr.location, self.location])
             if not attr.hasValue():
                 raise WebIDLError("[PutForwards] takes an identifier",
                                   [attr.location, self.location])
         elif identifier == "Replaceable":
+            if not attr.noArguments():
+                raise WebIDLError("[Replaceable] must take no arguments",
+                                  [attr.location])
+            if not self.readonly:
+                raise WebIDLError("[Replaceable] is only allowed on readonly "
+                                  "attributes", [attr.location, self.location])
+            if self.isStatic():
+                raise WebIDLError("[Replaceable] is only allowed on non-static "
+                                  "attributes", [attr.location, self.location])
             if self.getExtendedAttribute("PutForwards") is not None:
                 raise WebIDLError("[PutForwards] and [Replaceable] can't both "
                                   "appear on the same attribute",
                                   [attr.location, self.location])
         elif identifier == "LenientFloat":
             if self.readonly:
                 raise WebIDLError("[LenientFloat] used on a readonly attribute",
                                   [attr.location, self.location])
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_replaceable.py
@@ -0,0 +1,58 @@
+# 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/.
+
+def should_throw(parser, harness, message, code):
+    parser = parser.reset();
+    threw = False
+    try:
+        parser.parse(code)
+        parser.finish()
+    except:
+        threw = True
+
+    harness.ok(threw, "Should have thrown: %s" % message)
+
+
+def WebIDLTest(parser, harness):
+    # The [Replaceable] extended attribute MUST take no arguments.
+    should_throw(parser, harness, "no arguments", """
+        interface I {
+          [Replaceable=X] readonly attribute long A;
+        };
+    """)
+
+    # An attribute with the [Replaceable] extended attribute MUST NOT also be
+    # declared with the [PutForwards] extended attribute.
+    should_throw(parser, harness, "PutForwards", """
+        interface I {
+          [PutForwards=B, Replaceable] readonly attribute J A;
+        };
+        interface J {
+          attribute long B;
+        };
+    """)
+
+    # The [Replaceable] extended attribute MUST NOT be used on an attribute
+    # that is not read only.
+    should_throw(parser, harness, "writable attribute", """
+        interface I {
+          [Replaceable] attribute long A;
+        };
+    """)
+
+    # The [Replaceable] extended attribute MUST NOT be used on a static
+    # attribute.
+    should_throw(parser, harness, "static attribute", """
+        interface I {
+          [Replaceable] static readonly attribute long A;
+        };
+    """)
+
+    # The [Replaceable] extended attribute MUST NOT be used on an attribute
+    # declared on a callback interface.
+    should_throw(parser, harness, "callback interface", """
+        callback interface I {
+          [Replaceable] readonly attribute long A;
+        };
+    """)
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -63,16 +63,18 @@ this.BrowserElementParentBuilder = {
   create: function create(frameLoader, hasRemoteFrame, isPendingFrame) {
     return new BrowserElementParent(frameLoader, hasRemoteFrame);
   }
 }
 
 function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
   debug("Creating new BrowserElementParent object for " + frameLoader);
   this._domRequestCounter = 0;
+  this._domRequestReady = false;
+  this._pendingAPICalls = [];
   this._pendingDOMRequests = {};
   this._pendingSetInputMethodActive = [];
   this._hasRemoteFrame = hasRemoteFrame;
   this._nextPaintListeners = [];
 
   this._frameLoader = frameLoader;
   this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
   let self = this;
@@ -89,17 +91,17 @@ function BrowserElementParent(frameLoade
       if (self._isAlive()) {
         return fn.apply(self, arguments);
       }
     };
   }
 
   let defineNoReturnMethod = function(name, fn) {
     XPCNativeWrapper.unwrap(self._frameElement)[name] = function method() {
-      if (!self._mm) {
+      if (!self._domRequestReady) {
         // Remote browser haven't been created, we just queue the API call.
         let args = Array.slice(arguments);
         args.unshift(self);
         self._pendingAPICalls.push(method.bind.apply(fn, args));
         return;
       }
       if (self._isAlive()) {
         fn.apply(self, arguments);
@@ -176,17 +178,16 @@ function BrowserElementParent(frameLoade
   // Insert ourself into the prompt service.
   BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
   if (!isPendingFrame) {
     this._setupMessageListener();
     this._registerAppManifest();
   } else {
     // if we are a pending frame, we setup message manager after
     // observing remote-browser-frame-shown
-    this._pendingAPICalls = [];
     Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
   }
 }
 
 BrowserElementParent.prototype = {
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
@@ -365,16 +366,23 @@ BrowserElementParent.prototype = {
     // Inform our child if our owner element's document is invisible.  Note
     // that we must do so here, rather than in the BrowserElementParent
     // constructor, because the BrowserElementChild may not be initialized when
     // we run our constructor.
     if (this._window.document.hidden) {
       this._ownerVisibilityChange();
     }
 
+    if (!this._domRequestReady) {
+      // At least, one message listener such as for hello is registered.
+      // So we can use sendAsyncMessage now.
+      this._domRequestReady = true;
+      this._runPendingAPICall();
+    }
+
     return {
       name: this._frameElement.getAttribute('name'),
       fullscreenAllowed:
         this._frameElement.hasAttribute('allowfullscreen') ||
         this._frameElement.hasAttribute('mozallowfullscreen')
     };
   },
 
@@ -526,17 +534,17 @@ BrowserElementParent.prototype = {
         return;
       }
       if (self._sendAsyncMsg(msgName, {id: id, args: args})) {
         self._pendingDOMRequests[id] = req;
       } else {
         Services.DOMRequest.fireErrorAsync(req, "fail");
       }
     };
-    if (this._mm) {
+    if (this._domRequestReady) {
       send();
     } else {
       // Child haven't been loaded.
       this._pendingAPICalls.push(send);
     }
     return req;
   },
 
@@ -792,17 +800,17 @@ BrowserElementParent.prototype = {
     if (typeof listener != 'function')
       throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
 
     let self = this;
     let run = function() {
       if (self._nextPaintListeners.push(listener) == 1)
         self._sendAsyncMsg('activate-next-paint-listener');
     };
-    if (!this._mm) {
+    if (!this._domRequestReady) {
       this._pendingAPICalls.push(run);
     } else {
       run();
     }
   },
 
   _removeNextPaintListener: function(listener) {
     if (typeof listener != 'function')
@@ -815,17 +823,17 @@ BrowserElementParent.prototype = {
           self._nextPaintListeners.splice(i, 1);
           break;
         }
       }
 
       if (self._nextPaintListeners.length == 0)
         self._sendAsyncMsg('deactivate-next-paint-listener');
     };
-    if (!this._mm) {
+    if (!this._domRequestReady) {
       this._pendingAPICalls.push(run);
     } else {
       run();
     }
   },
 
   _setInputMethodActive: function(isActive) {
     if (typeof isActive !== 'boolean') {
@@ -903,17 +911,16 @@ BrowserElementParent.prototype = {
         this._sendAsyncMsg('exit-fullscreen');
       }
       break;
     case 'remote-browser-frame-shown':
       if (this._frameLoader == subject) {
         if (!this._mm) {
           this._setupMessageListener();
           this._registerAppManifest();
-          this._runPendingAPICall();
         }
         Services.obs.removeObserver(this, 'remote-browser-frame-shown');
       }
     default:
       debug('Unknown topic: ' + topic);
       break;
     };
   },
--- a/dom/browser-element/mochitest/priority/test_BackgroundLRU.html
+++ b/dom/browser-element/mochitest/priority/test_BackgroundLRU.html
@@ -46,21 +46,26 @@ function runTest() {
     document.body.appendChild(iframe2);
 
     // At this point, we should have iframe1 in background already.
     // We wait until another one is set to background, too.
     // Once there are two in background, the first one (LRU order)
     // should have 'backgroundLRU' equals 1
     var p = expectPriorityWithBackgroundLRUSet(childID, '1');
     iframe2.setVisible(false);
-    document.body.removeChild(iframe2);
 
     return p;
 
-  }).then(SimpleTest.finish);
+  }).then(function() {
+    // Don't call removeChild immediately after calling setVisible.
+    // setVisible on remote browser is async method, so we should wait
+    // until it sends to the child process.
+    document.body.removeChild(iframe2);
+    SimpleTest.finish();
+  });
 
   document.body.appendChild(iframe1);
 }
 
 addEventListener('testready', runTest);
 
 </script>
 </body>
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2016,16 +2016,19 @@ EventStateManager::DoScrollZoom(nsIFrame
       // positive adjustment to decrease zoom, negative to increase
       int32_t change = (adjustment > 0) ? -1 : 1;
 
       if (Preferences::GetBool("browser.zoom.full") || content->OwnerDoc()->IsSyntheticDocument()) {
         ChangeFullZoom(change);
       } else {
         ChangeTextSize(change);
       }
+      nsContentUtils::DispatchChromeEvent(mDocument, static_cast<nsIDocument*>(mDocument),
+                                          NS_LITERAL_STRING("ZoomChangeUsingMouseWheel"),
+                                          true, true);
     }
 }
 
 static nsIFrame*
 GetParentFrameToScroll(nsIFrame* aFrame)
 {
   if (!aFrame)
     return nullptr;
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -60,16 +60,19 @@ inlineScriptBlocked = An attempt to exec
 # inline style refers to CSS code that is embedded into the HTML document.
 inlineStyleBlocked = An attempt to apply inline style sheets has been blocked
 # LOCALIZATION NOTE (scriptFromStringBlocked):
 # eval is a name and should not be localized.
 scriptFromStringBlocked = An attempt to call JavaScript from a string (by calling a function like eval) has been blocked
 # LOCALIZATION NOTE (hostNameMightBeKeyword):
 # %1$S is the hostname in question and %2$S is the keyword
 hostNameMightBeKeyword = Interpreting %1$S as a hostname, not a keyword. If you intended this to be a keyword, use '%2$S' (wrapped in single quotes).
+# LOCALIZATION NOTE (notSupportingDirective):
+# directive is not supported (e.g. 'reflected-xss')
+notSupportingDirective = Not supporting directive '%1$S'. Directive and values will be ignored.
 
 # CSP Errors:
 policyURINotAlone = policy-uri directive can only appear alone
 noParentRequest = The policy-uri cannot be fetched without a parent request and a CSP.
 # LOCALIZATION NOTE (policyURIParseError):
 # %1$S is the URI that could not be parsed
 policyURIParseError = could not parse URI in policy URI: %1$S
 # LOCALIZATION NOTE (nonMatchingHost):
--- a/editor/composer/moz.build
+++ b/editor/composer/moz.build
@@ -54,12 +54,9 @@ RESOURCE_FILES += [
     'res/text_caret_tilt_left.png',
     'res/text_caret_tilt_left@1.5x.png',
     'res/text_caret_tilt_left@2.25x.png',
     'res/text_caret_tilt_left@2x.png',
     'res/text_caret_tilt_right.png',
     'res/text_caret_tilt_right@1.5x.png',
     'res/text_caret_tilt_right@2.25x.png',
     'res/text_caret_tilt_right@2x.png',
-    'res/text_selection_handle.png',
-    'res/text_selection_handle@1.5.png',
-    'res/text_selection_handle@2.png',
 ]
index 167cb6526ce7b354b9a395df1c8b2d9e99918f30..b7159c24b8a6b2b91202f7a3e52aa44d65a14908
GIT binary patch
literal 1733
zc%17D@N?(olHy`uVBq!ia0vp^vOp})!3HFyqmQuwDajJoh?3y^w370~qErUQl>DSr
z1<%~X^wgl##FWaylc_cg49qH-ArU1JzCKpT`MG+DAT@dwxdjX$U}IlVkeHmETB4AY
znx2_wtMq>NekFy>6kDZmQ(pt$0_W6>OpmIf)Zi+=kmRcDWXlvKdpiZ23M-%ixv3?I
z3Kh9IdBs*0wn|`gt$=Khu)dN4SV>8?trEmh5xxNm&iO^D3Z{C-y2%EHh6-k8dWI&Z
zW@d&u3PuKoM*0RoWTtCqVr6P(Wn``Z1xi5Mic-?7f?V97b^&>|N*N_31y=g{<>lpi
z<;HsXMd|v6mX?+vGmMOMfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZ_=!pRr6smX
zN-!_v7Ql_oD~1LWFu?RH5)1SV^$hfp6#Dw&SDKp(S6y5Zl$wTLb#X{#L8^XGYH@yP
zQ8F;%(v(4(3#^=rQWHz^i$e1Ab6}wukda@KU!0L&px_*Arl8@Qn4Fmh63_(e@b$Iw
z%quQQ%u7!7bg@+eis)r#rdT<;I6E1dnOnL#x)~W7x*9t=xf)qGo0=G!TRJ;hnwi1$
zy5uL9=BDPA!1Sgd^g80y3rY+S-Kj;HWvMA{Mftf3U{70R;&zKUZnr@6rr>sq1x~&C
zK*#8#MKw$an0`P^c)|s8;7LC<518JIfC>AAaYHLGznFWvIEGZ*O8W8tzdbY0WCn@S
zY4<J*^Xn!tZCp4b<G>LItAs0&MaQR2Z2h`uLwb?Ug!61i&Mb-WnYNV2=giWCvv217
zbm$WAxan!w9pWu%`aEV!a!T4qw&I8g-l?;%t258EaX90&Cf{>YNb)OAMlrd@nJ3zc
zwO)pIRV>Y5nkjQ+miv~aUSC&RM6{@c85kI?Tv#!;b!xoLnu>J|u^V(Y9z4@ACreUN
zvhZhsK*qx;H?=hP7`DH^fBrqUd$0H5`T99~*q;1j;+&YRx%k_n*}Hsl=14bA;%ce+
z@c;e%@Au!w`Rt08-XMJP(f=h8r&gH0@8UBSzi+poh2x^&|Nr;<*PHvf-_Pb?jQqnq
z`>0RFqX~0oc9*MqiZ#tWb>!gEG#l?5%8s1jj}E13DlhL!w7S6H%T{D$WZb=BgS(Q3
z{%1x*?W!=Kq_D7dv>G#OgRhU?1YajdrYWlzsEXBjU;Xt>o4IzSU*}SX`=2;=tKUe{
zwwO_Hq)Uuhlwo$q7O5*iW=+%n{r^3^UuqhYa;Lb|o)+D|pFe)h&bsmQ_ubR$J+=x6
z$9eCMu`A+_UZy5mv%mf&>lHpVC(f7uPfxv@|G(~c%|s#Y|8;-A{5yQml;6SU3lr1o
zr4E0d|DOB#v*yMxpKsn}Z#??QUqQI#zd-WK_rKR(jIP^XT{vmaj+&2eUfy|C>+nom
z;a^)E<AW_C9sBKGonP|{7_!Wln!Y}6ci)>?+^G7YTH!nMl}GVMrg$uP@Z;OtYct!+
zUpE)k+wCoGli;r3TT$>oF80Ec{PJ`5Oht~C4|q>}d~vXQ^|Zv6X-W(JXz?A{^hhxE
z9n-XZ_i`Q;X&rdP{4inx<Ni4N%1<AEJbSD^t$ZHSH6w-lw(O781AZx+JmHAq;${t7
zadeRbkCyw<h*nkC%931>8I>$M>i5_E`uh9t>G$<}tNutv_13oi5||Pi=cs#R#RbmL
zM+V(H8zTfdS{InC$w+Sc^+4R@ox{Ew_9O2nKKdkYc~&$cV6(IC^QNR3OaCuut8ZU+
zWxdDEC-OIQRhER=aEokK63zX;fc1cU#+|=Mulgun4a(t5a5<G^Fl)u4$*nzm{?ETu
nG_y02fkC-*ujwj=1_ow^=V}H!N?SEbK^2RqtDnm{r-UW|2LrE=
index 8b2ec905aae4474fb2507831b0774ba1d6900bca..f350cd393d94a043c5756c40e4b73b93d8ca92fd
GIT binary patch
literal 2284
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
index dae08f27565737d34420b9962cc1031e1e5e6936..a3b4725cea1ec4e3a891ded2a5d7bd70d70b7f50
GIT binary patch
literal 2947
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
aHX1ZZ&SoR!_bAK1#hZNSl(VGB%>Mw1;m)=I
index f3151a37a7910a5a2c630ac447c1ce7df03f5d50..6599b66391d1d381a6d724d7ce846b15e1427cb2
GIT binary patch
literal 2692
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
index 9250b816288d13725a21da76d8c99f72d6f0c47a..1259d00f770c74afdef2f6d4b498d71ea77b33fb
GIT binary patch
literal 1655
zc%17D@N?(olHy`uVBq!ia0vp^vOp})!3HFyqmQuwDajJoh?3y^w370~qErUQl>DSr
z1<%~X^wgl##FWaylc_cg49qH-ArU1JzCKpT`MG+DAT@dwxdjX$U}IlVkeHmETB4AY
znx2_wtMq>NekFy>6kDZmQ(pt$0_W6>OpmIf)Zi+=kmRcDWXlvKdpiZ23M-%ixv3?I
z3Kh9IdBs*0wn|`gt$=Khu)dN4SV>8?trEmh5xxNm&iO^D3Z{C-y2%EHh6-k8dWI&Z
zW@d&u3PuKoM*0RoWTtCqVr6P(Wn``Z1xi5Mic-?7f?V97b^&>|N*N_31y=g{<>lpi
z<;HsXMd|v6mX?+vGmMOMfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZ_=!pRr6smX
zN-!_v7Ql_oD~1LWFu?RH5)1SV^$hfp6#Dw&SDKp(S6y5Zl$wTLb#X{#L8^XGYH@yP
zQ8F;%(v(4(3#^=rQWHz^i$e1Ab6}wukda@KU!0L&px_*Arl8@Qn4Fmh63_(e@b$Iw
z%quQQ%u7!7bg@+eis)r#rdT-{89EyqyO_9{y15w|x*9vW8d+MJyEqvdJ6jr<I6J}g
zy5uL9=BDPA!1Sgd^qS(-3rY+S-Kj;HWvMA{Mftf3U{70R;&zJ>Znr@6rr>sqF;2bu
zK*#8#MKw$an0`P^c)|s8;7LC<518JIfC*cKLA{58f$62Ei(^Q|tvAzsGlU&Qj+d8B
zp7Ct8Tkd2wLDsEe_5wCsayAtgoC7~7R0V$s5RF=)FmKP1Xh&~W|E3PHDM1<@{8cNZ
zw%B$mY!s5-eCyd^6W?1ZZ`15+l=BOEJvZOn`sC4p)93td{XF}B?@V+3Im<F-Huq-y
zmapS_x7++KTkN4@InUH*zio1#5Il9$6v3nCo7KHaYzoR6O1^x1`=sKZhWiQ0L)^<P
z4)q@hH$RYKaP*prhD**j#~CZc)$SZUHrKjpNAqG%uBi!gR^3|ie}B=NzdSa35?nW!
zE)FPq@bQkR(v3Xl{cFF)3y9b3TGej4b5d`+zRH5v?n@_~75L!!M{mXMBsHEOBfHgJ
ztKVKYn;c`hPw_+Gn)^Bvwr%|xA@#k_>+wy2TedPUTO{sn+Eo42X`92%gw-$Zl`cH;
z?)@IFjh`ZHOhsF~+@^ez(Vw%e%8&K@GqaEtiwa8@F6>T}e{)T4b-?dw3GBB|?7w=Y
zaBcDLcMW#kGjjJ_e(TD2{@Lo)TYh}ZeHg#Q(RXU546E~nE0-5eTBhY4ws7i}9SsJT
zPVC(;wo>Q$vNcYh-_NT*P?6=fz{}9eHp??r?`!SLuRp?VjEWZD^$uUE(x&IG)qT=D
z?eI)VdCB&4&fK*RCmx(S^@w$?`PENems+CZ)70eyu3zHanIqy=9xEB6Q@6Xmmf?}M
z)aB)wKTjOt{&0z9k8aanhZRR2d3_OOR;-utZtA?{uAjZzeuH|6X3f3g7dJfbJuJEE
z^Nu_InNjlL$vao32UeVI=*$p_N%OkiY~*htA+g50;J4Gi$Itn8f0uhHX7jJicbbUi
z+!H!`rMkry8*BE+hk7_0@lQK?I@)m27AYU&(_tHOD-^3YIz1@u+}ra#P3-E^0-tO0
zlF9*W+b*ACIn(UGzwdT|6KCk9A8R*Ua#_D2Qls0j**ajUZ19|8UV<#4C9nQz8P9Hf
z^>B$*=j(NPTg5!?pP8~C=j*DMv)i*BvpJ{aTshzQ|6Q)+m*ou13{3A;wzMi=lLS=-
Mp00i_>zopr0Lo{GZ2$lO
index 9837cbd0feb5350f07f24bc902ef24c879058555..4e529badb4f47efc85bdc77ea04262a82a30ddba
GIT binary patch
literal 2146
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
I9;}Ri13>I^`2YX_
index a76c610008827d78cd01f125f2e8438309f366d7..43244bfc79cce347e93feb903c18f7993eb2004b
GIT binary patch
literal 2676
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
CL|@hb
index 5ca79fc8738617127e44d306b42dfb692d19a8b2..f0a0a5dc8e4edd3d44bc8763deee0cb9cb015301
GIT binary patch
literal 2588
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
bW6Go)7*pafb{xg%%>Nq8aT7D&!9VUlV+B9)
index 965c749d2c36377e448a7729862ea1037228038c..076276acf6f83f35f2b8e2fe4e38db6cf157fda7
GIT binary patch
literal 1688
zc%17D@N?(olHy`uVBq!ia0vp^vOp})!3HFyqmQuwDajJoh?3y^w370~qErUQl>DSr
z1<%~X^wgl##FWaylc_cg49qH-ArU1JzCKpT`MG+DAT@dwxdjX$U}IlVkeHmETB4AY
znx2_wtMq>NekFy>6kDZmQ(pt$0_W6>OpmIf)Zi+=kmRcDWXlvKdpiZ23M-%ixv3?I
z3Kh9IdBs*0wn|`gt$=Khu)dN4SV>8?trEmh5xxNm&iO^D3Z{C-y2%EHh6-k8dWI&Z
zW@d&u3PuKoM*0RoWTtCqVr6P(Wn``Z1xi5Mic-?7f?V97b^&>|N*N_31y=g{<>lpi
z<;HsXMd|v6mX?+vGmMOMfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZ_=!pRr6smX
zN-!_v7Ql_oD~1LWFu?RH5)1SV^$hfp6#Dw&SDKp(S6y5Zl$wTLb#X{#L8^XGYH@yP
zQ8F;%(v(4(3#^=rQWHz^i$e1Ab6}wukda@KU!0L&px_*Arl8@Qn4Fmh63_(e@b$Iw
z%quQQ%u7!7bg@+eis)r#rdT-}7@1hQn3}jcx)~W7x*9t=xf)qGo0=G!TRJ;hnwi1$
zy5uL9=BDPA!1Sgd^qS(-3rY+S-Kj;HWvMA{Mftf3U{70R;&zJ>Znr@6rr>sqF;2bu
zK*#8#MKw$an0`P^c)|s8;7LC<518JIfC>97>kDsSe&O<TaSW-r^<<iFhOnbZLwcEc
z?xiJ}Ia#ZxU1_f05+Tyxq)@f7sY-<9Xpn7%rpT8i9pa8}9XE2giUclEn71V0%OX*&
zMWNkS%tE8H>o5FUel~vc*>`>UUv%@FH_a~TZnCR5c_z*Hes%t{z2;lK9h6`!%sRPa
z{rel!Jnb6Od!A_QZD-!O&r9O>R-LE2w&w1b*%hQ5Je5yZuzrc#^Z1_a^Zp6Fu3!0S
z`l{&ryIlJ9513qCd$L>jpzv+AA5wRAJaszyYPM}@kY{A|Eoq~gV~(;dLEkUk-?Bc+
zE6=}&FGkj?x}m*G`uL|tucp(F*Y3P4&60oG)9z4&Q|Fmjj(K<LWYg|=oaI^P+&aHZ
z-;J~73-8HrjZ&|;|4Uy*zmIk)?`*ej=$p~iFl{A+tioxLl{>a19zOcb*r4P&)3t?M
z!A~@koVm1C&GPy#caz0geBEr>u9pjgRw`t&d$Ddjv48zP5uUww*o^Z_yG<5ec&ci4
z^kMvV_I+U=_dJhGWL0gt9GfbxwQFyvz%wohv8PN+T?GCf*%$Eq*DS9)*VnMWGM}>X
z3YUXt(h~dD>MYqfQ#rXU+$k$leCEl77Q`MDlKOw+ZszUN7aJEW)G|DM+4|b0AB<X;
z)vYgF_}m^F!519Se|F)5ldbQLpWP|)PABf$dApsr*$+#^pI*bM)7W=E<5f?6-Suhu
z0lh8H+}2&W-=4@)BDZ^T(o@aD8(iAX$?g(+Sy{#Jw|N?O0H?2S-QJo#q4VUP+m^Mt
zOO{;J*&~wA_lHd(p>Ap;+o^BnZ@yhOmC@DU&pG$S-PiBP;q_L#_T9Do@6)U6b9nLl
z1<DI#<lOutkJV*01f9{nr&+Nu;`G(v-Pw!tpC6R%yE-*{#ft)shch#DepE^DomG(8
zBf+#xU={P%dtN>Z**3Ju{l1XBPW8u`L>uNE?TS3Fb<P<xZFahBqjy9qswXPtg`7<Z
z=LL<XMHc?4%**1#joxjoo9g(UOK`>YU{OX-=eVq}mualN<_;d(xjc%~uBPw2AS(R$
r=$ro2@r_=)PXFJt{6GJfb_QmK6$T-uC3{UYKy`(utDnm{r-UW|V?m1_
index 9018291b5c543ac9adc1655227d0e5ee7d4e6f67..6da5782e86ed44591466a07e745ea2d7c9d24e4c
GIT binary patch
literal 2191
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
F`VWAnqjUfO
index 83f4b931d7ecf1e8a7b8456f1b1031531b668354..f8f61d22116c7a5362cf460d5eaa230fd187182f
GIT binary patch
literal 2735
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
Kmv+XDmHHoQUTN|G
index ecb4c54e9c69d1abadbfddeb2195cf9837ef803b..1d7553d52a084553aae96bc20d1b265cd55ce344
GIT binary patch
literal 2596
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
deleted file mode 100644
index 656854decc593d3ccd8767654f1743fb2523b64e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d99b3c93b6c9471deb388c0693250dcbc77fc0dc..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index a7fc3901ae69ac20299ea421aab904807801831e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/editor/libeditor/CreateElementTxn.cpp
+++ b/editor/libeditor/CreateElementTxn.cpp
@@ -1,158 +1,141 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 <algorithm>
 #include <stdio.h>
 
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Selection.h"
+
+#include "mozilla/Casting.h"
+
 #include "CreateElementTxn.h"
-#include "mozilla/dom/Element.h"
 #include "nsAlgorithm.h"
+#include "nsAString.h"
 #include "nsDebug.h"
 #include "nsEditor.h"
 #include "nsError.h"
 #include "nsIContent.h"
 #include "nsIDOMCharacterData.h"
 #include "nsIEditor.h"
 #include "nsINode.h"
 #include "nsISelection.h"
 #include "nsISupportsUtils.h"
 #include "nsMemory.h"
 #include "nsReadableUtils.h"
 #include "nsStringFwd.h"
 #include "nsString.h"
-#include "nsAString.h"
-#include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-CreateElementTxn::CreateElementTxn()
+CreateElementTxn::CreateElementTxn(nsEditor& aEditor,
+                                   nsIAtom& aTag,
+                                   nsINode& aParent,
+                                   int32_t aOffsetInParent)
   : EditTxn()
+  , mEditor(&aEditor)
+  , mTag(&aTag)
+  , mParent(&aParent)
+  , mOffsetInParent(aOffsetInParent)
 {
 }
 
 CreateElementTxn::~CreateElementTxn()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(CreateElementTxn, EditTxn,
                                    mParent,
                                    mNewNode,
                                    mRefNode)
 
 NS_IMPL_ADDREF_INHERITED(CreateElementTxn, EditTxn)
 NS_IMPL_RELEASE_INHERITED(CreateElementTxn, EditTxn)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CreateElementTxn)
 NS_INTERFACE_MAP_END_INHERITING(EditTxn)
-NS_IMETHODIMP CreateElementTxn::Init(nsEditor      *aEditor,
-                                     const nsAString &aTag,
-                                     nsIDOMNode     *aParent,
-                                     uint32_t        aOffsetInParent)
-{
-  NS_ASSERTION(aEditor&&aParent, "null args");
-  if (!aEditor || !aParent) { return NS_ERROR_NULL_POINTER; }
-
-  mEditor = aEditor;
-  mTag = aTag;
-  mParent = do_QueryInterface(aParent);
-  mOffsetInParent = aOffsetInParent;
-  return NS_OK;
-}
 
 
-NS_IMETHODIMP CreateElementTxn::DoTransaction(void)
+NS_IMETHODIMP
+CreateElementTxn::DoTransaction()
 {
-  NS_ASSERTION(mEditor && mParent, "bad state");
-  NS_ENSURE_TRUE(mEditor && mParent, NS_ERROR_NOT_INITIALIZED);
+  MOZ_ASSERT(mEditor && mTag && mParent);
 
-  nsCOMPtr<Element> newContent =
-    mEditor->CreateHTMLContent(nsCOMPtr<nsIAtom>(do_GetAtom(mTag)));
-  NS_ENSURE_STATE(newContent);
+  mNewNode = mEditor->CreateHTMLContent(mTag);
+  NS_ENSURE_STATE(mNewNode);
 
-  mNewNode = newContent->AsDOMNode();
   // Try to insert formatting whitespace for the new node:
-  mEditor->MarkNodeDirty(mNewNode);
+  mEditor->MarkNodeDirty(GetAsDOMNode(mNewNode));
 
-  // insert the new node
-  if (CreateElementTxn::eAppend == int32_t(mOffsetInParent)) {
-    nsCOMPtr<nsIDOMNode> resultNode;
-    return mParent->AppendChild(mNewNode, getter_AddRefs(resultNode));
+  // Insert the new node
+  ErrorResult rv;
+  if (mOffsetInParent == -1) {
+    mParent->AppendChild(*mNewNode, rv);
+    return rv.ErrorCode();
   }
 
-  nsCOMPtr<nsINode> parent = do_QueryInterface(mParent);
-  NS_ENSURE_STATE(parent);
+  mOffsetInParent = std::min(mOffsetInParent,
+                             static_cast<int32_t>(mParent->GetChildCount()));
 
-  mOffsetInParent = std::min(mOffsetInParent, parent->GetChildCount());
-
-  // note, it's ok for mRefNode to be null.  that means append
-  nsIContent* refNode = parent->GetChildAt(mOffsetInParent);
-  mRefNode = refNode ? refNode->AsDOMNode() : nullptr;
+  // Note, it's ok for mRefNode to be null. That means append
+  mRefNode = mParent->GetChildAt(mOffsetInParent);
 
-  nsCOMPtr<nsIDOMNode> resultNode;
-  nsresult result = mParent->InsertBefore(mNewNode, mRefNode, getter_AddRefs(resultNode));
-  NS_ENSURE_SUCCESS(result, result); 
+  mParent->InsertBefore(*mNewNode, mRefNode, rv);
+  NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode());
 
-  // only set selection to insertion point if editor gives permission
-  bool bAdjustSelection;
-  mEditor->ShouldTxnSetSelection(&bAdjustSelection);
-  if (!bAdjustSelection) {
-    // do nothing - dom range gravity will adjust selection
+  // Only set selection to insertion point if editor gives permission
+  if (!mEditor->GetShouldTxnSetSelection()) {
+    // Do nothing - DOM range gravity will adjust selection
     return NS_OK;
   }
 
-  nsCOMPtr<nsISelection> selection;
-  result = mEditor->GetSelection(getter_AddRefs(selection));
-  NS_ENSURE_SUCCESS(result, result);
+  nsRefPtr<Selection> selection = mEditor->GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
-  nsCOMPtr<nsIContent> parentContent = do_QueryInterface(mParent);
-  NS_ENSURE_STATE(parentContent);
-
-  result = selection->CollapseNative(parentContent,
-                                     parentContent->IndexOf(newContent) + 1);
-  NS_ASSERTION((NS_SUCCEEDED(result)), "selection could not be collapsed after insert.");
-  return result;
-}
-
-NS_IMETHODIMP CreateElementTxn::UndoTransaction(void)
-{
-  NS_ASSERTION(mEditor && mParent, "bad state");
-  NS_ENSURE_TRUE(mEditor && mParent, NS_ERROR_NOT_INITIALIZED);
-
-  nsCOMPtr<nsIDOMNode> resultNode;
-  return mParent->RemoveChild(mNewNode, getter_AddRefs(resultNode));
-}
-
-NS_IMETHODIMP CreateElementTxn::RedoTransaction(void)
-{
-  NS_ASSERTION(mEditor && mParent, "bad state");
-  NS_ENSURE_TRUE(mEditor && mParent, NS_ERROR_NOT_INITIALIZED);
-
-  // first, reset mNewNode so it has no attributes or content
-  nsCOMPtr<nsIDOMCharacterData>nodeAsText = do_QueryInterface(mNewNode);
-  if (nodeAsText)
-  {
-    nodeAsText->SetData(EmptyString());
-  }
-  
-  // now, reinsert mNewNode
-  nsCOMPtr<nsIDOMNode> resultNode;
-  return mParent->InsertBefore(mNewNode, mRefNode, getter_AddRefs(resultNode));
-}
-
-NS_IMETHODIMP CreateElementTxn::GetTxnDescription(nsAString& aString)
-{
-  aString.AssignLiteral("CreateElementTxn: ");
-  aString += mTag;
+  rv = selection->CollapseNative(mParent, mParent->IndexOf(mNewNode) + 1);
+  NS_ASSERTION(!rv.Failed(),
+               "selection could not be collapsed after insert");
   return NS_OK;
 }
 
-NS_IMETHODIMP CreateElementTxn::GetNewNode(nsIDOMNode **aNewNode)
+NS_IMETHODIMP
+CreateElementTxn::UndoTransaction()
+{
+  MOZ_ASSERT(mEditor && mParent);
+
+  ErrorResult rv;
+  mParent->RemoveChild(*mNewNode, rv);
+
+  return rv.ErrorCode();
+}
+
+NS_IMETHODIMP
+CreateElementTxn::RedoTransaction()
 {
-  NS_ENSURE_TRUE(aNewNode, NS_ERROR_NULL_POINTER);
-  NS_ENSURE_TRUE(mNewNode, NS_ERROR_NOT_INITIALIZED);
-  *aNewNode = mNewNode;
-  NS_ADDREF(*aNewNode);
+  MOZ_ASSERT(mEditor && mParent);
+
+  // First, reset mNewNode so it has no attributes or content
+  // XXX We never actually did this, we only cleared mNewNode's contents if it
+  // was a CharacterData node (which it's not, it's an Element)
+
+  // Now, reinsert mNewNode
+  ErrorResult rv;
+  mParent->InsertBefore(*mNewNode, mRefNode, rv);
+  return rv.ErrorCode();
+}
+
+NS_IMETHODIMP
+CreateElementTxn::GetTxnDescription(nsAString& aString)
+{
+  aString.AssignLiteral("CreateElementTxn: ");
+  aString += nsDependentAtomString(mTag);
   return NS_OK;
 }
+
+already_AddRefed<Element>
+CreateElementTxn::GetNewNode()
+{
+  return nsCOMPtr<Element>(mNewNode).forget();
+}
--- a/editor/libeditor/CreateElementTxn.h
+++ b/editor/libeditor/CreateElementTxn.h
@@ -4,69 +4,73 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CreateElementTxn_h__
 #define CreateElementTxn_h__
 
 #include "EditTxn.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
-#include "nsIDOMNode.h"
 #include "nsISupportsImpl.h"
-#include "nsString.h"
-#include "nscore.h"
 
 class nsEditor;
+class nsIAtom;
+class nsIContent;
+class nsINode;
 
 /**
  * A transaction that creates a new node in the content tree.
  */
+namespace mozilla {
+namespace dom {
+
+class Element;
+
 class CreateElementTxn : public EditTxn
 {
 public:
-  enum { eAppend=-1 };
-
   /** Initialize the transaction.
     * @param aEditor the provider of basic editing functionality
     * @param aTag    the tag (P, HR, TABLE, etc.) for the new element
     * @param aParent the node into which the new element will be inserted
     * @param aOffsetInParent the location in aParent to insert the new element
     *                        if eAppend, the new element is appended as the last child
     */
-  NS_IMETHOD Init(nsEditor *aEditor,
-                  const nsAString& aTag,
-                  nsIDOMNode *aParent,
-                  uint32_t aOffsetInParent);
-
-  CreateElementTxn();
+  CreateElementTxn(nsEditor& aEditor,
+                   nsIAtom& aTag,
+                   nsINode& aParent,
+                   int32_t aOffsetInParent);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CreateElementTxn, EditTxn)
 
   NS_DECL_EDITTXN
 
   NS_IMETHOD RedoTransaction();
 
-  NS_IMETHOD GetNewNode(nsIDOMNode **aNewNode);
+  already_AddRefed<Element> GetNewNode();
 
 protected:
   virtual ~CreateElementTxn();
 
   /** the document into which the new node will be inserted */
   nsEditor* mEditor;
-  
+
   /** the tag (mapping to object type) for the new element */
-  nsString mTag;
+  nsCOMPtr<nsIAtom> mTag;
 
   /** the node into which the new node will be inserted */
-  nsCOMPtr<nsIDOMNode> mParent;
+  nsCOMPtr<nsINode> mParent;
 
   /** the index in mParent for the new node */
-  uint32_t mOffsetInParent;
+  int32_t mOffsetInParent;
 
   /** the new node to insert */
-  nsCOMPtr<nsIDOMNode> mNewNode;  
+  nsCOMPtr<Element> mNewNode;
 
   /** the node we will insert mNewNode before.  We compute this ourselves. */
-  nsCOMPtr<nsIDOMNode> mRefNode;
+  nsCOMPtr<nsIContent> mRefNode;
 };
 
+}
+}
+
 #endif
--- a/editor/libeditor/DeleteRangeTxn.cpp
+++ b/editor/libeditor/DeleteRangeTxn.cpp
@@ -134,28 +134,31 @@ DeleteRangeTxn::GetTxnDescription(nsAStr
 nsresult
 DeleteRangeTxn::CreateTxnsToDeleteBetween(nsINode* aNode,
                                           int32_t aStartOffset,
                                           int32_t aEndOffset)
 {
   // see what kind of node we have
   if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
     // if the node is a chardata node, then delete chardata content
-    nsRefPtr<DeleteTextTxn> txn = new DeleteTextTxn();
-
     int32_t numToDel;
     if (aStartOffset == aEndOffset) {
       numToDel = 1;
     } else {
       numToDel = aEndOffset - aStartOffset;
     }
 
-    nsCOMPtr<nsIDOMCharacterData> charDataNode = do_QueryInterface(aNode);
-    nsresult res = txn->Init(mEditor, charDataNode, aStartOffset, numToDel,
-                             mRangeUpdater);
+    nsRefPtr<nsGenericDOMDataNode> charDataNode =
+      static_cast<nsGenericDOMDataNode*>(aNode);
+
+    nsRefPtr<DeleteTextTxn> txn =
+      new DeleteTextTxn(*mEditor, *charDataNode, aStartOffset, numToDel,
+                        mRangeUpdater);
+
+    nsresult res = txn->Init();
     NS_ENSURE_SUCCESS(res, res);
 
     AppendChild(txn);
     return NS_OK;
   }
 
   nsCOMPtr<nsIContent> child = aNode->GetChildAt(aStartOffset);
   NS_ENSURE_STATE(child);
@@ -188,21 +191,22 @@ DeleteRangeTxn::CreateTxnsToDeleteConten
       start = aOffset;
       numToDelete = aNode->Length() - aOffset;
     } else {
       start = 0;
       numToDelete = aOffset;
     }
 
     if (numToDelete) {
-      nsRefPtr<DeleteTextTxn> txn = new DeleteTextTxn();
+      nsRefPtr<nsGenericDOMDataNode> dataNode =
+        static_cast<nsGenericDOMDataNode*>(aNode);
+      nsRefPtr<DeleteTextTxn> txn = new DeleteTextTxn(*mEditor, *dataNode,
+          start, numToDelete, mRangeUpdater);
 
-      nsCOMPtr<nsIDOMCharacterData> charDataNode = do_QueryInterface(aNode);
-      nsresult res = txn->Init(mEditor, charDataNode, start, numToDelete,
-                               mRangeUpdater);
+      nsresult res = txn->Init();
       NS_ENSURE_SUCCESS(res, res);
 
       AppendChild(txn);
     }
   }
 
   return NS_OK;
 }
--- a/editor/libeditor/DeleteTextTxn.cpp
+++ b/editor/libeditor/DeleteTextTxn.cpp
@@ -14,99 +14,83 @@
 #include "nsISelection.h"
 #include "nsISupportsImpl.h"
 #include "nsSelectionState.h"
 #include "nsAString.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-DeleteTextTxn::DeleteTextTxn() :
-  EditTxn(),
-  mEditor(nullptr),
-  mCharData(),
-  mOffset(0),
-  mNumCharsToDelete(0),
-  mRangeUpdater(nullptr)
+DeleteTextTxn::DeleteTextTxn(nsEditor& aEditor,
+                             nsGenericDOMDataNode& aCharData, uint32_t aOffset,
+                             uint32_t aNumCharsToDelete,
+                             nsRangeUpdater* aRangeUpdater)
+  : EditTxn()
+  , mEditor(aEditor)
+  , mCharData(&aCharData)
+  , mOffset(aOffset)
+  , mNumCharsToDelete(aNumCharsToDelete)
+  , mRangeUpdater(aRangeUpdater)
 {
+  NS_ASSERTION(mCharData->Length() >= aOffset + aNumCharsToDelete,
+               "Trying to delete more characters than in node");
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteTextTxn, EditTxn,
                                    mCharData)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteTextTxn)
 NS_INTERFACE_MAP_END_INHERITING(EditTxn)
 
-NS_IMETHODIMP
-DeleteTextTxn::Init(nsEditor* aEditor,
-                    nsIDOMCharacterData* aCharData,
-                    uint32_t aOffset,
-                    uint32_t aNumCharsToDelete,
-                    nsRangeUpdater* aRangeUpdater)
+nsresult
+DeleteTextTxn::Init()
 {
-  MOZ_ASSERT(aEditor && aCharData);
-
-  mEditor = aEditor;
-  mCharData = aCharData;
-
-  // do nothing if the node is read-only
-  if (!mEditor->IsModifiableNode(mCharData)) {
+  // Do nothing if the node is read-only
+  if (!mEditor.IsModifiableNode(mCharData)) {
     return NS_ERROR_FAILURE;
   }
 
-  mOffset = aOffset;
-  mNumCharsToDelete = aNumCharsToDelete;
-#ifdef DEBUG
-  uint32_t length;
-  mCharData->GetLength(&length);
-  NS_ASSERTION(length >= aOffset + aNumCharsToDelete,
-               "Trying to delete more characters than in node");
-#endif
-  mDeletedText.Truncate();
-  mRangeUpdater = aRangeUpdater;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DeleteTextTxn::DoTransaction()
 {
-  MOZ_ASSERT(mEditor && mCharData);
+  MOZ_ASSERT(mCharData);
 
-  // get the text that we're about to delete
+  // Get the text that we're about to delete
   nsresult res = mCharData->SubstringData(mOffset, mNumCharsToDelete,
                                           mDeletedText);
   MOZ_ASSERT(NS_SUCCEEDED(res));
   res = mCharData->DeleteData(mOffset, mNumCharsToDelete);
   NS_ENSURE_SUCCESS(res, res);
 
   if (mRangeUpdater) {
     mRangeUpdater->SelAdjDeleteText(mCharData, mOffset, mNumCharsToDelete);
   }
 
-  // only set selection to deletion point if editor gives permission
-  bool bAdjustSelection;
-  mEditor->ShouldTxnSetSelection(&bAdjustSelection);
-  if (bAdjustSelection) {
-    nsRefPtr<Selection> selection = mEditor->GetSelection();
+  // Only set selection to deletion point if editor gives permission
+  if (mEditor.GetShouldTxnSetSelection()) {
+    nsRefPtr<Selection> selection = mEditor.GetSelection();
     NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
     res = selection->Collapse(mCharData, mOffset);
     NS_ASSERTION(NS_SUCCEEDED(res),
-                 "selection could not be collapsed after undo of deletetext.");
+                 "Selection could not be collapsed after undo of deletetext");
     NS_ENSURE_SUCCESS(res, res);
   }
-  // else do nothing - dom range gravity will adjust selection
+  // Else do nothing - DOM Range gravity will adjust selection
   return NS_OK;
 }
 
-//XXX: we may want to store the selection state and restore it properly
-//     was it an insertion point or an extended selection?
+//XXX: We may want to store the selection state and restore it properly.  Was
+//     it an insertion point or an extended selection?
 NS_IMETHODIMP
 DeleteTextTxn::UndoTransaction()
 {
-  MOZ_ASSERT(mEditor && mCharData);
+  MOZ_ASSERT(mCharData);
 
   return mCharData->InsertData(mOffset, mDeletedText);
 }
 
 NS_IMETHODIMP
 DeleteTextTxn::GetTxnDescription(nsAString& aString)
 {
   aString.AssignLiteral("DeleteTextTxn: ");
--- a/editor/libeditor/DeleteTextTxn.h
+++ b/editor/libeditor/DeleteTextTxn.h
@@ -4,67 +4,73 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DeleteTextTxn_h__
 #define DeleteTextTxn_h__
 
 #include "EditTxn.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
+#include "nsGenericDOMDataNode.h"
 #include "nsID.h"
-#include "nsIDOMCharacterData.h"
 #include "nsString.h"
 #include "nscore.h"
 
 class nsEditor;
 class nsRangeUpdater;
 
+namespace mozilla {
+namespace dom {
+
 /**
  * A transaction that removes text from a content node.
  */
 class DeleteTextTxn : public EditTxn
 {
 public:
   /** initialize the transaction.
     * @param aEditor  the provider of basic editing operations
     * @param aElement the content node to remove text from
     * @param aOffset  the location in aElement to begin the deletion
     * @param aNumCharsToDelete  the number of characters to delete.  Not the number of bytes!
     */
-  NS_IMETHOD Init(nsEditor* aEditor,
-                  nsIDOMCharacterData* aCharData,
-                  uint32_t aOffset,
-                  uint32_t aNumCharsToDelete,
-                  nsRangeUpdater* aRangeUpdater);
+  DeleteTextTxn(nsEditor& aEditor,
+                nsGenericDOMDataNode& aCharData,
+                uint32_t aOffset,
+                uint32_t aNumCharsToDelete,
+                nsRangeUpdater* aRangeUpdater);
 
-  DeleteTextTxn();
+  nsresult Init();
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteTextTxn, EditTxn)
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
 
   NS_DECL_EDITTXN
 
   uint32_t GetOffset() { return mOffset; }
 
   uint32_t GetNumCharsToDelete() { return mNumCharsToDelete; }
 
 protected:
 
   /** the provider of basic editing operations */
-  nsEditor* mEditor;
+  nsEditor& mEditor;
 
   /** the CharacterData node to operate upon */
-  nsCOMPtr<nsIDOMCharacterData> mCharData;
+  nsRefPtr<nsGenericDOMDataNode> mCharData;
 
   /** the offset into mCharData where the deletion is to take place */
   uint32_t mOffset;
 
   /** the number of characters to delete */
   uint32_t mNumCharsToDelete;
 
   /** the text that was deleted */
   nsString mDeletedText;
 
   /** range updater object */
   nsRangeUpdater* mRangeUpdater;
 };
 
+}
+}
+
 #endif
rename from editor/libeditor/InsertElementTxn.cpp
rename to editor/libeditor/InsertNodeTxn.cpp
--- a/editor/libeditor/InsertElementTxn.cpp
+++ b/editor/libeditor/InsertNodeTxn.cpp
@@ -1,112 +1,93 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 <stdio.h>                      // for printf
 
-#include "InsertElementTxn.h"
+#include "mozilla/dom/Selection.h"      // for Selection
+
+#include "InsertNodeTxn.h"
 #include "nsAString.h"
 #include "nsDebug.h"                    // for NS_ENSURE_TRUE, etc
 #include "nsEditor.h"                   // for nsEditor
 #include "nsError.h"                    // for NS_ERROR_NULL_POINTER, etc
 #include "nsIContent.h"                 // for nsIContent
-#include "nsINode.h"                    // for nsINode
-#include "nsISelection.h"               // for nsISelection
 #include "nsMemory.h"                   // for nsMemory
 #include "nsReadableUtils.h"            // for ToNewCString
 #include "nsString.h"                   // for nsString
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
-InsertElementTxn::InsertElementTxn()
+InsertNodeTxn::InsertNodeTxn(nsIContent& aNode, nsINode& aParent,
+                             int32_t aOffset, nsEditor& aEditor)
   : EditTxn()
+  , mNode(&aNode)
+  , mParent(&aParent)
+  , mOffset(aOffset)
+  , mEditor(aEditor)
 {
 }
 
-InsertElementTxn::~InsertElementTxn()
+InsertNodeTxn::~InsertNodeTxn()
 {
 }
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertElementTxn, EditTxn,
+NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTxn, EditTxn,
                                    mNode,
                                    mParent)
 
-NS_IMPL_ADDREF_INHERITED(InsertElementTxn, EditTxn)
-NS_IMPL_RELEASE_INHERITED(InsertElementTxn, EditTxn)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertElementTxn)
+NS_IMPL_ADDREF_INHERITED(InsertNodeTxn, EditTxn)
+NS_IMPL_RELEASE_INHERITED(InsertNodeTxn, EditTxn)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertNodeTxn)
 NS_INTERFACE_MAP_END_INHERITING(EditTxn)
 
-NS_IMETHODIMP InsertElementTxn::Init(nsIDOMNode *aNode,
-                                     nsIDOMNode *aParent,
-                                     int32_t     aOffset,
-                                     nsIEditor  *aEditor)
+NS_IMETHODIMP
+InsertNodeTxn::DoTransaction()
 {
-  NS_ASSERTION(aNode && aParent && aEditor, "bad arg");
-  NS_ENSURE_TRUE(aNode && aParent && aEditor, NS_ERROR_NULL_POINTER);
+  MOZ_ASSERT(mNode && mParent);
 
-  mNode = do_QueryInterface(aNode);
-  mParent = do_QueryInterface(aParent);
-  mOffset = aOffset;
-  mEditor = aEditor;
-  NS_ENSURE_TRUE(mNode && mParent && mEditor, NS_ERROR_INVALID_ARG);
-  return NS_OK;
-}
-
-
-NS_IMETHODIMP InsertElementTxn::DoTransaction(void)
-{
-  NS_ENSURE_TRUE(mNode && mParent, NS_ERROR_NOT_INITIALIZED);
-
-  nsCOMPtr<nsINode> parent = do_QueryInterface(mParent);
-  NS_ENSURE_STATE(parent);
-
-  uint32_t count = parent->GetChildCount();
-  if (mOffset > int32_t(count) || mOffset == -1) {
+  uint32_t count = mParent->GetChildCount();
+  if (mOffset > static_cast<int32_t>(count) || mOffset == -1) {
     // -1 is sentinel value meaning "append at end"
     mOffset = count;
   }
 
-  // note, it's ok for refContent to be null.  that means append
-  nsCOMPtr<nsIContent> refContent = parent->GetChildAt(mOffset);
-  nsCOMPtr<nsIDOMNode> refNode = refContent ? refContent->AsDOMNode() : nullptr;
+  // Note, it's ok for ref to be null. That means append.
+  nsCOMPtr<nsIContent> ref = mParent->GetChildAt(mOffset);
 
-  mEditor->MarkNodeDirty(mNode);
+  mEditor.MarkNodeDirty(GetAsDOMNode(mNode));
 
-  nsCOMPtr<nsIDOMNode> resultNode;
-  nsresult result = mParent->InsertBefore(mNode, refNode, getter_AddRefs(resultNode));
-  NS_ENSURE_SUCCESS(result, result);
-  NS_ENSURE_TRUE(resultNode, NS_ERROR_NULL_POINTER);
+  ErrorResult rv;
+  mParent->InsertBefore(*mNode, ref, rv);
+  NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode());
 
-  // only set selection to insertion point if editor gives permission
-  bool bAdjustSelection;
-  mEditor->ShouldTxnSetSelection(&bAdjustSelection);
-  if (bAdjustSelection)
-  {
-    nsCOMPtr<nsISelection> selection;
-    result = mEditor->GetSelection(getter_AddRefs(selection));
-    NS_ENSURE_SUCCESS(result, result);
+  // Only set selection to insertion point if editor gives permission
+  if (mEditor.GetShouldTxnSetSelection()) {
+    nsRefPtr<Selection> selection = mEditor.GetSelection();
     NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-    // place the selection just after the inserted element
-    selection->Collapse(mParent, mOffset+1);
+    // Place the selection just after the inserted element
+    selection->Collapse(mParent, mOffset + 1);
+  } else {
+    // Do nothing - DOM Range gravity will adjust selection
   }
-  else
-  {
-    // do nothing - dom range gravity will adjust selection
-  }
-  return result;
+  return NS_OK;
 }
 
-NS_IMETHODIMP InsertElementTxn::UndoTransaction(void)
+NS_IMETHODIMP
+InsertNodeTxn::UndoTransaction()
 {
-  NS_ENSURE_TRUE(mNode && mParent, NS_ERROR_NOT_INITIALIZED);
+  MOZ_ASSERT(mNode && mParent);
 
-  nsCOMPtr<nsIDOMNode> resultNode;
-  return mParent->RemoveChild(mNode, getter_AddRefs(resultNode));
+  ErrorResult rv;
+  mParent->RemoveChild(*mNode, rv);
+  return rv.ErrorCode();
 }
 
-NS_IMETHODIMP InsertElementTxn::GetTxnDescription(nsAString& aString)
+NS_IMETHODIMP
+InsertNodeTxn::GetTxnDescription(nsAString& aString)
 {
-  aString.AssignLiteral("InsertElementTxn");
+  aString.AssignLiteral("InsertNodeTxn");
   return NS_OK;
 }
rename from editor/libeditor/InsertElementTxn.h
rename to editor/libeditor/InsertNodeTxn.h
--- a/editor/libeditor/InsertElementTxn.h
+++ b/editor/libeditor/InsertNodeTxn.h
@@ -1,57 +1,58 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
 
-#ifndef InsertElementTxn_h__
-#define InsertElementTxn_h__
+#ifndef InsertNodeTxn_h__
+#define InsertNodeTxn_h__
 
 #include "EditTxn.h"                    // for EditTxn, NS_DECL_EDITTXN
 #include "nsCOMPtr.h"                   // for nsCOMPtr
 #include "nsCycleCollectionParticipant.h"
-#include "nsIDOMNode.h"                 // for nsIDOMNode
+#include "nsIContent.h"                 // for nsIContent
 #include "nsISupportsImpl.h"            // for NS_DECL_ISUPPORTS_INHERITED
-#include "nscore.h"                     // for NS_IMETHOD
+
+class nsEditor;
 
-class nsIEditor;
+namespace mozilla {
+namespace dom {
 
 /**
  * A transaction that inserts a single element
  */
-class InsertElementTxn : public EditTxn
+class InsertNodeTxn : public EditTxn
 {
 public:
   /** initialize the transaction.
     * @param aNode   the node to insert
     * @param aParent the node to insert into
     * @param aOffset the offset in aParent to insert aNode
     */
-  NS_IMETHOD Init(nsIDOMNode *aNode,
-                  nsIDOMNode *aParent,
-                  int32_t     aOffset,
-                  nsIEditor  *aEditor);
-
-  InsertElementTxn();
+  InsertNodeTxn(nsIContent& aNode, nsINode& aParent, int32_t aOffset,
+                nsEditor& aEditor);
 
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertElementTxn, EditTxn)
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertNodeTxn, EditTxn)
 
   NS_DECL_EDITTXN
 
 protected:
-  virtual ~InsertElementTxn();
+  virtual ~InsertNodeTxn();
 
   /** the element to insert */
-  nsCOMPtr<nsIDOMNode> mNode;
+  nsCOMPtr<nsIContent> mNode;
 
   /** the node into which the new node will be inserted */
-  nsCOMPtr<nsIDOMNode> mParent;
-
-  /** the editor for this transaction */
-  nsIEditor*           mEditor;
+  nsCOMPtr<nsINode> mParent;
 
   /** the index in mParent for the new node */
   int32_t mOffset;
+
+  /** the editor for this transaction */
+  nsEditor& mEditor;
 };
 
+}
+}
+
 #endif
--- a/editor/libeditor/moz.build
+++ b/editor/libeditor/moz.build
@@ -11,17 +11,17 @@ UNIFIED_SOURCES += [
     'ChangeCSSInlineStyleTxn.cpp',
     'CreateElementTxn.cpp',
     'DeleteNodeTxn.cpp',
     'DeleteRangeTxn.cpp',
     'DeleteTextTxn.cpp',
     'EditAggregateTxn.cpp',
     'EditTxn.cpp',
     'IMETextTxn.cpp',
-    'InsertElementTxn.cpp',
+    'InsertNodeTxn.cpp',
     'InsertTextTxn.cpp',
     'JoinElementTxn.cpp',
     'nsEditor.cpp',
     'nsEditorCommands.cpp',
     'nsEditorController.cpp',
     'nsEditorEventListener.cpp',
     'nsEditorUtils.cpp',
     'nsEditProperty.cpp',
--- a/editor/libeditor/nsEditor.cpp
+++ b/editor/libeditor/nsEditor.cpp
@@ -11,17 +11,17 @@
 #include "ChangeAttributeTxn.h"         // for ChangeAttributeTxn
 #include "CreateElementTxn.h"           // for CreateElementTxn
 #include "DeleteNodeTxn.h"              // for DeleteNodeTxn
 #include "DeleteRangeTxn.h"             // for DeleteRangeTxn
 #include "DeleteTextTxn.h"              // for DeleteTextTxn
 #include "EditAggregateTxn.h"           // for EditAggregateTxn
 #include "EditTxn.h"                    // for EditTxn
 #include "IMETextTxn.h"                 // for IMETextTxn
-#include "InsertElementTxn.h"           // for InsertElementTxn
+#include "InsertNodeTxn.h"              // for InsertNodeTxn
 #include "InsertTextTxn.h"              // for InsertTextTxn
 #include "JoinElementTxn.h"             // for JoinElementTxn
 #include "PlaceholderTxn.h"             // for PlaceholderTxn
 #include "SplitElementTxn.h"            // for SplitElementTxn
 #include "mozFlushType.h"               // for mozFlushType::Flush_Frames
 #include "mozISpellCheckingEngine.h"
 #include "mozInlineSpellChecker.h"      // for mozInlineSpellChecker
 #include "mozilla/IMEStateManager.h"    // for IMEStateManager
@@ -1330,82 +1330,99 @@ NS_IMETHODIMP nsEditor::SyncRealTimeSpel
 
 NS_IMETHODIMP nsEditor::SetSpellcheckUserOverride(bool enable)
 {
   mSpellcheckCheckboxState = enable ? eTriTrue : eTriFalse;
 
   return SyncRealTimeSpell();
 }
 
-NS_IMETHODIMP nsEditor::CreateNode(const nsAString& aTag,
-                                   nsIDOMNode *    aParent,
-                                   int32_t         aPosition,
-                                   nsIDOMNode **   aNewNode)
-{
-  int32_t i;
+NS_IMETHODIMP
+nsEditor::CreateNode(const nsAString& aTag,
+                     nsIDOMNode* aParent,
+                     int32_t aPosition,
+                     nsIDOMNode** aNewNode)
+{
+  nsCOMPtr<nsIAtom> tag = do_GetAtom(aTag);
+  nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
+  NS_ENSURE_STATE(parent);
+  *aNewNode = GetAsDOMNode(CreateNode(tag, parent, aPosition).take());
+  NS_ENSURE_STATE(*aNewNode);
+  return NS_OK;
+}
+
+already_AddRefed<Element>
+nsEditor::CreateNode(nsIAtom* aTag,
+                     nsINode* aParent,
+                     int32_t aPosition)
+{
+  MOZ_ASSERT(aTag && aParent);
 
   nsAutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::eNext);
 
-  for (i = 0; i < mActionListeners.Count(); i++)
-    mActionListeners[i]->WillCreateNode(aTag, aParent, aPosition);
-
-  nsRefPtr<CreateElementTxn> txn;
-  nsresult result = CreateTxnForCreateElement(aTag, aParent, aPosition,
-                                              getter_AddRefs(txn));
-  if (NS_SUCCEEDED(result))
-  {
-    result = DoTransaction(txn);
-    if (NS_SUCCEEDED(result))
-    {
-      result = txn->GetNewNode(aNewNode);
-      NS_ASSERTION((NS_SUCCEEDED(result)), "GetNewNode can't fail if txn::DoTransaction succeeded.");
-    }
+  for (int32_t i = 0; i < mActionListeners.Count(); i++) {
+    mActionListeners[i]->WillCreateNode(nsDependentAtomString(aTag),
+                                        GetAsDOMNode(aParent), aPosition);
+  }
+
+  nsCOMPtr<Element> ret;
+
+  nsRefPtr<CreateElementTxn> txn =
+    CreateTxnForCreateElement(*aTag, *aParent, aPosition);
+  nsresult res = DoTransaction(txn);
+  if (NS_SUCCEEDED(res)) {
+    ret = txn->GetNewNode();
+    MOZ_ASSERT(ret);
   }
 
   mRangeUpdater.SelAdjCreateNode(aParent, aPosition);
 
-  for (i = 0; i < mActionListeners.Count(); i++)
-    mActionListeners[i]->DidCreateNode(aTag, *aNewNode, aParent, aPosition, result);
-
-  return result;
-}
-
+  for (int32_t i = 0; i < mActionListeners.Count(); i++) {
+    mActionListeners[i]->DidCreateNode(nsDependentAtomString(aTag),
+                                       GetAsDOMNode(ret),
+                                       GetAsDOMNode(aParent), aPosition,
+                                       res);
+  }
+
+  return ret.forget();
+}
+
+
+NS_IMETHODIMP
+nsEditor::InsertNode(nsIDOMNode* aNode, nsIDOMNode* aParent, int32_t aPosition)
+{
+  nsCOMPtr<nsIContent> node = do_QueryInterface(aNode);
+  nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
+  NS_ENSURE_TRUE(node && parent, NS_ERROR_NULL_POINTER);
+
+  return InsertNode(*node, *parent, aPosition);
+}
 
 nsresult
-nsEditor::InsertNode(nsIContent* aContent, nsINode* aParent, int32_t aPosition)
-{
-  MOZ_ASSERT(aContent && aParent);
-  return InsertNode(GetAsDOMNode(aContent), GetAsDOMNode(aParent), aPosition);
-}
-
-NS_IMETHODIMP nsEditor::InsertNode(nsIDOMNode * aNode,
-                                   nsIDOMNode * aParent,
-                                   int32_t      aPosition)
-{
-  int32_t i;
+nsEditor::InsertNode(nsIContent& aNode, nsINode& aParent, int32_t aPosition)
+{
   nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
 
-  for (i = 0; i < mActionListeners.Count(); i++)
-    mActionListeners[i]->WillInsertNode(aNode, aParent, aPosition);
-
-  nsRefPtr<InsertElementTxn> txn;
-  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
-  nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
-  nsresult result = CreateTxnForInsertElement(node->AsDOMNode(), parent->AsDOMNode(),
-                                              aPosition, getter_AddRefs(txn));
-  if (NS_SUCCEEDED(result))  {
-    result = DoTransaction(txn);
-  }
-
-  mRangeUpdater.SelAdjInsertNode(aParent, aPosition);
-
-  for (i = 0; i < mActionListeners.Count(); i++)
-    mActionListeners[i]->DidInsertNode(aNode, aParent, aPosition, result);
-
-  return result;
+  for (int32_t i = 0; i < mActionListeners.Count(); i++) {
+    mActionListeners[i]->WillInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(),
+                                        aPosition);
+  }
+
+  nsRefPtr<InsertNodeTxn> txn = CreateTxnForInsertNode(aNode, aParent,
+                                                       aPosition);
+  nsresult res = DoTransaction(txn);
+
+  mRangeUpdater.SelAdjInsertNode(aParent.AsDOMNode(), aPosition);
+
+  for (int32_t i = 0; i < mActionListeners.Count(); i++) {
+    mActionListeners[i]->DidInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(),
+                                       aPosition, res);
+  }
+
+  return res;
 }
 
 
 NS_IMETHODIMP 
 nsEditor::SplitNode(nsIDOMNode * aNode,
                     int32_t      aOffset,
                     nsIDOMNode **aNewLeftNode)
 {
@@ -1550,207 +1567,165 @@ nsEditor::ReplaceContainer(Element* aOld
   }
   if (aCloneAttributes == eCloneAttributes) {
     CloneAttributes(ret, aOldContainer);
   }
   
   // notify our internal selection state listener
   // (Note: A nsAutoSelectionReset object must be created
   //  before calling this to initialize mRangeUpdater)
-  nsAutoReplaceContainerSelNotify selStateNotify(mRangeUpdater,
-      aOldContainer->AsDOMNode(), ret->AsDOMNode());
+  AutoReplaceContainerSelNotify selStateNotify(mRangeUpdater, aOldContainer,
+                                               ret);
   {
     nsAutoTxnsConserveSelection conserveSelection(this);
     while (aOldContainer->HasChildren()) {
       nsCOMPtr<nsIContent> child = aOldContainer->GetFirstChild();
 
       res = DeleteNode(child);
       NS_ENSURE_SUCCESS(res, nullptr);
 
-      res = InsertNode(child, ret, -1);
+      res = InsertNode(*child, *ret, -1);
       NS_ENSURE_SUCCESS(res, nullptr);
     }
   }
 
   // insert new container into tree
-  res = InsertNode(ret, parent, offset);
+  res = InsertNode(*ret, *parent, offset);
   NS_ENSURE_SUCCESS(res, nullptr);
   
   // delete old container
   res = DeleteNode(aOldContainer);
   NS_ENSURE_SUCCESS(res, nullptr);
 
   return ret.forget();
 }
 
-///////////////////////////////////////////////////////////////////////////
-// RemoveContainer: remove inNode, reparenting its children into their
-//                  the parent of inNode
+///////////////////////////////////////////////////////////////////////////////
+// RemoveContainer: remove inNode, reparenting its children (if any) into the
+//                  parent of inNode
 //
 nsresult
-nsEditor::RemoveContainer(nsIDOMNode* aNode)
-{
-  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
-  return RemoveContainer(node);
-}
-
-nsresult
-nsEditor::RemoveContainer(nsINode* aNode)
-{
-  NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
+nsEditor::RemoveContainer(nsIContent* aNode)
+{
+  MOZ_ASSERT(aNode);
 
   nsCOMPtr<nsINode> parent = aNode->GetParentNode();
   NS_ENSURE_STATE(parent);
 
   int32_t offset = parent->IndexOf(aNode);
-  
-  // loop through the child nodes of inNode and promote them
-  // into inNode's parent.
+
+  // Loop through the children of inNode and promote them into inNode's parent
   uint32_t nodeOrigLen = aNode->GetChildCount();
 
   // notify our internal selection state listener
-  nsAutoRemoveContainerSelNotify selNotify(mRangeUpdater, aNode, parent, offset, nodeOrigLen);
-                                   
+  nsAutoRemoveContainerSelNotify selNotify(mRangeUpdater, aNode, parent,
+                                           offset, nodeOrigLen);
+
   while (aNode->HasChildren()) {
     nsCOMPtr<nsIContent> child = aNode->GetLastChild();
-    nsresult rv = DeleteNode(child->AsDOMNode());
+    nsresult rv = DeleteNode(child);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = InsertNode(child->AsDOMNode(), parent->AsDOMNode(), offset);
+    rv = InsertNode(*child, *parent, offset);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  return DeleteNode(aNode->AsDOMNode());
-}
-
-
-///////////////////////////////////////////////////////////////////////////
-// InsertContainerAbove:  insert a new parent for inNode, returned in outNode,
-//                   which is contructed to be of type aNodeType.  outNode becomes
-//                   a child of inNode's earlier parent.
-//                   Callers responsibility to make sure inNode's can be child
-//                   of outNode, and outNode can be child of old parent.
-nsresult
-nsEditor::InsertContainerAbove( nsIDOMNode *inNode, 
-                                nsCOMPtr<nsIDOMNode> *outNode, 
-                                const nsAString &aNodeType,
-                                const nsAString *aAttribute,
-                                const nsAString *aValue)
-{
-  NS_ENSURE_TRUE(inNode && outNode, NS_ERROR_NULL_POINTER);
-
-  nsCOMPtr<nsIContent> node = do_QueryInterface(inNode);
-  NS_ENSURE_STATE(node);
-
-  nsCOMPtr<dom::Element> element;
-  nsresult rv = InsertContainerAbove(node, getter_AddRefs(element), aNodeType,
-                                     aAttribute, aValue);
-  *outNode = element ? element->AsDOMNode() : nullptr;
-  return rv;
-}
-
-nsresult
+  return DeleteNode(aNode);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// InsertContainerAbove: Insert a new parent for inNode, which is contructed to
+//                       be of type aNodeType.  outNode becomes a child of
+//                       inNode's earlier parent.  Caller's responsibility to
+//                       make sure inNode's can be child of outNode, and
+//                       outNode can be child of old parent.
+already_AddRefed<Element>
 nsEditor::InsertContainerAbove(nsIContent* aNode,
-                               dom::Element** aOutNode,
-                               const nsAString& aNodeType,
-                               const nsAString* aAttribute,
+                               nsIAtom* aNodeType,
+                               nsIAtom* aAttribute,
                                const nsAString* aValue)
 {
-  MOZ_ASSERT(aNode);
+  MOZ_ASSERT(aNode && aNodeType);
 
   nsCOMPtr<nsIContent> parent = aNode->GetParent();
-  NS_ENSURE_STATE(parent);
+  NS_ENSURE_TRUE(parent, nullptr);
   int32_t offset = parent->IndexOf(aNode);
 
-  // create new container
-  nsCOMPtr<Element> newContent =
-    CreateHTMLContent(nsCOMPtr<nsIAtom>(do_GetAtom(aNodeType)));
-  NS_ENSURE_STATE(newContent);
-
-  // set attribute if needed
+  // Create new container
+  nsCOMPtr<Element> newContent = CreateHTMLContent(aNodeType);
+  NS_ENSURE_TRUE(newContent, nullptr);
+
+  // Set attribute if needed
   nsresult res;
-  if (aAttribute && aValue && !aAttribute->IsEmpty()) {
-    nsIDOMNode* elem = newContent->AsDOMNode();
-    res = static_cast<nsIDOMElement*>(elem)->SetAttribute(*aAttribute, *aValue);
-    NS_ENSURE_SUCCESS(res, res);
-  }
-  
-  // notify our internal selection state listener
+  if (aAttribute && aValue && aAttribute != nsGkAtoms::_empty) {
+    res = newContent->SetAttr(kNameSpaceID_None, aAttribute, *aValue, true);
+    NS_ENSURE_SUCCESS(res, nullptr);
+  }
+
+  // Notify our internal selection state listener
   nsAutoInsertContainerSelNotify selNotify(mRangeUpdater);
-  
-  // put inNode in new parent, outNode
-  res = DeleteNode(aNode->AsDOMNode());
-  NS_ENSURE_SUCCESS(res, res);
+
+  // Put inNode in new parent, outNode
+  res = DeleteNode(aNode);
+  NS_ENSURE_SUCCESS(res, nullptr);
 
   {
     nsAutoTxnsConserveSelection conserveSelection(this);
-    res = InsertNode(aNode->AsDOMNode(), newContent->AsDOMNode(), 0);
-    NS_ENSURE_SUCCESS(res, res);
-  }
-
-  // put new parent in doc
-  res = InsertNode(newContent->AsDOMNode(), parent->AsDOMNode(), offset);
-  newContent.forget(aOutNode);
-  return res;  
+    res = InsertNode(*aNode, *newContent, 0);
+    NS_ENSURE_SUCCESS(res, nullptr);
+  }
+
+  // Put new parent in doc
+  res = InsertNode(*newContent, *parent, offset);
+  NS_ENSURE_SUCCESS(res, nullptr);
+
+  return newContent.forget();
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // MoveNode:  move aNode to {aParent,aOffset}
 nsresult
-nsEditor::MoveNode(nsIDOMNode* aNode, nsIDOMNode* aParent, int32_t aOffset)
-{
-  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
-  NS_ENSURE_STATE(node);
-
-  nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
-  NS_ENSURE_STATE(parent);
-
-  return MoveNode(node, parent, aOffset);
-}
-
-nsresult
-nsEditor::MoveNode(nsINode* aNode, nsINode* aParent, int32_t aOffset)
+nsEditor::MoveNode(nsIContent* aNode, nsINode* aParent, int32_t aOffset)
 {
   MOZ_ASSERT(aNode);
   MOZ_ASSERT(aParent);
   MOZ_ASSERT(aOffset == -1 ||
              (0 <= aOffset && SafeCast<uint32_t>(aOffset) <= aParent->Length()));
 
-  int32_t oldOffset;
-  nsCOMPtr<nsINode> oldParent = GetNodeLocation(aNode, &oldOffset);
-  
+  nsCOMPtr<nsINode> oldParent = aNode->GetParentNode();
+  int32_t oldOffset = oldParent ? oldParent->IndexOf(aNode) : -1;
+
   if (aOffset == -1) {
-    // Magic value meaning "move to end of aParent".
+    // Magic value meaning "move to end of aParent"
     aOffset = SafeCast<int32_t>(aParent->Length());
   }
-  
-  // Don't do anything if it's already in right place.
+
+  // Don't do anything if it's already in right place
   if (aParent == oldParent && aOffset == oldOffset) {
     return NS_OK;
   }
-  
-  // Notify our internal selection state listener.
+
+  // Notify our internal selection state listener
   nsAutoMoveNodeSelNotify selNotify(mRangeUpdater, oldParent, oldOffset,
                                     aParent, aOffset);
-  
-  // Need to adjust aOffset if we are moving aNode further along in its current
-  // parent.
+
+  // Need to adjust aOffset if we're moving aNode later in its current parent
   if (aParent == oldParent && oldOffset < aOffset) {
-    // This is because when we delete aNode, it will make the offsets after it
-    // off by one.
+    // When we delete aNode, it will make the offsets after it off by one
     aOffset--;
   }
 
-  // Hold a reference so aNode doesn't go away when we remove it (bug 772282).
+  // Hold a reference so aNode doesn't go away when we remove it (bug 772282)
   nsCOMPtr<nsINode> kungFuDeathGrip = aNode;
 
   nsresult rv = DeleteNode(aNode);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return InsertNode(aNode->AsDOMNode(), aParent->AsDOMNode(), aOffset);
+  return InsertNode(*aNode, *aParent, aOffset);
 }
 
 
 NS_IMETHODIMP
 nsEditor::AddEditorObserver(nsIEditorObserver *aObserver)
 {
   // we don't keep ownership of the observers.  They must
   // remove themselves as observers before they are destroyed.
@@ -2587,56 +2562,57 @@ NS_IMETHODIMP nsEditor::CreateTxnForInse
   {
     txn.forget(aTxn);
   }
 
   return rv;
 }
 
 
-NS_IMETHODIMP nsEditor::DeleteText(nsIDOMCharacterData *aElement,
-                              uint32_t             aOffset,
-                              uint32_t             aLength)
-{
-  nsRefPtr<DeleteTextTxn> txn;
-  nsresult result = CreateTxnForDeleteText(aElement, aOffset, aLength,
-                                           getter_AddRefs(txn));
+nsresult
+nsEditor::DeleteText(nsGenericDOMDataNode& aCharData, uint32_t aOffset,
+                     uint32_t aLength)
+{
+  nsRefPtr<DeleteTextTxn> txn =
+    CreateTxnForDeleteText(aCharData, aOffset, aLength);
+  NS_ENSURE_STATE(txn);
+
   nsAutoRules beginRulesSniffing(this, EditAction::deleteText, nsIEditor::ePrevious);
-  if (NS_SUCCEEDED(result))  
-  {
-    // let listeners know what's up
-    int32_t i;
-    for (i = 0; i < mActionListeners.Count(); i++)
-      mActionListeners[i]->WillDeleteText(aElement, aOffset, aLength);
-    
-    result = DoTransaction(txn); 
-    
-    // let listeners know what happened
-    for (i = 0; i < mActionListeners.Count(); i++)
-      mActionListeners[i]->DidDeleteText(aElement, aOffset, aLength, result);
-  }
-  return result;
-}
-
-
-nsresult
-nsEditor::CreateTxnForDeleteText(nsIDOMCharacterData* aElement,
-                                 uint32_t             aOffset,
-                                 uint32_t             aLength,
-                                 DeleteTextTxn**      aTxn)
-{
-  NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
-
-  nsRefPtr<DeleteTextTxn> txn = new DeleteTextTxn();
-
-  nsresult res = txn->Init(this, aElement, aOffset, aLength, &mRangeUpdater);
-  NS_ENSURE_SUCCESS(res, res);
-
-  txn.forget(aTxn);
-  return NS_OK;
+
+  // Let listeners know what's up
+  for (int32_t i = 0; i < mActionListeners.Count(); i++) {
+    mActionListeners[i]->WillDeleteText(
+        static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset,
+        aLength);
+  }
+
+  nsresult res = DoTransaction(txn);
+
+  // Let listeners know what happened
+  for (int32_t i = 0; i < mActionListeners.Count(); i++) {
+    mActionListeners[i]->DidDeleteText(
+        static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset,
+        aLength, res);
+  }
+
+  return res;
+}
+
+
+already_AddRefed<DeleteTextTxn>
+nsEditor::CreateTxnForDeleteText(nsGenericDOMDataNode& aCharData,
+                                 uint32_t aOffset, uint32_t aLength)
+{
+  nsRefPtr<DeleteTextTxn> txn =
+    new DeleteTextTxn(*this, aCharData, aOffset, aLength, &mRangeUpdater);
+
+  nsresult res = txn->Init();
+  NS_ENSURE_SUCCESS(res, nullptr);
+
+  return txn.forget();
 }
 
 
 
 
 NS_IMETHODIMP nsEditor::CreateTxnForSplitNode(nsIDOMNode *aNode,
                                          uint32_t    aOffset,
                                          SplitElementTxn **aTxn)
@@ -4141,32 +4117,31 @@ nsEditor::DeleteSelectionImpl(EDirection
   return res;
 }
 
 // XXX: error handling in this routine needs to be cleaned up!
 NS_IMETHODIMP
 nsEditor::DeleteSelectionAndCreateNode(const nsAString& aTag,
                                            nsIDOMNode ** aNewNode)
 {
+  nsCOMPtr<nsIAtom> tag = do_GetAtom(aTag);
+
   nsresult result = DeleteSelectionAndPrepareToCreateNode();
   NS_ENSURE_SUCCESS(result, result);
 
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
   nsCOMPtr<nsINode> node = selection->GetAnchorNode();
   uint32_t offset = selection->AnchorOffset();
 
   nsCOMPtr<nsIDOMNode> newNode;
-  result = CreateNode(aTag, node->AsDOMNode(), offset,
-                      getter_AddRefs(newNode));
+  *aNewNode = GetAsDOMNode(CreateNode(tag, node, offset).take());
   // XXX: ERROR_HANDLING  check result, and make sure aNewNode is set correctly
   // in success/failure cases
-  *aNewNode = newNode;
-  NS_IF_ADDREF(*aNewNode);
 
   // we want the selection to be just after the new node
   return selection->Collapse(node, offset + 1);
 }
 
 
 /* Non-interface, protected methods */
 
@@ -4312,51 +4287,36 @@ nsEditor::CreateTxnForRemoveAttribute(ns
   {
     txn.forget(aTxn);
   }
 
   return rv;
 }
 
 
-NS_IMETHODIMP nsEditor::CreateTxnForCreateElement(const nsAString& aTag,
-                                                  nsIDOMNode     *aParent,
-                                                  int32_t         aPosition,
-                                                  CreateElementTxn ** aTxn)
-{
-  NS_ENSURE_TRUE(aParent, NS_ERROR_NULL_POINTER);
-
-  nsRefPtr<CreateElementTxn> txn = new CreateElementTxn();
-
-  nsresult rv = txn->Init(this, aTag, aParent, aPosition);
-  if (NS_SUCCEEDED(rv))
-  {
-    txn.forget(aTxn);
-  }
-
-  return rv;
-}
-
-
-NS_IMETHODIMP nsEditor::CreateTxnForInsertElement(nsIDOMNode * aNode,
-                                                  nsIDOMNode * aParent,
-                                                  int32_t      aPosition,
-                                                  InsertElementTxn ** aTxn)
-{
-  NS_ENSURE_TRUE(aNode && aParent, NS_ERROR_NULL_POINTER);
-
-  nsRefPtr<InsertElementTxn> txn = new InsertElementTxn();
-
-  nsresult rv = txn->Init(aNode, aParent, aPosition, this);
-  if (NS_SUCCEEDED(rv))
-  {
-    txn.forget(aTxn);
-  }
-
-  return rv;
+already_AddRefed<CreateElementTxn>
+nsEditor::CreateTxnForCreateElement(nsIAtom& aTag,
+                                    nsINode& aParent,
+                                    int32_t aPosition)
+{
+  nsRefPtr<CreateElementTxn> txn =
+    new CreateElementTxn(*this, aTag, aParent, aPosition);
+
+  return txn.forget();
+}
+
+
+already_AddRefed<InsertNodeTxn>
+nsEditor::CreateTxnForInsertNode(nsIContent& aNode,
+                                 nsINode& aParent,
+                                 int32_t aPosition)
+{
+  nsRefPtr<InsertNodeTxn> txn = new InsertNodeTxn(aNode, aParent, aPosition,
+                                                  *this);
+  return txn.forget();
 }
 
 nsresult
 nsEditor::CreateTxnForDeleteNode(nsINode* aNode, DeleteNodeTxn** aTxn)
 {
   NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
 
   nsRefPtr<DeleteNodeTxn> txn = new DeleteNodeTxn();
@@ -4461,49 +4421,47 @@ nsEditor::CreateTxnForDeleteSelection(ED
     }
   }
 
   aggTxn.forget(aTxn);
 
   return NS_OK;
 }
 
-nsresult
-nsEditor::CreateTxnForDeleteCharacter(nsIDOMCharacterData* aData,
-                                      uint32_t             aOffset,
-                                      EDirection           aDirection,
-                                      DeleteTextTxn**      aTxn)
+already_AddRefed<DeleteTextTxn>
+nsEditor::CreateTxnForDeleteCharacter(nsGenericDOMDataNode& aData,
+                                      uint32_t aOffset, EDirection aDirection)
 {
   NS_ASSERTION(aDirection == eNext || aDirection == ePrevious,
-               "invalid direction");
+               "Invalid direction");
   nsAutoString data;
-  aData->GetData(data);
+  aData.GetData(data);
   NS_ASSERTION(data.Length(), "Trying to delete from a zero-length node");
-  NS_ENSURE_STATE(data.Length());
+  NS_ENSURE_TRUE(data.Length(), nullptr);
 
   uint32_t segOffset = aOffset, segLength = 1;
   if (aDirection == eNext) {
     if (segOffset + 1 < data.Length() &&
         NS_IS_HIGH_SURROGATE(data[segOffset]) &&
         NS_IS_LOW_SURROGATE(data[segOffset+1])) {
-      // delete both halves of the surrogate pair
+      // Delete both halves of the surrogate pair
       ++segLength;
     }
   } else if (aOffset > 0) {
     --segOffset;
     if (segOffset > 0 &&
       NS_IS_LOW_SURROGATE(data[segOffset]) &&
       NS_IS_HIGH_SURROGATE(data[segOffset-1])) {
       ++segLength;
       --segOffset;
     }
   } else {
-    return NS_ERROR_FAILURE;
-  }
-  return CreateTxnForDeleteText(aData, segOffset, segLength, aTxn);
+    return nullptr;
+  }
+  return CreateTxnForDeleteText(aData, segOffset, segLength);
 }
 
 //XXX: currently, this doesn't handle edge conditions because GetNext/GetPrior
 //are not implemented
 nsresult
 nsEditor::CreateTxnForDeleteInsertionPoint(nsRange*          aRange,
                                            EDirection        aAction,
                                            EditAggregateTxn* aTxn,
@@ -4518,17 +4476,16 @@ nsEditor::CreateTxnForDeleteInsertionPoi
   // get the node and offset of the insertion point
   nsCOMPtr<nsINode> node = aRange->GetStartParent();
   NS_ENSURE_STATE(node);
 
   int32_t offset = aRange->StartOffset();
 
   // determine if the insertion point is at the beginning, middle, or end of
   // the node
-  nsCOMPtr<nsIDOMCharacterData> nodeAsCharData = do_QueryInterface(node);
 
   uint32_t count = node->Length();
 
   bool isFirst = (0 == offset);
   bool isLast  = (count == (uint32_t)offset);
 
   // XXX: if isFirst && isLast, then we'll need to delete the node
   //      as well as the 1 child
@@ -4538,26 +4495,25 @@ nsEditor::CreateTxnForDeleteInsertionPoi
   if (aAction == ePrevious && isFirst) {
     // we're backspacing from the beginning of the node.  Delete the first
     // thing to our left
     nsCOMPtr<nsIContent> priorNode = GetPriorNode(node, true);
     NS_ENSURE_STATE(priorNode);
 
     // there is a priorNode, so delete its last child (if chardata, delete the
     // last char). if it has no children, delete it
-    nsCOMPtr<nsIDOMCharacterData> priorNodeAsCharData =
-      do_QueryInterface(priorNode);
-    if (priorNodeAsCharData) {
+    if (priorNode->IsNodeOfType(nsINode::eDATA_NODE)) {
+      nsRefPtr<nsGenericDOMDataNode> priorNodeAsCharData =
+        static_cast<nsGenericDOMDataNode*>(priorNode.get());
       uint32_t length = priorNode->Length();
       // Bail out for empty chardata XXX: Do we want to do something else?
       NS_ENSURE_STATE(length);
-      nsRefPtr<DeleteTextTxn> txn;
-      res = CreateTxnForDeleteCharacter(priorNodeAsCharData, length,
-                                        ePrevious, getter_AddRefs(txn));
-      NS_ENSURE_SUCCESS(res, res);
+      nsRefPtr<DeleteTextTxn> txn =
+        CreateTxnForDeleteCharacter(*priorNodeAsCharData, length, ePrevious);
+      NS_ENSURE_STATE(txn);
 
       *aOffset = txn->GetOffset();
       *aLength = txn->GetNumCharsToDelete();
       aTxn->AppendChild(txn);
     } else {
       // priorNode is not chardata, so tell its parent to delete it
       nsRefPtr<DeleteNodeTxn> txn;
       res = CreateTxnForDeleteNode(priorNode, getter_AddRefs(txn));
@@ -4574,26 +4530,25 @@ nsEditor::CreateTxnForDeleteInsertionPoi
   if (aAction == eNext && isLast) {
     // we're deleting from the end of the node.  Delete the first thing to our
     // right
     nsCOMPtr<nsIContent> nextNode = GetNextNode(node, true);
     NS_ENSURE_STATE(nextNode);
 
     // there is a nextNode, so delete its first child (if chardata, delete the
     // first char). if it has no children, delete it
-    nsCOMPtr<nsIDOMCharacterData> nextNodeAsCharData =
-      do_QueryInterface(nextNode);
-    if (nextNodeAsCharData) {
+    if (nextNode->IsNodeOfType(nsINode::eDATA_NODE)) {
+      nsRefPtr<nsGenericDOMDataNode> nextNodeAsCharData =
+        static_cast<nsGenericDOMDataNode*>(nextNode.get());
       uint32_t length = nextNode->Length();
       // Bail out for empty chardata XXX: Do we want to do something else?
       NS_ENSURE_STATE(length);
-      nsRefPtr<DeleteTextTxn> txn;
-      res = CreateTxnForDeleteCharacter(nextNodeAsCharData, 0, eNext,
-                                        getter_AddRefs(txn));
-      NS_ENSURE_SUCCESS(res, res);
+      nsRefPtr<DeleteTextTxn> txn =
+        CreateTxnForDeleteCharacter(*nextNodeAsCharData, 0, eNext);
+      NS_ENSURE_STATE(txn);
 
       *aOffset = txn->GetOffset();
       *aLength = txn->GetNumCharsToDelete();
       aTxn->AppendChild(txn);
     } else {
       // nextNode is not chardata, so tell its parent to delete it
       nsRefPtr<DeleteNodeTxn> txn;
       res = CreateTxnForDeleteNode(nextNode, getter_AddRefs(txn));
@@ -4601,22 +4556,23 @@ nsEditor::CreateTxnForDeleteInsertionPoi
       aTxn->AppendChild(txn);
     }
 
     NS_ADDREF(*aNode = nextNode);
 
     return NS_OK;
   }
 
-  if (nodeAsCharData) {
+  if (node->IsNodeOfType(nsINode::eDATA_NODE)) {
+    nsRefPtr<nsGenericDOMDataNode> nodeAsCharData =
+      static_cast<nsGenericDOMDataNode*>(node.get());
     // we have chardata, so delete a char at the proper offset
-    nsRefPtr<DeleteTextTxn> txn;
-    res = CreateTxnForDeleteCharacter(nodeAsCharData, offset, aAction,
-                                      getter_AddRefs(txn));
-    NS_ENSURE_SUCCESS(res, res);
+    nsRefPtr<DeleteTextTxn> txn = CreateTxnForDeleteCharacter(*nodeAsCharData,
+                                                              offset, aAction);
+    NS_ENSURE_STATE(txn);
 
     aTxn->AppendChild(txn);
     NS_ADDREF(*aNode = node);
     *aOffset = txn->GetOffset();
     *aLength = txn->GetNumCharsToDelete();
   } else {
     // we're either deleting a node or chardata, need to dig into the next/prev
     // node to find out
@@ -4634,28 +4590,27 @@ nsEditor::CreateTxnForDeleteInsertionPoi
       if (aAction == ePrevious) {
         selectedNode = GetPriorNode(selectedNode, true);
       } else if (aAction == eNext) {
         selectedNode = GetNextNode(selectedNode, true);
       }
     }
     NS_ENSURE_STATE(selectedNode);
 
-    nsCOMPtr<nsIDOMCharacterData> selectedNodeAsCharData =
-      do_QueryInterface(selectedNode);
-    if (selectedNodeAsCharData) {
+    if (selectedNode->IsNodeOfType(nsINode::eDATA_NODE)) {
+      nsRefPtr<nsGenericDOMDataNode> selectedNodeAsCharData =
+        static_cast<nsGenericDOMDataNode*>(selectedNode.get());
       // we are deleting from a chardata node, so do a character deletion
       uint32_t position = 0;
       if (aAction == ePrevious) {
         position = selectedNode->Length();
       }
-      nsRefPtr<DeleteTextTxn> delTextTxn;
-      res = CreateTxnForDeleteCharacter(selectedNodeAsCharData, position,
-                                        aAction, getter_AddRefs(delTextTxn));
-      NS_ENSURE_SUCCESS(res, res);
+      nsRefPtr<DeleteTextTxn> delTextTxn =
+        CreateTxnForDeleteCharacter(*selectedNodeAsCharData, position,
+                                    aAction);
       NS_ENSURE_TRUE(delTextTxn, NS_ERROR_NULL_POINTER);
 
       aTxn->AppendChild(delTextTxn);
       *aOffset = delTextTxn->GetOffset();
       *aLength = delTextTxn->GetNumCharsToDelete();
     } else {
       nsRefPtr<DeleteNodeTxn> delElementTxn;
       res = CreateTxnForDeleteNode(selectedNode, getter_AddRefs(delElementTxn));
--- a/editor/libeditor/nsEditor.h
+++ b/editor/libeditor/nsEditor.h
@@ -24,22 +24,19 @@
 #include "nsLiteralString.h"            // for NS_LITERAL_STRING
 #include "nsSelectionState.h"           // for nsRangeUpdater, etc
 #include "nsString.h"                   // for nsCString
 #include "nsWeakReference.h"            // for nsSupportsWeakReference
 #include "nscore.h"                     // for nsresult, nsAString, etc
 
 class AddStyleSheetTxn;
 class ChangeAttributeTxn;
-class CreateElementTxn;
 class DeleteNodeTxn;
-class DeleteTextTxn;
 class EditAggregateTxn;
 class IMETextTxn;
-class InsertElementTxn;
 class InsertTextTxn;
 class JoinElementTxn;
 class RemoveStyleSheetTxn;
 class SplitElementTxn;
 class nsIAtom;
 class nsIContent;
 class nsIDOMCharacterData;
 class nsIDOMDataTransfer;
@@ -67,19 +64,22 @@ class nsString;
 class nsTransactionManager;
 
 namespace mozilla {
 class CSSStyleSheet;
 class ErrorResult;
 class TextComposition;
 
 namespace dom {
+class CreateElementTxn;
 class DataTransfer;
+class DeleteTextTxn;
 class Element;
 class EventTarget;
+class InsertNodeTxn;
 class Selection;
 class Text;
 }  // namespace dom
 }  // namespace mozilla
 
 namespace mozilla {
 namespace widget {
 struct IMEState;
@@ -195,17 +195,16 @@ public:
   /* ------------ nsIObserver methods -------------- */
   NS_DECL_NSIOBSERVER
 
   // nsIPhonetic
   NS_DECL_NSIPHONETIC
 
 public:
 
-  nsresult MarkNodeDirty(nsINode* aNode);
   virtual bool IsModifiableNode(nsINode *aNode);
 
   NS_IMETHOD InsertTextImpl(const nsAString& aStringToInsert, 
                                nsCOMPtr<nsIDOMNode> *aInOutNode, 
                                int32_t *aInOutOffset,
                                nsIDOMDocument *aDoc);
   nsresult InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
                                       mozilla::dom::Text* aTextNode,
@@ -217,43 +216,35 @@ public:
                                       bool aSuppressIME = false);
   NS_IMETHOD DeleteSelectionImpl(EDirection aAction,
                                  EStripWrappers aStripWrappers);
   NS_IMETHOD DeleteSelectionAndCreateNode(const nsAString& aTag,
                                            nsIDOMNode ** aNewNode);
 
   /* helper routines for node/parent manipulations */
   nsresult DeleteNode(nsINode* aNode);
-  nsresult InsertNode(nsIContent* aContent, nsINode* aParent,
-                      int32_t aPosition);
+  nsresult InsertNode(nsIContent& aNode, nsINode& aParent, int32_t aPosition);
   enum ECloneAttributes { eDontCloneAttributes, eCloneAttributes };
   already_AddRefed<mozilla::dom::Element> ReplaceContainer(
                             mozilla::dom::Element* aOldContainer,
                             nsIAtom* aNodeType,
                             nsIAtom* aAttribute = nullptr,
                             const nsAString* aValue = nullptr,
                             ECloneAttributes aCloneAttributes = eDontCloneAttributes);
   void CloneAttributes(mozilla::dom::Element* aDest,
                        mozilla::dom::Element* aSource);
 
-  nsresult RemoveContainer(nsINode* aNode);
-  nsresult RemoveContainer(nsIDOMNode *inNode);
-  nsresult InsertContainerAbove(nsIContent* aNode,
-                                mozilla::dom::Element** aOutNode,
-                                const nsAString& aNodeType,
-                                const nsAString* aAttribute = nullptr,
+  nsresult RemoveContainer(nsIContent* aNode);
+  already_AddRefed<mozilla::dom::Element> InsertContainerAbove(
+                                nsIContent* aNode,
+                                nsIAtom* aNodeType,
+                                nsIAtom* aAttribute = nullptr,
                                 const nsAString* aValue = nullptr);
-  nsresult InsertContainerAbove(nsIDOMNode *inNode, 
-                                nsCOMPtr<nsIDOMNode> *outNode, 
-                                const nsAString &aNodeType,
-                                const nsAString *aAttribute = nullptr,
-                                const nsAString *aValue = nullptr);
   nsresult JoinNodes(nsINode* aNodeToKeep, nsIContent* aNodeToMove);
-  nsresult MoveNode(nsINode* aNode, nsINode* aParent, int32_t aOffset);
-  nsresult MoveNode(nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aOffset);
+  nsresult MoveNode(nsIContent* aNode, nsINode* aParent, int32_t aOffset);
 
   /* Method to replace certain CreateElementNS() calls. 
      Arguments:
       nsIAtom* aTag          - tag you want
   */
   already_AddRefed<mozilla::dom::Element> CreateHTMLContent(nsIAtom* aTag);
 
   // IME event handlers
@@ -277,27 +268,29 @@ protected:
   /** create a transaction for removing aAttribute on aElement
     */
   NS_IMETHOD CreateTxnForRemoveAttribute(nsIDOMElement *aElement,
                                          const nsAString &  aAttribute,
                                          ChangeAttributeTxn ** aTxn);
 
   /** create a transaction for creating a new child node of aParent of type aTag.
     */
-  NS_IMETHOD CreateTxnForCreateElement(const nsAString & aTag,
-                                       nsIDOMNode      *aParent,
-                                       int32_t         aPosition,
-                                       CreateElementTxn ** aTxn);
+  already_AddRefed<mozilla::dom::CreateElementTxn>
+  CreateTxnForCreateElement(nsIAtom& aTag,
+                            nsINode& aParent,
+                            int32_t aPosition);
+
+  already_AddRefed<mozilla::dom::Element> CreateNode(nsIAtom* aTag,
+                                                     nsINode* aParent,
+                                                     int32_t aPosition);
 
   /** create a transaction for inserting aNode as a child of aParent.
     */
-  NS_IMETHOD CreateTxnForInsertElement(nsIDOMNode * aNode,
-                                       nsIDOMNode * aParent,
-                                       int32_t      aOffset,
-                                       InsertElementTxn ** aTxn);
+  already_AddRefed<mozilla::dom::InsertNodeTxn>
+  CreateTxnForInsertNode(nsIContent& aNode, nsINode& aParent, int32_t aOffset);
 
   /** create a transaction for removing aNode from its parent.
     */
   nsresult CreateTxnForDeleteNode(nsINode* aNode, DeleteNodeTxn** aTxn);
 
 
   nsresult CreateTxnForDeleteSelection(EDirection aAction,
                                        EditAggregateTxn** aTxn,
@@ -329,38 +322,28 @@ protected:
   NS_IMETHOD CreateTxnForAddStyleSheet(mozilla::CSSStyleSheet* aSheet,
                                        AddStyleSheetTxn* *aTxn);
 
   /** create a transaction for removing a style sheet
     */
   NS_IMETHOD CreateTxnForRemoveStyleSheet(mozilla::CSSStyleSheet* aSheet,
                                           RemoveStyleSheetTxn* *aTxn);
   
-  NS_IMETHOD DeleteText(nsIDOMCharacterData *aElement,
-                        uint32_t             aOffset,
-                        uint32_t             aLength);
-
-  inline nsresult DeleteText(mozilla::dom::Text* aText, uint32_t aOffset,
-                             uint32_t aLength)
-  {
-    return DeleteText(static_cast<nsIDOMCharacterData*>(GetAsDOMNode(aText)),
-                      aOffset, aLength);
-  }
+  nsresult DeleteText(nsGenericDOMDataNode& aElement,
+                      uint32_t aOffset, uint32_t aLength);
 
 //  NS_IMETHOD DeleteRange(nsIDOMRange *aRange);
 
-  nsresult CreateTxnForDeleteText(nsIDOMCharacterData* aElement,
-                                  uint32_t             aOffset,
-                                  uint32_t             aLength,
-                                  DeleteTextTxn**      aTxn);
+  already_AddRefed<mozilla::dom::DeleteTextTxn>
+  CreateTxnForDeleteText(nsGenericDOMDataNode& aElement,
+                         uint32_t aOffset, uint32_t aLength);
 
-  nsresult CreateTxnForDeleteCharacter(nsIDOMCharacterData* aData,
-                                       uint32_t             aOffset,
-                                       EDirection           aDirection,
-                                       DeleteTextTxn**      aTxn);
+  already_AddRefed<mozilla::dom::DeleteTextTxn>
+  CreateTxnForDeleteCharacter(nsGenericDOMDataNode& aData, uint32_t aOffset,
+                              EDirection aDirection);
 	
   NS_IMETHOD CreateTxnForSplitNode(nsIDOMNode *aNode,
                                    uint32_t    aOffset,
                                    SplitElementTxn **aTxn);
 
   NS_IMETHOD CreateTxnForJoinNode(nsIDOMNode  *aLeftNode,
                                   nsIDOMNode  *aRightNode,
                                   JoinElementTxn **aTxn);
--- a/editor/libeditor/nsHTMLAbsPosition.cpp
+++ b/editor/libeditor/nsHTMLAbsPosition.cpp
@@ -564,17 +564,17 @@ nsHTMLEditor::AbsolutelyPositionElement(
     }
 
     nsCOMPtr<dom::Element> element = do_QueryInterface(aElement);
     if (element && element->IsHTML(nsGkAtoms::div) && !HasStyleOrIdOrClass(element)) {
       nsRefPtr<nsHTMLEditRules> htmlRules = static_cast<nsHTMLEditRules*>(mRules.get());
       NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
       nsresult res = htmlRules->MakeSureElemStartsOrEndsOnCR(aElement);
       NS_ENSURE_SUCCESS(res, res);
-      res = RemoveContainer(aElement);
+      res = RemoveContainer(element);
       NS_ENSURE_SUCCESS(res, res);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLEditor::SetSnapToGridEnabled(bool aEnabled)
--- a/editor/libeditor/nsHTMLCSSUtils.cpp
+++ b/editor/libeditor/nsHTMLCSSUtils.cpp
@@ -608,17 +608,17 @@ nsHTMLCSSUtils::RemoveCSSInlineStyle(nsI
   NS_ENSURE_SUCCESS(res, res);
 
   nsCOMPtr<dom::Element> element = do_QueryInterface(aNode);
   if (!element || !element->IsHTML(nsGkAtoms::span) ||
       nsHTMLEditor::HasAttributes(element)) {
     return NS_OK;
   }
 
-  return mHTMLEditor->RemoveContainer(aNode);
+  return mHTMLEditor->RemoveContainer(element);
 }
 
 // Answers true is the property can be removed by setting a "none" CSS value
 // on a node
 bool
 nsHTMLCSSUtils::IsCSSInvertable(nsIAtom *aProperty, const nsAString *aAttribute)
 {
   return bool(nsEditProperty::b == aProperty);
--- a/editor/libeditor/nsHTMLEditRules.cpp
+++ b/editor/libeditor/nsHTMLEditRules.cpp
@@ -1748,17 +1748,17 @@ nsHTMLEditRules::StandardBreakImpl(nsIDO
       // from being in different inline nodes, which would break
       // SetInterlinePosition().  It will also assure that if the user clicks
       // away and then clicks back on their new blank line, they will still
       // get the style from the line above.
       int32_t brOffset;
       nsCOMPtr<nsIDOMNode> brParent = nsEditor::GetNodeLocation(GetAsDOMNode(secondBR), &brOffset);
       if (brParent != node || brOffset != aOffset + 1) {
         NS_ENSURE_STATE(mHTMLEditor);
-        res = mHTMLEditor->MoveNode(GetAsDOMNode(secondBR), node, aOffset+1);
+        res = mHTMLEditor->MoveNode(secondBR->AsContent(), node_, aOffset + 1);
         NS_ENSURE_SUCCESS(res, res);
       }
     }
     // SetInterlinePosition(true) means we want the caret to stick to the
     // content on the "right".  We want the caret to stick to whatever is past
     // the break.  This is because the break is on the same line we were on,
     // but the next content will be on the following line.
 
@@ -2051,19 +2051,20 @@ nsHTMLEditRules::WillDeleteSelection(Sel
         res = range->GetEndOffset(&eo);
         NS_ENSURE_SUCCESS(res, res);
       }
       NS_ENSURE_STATE(mHTMLEditor);
       res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
           address_of(visNode_), &so, address_of(visNode_), &eo);
       NS_ENSURE_SUCCESS(res, res);
       visNode = GetAsDOMNode(visNode_);
-      nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(visNode));
-      NS_ENSURE_STATE(mHTMLEditor);
-      res = mHTMLEditor->DeleteText(nodeAsText, std::min(so, eo), DeprecatedAbs(eo - so));
+      nsRefPtr<Text> nodeAsText = visNode_->GetAsText();
+      NS_ENSURE_STATE(mHTMLEditor);
+      res = mHTMLEditor->DeleteText(*nodeAsText, std::min(so, eo),
+                                    DeprecatedAbs(eo - so));
       *aHandled = true;
       NS_ENSURE_SUCCESS(res, res);    
       res = InsertBRIfNeeded(aSelection);
       return res;
     } else if (wsType == WSType::special || wsType == WSType::br ||
                nsHTMLEditUtils::IsHR(visNode)) {
       // short circuit for invisible breaks.  delete them and recurse.
       if (nsTextEditUtils::IsBreak(visNode) &&
@@ -2522,37 +2523,39 @@ nsHTMLEditRules::WillDeleteSelection(Sel
         // we can assume that if text node is found, we can
         // delete to end or to begining as appropriate,
         // since the case where both sel endpoints in same
         // text node was already handled (we wouldn't be here)
         NS_ENSURE_STATE(mHTMLEditor);
         if ( mHTMLEditor->IsTextNode(startNode) )
         {
           // delete to last character
-          nsCOMPtr<nsIDOMCharacterData>nodeAsText;
-          uint32_t len;
-          nodeAsText = do_QueryInterface(startNode);
-          nodeAsText->GetLength(&len);
+          nsCOMPtr<nsINode> node = do_QueryInterface(startNode);
+          uint32_t len = node->Length();
           if (len > (uint32_t)startOffset)
           {
+            nsRefPtr<nsGenericDOMDataNode> dataNode =
+              static_cast<nsGenericDOMDataNode*>(node.get());
             NS_ENSURE_STATE(mHTMLEditor);
-            res = mHTMLEditor->DeleteText(nodeAsText,startOffset,len-startOffset);
+            res = mHTMLEditor->DeleteText(*dataNode, startOffset,
+                                          len - startOffset);
             NS_ENSURE_SUCCESS(res, res);
           }
         }
         NS_ENSURE_STATE(mHTMLEditor);
         if ( mHTMLEditor->IsTextNode(endNode) )
         {
           // delete to first character
-          nsCOMPtr<nsIDOMCharacterData>nodeAsText;
-          nodeAsText = do_QueryInterface(endNode);
+          nsCOMPtr<nsINode> node = do_QueryInterface(endNode);
           if (endOffset)
           {
             NS_ENSURE_STATE(mHTMLEditor);
-            res = mHTMLEditor->DeleteText(nodeAsText,0,endOffset);
+            nsRefPtr<nsGenericDOMDataNode> dataNode =
+              static_cast<nsGenericDOMDataNode*>(node.get());
+            res = mHTMLEditor->DeleteText(*dataNode, 0, endOffset);
             NS_ENSURE_SUCCESS(res, res);
           }
         }
 
         if (join) {
           res = JoinBlocks(leftParent, rightParent, aCancel);
           NS_ENSURE_SUCCESS(res, res);
         }
@@ -2799,26 +2802,26 @@ nsHTMLEditRules::JoinBlocks(nsIDOMNode *
     // Do br adjustment.
     nsCOMPtr<nsIDOMNode> brNode;
     res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode));
     NS_ENSURE_SUCCESS(res, res);
     if (bMergeLists)
     {
       // idea here is to take all children in  rightList that are past
       // theOffset, and pull them into leftlist.
-      nsCOMPtr<nsIDOMNode> childToMove;
       nsCOMPtr<nsIContent> parent(do_QueryInterface(rightList));
       NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
 
       nsIContent *child = parent->GetChildAt(theOffset);
+      nsCOMPtr<nsINode> leftList_ = do_QueryInterface(leftList);
+      NS_ENSURE_STATE(leftList_);
       while (child)
       {
-        childToMove = do_QueryInterface(child);
         NS_ENSURE_STATE(mHTMLEditor);
-        res = mHTMLEditor->MoveNode(childToMove, leftList, -1);
+        res = mHTMLEditor->MoveNode(child, leftList_, -1);
         NS_ENSURE_SUCCESS(res, res);
 
         child = parent->GetChildAt(rightOffset);
       }
     }
     else
     {
       res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset);
@@ -3007,25 +3010,27 @@ nsHTMLEditRules::MoveBlock(nsIDOMNode *a
 *    inserted content.
 *         nsIDOMNode *aSource       the selection.  
 *         nsIDOMNode *aDest         parent to receive moved content
 *         int32_t *aOffset          offset in aDest to move content to
 */
 nsresult
 nsHTMLEditRules::MoveNodeSmart(nsIDOMNode *aSource, nsIDOMNode *aDest, int32_t *aOffset)
 {
-  NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER);
+  nsCOMPtr<nsIContent> source = do_QueryInterface(aSource);
+  nsCOMPtr<nsINode> dest = do_QueryInterface(aDest);
+  NS_ENSURE_TRUE(source && dest && aOffset, NS_ERROR_NULL_POINTER);
 
   nsresult res;
   // check if this node can go into the destination node
   NS_ENSURE_STATE(mHTMLEditor);
   if (mHTMLEditor->CanContain(aDest, aSource)) {
     // if it can, move it there
     NS_ENSURE_STATE(mHTMLEditor);
-    res = mHTMLEditor->MoveNode(aSource, aDest, *aOffset);
+    res = mHTMLEditor->MoveNode(source, dest, *aOffset);
     NS_ENSURE_SUCCESS(res, res);
     if (*aOffset != -1) ++(*aOffset);
   }
   else