Merge last PGO-green changeset of mozilla-inbound to mozilla-central
authorEd Morley <emorley@mozilla.com>
Fri, 07 Dec 2012 14:14:03 +0000
changeset 115317 8432562db685e244ab6431a8bc307d8a32183451
parent 115316 65209d041acc1b309dd74eeea428e8cc7669413d (current diff)
parent 115292 143acb93b300318cd1e44a6f9f24ed2aa1811334 (diff)
child 115318 925148509c71a849cda46643e4ead04637b62ed6
child 115333 83d589dc84c1d831c104368d78f382faef1c79c9
push id19273
push useremorley@mozilla.com
push dateFri, 07 Dec 2012 14:18:32 +0000
treeherdermozilla-inbound@925148509c71 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone20.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 last PGO-green changeset of mozilla-inbound to mozilla-central
image/test/reftest/generic/accept-image-catchall-ref.html
image/test/reftest/generic/accept-image-catchall.html
image/test/reftest/generic/check-header.sjs
image/test/reftest/generic/green.png
js/src/jit-test/progressbar.py
media/webrtc/signaling/src/sipcc/core/common/vcm_util.c
media/webrtc/signaling/src/sipcc/core/includes/vcm_util.h
python/mozbuild/mozbuild/base.py
toolkit/components/downloads/test/unit/test_private_resume.js
toolkit/components/downloads/test/unit/test_privatebrowsing.js
toolkit/components/downloads/test/unit/test_privatebrowsing_cancel.js
toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
widget/tests/test_bug462106.xul
--- a/accessible/tests/mochitest/states/test_link.html
+++ b/accessible/tests/mochitest/states/test_link.html
@@ -16,31 +16,33 @@
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
   <script type="application/javascript"
           src="../events.js"></script>
 
   <script type="application/javascript">
-    gA11yEventDumpToConsole = true; // debug stuff
+    //gA11yEventDumpToConsole = true; // debug stuff
 
     var gLinkWindow = null;
     function closeDocChecker()
     {
       this.__proto__ = new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE);
 
       this.check = function closeDocChecker_check(aEvent)
       {
         gLinkWindow = aEvent.accessible.rootDocument.window;
       }
 
       this.match = function closeDocChecker_match(aEvent)
       {
-        return true;
+        // A temporary about:blank document gets loaded before 'example.com'
+        // document.
+        return aEvent.DOMNode.URL == "http://www.example.com/";
       }
     }
 
     function clickLink(aID)
     {
       this.eventSeq = [
         new stateChangeChecker(STATE_TRAVERSED, false, true, "link_traversed"),
         new closeDocChecker()
@@ -81,21 +83,27 @@
 
       // a (no @href, no click event listener)
       testStates("link_notlink", 0, 0, STATE_LINKED);
 
       // a: no traversed state
       testStates("link_traversed", 0, 0, STATE_TRAVERSED);
 
       // a: traversed state
-      enableLogging("docload"); // debug stuff
+      //enableLogging("docload"); // debug stuff
 
       gQueue = new eventQueue();
       gQueue.push(new clickLink("link_traversed"));
-      gQueue.onFinish = function() { gLinkWindow.close(); disableLogging(); }
+      gQueue.onFinish =
+        function()
+        {
+          gLinkWindow.close();
+          //disableLogging(); // debug stuff
+        }
+
       gQueue.invoke(); // will call SimpleTest.finsih();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 
 </head>
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -402,16 +402,20 @@ var shell = {
         DOMApplicationRegistry.allAppsLaunchable = true;
 
         this.sendEvent(window, 'ContentStart');
 
         content.addEventListener('load', function shell_homeLoaded() {
           content.removeEventListener('load', shell_homeLoaded);
           shell.isHomeLoaded = true;
 
+#ifdef MOZ_WIDGET_GONK
+          libcutils.property_set('sys.boot_completed', '1');
+#endif
+
           Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
 
           if ('pendingChromeEvents' in shell) {
             shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell));
           }
           delete shell.pendingChromeEvents;
         });
 
@@ -617,23 +621,32 @@ var AlertsHelper = {
 
     let uid = detail.id;
     let listener = this._listeners[uid];
     if (!listener)
      return;
 
     let topic = detail.type == "desktop-notification-click" ? "alertclickcallback"
                                                             : "alertfinished";
-
     if (uid.startsWith("app-notif")) {
-      listener.mm.sendAsyncMessage("app-notification-return", {
-        uid: uid,
-        topic: topic,
-        target: listener.target
-      });
+      try {
+        listener.mm.sendAsyncMessage("app-notification-return", {
+          uid: uid,
+          topic: topic,
+          target: listener.target
+        });
+      } catch(e) {
+        gSystemMessenger.sendMessage("notification", {
+          title: listener.title,
+          body: listener.text,
+          imageURL: listener.imageURL
+        },
+        Services.io.newURI(listener.target, null, null),
+        Services.io.newURI(listener.manifestURL, null, null));
+      }
     } else if (uid.startsWith("alert")) {
       try {
         listener.observer.observe(null, topic, listener.cookie);
       } catch (e) { }
     }
 
     // we're done with this notification
     if (topic === "alertfinished") {
@@ -932,16 +945,25 @@ window.addEventListener('ContentStart', 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     shell.sendChromeEvent({
       type: 'audio-channel-changed',
       channel: aData
     });
 }, "audio-channel-changed", false);
 })();
 
+(function audioChannelChangedTracker() {
+  Services.obs.addObserver(function(aSubject, aTopic, aData) {
+    shell.sendChromeEvent({
+      type: 'audio-channel-changed',
+      channel: aData
+    });
+}, "audio-channel-changed", false);
+})();
+
 (function recordingStatusTracker() {
   let gRecordingActiveCount = 0;
 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     let oldCount = gRecordingActiveCount;
     if (aData == "starting") {
       gRecordingActiveCount += 1;
     } else if (aData == "shutdown") {
--- a/b2g/components/AlertsService.js
+++ b/b2g/components/AlertsService.js
@@ -102,18 +102,16 @@ AlertsService.prototype = {
         gSystemMessenger.sendMessage("notification", {
             title: listener.title,
             body: listener.text,
             imageURL: listener.imageURL
           },
           Services.io.newURI(data.target, null, null),
           Services.io.newURI(listener.manifestURL, null, null));
       }
-
-      cpmm.sendAsyncMessage("app-notification-sysmsg-request", listener);
     }
 
     // we're done with this notification
     if (topic === "alertfinished") {
       delete this._listeners[data.uid];
     }
   }
 };
--- a/b2g/installer/Makefile.in
+++ b/b2g/installer/Makefile.in
@@ -15,17 +15,17 @@ MOZ_PKG_REMOVALS = $(srcdir)/removed-fil
 MOZ_PKG_MANIFEST_P = $(srcdir)/package-manifest.in
 
 MOZ_NONLOCALIZED_PKG_LIST = \
 	xpcom \
 	browser \
 	b2g \
 	$(NULL)
 
-MOZ_LOCALIZED_PKG_LIST = $(AB_CD)
+MOZ_LOCALIZED_PKG_LIST = $(AB_CD) multilocale
 
 DEFINES += \
 	-DAB_CD=$(AB_CD) \
 	-DMOZ_APP_NAME=$(MOZ_APP_NAME) \
 	-DPREF_DIR=$(PREF_DIR) \
 	$(NULL)
 
 ifeq ($(MOZ_CHROME_FILE_FORMAT),jar)
@@ -66,11 +66,19 @@ DEFINES += -DBINPATH=$(BINPATH)
 
 ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET)))
 DEFINES += -DMOZ_SHARED_MOZGLUE=1
 endif
 
 ifdef MOZ_PKG_MANIFEST_P
 $(MOZ_PKG_MANIFEST): $(MOZ_PKG_MANIFEST_P) FORCE
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $< > $@
+ifdef MOZ_CHROME_MULTILOCALE
+	printf "\n[multilocale]\n" >> $@
+	for LOCALE in $(MOZ_CHROME_MULTILOCALE) ;\
+	do \
+	  printf "$(BINPATH)/chrome/$$LOCALE$(JAREXT)\n" >> $@; \
+	  printf "$(BINPATH)/chrome/$$LOCALE.manifest\n" >> $@; \
+	done
+endif
 
 GARBAGE += $(MOZ_PKG_MANIFEST)
 endif
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -232,24 +232,32 @@ var gPluginHandler = {
     // Hide the in-content UI if it's too big. The crashed plugin handler already did this.
     if (eventType != "PluginCrashed") {
       let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
       if (overlay != null && self.isTooSmall(plugin, overlay))
           overlay.style.visibility = "hidden";
     }
   },
 
+  isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) {
+    return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
+            Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
+  },
+
   canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
+    // if this isn't a known plugin, we can't activate it
+    // (this also guards pluginHost.getPermissionStringForType against
+    // unexpected input)
+    if (!gPluginHandler.isKnownPlugin(objLoadingContent))
+      return false;
+
     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-    let pluginPermission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
-    if (objLoadingContent.actualType) {
-      let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
-      let browser = gBrowser.getBrowserForDocument(objLoadingContent.ownerDocument.defaultView.top.document);
-      pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
-    }
+    let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+    let browser = gBrowser.getBrowserForDocument(objLoadingContent.ownerDocument.defaultView.top.document);
+    let pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
 
     return !objLoadingContent.activated &&
            pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
            objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
            objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
   },
 
   activatePlugins: function PH_activatePlugins(aContentWindow) {
@@ -357,22 +365,24 @@ var gPluginHandler = {
     openHelpLink("plugin-crashed", false);
   },
 
   // Event listener for click-to-play plugins.
   _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) {
     let doc = aPlugin.ownerDocument;
     let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-    let pluginPermission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
     let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
-    if (objLoadingContent.actualType) {
-      let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
-      pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
-    }
+    // guard against giving pluginHost.getPermissionStringForType a type
+    // not associated with any known plugin
+    if (!gPluginHandler.isKnownPlugin(objLoadingContent))
+      return;
+    let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+    let pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
+
     let overlay = doc.getAnonymousElementByAttribute(aPlugin, "class", "mainBox");
 
     if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
       if (overlay)
         overlay.style.visibility = "hidden";
       return;
     }
 
@@ -527,18 +537,19 @@ var gPluginHandler = {
 
     return centerActions;
   },
 
   _setPermissionForPlugins: function PH_setPermissionForPlugins(aBrowser, aPermission, aPluginList) {
     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
     for (let plugin of aPluginList) {
       let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-      if (gPluginHandler.canActivatePlugin(objLoadingContent) &&
-          objLoadingContent.actualType) {
+      // canActivatePlugin will return false if this isn't a known plugin type,
+      // so the pluginHost.getPermissionStringForType call is protected
+      if (gPluginHandler.canActivatePlugin(objLoadingContent)) {
         let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
         Services.perms.add(aBrowser.currentURI, permissionString, aPermission);
       }
     }
   },
 
   _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser) {
     aBrowser._clickToPlayDoorhangerShown = true;
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -1,16 +1,19 @@
 // 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/.
 
 // The minimum sizes for the auto-resize panel code.
 const PANEL_MIN_HEIGHT = 100;
 const PANEL_MIN_WIDTH = 330;
 
+XPCOMUtils.defineLazyModuleGetter(this, "SharedFrame",
+  "resource:///modules/SharedFrame.jsm");
+
 let SocialUI = {
   // Called on delayed startup to initialize UI
   init: function SocialUI_init() {
     Services.obs.addObserver(this, "social:pref-changed", false);
     Services.obs.addObserver(this, "social:ambient-notification-changed", false);
     Services.obs.addObserver(this, "social:profile-changed", false);
     Services.obs.addObserver(this, "social:recommend-info-changed", false);
     Services.obs.addObserver(this, "social:frameworker-error", false);
@@ -245,20 +248,23 @@ let SocialChatBar = {
     return document.getElementById("pinnedchats");
   },
   // Whether the chatbar is available for this window.  Note that in full-screen
   // mode chats are available, but not shown.
   get isAvailable() {
     if (!SocialUI.haveLoggedInUser())
       return false;
     let docElem = document.documentElement;
-    let chromeless = docElem.getAttribute("disablechrome") ||
-                     docElem.getAttribute("chromehidden").indexOf("extrachrome") >= 0;
+    let chromeless = docElem.getAttribute("chromehidden").indexOf("extrachrome") >= 0;
     return Social.uiVisible && !chromeless;
   },
+  // Does this chatbar have any chats (whether minimized, collapsed or normal)
+  get hasChats() {
+    return !!this.chatbar.firstElementChild;
+  },
   openChat: function(aProvider, aURL, aCallback, aMode) {
     if (this.isAvailable)
       this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
   },
   update: function() {
     let command = document.getElementById("Social:FocusChat");
     if (!this.isAvailable) {
       this.chatbar.removeAll();
@@ -670,18 +676,21 @@ var SocialToolbar = {
       for (let element of document.getElementsByClassName(className))
         element.hidden = !socialEnabled;
     }
     let toggleNotificationsCommand = document.getElementById("Social:ToggleNotifications");
     toggleNotificationsCommand.setAttribute("hidden", !socialEnabled);
 
     if (!SocialUI.haveLoggedInUser() || !socialEnabled) {
       let parent = document.getElementById("social-notification-panel");
-      while (parent.hasChildNodes())
-        parent.removeChild(parent.firstChild);
+      while (parent.hasChildNodes()) {
+        let frame = parent.firstChild;
+        SharedFrame.forgetGroup(frame.id);
+        parent.removeChild(frame);
+      }
 
       while (tbi.lastChild != tbi.firstChild)
         tbi.removeChild(tbi.lastChild);
     }
   },
 
   updateProfile: function SocialToolbar_updateProfile() {
     // Profile may not have been initialized yet, since it depends on a worker
@@ -749,42 +758,51 @@ var SocialToolbar = {
       // "cache" so we can use them next startup.
       let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
       str.data = JSON.stringify({provider: Social.provider.origin, data: icons});
       Services.prefs.setComplexValue(CACHE_PREF_NAME,
                                      Ci.nsISupportsString,
                                      str);
     }
 
-    let notificationFrames = document.createDocumentFragment();
     let iconContainers = document.createDocumentFragment();
 
     let createdFrames = [];
 
     for each(let name in iconNames) {
       let icon = icons[name];
 
       let notificationFrameId = "social-status-" + icon.name;
       let notificationFrame = document.getElementById(notificationFrameId);
+
       if (!notificationFrame) {
-        notificationFrame = document.createElement("iframe");
-        notificationFrame.setAttribute("type", "content");
-        notificationFrame.setAttribute("class", "social-panel-frame");
-        notificationFrame.setAttribute("id", notificationFrameId);
-        notificationFrame.setAttribute("mozbrowser", "true");
-        // work around bug 793057 - by making the panel roughly the final size
-        // we are more likely to have the anchor in the correct position.
-        notificationFrame.style.width = PANEL_MIN_WIDTH + "px";
+
+        notificationFrame = SharedFrame.createFrame(
+          notificationFrameId, /* frame name */
+          panel, /* parent */
+          {
+            "type": "content",
+            "mozbrowser": "true",
+            "class": "social-panel-frame",
+            "id": notificationFrameId,
+
+            // work around bug 793057 - by making the panel roughly the final size
+            // we are more likely to have the anchor in the correct position.
+            "style": "width: " + PANEL_MIN_WIDTH + "px;",
+
+            "origin": provider.origin,
+            "src": icon.contentPanel
+          }
+        );
 
         createdFrames.push(notificationFrame);
-        notificationFrames.appendChild(notificationFrame);
+      } else {
+        notificationFrame.setAttribute("origin", provider.origin);
+        SharedFrame.updateURL(notificationFrameId, icon.contentPanel);
       }
-      notificationFrame.setAttribute("origin", provider.origin);
-      if (notificationFrame.getAttribute("src") != icon.contentPanel)
-        notificationFrame.setAttribute("src", icon.contentPanel);
 
       let iconId = "social-notification-icon-" + icon.name;
       let imageId = iconId + "-image";
       let labelId = iconId + "-label";
       let stackId = iconId + "-stack";
       let stack = document.getElementById(stackId);
       let image, label;
       if (stack) {
@@ -825,17 +843,16 @@ var SocialToolbar = {
 
       let labelValue = icon.counter || "";
       // Only update the value attribute if it has changed to reduce layout changes.
       if (!label.hasAttribute("value") || label.getAttribute("value") != labelValue)
         label.setAttribute("value", labelValue);
 
       image.style.listStyleImage = "url(" + icon.iconURL + ")";
     }
-    panel.appendChild(notificationFrames);
     iconBox.appendChild(iconContainers);
 
     for (let frame of createdFrames) {
       if (frame.docShell) {
         frame.docShell.isActive = false;
         frame.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIWebProgress)
                       .addProgressListener(new SocialErrorListener("notification-panel"),
@@ -848,16 +865,19 @@ var SocialToolbar = {
   showAmbientPopup: function SocialToolbar_showAmbientPopup(aToolbarButtonBox) {
     // Hide any other social panels that may be open.
     SocialFlyout.panel.hidePopup();
 
     let panel = document.getElementById("social-notification-panel");
     let notificationFrameId = aToolbarButtonBox.getAttribute("notificationFrameId");
     let notificationFrame = document.getElementById(notificationFrameId);
 
+    let wasAlive = SharedFrame.isGroupAlive(notificationFrameId);
+    SharedFrame.setOwner(notificationFrameId, notificationFrame);
+
     // Clear dimensions on all browsers so the panel size will
     // only use the selected browser.
     let frameIter = panel.firstElementChild;
     while (frameIter) {
       frameIter.collapsed = (frameIter != notificationFrame);
       frameIter = frameIter.nextElementSibling;
     }
 
@@ -876,17 +896,17 @@ var SocialToolbar = {
       dispatchPanelEvent("socialFrameHide");
     });
 
     panel.addEventListener("popupshown", function onpopupshown() {
       panel.removeEventListener("popupshown", onpopupshown);
       aToolbarButtonBox.setAttribute("open", "true");
       notificationFrame.docShell.isActive = true;
       notificationFrame.docShell.isAppTab = true;
-      if (notificationFrame.contentDocument.readyState == "complete") {
+      if (notificationFrame.contentDocument.readyState == "complete" && wasAlive) {
         dynamicResizer.start(panel, notificationFrame);
         dispatchPanelEvent("socialFrameShow");
       } else {
         // first time load, wait for load and dispatch after load
         notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
           notificationFrame.removeEventListener("load", panelBrowserOnload, true);
           dynamicResizer.start(panel, notificationFrame);
           setTimeout(function() {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3751,24 +3751,25 @@
         if (draggedTab && dropEffect == "copy") {
           // copy the dropped tab (wherever it's from)
           let newIndex = this._getDropIndex(event);
           let newTab = this.tabbrowser.duplicateTab(draggedTab);
           this.tabbrowser.moveTabTo(newTab, newIndex);
           if (draggedTab.parentNode != this || event.shiftKey)
             this.selectedItem = newTab;
         } else if (draggedTab && draggedTab.parentNode == this) {
+          this._finishAnimateTabMove();
+
           // actually move the dragged tab
           if ("animDropIndex" in draggedTab._dragData) {
             let newIndex = draggedTab._dragData.animDropIndex;
             if (newIndex > draggedTab._tPos)
               newIndex--;
             this.tabbrowser.moveTabTo(draggedTab, newIndex);
           }
-          this._finishAnimateTabMove();
         } else if (draggedTab) {
           // swap the dropped tab with a new one we create and then close
           // it in the other window (making it seem to have moved between
           // windows)
           let newIndex = this._getDropIndex(event);
           let newTab = this.tabbrowser.addTab("about:blank");
           let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
           // Stop the about:blank load
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -269,16 +269,17 @@ endif
                  browser_aboutSyncProgress.js \
                  browser_middleMouse_inherit.js \
                  redirect_bug623155.sjs \
                  browser_tabDrop.js \
                  browser_lastAccessedTab.js \
                  browser_bug734076.js \
                  browser_bug812562.js \
                  browser_bug818009.js \
+                 browser_bug818118.js \
                  blockPluginVulnerableUpdatable.xml \
                  blockPluginVulnerableNoUpdate.xml \
                  blockNoPlugins.xml \
                  browser_utilityOverlay.js \
                  browser_social.js \
                  browser_social_toolbar.js \
                  browser_social_shareButton.js \
                  browser_social_sidebar.js \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_bug818118.js
@@ -0,0 +1,41 @@
+var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function test() {
+  waitForExplicitFinish();
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("plugins.click_to_play");
+    gTestBrowser.removeEventListener("load", pageLoad, true);
+  });
+
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  gBrowser.selectedTab = gBrowser.addTab();
+  gTestBrowser = gBrowser.selectedBrowser;
+  gTestBrowser.addEventListener("load", pageLoad, true);
+  gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_both.html";
+}
+
+function pageLoad(aEvent) {
+  // The plugin events are async dispatched and can come after the load event
+  // This just allows the events to fire before we then go on to test the states
+  executeSoon(actualTest);
+}
+
+function actualTest() {
+  var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(popupNotification, "should have a click-to-play notification");
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  ok(plugin, "should have known plugin in page");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+  ok(!objLoadingContent.activated, "plugin should not be activated");
+
+  var unknown = gTestBrowser.contentDocument.getElementById("unknown");
+  ok(unknown, "should have unknown plugin in page");
+
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
--- a/browser/base/content/test/browser_social_chatwindow.js
+++ b/browser/base/content/test/browser_social_chatwindow.js
@@ -429,16 +429,60 @@ var tests = {
         is(secondWindow.SocialChatBar.chatbar.childElementCount, 1);
         secondWindow.close();
         next();
       }
     }
     port.postMessage({topic: "test-init"});
   },
 
+  testChatWindowChooser: function(next) {
+    // Tests that when a worker creates a chat, it is opened in the correct
+    // window.
+    const chatUrl = "https://example.com/browser/browser/base/content/test/social_chat.html";
+    let chatId = 1;
+    let port = Social.provider.getWorkerPort();
+    port.postMessage({topic: "test-init"});
+
+    function openChat(callback) {
+      port.onmessage = function(e) {
+        if (e.data.topic == "got-chatbox-message")
+          callback();
+      }
+      let url = chatUrl + "?" + (chatId++);
+      port.postMessage({topic: "test-worker-chat", data: url});
+    }
+
+    // open a chat (it will open in the main window)
+    ok(!window.SocialChatBar.hasChats, "first window should start with no chats");
+    openChat(function() {
+      ok(window.SocialChatBar.hasChats, "first window has the chat");
+      // create a second window - although this will be the "most recent",
+      // the fact the first window has a chat open means the first will be targetted.
+      let secondWindow = OpenBrowserWindow();
+      secondWindow.addEventListener("load", function loadListener() {
+        secondWindow.removeEventListener("load", loadListener);
+        ok(!secondWindow.SocialChatBar.hasChats, "second window has no chats");
+        openChat(function() {
+          ok(!secondWindow.SocialChatBar.hasChats, "second window still has no chats");
+          is(window.SocialChatBar.chatbar.childElementCount, 2, "first window now has 2 chats");
+          window.SocialChatBar.chatbar.removeAll();
+          // now open another chat - it should open in the second window (as
+          // it is the "most recent" and no other windows have chats)
+          openChat(function() {
+            ok(!window.SocialChatBar.hasChats, "first window has no chats");
+            ok(secondWindow.SocialChatBar.hasChats, "second window has a chat");
+            secondWindow.close();
+            next();
+          });
+        });
+      })
+    });
+  },
+
   // XXX - note this must be the last test until we restore the login state
   // between tests...
   testCloseOnLogout: function(next) {
     const chatUrl = "https://example.com/browser/browser/base/content/test/social_chat.html";
     let port = Social.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.postMessage({topic: "test-init"});
     port.onmessage = function (e) {
--- a/browser/modules/Makefile.in
+++ b/browser/modules/Makefile.in
@@ -21,16 +21,17 @@ EXTRA_JS_MODULES = \
 	NewTabUtils.jsm \
 	offlineAppCache.jsm \
 	SignInToWebsite.jsm \
 	TelemetryTimestamps.jsm \
 	Social.jsm \
 	webappsUI.jsm \
 	webrtcUI.jsm \
 	KeywordURLResetPrompter.jsm \
+	SharedFrame.jsm \
 	$(NULL)
 
 EXTRA_PP_JS_MODULES = RecentWindow.jsm
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 EXTRA_JS_MODULES += \
 	WindowsPreviewPerTab.jsm \
 	$(NULL)
new file mode 100644
--- /dev/null
+++ b/browser/modules/SharedFrame.jsm
@@ -0,0 +1,221 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [ "SharedFrame" ];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+/**
+ * The purpose of this module is to create and group various iframe
+ * elements that are meant to all display the same content and only
+ * one at a time. This makes it possible to have the content loaded
+ * only once, while the other iframes can be kept as placeholders to
+ * quickly move the content to them through the swapFrameLoaders function
+ * when another one of the placeholder is meant to be displayed.
+ * */
+
+let Frames = new Map();
+
+/**
+ * The Frames map is the main data structure that holds information
+ * about the groups being tracked. Each entry's key is the group name,
+ * and the object holds information about what is the URL being displayed
+ * on that group, and what is the active element on the group (the frame that
+ * holds the loaded content).
+ * The reference to the activeFrame is a weak reference, which allows the
+ * frame to go away at any time, and when that happens the module considers that
+ * there are no active elements in that group. The group can be reactivated
+ * by changing the URL, calling preload again or adding a new element.
+ *
+ *
+ *  Frames = {
+ *    "messages-panel": {
+ *      url: string,
+ *      activeFrame: weakref
+ *    }
+ *  }
+ *
+ * Each object on the map is called a _SharedFrameGroup, which is an internal
+ * class of this module which does not automatically keep track of its state. This
+ * object should not be used externally, and all control should be handled by the
+ * module's functions.
+ */
+
+function UNLOADED_URL(aStr) "data:text/html;charset=utf-8,<!-- Unloaded frame " + aStr + " -->";
+
+
+this.SharedFrame = {
+  /**
+   * Creates an iframe element and track it as part of the specified group
+   * The module must create the iframe itself because it needs to do some special
+   * handling for the element's src attribute.
+   *
+   * @param aGroupName        the name of the group to which this frame belongs
+   * @param aParent           the parent element to which the frame will be appended to
+   * @param aFrameAttributes  an object with a list of attributes to set in the iframe
+   *                          before appending it to the DOM. The "src" attribute has
+   *                          special meaning here and if it's not blank it specifies
+   *                          the URL that will be initially assigned to this group
+   * @param aPreload          optional, tells if the URL specified in the src attribute
+   *                          should be preloaded in the frame being created, in case
+   *                          it's not yet preloaded in any other frame of the group.
+   *                          This parameter has no meaning if src is blank.
+   */
+  createFrame: function (aGroupName, aParent, aFrameAttributes, aPreload = true) {
+    let frame = aParent.ownerDocument.createElement("iframe");
+
+    for (let [key, val] of Iterator(aFrameAttributes)) {
+      frame.setAttribute(key, val);
+    }
+
+    let src = aFrameAttributes.src;
+    if (!src) {
+      aPreload = false;
+    }
+
+    let group = Frames.get(aGroupName);
+
+    if (group) {
+      // If this group has already been created
+
+      if (aPreload && !group.isAlive) {
+        // If aPreload is set and the group is not already loaded, load it.
+        // This can happen if:
+        // - aPreload was not used while creating the previous frames of this group, or
+        // - the previously active frame went dead in the meantime
+        group.url = src;
+        this.preload(aGroupName, frame);
+      } else {
+        // If aPreload is not set, or the group is already loaded in a different frame,
+        // there's not much that we need to do here: just create this frame as an
+        // inactivate placeholder
+        frame.setAttribute("src", UNLOADED_URL(aGroupName));
+      }
+
+    } else {
+      // This is the first time we hear about this group, so let's start tracking it,
+      // and also preload it if the src attribute was set and aPreload = true
+      group = new _SharedFrameGroup(src);
+      Frames.set(aGroupName, group);
+
+      if (aPreload) {
+        this.preload(aGroupName, frame);
+      } else {
+        frame.setAttribute("src", UNLOADED_URL(aGroupName));
+      }
+    }
+
+    aParent.appendChild(frame);
+    return frame;
+
+  },
+
+  /**
+   * Function that moves the loaded content from one active frame to
+   * another one that is currently a placeholder. If there's no active
+   * frame in the group, the content is loaded/reloaded.
+   *
+   * @param aGroupName   the name of the group
+   * @param aTargetFrame the frame element to which the content should
+   *                     be moved to.
+   */
+  setOwner: function (aGroupName, aTargetFrame) {
+    let group = Frames.get(aGroupName);
+    let frame = group.activeFrame;
+
+    if (frame == aTargetFrame) {
+      // nothing to do here
+      return;
+    }
+
+    if (group.isAlive) {
+      // Move document ownership to the desired frame, and make it the active one
+      frame.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(aTargetFrame);
+      group.activeFrame = aTargetFrame;
+    } else {
+      // Previous owner was dead, reload the document at the new owner and make it the active one
+      aTargetFrame.setAttribute("src", group.url);
+      group.activeFrame = aTargetFrame;
+    }
+  },
+
+  /**
+   * Updates the current URL in use by this group, and loads it into the active frame.
+   *
+   * @param aGroupName  the name of the group
+   * @param aURL        the new url
+   */
+  updateURL: function (aGroupName, aURL) {
+    let group = Frames.get(aGroupName);
+    group.url = aURL;
+
+    if (group.isAlive) {
+      group.activeFrame.setAttribute("src", aURL);
+    }
+  },
+
+  /**
+   * Loads the group's url into a target frame, if the group doesn't have a currently
+   * active frame.
+   *
+   * @param aGroupName    the name of the group
+   * @param aTargetFrame  the frame element which should be made active and
+   *                      have the group's content loaded to
+   */
+  preload: function (aGroupName, aTargetFrame) {
+    let group = Frames.get(aGroupName);
+    if (!group.isAlive) {
+      aTargetFrame.setAttribute("src", group.url);
+      group.activeFrame = aTargetFrame;
+    }
+  },
+
+  /**
+   * Tells if a group currently have an active element.
+   *
+   * @param aGroupName  the name of the group
+   */
+  isGroupAlive: function (aGroupName) {
+    return Frames.get(aGroupName).isAlive;
+  },
+
+  /**
+   * Forgets about this group. This function doesn't need to be used
+   * unless the group's name needs to be reused.
+   *
+   * @param aGroupName  the name of the group
+   */
+  forgetGroup: function (aGroupName) {
+    Frames.delete(aGroupName);
+  }
+}
+
+
+function _SharedFrameGroup(aURL) {
+  this.url = aURL;
+  this._activeFrame = null;
+}
+
+_SharedFrameGroup.prototype = {
+  get isAlive() {
+    let frame = this.activeFrame;
+    return !!(frame &&
+              frame.contentDocument &&
+              frame.contentDocument.location);
+  },
+
+  get activeFrame() {
+    return this._activeFrame &&
+           this._activeFrame.get();
+  },
+
+  set activeFrame(aActiveFrame) {
+    this._activeFrame = aActiveFrame
+                        ? Cu.getWeakReference(aActiveFrame)
+                        : null;
+  }
+}
--- a/browser/modules/test/Makefile.in
+++ b/browser/modules/test/Makefile.in
@@ -3,16 +3,20 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DEPTH		= @DEPTH@
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = @relativesrcdir@
 
+DIRS = \
+	chrome \
+	$(NULL)
+
 include $(DEPTH)/config/autoconf.mk
 
 XPCSHELL_TESTS = unit
 
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
                  browser_NetworkPrioritizer.js \
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/chrome/Makefile.in
@@ -0,0 +1,18 @@
+# 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/.
+
+DEPTH		= @DEPTH@
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_CHROME_FILES = \
+		test_sharedframe.xul \
+		sharedframe.xul \
+		$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/chrome/sharedframe.xul
@@ -0,0 +1,174 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test SharedFrame - Bug 811247"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="runTest();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+  <script type="application/javascript">
+  <![CDATA[
+
+    function is(a,b,c) opener.wrappedJSObject.is(a,b,c);
+    function ok(a,b) opener.wrappedJSObject.ok(a,b);
+    function done() opener.wrappedJSObject.done();
+
+    Components.utils.import("resource:///modules/SharedFrame.jsm");
+    ok(SharedFrame, "SharedFrame module exists");
+
+    let box, gGen;
+    function runTest() {
+      box = document.getElementById("frames-container");
+      gGen = test_module();
+      gGen.next();
+    }
+
+    function test_module() {
+      // note: no 'src' attribute means aPreload = false;
+      let frame1 = SharedFrame.createFrame("group1", box, {id: "group1-frame1", type: "content"});
+      let frame2 = SharedFrame.createFrame("group1", box, {id: "group1-frame2", type: "content"});
+      let frame3 = SharedFrame.createFrame("group1", box, {id: "group1-frame3", type: "content"});
+
+      // Check proper attribute assignment
+      is(frame1.id, "group1-frame1", "correct id");
+      is(frame2.id, "group1-frame2", "correct id");
+      is(frame3.id, "group1-frame3", "correct id");
+
+      is(frame1.getAttribute("type"), "content", "correct type");
+      is(frame2.getAttribute("type"), "content", "correct type");
+      is(frame3.getAttribute("type"), "content", "correct type");
+
+      //--------------------------
+      yield waitForLoad([frame1, frame2, frame3]);
+
+      // Check for unloaded in the src URL
+      ok(/Unloaded/.test(frame1.contentDocument.location), "frame 1 is unloaded");
+      ok(/Unloaded/.test(frame2.contentDocument.location), "frame 2 is unloaded");
+      ok(/Unloaded/.test(frame3.contentDocument.location), "frame 3 is unloaded");
+
+      // Check that there is no frame alive in the group
+      ok(!SharedFrame.isGroupAlive("group1"), "group 1 is not alive");
+
+      // Set the URL and load the group
+      SharedFrame.updateURL("group1", "http://www.example.com");
+      SharedFrame.preload("group1", frame1);
+
+      //--------------------------
+      yield waitForLoad([frame1]);
+
+      // Check that frame 1 was properly loaded and the group is alive
+      ok(SharedFrame.isGroupAlive("group1"), "group 1 is now alive");
+      ok(!/Unloaded/.test(frame1.contentDocument.location), "frame 1 is now loaded");
+      ok(/Unloaded/.test(frame2.contentDocument.location), "frame 2 is unloaded");
+      ok(/Unloaded/.test(frame3.contentDocument.location), "frame 3 is unloaded");
+
+      // Move content to frame 2
+      SharedFrame.setOwner("group1", frame2);
+
+      ok(/Unloaded/.test(frame1.contentDocument.location), "frame 1 is unloaded");
+      ok(!/Unloaded/.test(frame2.contentDocument.location), "content was transfered to frame 2");
+      ok(/Unloaded/.test(frame3.contentDocument.location), "frame 3 is unloaded");
+
+      // Update URL and check that new content got loaded
+      SharedFrame.updateURL("group1", "http://www.example.com/new");
+
+      //--------------------------
+      yield waitForLoad([frame2]);
+
+      ok(/new$/.test(frame2.contentDocument.location), "new url loaded");
+
+      // Now remove the loaded content and check if the group is properly reported as unloaded
+      box.removeChild(frame2);
+      ok(!SharedFrame.isGroupAlive("group1"), "group 1 is not alive");
+
+      // And see if setOwnering will reload the group
+      SharedFrame.setOwner("group1", frame3);
+
+      //--------------------------
+      yield waitForLoad([frame3]);
+
+      ok(SharedFrame.isGroupAlive("group1"), "group 1 is alive");
+      ok(/new$/.test(frame3.contentDocument.location), "content was transfered to frame 3");
+
+      // Create a second group to verify it doesn't interact with the first one. Also test
+      // that preloading works
+      let frame4 = SharedFrame.createFrame("group2", box, {src: "http://www.example.com/group2", type: "content"});
+      let frame5 = SharedFrame.createFrame("group2", box, {src: "http://www.example.com/group2", type: "content"});
+
+      //--------------------------
+      yield waitForLoad([frame4, frame5]);
+
+      ok(SharedFrame.isGroupAlive("group2"), "group 2 was preloaded due to the src attribute");
+
+      // Check for unloaded in the src URL
+      ok(/group2$/.test(frame4.contentDocument.location), "frame 4 is loaded");
+      ok(/Unloaded/.test(frame5.contentDocument.location), "frame 5 is unloaded");
+
+      SharedFrame.setOwner("group2", frame5);
+
+      ok(/Unloaded/.test(frame4.contentDocument.location), "frame 4 is unloaded");
+      ok(/group2$/.test(frame5.contentDocument.location), "frame 5 is loaded");
+
+      SharedFrame.updateURL("group2", "http://www.example.com/new2");
+
+      //--------------------------
+      yield waitForLoad([frame5]);
+
+      ok(/new2$/.test(frame5.contentDocument.location), "frame 5 changed");
+      ok(/Unloaded/.test(frame1.contentDocument.location), "frame 1 still has its previous value");
+      ok(/new$/.test(frame3.contentDocument.location), "frame 3 still has its previous value");
+
+      //And now check that aPreload parameter works
+      let frame7 = SharedFrame.createFrame("group3", box, {src: "http://www.example.com/group3", type: "content"}, false);
+
+      //--------------------------
+      yield waitForLoad([frame7]);
+
+      ok(!SharedFrame.isGroupAlive("group3"), "aPreload = false works");
+      ok(/Unloaded/.test(frame7.contentDocument.location), "frame 7 is unloaded");
+
+      let frame8 = SharedFrame.createFrame("group3", box, {src: "http://www.example.com/group3", type: "content"});
+
+      //--------------------------
+      yield waitForLoad([frame8]);
+
+      ok(SharedFrame.isGroupAlive("group3"), "aPreload defauls to true");
+      ok(/group3/.test(frame8.contentDocument.location), "aPreload + src loads/reloads group");
+
+      done();
+    }
+
+
+    function waitForLoad(frames) {
+      let count = frames.length;
+      for (let frame of frames) {
+        let f = frame;
+        f.addEventListener("DOMContentLoaded", function frameloaded(event) {
+          f.removeEventListener("DOMContentLoaded", frameloaded, false);
+          if (--count == 0) {
+            try { gGen.next() } catch (ex if ex instanceof StopIteration) { }
+          }
+        }, false);
+      }
+    }
+  ]]>
+  </script>
+
+  <box id="frames-container"/>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+  </body>
+  <label id="test-result"/>
+</window>
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/chrome/test_sharedframe.xul
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=811247
+-->
+<window title="Mozilla Bug 811247"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=811247"
+     target="_blank">Mozilla Bug 811247</a>
+  </body>
+
+  <script type="application/javascript"><![CDATA[
+
+  /* Test for Bug 811247
+   *
+   * The test must run on a separate window becase .swapFrameLoaders currently won't swap
+   * iframes that are inside frames with history enabled, which is the case of the test running
+   * in the content area of a regular browser window; so we need a blank xul window for that
+   */
+  SimpleTest.waitForExplicitFinish();
+
+  let testWin;
+
+  function done() {
+    testWin.close();
+    SimpleTest.finish();
+  }
+
+  addLoadEvent(function() {
+    testWin = window.open("sharedframe.xul", "", "chrome,dialog,width=400,height=400");
+  });
+  ]]></script>
+</window>
--- a/browser/themes/pinstripe/browser.css
+++ b/browser/themes/pinstripe/browser.css
@@ -3999,17 +3999,17 @@ chatbox[minimized="true"] {
 
 panel[type="arrow"][popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="top"],
 panel[type="arrow"][popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="bottom"] {
   list-style-image: url("chrome://global/skin/arrow/panelarrow-light-vertical.png");
 }
 @media (min-resolution: 2dppx) {
   panel[type="arrow"][popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="top"],
   panel[type="arrow"][popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="bottom"] {
-    list-style-image: url("chrome://global/skin/arrow/panelarrow-light-vertical@2x.png") !important;
+    list-style-image: url("chrome://global/skin/arrow/panelarrow-light-vertical@2x.png");
  }
 }
 
 .click-to-play-plugins-notification-content {
   margin: -16px;
   border-radius: 5px;
 }
 
--- a/caps/include/nsScriptSecurityManager.h
+++ b/caps/include/nsScriptSecurityManager.h
@@ -478,16 +478,25 @@ private:
      *                                or equal privileges to the object.
      */
     nsresult
     CheckXPCPermissions(JSContext* cx,
                         nsISupports* aObj, JSObject* aJSObject,
                         nsIPrincipal* aSubjectPrincipal,
                         const char* aObjectSecurityLevel);
 
+    /**
+     * Helper for CanExecuteScripts that allows the caller to specify
+     * whether execution should be allowed if cx has no
+     * nsIScriptContext.
+     */
+    nsresult
+    CanExecuteScripts(JSContext* cx, nsIPrincipal *aPrincipal,
+                      bool aAllowIfNoScriptContext, bool *result);
+
     nsresult
     Init();
 
     nsresult
     InitPrefs();
 
     nsresult
     InitPolicies();
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -1634,17 +1634,17 @@ nsScriptSecurityManager::CheckFunctionAc
     if (subject == mSystemPrincipal)
         // This is the system principal: just allow access
         return NS_OK;
 
     // Check if the principal the function was compiled under is
     // allowed to execute scripts.
 
     bool result;
-    rv = CanExecuteScripts(aCx, subject, &result);
+    rv = CanExecuteScripts(aCx, subject, true, &result);
     if (NS_FAILED(rv))
       return rv;
 
     if (!result)
       return NS_ERROR_DOM_SECURITY_ERR;
 
     if (!aTargetObj) {
         // We're done here
@@ -1668,28 +1668,43 @@ nsScriptSecurityManager::CheckFunctionAc
     return rv;
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::CanExecuteScripts(JSContext* cx,
                                            nsIPrincipal *aPrincipal,
                                            bool *result)
 {
+    return CanExecuteScripts(cx, aPrincipal, false, result);
+}
+
+nsresult
+nsScriptSecurityManager::CanExecuteScripts(JSContext* cx,
+                                           nsIPrincipal *aPrincipal,
+                                           bool aAllowIfNoScriptContext,
+                                           bool *result)
+{
     *result = false; 
 
     if (aPrincipal == mSystemPrincipal)
     {
         // Even if JavaScript is disabled, we must still execute system scripts
         *result = true;
         return NS_OK;
     }
 
     //-- See if the current window allows JS execution
     nsIScriptContext *scriptContext = GetScriptContext(cx);
-    if (!scriptContext) return NS_ERROR_FAILURE;
+    if (!scriptContext) {
+        if (aAllowIfNoScriptContext) {
+            *result = true;
+            return NS_OK;
+        }
+        return NS_ERROR_FAILURE;
+    }
 
     if (!scriptContext->GetScriptsEnabled()) {
         // No scripting on this context, folks
         *result = false;
         return NS_OK;
     }
     
     nsIScriptGlobalObject *sgo = scriptContext->GetGlobalObject();
--- a/configure.in
+++ b/configure.in
@@ -4230,17 +4230,17 @@ MOZ_CUBEB=
 MOZ_VORBIS=
 MOZ_TREMOR=
 MOZ_WAVE=1
 MOZ_SAMPLE_TYPE_FLOAT32=
 MOZ_SAMPLE_TYPE_S16=
 MOZ_MEDIA=
 MOZ_OPUS=1
 MOZ_WEBM=1
-MOZ_DASH=
+MOZ_DASH=1
 MOZ_WEBRTC=1
 MOZ_PEERCONNECTION=
 MOZ_SRTP=
 MOZ_WEBRTC_SIGNALING=
 MOZ_WEBRTC_IN_LIBXUL=
 MOZ_SCTP=
 MOZ_MEDIA_PLUGINS=
 MOZ_MEDIA_NAVIGATOR=
@@ -5376,22 +5376,22 @@ MOZ_ARG_DISABLE_BOOL(webm,
     MOZ_WEBM=1)
 
 if test -n "$MOZ_WEBM"; then
     AC_DEFINE(MOZ_WEBM)
     MOZ_VP8=1
 fi;
 
 dnl ========================================================
-dnl = Enable DASH-WebM support
-dnl ========================================================
-MOZ_ARG_ENABLE_BOOL(dash,
-[  --enable-dash          Enable support for DASH-WebM],
-    MOZ_DASH=1,
-    MOZ_DASH=)
+dnl = Disable DASH-WebM support
+dnl ========================================================
+MOZ_ARG_DISABLE_BOOL(dash,
+[  --disable-dash          Disable support for DASH-WebM],
+    MOZ_DASH=,
+    MOZ_DASH=1)
 
 if test -n "$MOZ_DASH"; then
     if test -n "$MOZ_WEBM"; then
         AC_DEFINE(MOZ_DASH)
     else
         dnl Fail if WebM is not enabled as well as DASH.
         AC_MSG_ERROR([WebM is currently disabled and must be enabled for DASH
                      to work.])
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -74,18 +74,18 @@ class ImageLoader;
 
 namespace dom {
 class Link;
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID \
-{ 0xd69b94c2, 0x92ed, 0x4baa, \
-  { 0x82, 0x08, 0x56, 0xe4, 0xc4, 0xb3, 0xf3, 0xc8 } }
+{ 0xcc604bdc, 0xd55e, 0x4918, \
+ { 0xaa, 0x82, 0xb2, 0xde, 0xbf, 0x01, 0x09, 0x5d } }
 
 // Flag for AddStyleSheet().
 #define NS_STYLESHEET_FROM_CATALOG                (1 << 0)
 
 // Enum for requesting a particular type of document when creating a doc
 enum DocumentFlavor {
   DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
   DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
@@ -1664,17 +1664,19 @@ public:
 #define DEPRECATED_OPERATION(_op) e##_op,
   enum DeprecatedOperations {
 #include "nsDeprecatedOperationList.h"
     eDeprecatedOperationCount
   };
 #undef DEPRECATED_OPERATION
   void WarnOnceAbout(DeprecatedOperations aOperation, bool asError = false);
 
-  virtual void PostVisibilityUpdateEvent() = 0;
+  // This method may fire a DOM event; if it does so it will happen
+  // synchronously if aFireEventSync is true, asynchronously otherwise.
+  virtual void UpdateVisibilityState(bool aFireEventSync) = 0;
   
   bool IsSyntheticDocument() { return mIsSyntheticDocument; }
 
   void SetNeedLayoutFlush() {
     mNeedLayoutFlush = true;
     if (mDisplayDocument) {
       mDisplayDocument->SetNeedLayoutFlush();
     }
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -14,17 +14,17 @@
 #include "nsIDOMNode.h"
 #include "nsIDOMNodeSelector.h"     // base class
 #include "nsINodeInfo.h"            // member (in nsCOMPtr)
 #include "nsIVariant.h"             // for use in GetUserData()
 #include "nsNodeInfoManager.h"      // for use in NodePrincipal()
 #include "nsPropertyTable.h"        // for typedefs
 #include "nsTObserverArray.h"       // for member
 #include "nsWindowMemoryReporter.h" // for NS_DECL_SIZEOF_EXCLUDING_THIS
-#include "nsWrapperCache.h"         // for base class
+#include "mozilla/dom/EventTarget.h" // for base class
 
 // Including 'windows.h' will #define GetClassInfo to something else.
 #ifdef XP_WIN
 #ifdef GetClassInfo
 #undef GetClassInfo
 #endif
 #endif
 
@@ -262,18 +262,17 @@ private:
 { 0xb3ee8053, 0x43b0, 0x44bc, \
   { 0xa0, 0x97, 0x18, 0x24, 0xd2, 0xac, 0x65, 0xb6 } }
 
 /**
  * An internal interface that abstracts some DOMNode-related parts that both
  * nsIContent and nsIDocument share.  An instance of this interface has a list
  * of nsIContent children and provides access to them.
  */
-class nsINode : public nsIDOMEventTarget,
-                public nsWrapperCache
+class nsINode : public mozilla::dom::EventTarget
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODE_IID)
 
   // Among the sub-classes that inherit (directly or indirectly) from nsINode,
   // measurement of the following members may be added later if DMD finds it is
   // worthwhile:
   // - nsGenericHTMLElement:  mForm, mFieldSet
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -7058,17 +7058,17 @@ nsDocument::OnPageShow(bool aPersisted,
   if (mAnimationController) {
     mAnimationController->OnPageShow();
   }
 
   if (aPersisted) {
     SetImagesNeedAnimating(true);
   }
 
-  UpdateVisibilityState();
+  UpdateVisibilityState(true);
 
   nsCOMPtr<nsIDOMEventTarget> target = aDispatchStartTarget;
   if (!target) {
     target = do_QueryInterface(GetWindow());
   }
   DispatchPageTransition(target, NS_LITERAL_STRING("pageshow"), aPersisted);
 }
 
@@ -7120,17 +7120,17 @@ nsDocument::OnPageHide(bool aPersisted,
   nsCOMPtr<nsIDOMEventTarget> target = aDispatchStartTarget;
   if (!target) {
     target = do_QueryInterface(GetWindow());
   }
   DispatchPageTransition(target, NS_LITERAL_STRING("pagehide"), aPersisted);
 
   mVisible = false;
 
-  UpdateVisibilityState();
+  UpdateVisibilityState(true);
 
   EnumerateExternalResources(NotifyPageHide, &aPersisted);
   EnumerateFreezableElements(NotifyActivityChanged, nullptr);
 
   if (IsFullScreenDoc()) {
     // A full-screen doc has been hidden. We need to ensure we exit
     // full-screen, i.e. remove full-screen state from all full-screen
     // documents, and exit the top-level window from full-screen mode.
@@ -9477,35 +9477,47 @@ nsDocument::GetMozPointerLockElement(nsI
   }
 #define TOUCH_EVENT EVENT
 #define DOCUMENT_ONLY_EVENT EVENT
 #include "nsEventNameList.h"
 #undef DOCUMENT_ONLY_EVENT
 #undef TOUCH_EVENT
 #undef EVENT
 
-void
-nsDocument::UpdateVisibilityState()
+/* virtual */ void
+nsDocument::UpdateVisibilityState(bool aFireEventSync)
 {
   VisibilityState oldState = mVisibilityState;
   mVisibilityState = GetVisibilityState();
   if (oldState != mVisibilityState) {
-    nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
-                                         NS_LITERAL_STRING("visibilitychange"),
-                                         /* bubbles = */ true,
-                                         /* cancelable = */ false);
-    nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
-                                         NS_LITERAL_STRING("mozvisibilitychange"),
-                                         /* bubbles = */ true,
-                                         /* cancelable = */ false);
+    if (aFireEventSync) {
+      FireVisibilityChangeEvent();
+    } else {
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableMethod(this, &nsDocument::FireVisibilityChangeEvent);
+      NS_DispatchToMainThread(event);
+    }
 
     EnumerateFreezableElements(NotifyActivityChanged, nullptr);
   }
 }
 
+void
+nsDocument::FireVisibilityChangeEvent()
+{
+  nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
+                                       NS_LITERAL_STRING("visibilitychange"),
+                                       /* bubbles = */ true,
+                                       /* cancelable = */ false);
+  nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
+                                       NS_LITERAL_STRING("mozvisibilitychange"),
+                                       /* bubbles = */ true,
+                                       /* cancelable = */ false);
+}
+
 nsDocument::VisibilityState
 nsDocument::GetVisibilityState() const
 {
   // We have to check a few pieces of information here:
   // 1)  Are we in bfcache (!IsVisible())?  If so, nothing else matters.
   // 2)  Do we have an outer window?  If not, we're hidden.  Note that we don't
   //     want to use GetWindow here because it does weird groveling for windows
   //     in some cases.
@@ -9514,24 +9526,16 @@ nsDocument::GetVisibilityState() const
   if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
       mWindow->GetOuterWindow()->IsBackground()) {
     return eHidden;
   }
 
   return eVisible;
 }
 
-/* virtual */ void
-nsDocument::PostVisibilityUpdateEvent()
-{
-  nsCOMPtr<nsIRunnable> event =
-    NS_NewRunnableMethod(this, &nsDocument::UpdateVisibilityState);
-  NS_DispatchToMainThread(event);
-}
-
 NS_IMETHODIMP
 nsDocument::GetMozHidden(bool* aHidden)
 {
   WarnOnceAbout(ePrefixedVisibilityAPI);
   return GetHidden(aHidden);
 }
 
 NS_IMETHODIMP
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -990,21 +990,18 @@ public:
   // Returns the top element from the full-screen stack.
   Element* FullScreenStackTop();
 
   void RequestPointerLock(Element* aElement);
   bool ShouldLockPointer(Element* aElement);
   bool SetPointerLock(Element* aElement, int aCursorStyle);
   static void UnlockPointer();
 
-  // This method may fire a DOM event; if it does so it will happen
-  // synchronously.
-  void UpdateVisibilityState();
-  // Posts an event to call UpdateVisibilityState
-  virtual void PostVisibilityUpdateEvent();
+  virtual void UpdateVisibilityState(bool aFireEventSync);
+  void FireVisibilityChangeEvent();
 
   virtual void DocSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const;
   // DocSizeOfIncludingThis is inherited from nsIDocument.
 
   virtual nsIDOMNode* AsDOMNode() { return this; }
 protected:
   friend class nsNodeUtils;
 
new file mode 100644
--- /dev/null
+++ b/content/events/public/EventTarget.h
@@ -0,0 +1,62 @@
+/* -*- 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 mozilla_dom_EventTarget_h_
+#define mozilla_dom_EventTarget_h_
+
+#include "nsIDOMEventTarget.h"
+#include "nsWrapperCache.h"
+#include "nsIDOMEventListener.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Nullable.h"
+#include "nsIDOMEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+// IID for the dom::Element interface
+#define NS_EVENTTARGET_IID \
+{ 0x0a5aed21, 0x0bab, 0x48b3, \
+ { 0xbe, 0x4b, 0xd4, 0xf9, 0xd4, 0xea, 0xc7, 0xdb } }
+
+class EventTarget : public nsIDOMEventTarget,
+                    public nsWrapperCache
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_EVENTTARGET_IID)
+
+  // WebIDL API
+  using nsIDOMEventTarget::AddEventListener;
+  using nsIDOMEventTarget::RemoveEventListener;
+  using nsIDOMEventTarget::DispatchEvent;
+  void AddEventListener(const nsAString& aType,
+                        nsIDOMEventListener* aCallback, // XXX nullable
+                        bool aCapture, const Nullable<bool>& aWantsUntrusted,
+                        mozilla::ErrorResult& aRv)
+  {
+    aRv = AddEventListener(aType, aCallback, aCapture,
+                           !aWantsUntrusted.IsNull() && aWantsUntrusted.Value(),
+                           aWantsUntrusted.IsNull() ? 1 : 2);
+  }
+  void RemoveEventListener(const nsAString& aType,
+                           nsIDOMEventListener* aCallback,
+                           bool aCapture, mozilla::ErrorResult& aRv)
+  {
+    aRv = RemoveEventListener(aType, aCallback, aCapture);
+  }
+  bool DispatchEvent(nsIDOMEvent* aEvent, mozilla::ErrorResult& aRv)
+  {
+    bool result = false;
+    aRv = DispatchEvent(aEvent, &result);
+    return result;
+  }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(EventTarget, NS_EVENTTARGET_IID)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_EventTarget_h_
--- a/content/events/public/Makefile.in
+++ b/content/events/public/Makefile.in
@@ -19,14 +19,20 @@ EXPORTS		= \
 		nsIPrivateTextRange.h \
 		nsAsyncDOMEvent.h \
 		nsEventDispatcher.h \
 		nsEventStates.h \
 		nsEventNameList.h \
 		nsVKList.h \
 		$(NULL)
 
+EXPORTS_NAMESPACES = mozilla/dom
+
+EXPORTS_mozilla/dom = \
+  EventTarget.h \
+  $(NULL)
+
 XPIDLSRCS	= \
 		nsIEventListenerService.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
--- a/content/events/src/nsDOMEventTargetHelper.h
+++ b/content/events/src/nsDOMEventTargetHelper.h
@@ -3,60 +3,35 @@
  * 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 nsDOMEventTargetHelper_h_
 #define nsDOMEventTargetHelper_h_
 
 #include "nsCOMPtr.h"
 #include "nsGkAtoms.h"
-#include "nsIDOMEventTarget.h"
-#include "nsIDOMEventListener.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsPIDOMWindow.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsEventListenerManager.h"
 #include "nsIScriptContext.h"
-#include "nsWrapperCache.h"
-#include "mozilla/ErrorResult.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/dom/EventTarget.h"
 
 class nsDOMEvent;
 
-class nsDOMEventTargetHelper : public nsIDOMEventTarget,
-                               public nsWrapperCache
+class nsDOMEventTargetHelper : public mozilla::dom::EventTarget
 {
 public:
   nsDOMEventTargetHelper() : mOwner(nullptr), mHasOrHasHadOwner(false) {}
   virtual ~nsDOMEventTargetHelper();
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(nsDOMEventTargetHelper)
 
   NS_DECL_NSIDOMEVENTTARGET
-  void AddEventListener(const nsAString& aType,
-                        nsIDOMEventListener* aCallback, // XXX nullable
-                        bool aCapture, const Nullable<bool>& aWantsUntrusted,
-                        mozilla::ErrorResult& aRv)
-  {
-    aRv = AddEventListener(aType, aCallback, aCapture,
-                           !aWantsUntrusted.IsNull() && aWantsUntrusted.Value(),
-                           aWantsUntrusted.IsNull() ? 1 : 2);
-  }
-  void RemoveEventListener(const nsAString& aType,
-                           nsIDOMEventListener* aCallback,
-                           bool aCapture, mozilla::ErrorResult& aRv)
-  {
-    aRv = RemoveEventListener(aType, aCallback, aCapture);
-  }
-  bool DispatchEvent(nsIDOMEvent* aEvent, mozilla::ErrorResult& aRv)
-  {
-    bool result = false;
-    aRv = DispatchEvent(aEvent, &result);
-    return result;
-  }
 
   void GetParentObject(nsIScriptGlobalObject **aParentObject)
   {
     if (mOwner) {
       CallQueryInterface(mOwner, aParentObject);
     }
     else {
       *aParentObject = nullptr;
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -21,50 +21,45 @@
 #include "mozilla/CORSMode.h"
 #include "nsDOMMediaStream.h"
 #include "mozilla/Mutex.h"
 #include "nsTimeRanges.h"
 #include "nsIDOMWakeLock.h"
 #include "AudioChannelCommon.h"
 #include "DecoderTraits.h"
 #include "MediaMetadataManager.h"
+#include "AudioChannelAgent.h"
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
 typedef uint16_t nsMediaNetworkState;
 typedef uint16_t nsMediaReadyState;
 
 namespace mozilla {
 class MediaResource;
 class MediaDecoder;
-#ifdef MOZ_DASH
-class DASHDecoder;
-#endif
 }
 
 class nsHTMLMediaElement : public nsGenericHTMLElement,
                            public nsIObserver,
-                           public mozilla::MediaDecoderOwner
+                           public mozilla::MediaDecoderOwner,
+                           public nsIAudioChannelAgentCallback
 {
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::layers::ImageContainer ImageContainer;
   typedef mozilla::VideoFrameContainer VideoFrameContainer;
   typedef mozilla::MediaStream MediaStream;
   typedef mozilla::MediaResource MediaResource;
   typedef mozilla::MediaDecoderOwner MediaDecoderOwner;
   typedef mozilla::MetadataTags MetadataTags;
   typedef mozilla::AudioStream AudioStream;
   typedef mozilla::MediaDecoder MediaDecoder;
 
-#ifdef MOZ_DASH
-  friend class DASHDecoder;
-#endif
-
   mozilla::CORSMode GetCORSMode() {
     return mCORSMode;
   }
 
   nsHTMLMediaElement(already_AddRefed<nsINodeInfo> aNodeInfo);
   virtual ~nsHTMLMediaElement();
 
   /**
@@ -77,16 +72,18 @@ public:
    */
   nsresult LoadWithChannel(nsIChannel *aChannel, nsIStreamListener **aListener);
 
   // nsIDOMHTMLMediaElement
   NS_DECL_NSIDOMHTMLMEDIAELEMENT
 
   NS_DECL_NSIOBSERVER
 
+  NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
+
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHTMLMediaElement,
                                            nsGenericHTMLElement)
 
   virtual bool ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
@@ -321,19 +318,16 @@ public:
   virtual void FireTimeUpdate(bool aPeriodic) MOZ_FINAL MOZ_OVERRIDE;
 
   MediaStream* GetSrcMediaStream()
   {
     NS_ASSERTION(mSrcStream, "Don't call this when not playing a stream");
     return mSrcStream->GetStream();
   }
 
-  // Notification from the AudioChannelService.
-   nsresult NotifyAudioChannelStateChanged();
-
 protected:
   class MediaLoadListener;
   class StreamListener;
 
   virtual void GetItemValueText(nsAString& text);
   virtual void SetItemValueText(const nsAString& text);
 
   class WakeLockBoolWrapper {
@@ -607,17 +601,17 @@ protected:
     GetPaused(&isPaused);
     return isPaused;
   }
 
   // Check the permissions for audiochannel.
   bool CheckAudioChannelPermissions(const nsAString& aType);
 
   // This method does the check for muting/unmuting the audio channel.
-  nsresult UpdateChannelMuteState();
+  nsresult UpdateChannelMuteState(bool aCanPlay);
 
   // Update the audio channel playing state
   void UpdateAudioChannelPlayingState();
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   nsRefPtr<MediaDecoder> mDecoder;
 
@@ -892,11 +886,14 @@ protected:
   // Audio Channel Type.
   mozilla::dom::AudioChannelType mAudioChannelType;
 
   // The audiochannel has been muted
   bool mChannelMuted;
 
   // Is this media element playing?
   bool mPlayingThroughTheAudioChannel;
+
+  // An agent used to join audio channel service.
+  nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 };
 
 #endif
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -96,16 +96,17 @@
 #include "nsVariant.h"
 #include "nsDOMSettableTokenList.h"
 #include "nsThreadUtils.h"
 #include "nsTextFragment.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/ErrorResult.h"
 #include "nsHTMLDocument.h"
 #include "nsDOMTouchEvent.h"
+#include "nsGlobalWindow.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 class nsINodeInfo;
 class nsIDOMNodeList;
 class nsRuleWalker;
 
@@ -1811,16 +1812,103 @@ nsGenericHTMLElement::GetEventListenerMa
 
     return nullptr;
   }
 
   return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName,
                                                                   aDefer);
 }
 
+// FIXME (https://bugzilla.mozilla.org/show_bug.cgi?id=431767)
+// nsDocument::GetInnerWindow can return an outer window in some
+// cases.  We don't want to stick an event listener on an outer
+// window, so bail if it does.  See also similar code in
+// nsGenericHTMLElement::GetEventListenerManagerForAttr.
+#define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
+#define FORWARDED_EVENT(name_, id_, type_, struct_)                           \
+EventHandlerNonNull*                                                          \
+nsGenericHTMLElement::GetOn##name_()                                          \
+{                                                                             \
+  if (Tag() == nsGkAtoms::body || Tag() == nsGkAtoms::frameset) {             \
+    /* XXXbz note to self: add tests for this! */                             \
+    nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();                        \
+    if (win && win->IsInnerWindow()) {                                        \
+      nsCOMPtr<nsISupports> supports = do_QueryInterface(win);                \
+      nsGlobalWindow* globalWin = nsGlobalWindow::FromSupports(supports);     \
+      return globalWin->GetOn##name_();                                       \
+    }                                                                         \
+    return nullptr;                                                           \
+  }                                                                           \
+                                                                              \
+  return nsINode::GetOn##name_();                                             \
+}                                                                             \
+void                                                                          \
+nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler,              \
+                                   ErrorResult& error)                        \
+{                                                                             \
+  if (Tag() == nsGkAtoms::body || Tag() == nsGkAtoms::frameset) {             \
+    nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();                        \
+    if (!win || !win->IsInnerWindow()) {                                      \
+      return;                                                                 \
+    }                                                                         \
+                                                                              \
+    nsCOMPtr<nsISupports> supports = do_QueryInterface(win);                  \
+    nsGlobalWindow* globalWin = nsGlobalWindow::FromSupports(supports);       \
+    return globalWin->SetOn##name_(handler, error);                           \
+  }                                                                           \
+                                                                              \
+  return nsINode::SetOn##name_(handler, error);                               \
+}
+#define ERROR_EVENT(name_, id_, type_, struct_)                               \
+already_AddRefed<EventHandlerNonNull>                                         \
+nsGenericHTMLElement::GetOn##name_()                                          \
+{                                                                             \
+  if (Tag() == nsGkAtoms::body || Tag() == nsGkAtoms::frameset) {             \
+    /* XXXbz note to self: add tests for this! */                             \
+    nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();                        \
+    if (win && win->IsInnerWindow()) {                                        \
+      nsCOMPtr<nsISupports> supports = do_QueryInterface(win);                \
+      nsGlobalWindow* globalWin = nsGlobalWindow::FromSupports(supports);     \
+      OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_();   \
+      if (errorHandler) {                                                     \
+        nsRefPtr<EventHandlerNonNull> handler =                               \
+          new EventHandlerNonNull(errorHandler);                              \
+        return handler.forget();                                              \
+      }                                                                       \
+    }                                                                         \
+    return nullptr;                                                           \
+  }                                                                           \
+                                                                              \
+  nsRefPtr<EventHandlerNonNull> handler = nsINode::GetOn##name_();            \
+  return handler.forget();                                                    \
+}                                                                             \
+void                                                                          \
+nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler,              \
+                                   ErrorResult& error)                        \
+{                                                                             \
+  if (Tag() == nsGkAtoms::body || Tag() == nsGkAtoms::frameset) {             \
+    nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();                        \
+    if (!win || !win->IsInnerWindow()) {                                      \
+      return;                                                                 \
+    }                                                                         \
+                                                                              \
+    nsCOMPtr<nsISupports> supports = do_QueryInterface(win);                  \
+    nsGlobalWindow* globalWin = nsGlobalWindow::FromSupports(supports);       \
+    nsRefPtr<OnErrorEventHandlerNonNull> errorHandler =                       \
+      new OnErrorEventHandlerNonNull(handler);                                \
+    return globalWin->SetOn##name_(errorHandler, error);                      \
+  }                                                                           \
+                                                                              \
+  return nsINode::SetOn##name_(handler, error);                               \
+}
+#include "nsEventNameList.h"
+#undef ERROR_EVENT
+#undef FORWARDED_EVENT
+#undef EVENT
+
 nsresult
 nsGenericHTMLElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                               nsIAtom* aPrefix, const nsAString& aValue,
                               bool aNotify)
 {
   bool contentEditable = aNameSpaceID == kNameSpaceID_None &&
                            aName == nsGkAtoms::contenteditable;
   bool accessKey = aName == nsGkAtoms::accesskey && 
@@ -1989,17 +2077,18 @@ nsGenericHTMLElement::ParseBackgroundAtt
 
     nsString value(aValue);
     nsRefPtr<nsStringBuffer> buffer = nsCSSValue::BufferFromString(value);
     if (MOZ_UNLIKELY(!buffer)) {
       return false;
     }
 
     mozilla::css::URLValue *url =
-      new mozilla::css::URLValue(buffer, baseURI, uri, NodePrincipal());
+      new mozilla::css::URLValue(uri, buffer, doc->GetDocumentURI(),
+                                 NodePrincipal());
     aResult.SetTo(url, &aValue);
     return true;
   }
 
   return false;
 }
 
 bool
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -234,16 +234,35 @@ public:
   {
     nsresult rv;
     nsICSSDeclaration* style = nsMappedAttributeElement::GetStyle(&rv);
     if (NS_FAILED(rv)) {
       aError.Throw(rv);
     }
     return style;
   }
+#define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
+// The using nsINode::Get/SetOn* are to avoid warnings about shadowing the XPCOM
+// getter and setter on nsINode.
+#define FORWARDED_EVENT(name_, id_, type_, struct_)                           \
+  using nsINode::GetOn##name_;                                                \
+  using nsINode::SetOn##name_;                                                \
+  mozilla::dom::EventHandlerNonNull* GetOn##name_();                          \
+  void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler,               \
+                    mozilla::ErrorResult& error);
+#define ERROR_EVENT(name_, id_, type_, struct_)                               \
+  using nsINode::GetOn##name_;                                                \
+  using nsINode::SetOn##name_;                                                \
+  already_AddRefed<mozilla::dom::EventHandlerNonNull> GetOn##name_();         \
+  void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler,               \
+                    mozilla::ErrorResult& error);
+#include "nsEventNameList.h"
+#undef ERROR_EVENT
+#undef FORWARDED_EVENT
+#undef EVENT
   void GetClassName(nsAString& aClassName)
   {
     GetAttr(kNameSpaceID_None, nsGkAtoms::_class, aClassName);
   }
   void SetClassName(const nsAString& aClassName)
   {
     SetAttr(kNameSpaceID_None, nsGkAtoms::_class, aClassName, true);
   }
--- a/content/html/content/src/nsHTMLBodyElement.cpp
+++ b/content/html/content/src/nsHTMLBodyElement.cpp
@@ -18,16 +18,17 @@
 #include "nsHTMLStyleSheet.h"
 #include "nsIMarkupDocumentViewer.h"
 #include "nsMappedAttributes.h"
 #include "nsRuleData.h"
 #include "nsIDocShell.h"
 #include "nsIEditorDocShell.h"
 #include "nsRuleWalker.h"
 #include "jspubtd.h"
+#include "mozilla/dom/EventHandlerBinding.h"
 
 //----------------------------------------------------------------------
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 class nsHTMLBodyElement;
 
@@ -469,44 +470,52 @@ nsHTMLBodyElement::GetAssociatedEditor()
   if (!editorDocShell) {
     return nullptr;
   }
 
   editorDocShell->GetEditor(&editor);
   return editor;
 }
 
-// Event listener stuff
-// FIXME (https://bugzilla.mozilla.org/show_bug.cgi?id=431767)
-// nsDocument::GetInnerWindow can return an outer window in some
-// cases.  We don't want to stick an event listener on an outer
-// window, so bail if it does.  See also similar code in
-// nsGenericHTMLElement::GetEventListenerManagerForAttr.
 #define EVENT(name_, id_, type_, struct_) /* nothing; handled by the superclass */
-#define FORWARDED_EVENT(name_, id_, type_, struct_)                 \
+// nsGenericHTMLElement::GetOnError returns
+// already_AddRefed<EventHandlerNonNull> while other getters return
+// EventHandlerNonNull*, so allow passing in the type to use here.
+#define FORWARDED_EVENT_HELPER(name_, getter_type_)                 \
   NS_IMETHODIMP nsHTMLBodyElement::GetOn##name_(JSContext *cx,      \
                                            jsval *vp) {             \
-    /* XXXbz note to self: add tests for this! */                   \
-    nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();           \
-    if (win && win->IsInnerWindow()) {                              \
-      nsCOMPtr<nsIInlineEventHandlers> ev = do_QueryInterface(win); \
-      return ev->GetOn##name_(cx, vp);                              \
-    }                                                               \
-    *vp = JSVAL_NULL;                                               \
+    getter_type_ h = nsGenericHTMLElement::GetOn##name_();          \
+    vp->setObjectOrNull(h ? h->Callable() : nullptr);               \
     return NS_OK;                                                   \
   }                                                                 \
   NS_IMETHODIMP nsHTMLBodyElement::SetOn##name_(JSContext *cx,      \
                                            const jsval &v) {        \
-    nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();           \
-    if (win && win->IsInnerWindow()) {                              \
-      nsCOMPtr<nsIInlineEventHandlers> ev = do_QueryInterface(win); \
-      return ev->SetOn##name_(cx, v);                               \
+    JSObject *obj = GetWrapper();                                   \
+    if (!obj) {                                                     \
+      /* Just silently do nothing */                                \
+      return NS_OK;                                                 \
     }                                                               \
-    return NS_OK;                                                   \
+    nsRefPtr<EventHandlerNonNull> handler;                          \
+    JSObject *callable;                                             \
+    if (v.isObject() &&                                             \
+        JS_ObjectIsCallable(cx, callable = &v.toObject())) {        \
+      bool ok;                                                      \
+      handler = new EventHandlerNonNull(cx, obj, callable, &ok);    \
+      if (!ok) {                                                    \
+        return NS_ERROR_OUT_OF_MEMORY;                              \
+      }                                                             \
+    }                                                               \
+    ErrorResult rv;                                                 \
+    nsGenericHTMLElement::SetOn##name_(handler, rv);                \
+    return rv.ErrorCode();                                          \
   }
+#define FORWARDED_EVENT(name_, id_, type_, struct_)                 \
+  FORWARDED_EVENT_HELPER(name_, EventHandlerNonNull*)
+#define ERROR_EVENT(name_, id_, type_, struct_)                     \
+  FORWARDED_EVENT_HELPER(name_, nsCOMPtr<EventHandlerNonNull>)
 #define WINDOW_EVENT(name_, id_, type_, struct_)                  \
   NS_IMETHODIMP nsHTMLBodyElement::GetOn##name_(JSContext *cx,    \
                                                 jsval *vp) {      \
     nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();         \
     if (win && win->IsInnerWindow()) {                            \
       return win->GetOn##name_(cx, vp);                           \
     }                                                             \
     *vp = JSVAL_NULL;                                             \
@@ -517,10 +526,12 @@ nsHTMLBodyElement::GetAssociatedEditor()
     nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();         \
     if (win && win->IsInnerWindow()) {                            \
       return win->SetOn##name_(cx, v);                            \
     }                                                             \
     return NS_OK;                                                 \
   }
 #include "nsEventNameList.h"
 #undef WINDOW_EVENT
+#undef ERROR_EVENT
 #undef FORWARDED_EVENT
+#undef FORWARDED_EVENT_HELPER
 #undef EVENT
--- a/content/html/content/src/nsHTMLFrameSetElement.cpp
+++ b/content/html/content/src/nsHTMLFrameSetElement.cpp
@@ -1,16 +1,18 @@
 /* -*- 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 "nsHTMLFrameSetElement.h"
 #include "jsapi.h"
+#include "mozilla/dom/EventHandlerBinding.h"
 
+using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(FrameSet)
 
 nsHTMLFrameSetElement::nsHTMLFrameSetElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo), mNumRows(0), mNumCols(0),
     mCurrentRowColHint(NS_STYLE_HINT_REFLOW)
 {
@@ -313,44 +315,52 @@ nsHTMLFrameSetElement::ParseRowCol(const
 
   aNumSpecs = count;
   // Transfer ownership to caller here
   *aSpecs = specs;
   
   return NS_OK;
 }
 
-// Event listener stuff
-// FIXME (https://bugzilla.mozilla.org/show_bug.cgi?id=431767)
-// nsDocument::GetInnerWindow can return an outer window in some
-// cases.  We don't want to stick an event listener on an outer
-// window, so bail if it does.  See also similar code in
-// nsGenericHTMLElement::GetEventListenerManagerForAttr.
 #define EVENT(name_, id_, type_, struct_) /* nothing; handled by the shim */
-#define FORWARDED_EVENT(name_, id_, type_, struct_)                   \
+// nsGenericHTMLElement::GetOnError returns
+// already_AddRefed<EventHandlerNonNull> while other getters return
+// EventHandlerNonNull*, so allow passing in the type to use here.
+#define FORWARDED_EVENT_HELPER(name_, getter_type_)                   \
   NS_IMETHODIMP nsHTMLFrameSetElement::GetOn##name_(JSContext *cx,    \
                                                jsval *vp) {           \
-    /* XXXbz note to self: add tests for this! */                     \
-    nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();             \
-    if (win && win->IsInnerWindow()) {                                \
-      nsCOMPtr<nsIInlineEventHandlers> ev = do_QueryInterface(win);   \
-      return ev->GetOn##name_(cx, vp);                                \
-    }                                                                 \
-    *vp = JSVAL_NULL;                                                 \
+    getter_type_ h = nsGenericHTMLElement::GetOn##name_();            \
+    vp->setObjectOrNull(h ? h->Callable() : nullptr);                 \
     return NS_OK;                                                     \
   }                                                                   \
   NS_IMETHODIMP nsHTMLFrameSetElement::SetOn##name_(JSContext *cx,    \
                                                const jsval &v) {      \
-    nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();             \
-    if (win && win->IsInnerWindow()) {                                \
-      nsCOMPtr<nsIInlineEventHandlers> ev = do_QueryInterface(win);   \
-      return ev->SetOn##name_(cx, v);                                 \
+    JSObject *obj = GetWrapper();                                     \
+    if (!obj) {                                                       \
+      /* Just silently do nothing */                                  \
+      return NS_OK;                                                   \
     }                                                                 \
-    return NS_OK;                                                     \
+    nsRefPtr<EventHandlerNonNull> handler;                            \
+    JSObject *callable;                                               \
+    if (v.isObject() &&                                               \
+        JS_ObjectIsCallable(cx, callable = &v.toObject())) {          \
+      bool ok;                                                        \
+      handler = new EventHandlerNonNull(cx, obj, callable, &ok);      \
+      if (!ok) {                                                      \
+        return NS_ERROR_OUT_OF_MEMORY;                                \
+      }                                                               \
+    }                                                                 \
+    ErrorResult rv;                                                   \
+    nsGenericHTMLElement::SetOn##name_(handler, rv);                  \
+    return rv.ErrorCode();                                            \
   }
+#define FORWARDED_EVENT(name_, id_, type_, struct_)                   \
+  FORWARDED_EVENT_HELPER(name_, EventHandlerNonNull*)
+#define ERROR_EVENT(name_, id_, type_, struct_)                       \
+  FORWARDED_EVENT_HELPER(name_, nsCOMPtr<EventHandlerNonNull>)
 #define WINDOW_EVENT(name_, id_, type_, struct_)                      \
   NS_IMETHODIMP nsHTMLFrameSetElement::GetOn##name_(JSContext *cx,    \
                                                     jsval *vp) {      \
     /* XXXbz note to self: add tests for this! */                     \
     nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();             \
     if (win && win->IsInnerWindow()) {                                \
       return win->GetOn##name_(cx, vp);                               \
     }                                                                 \
@@ -362,10 +372,12 @@ nsHTMLFrameSetElement::ParseRowCol(const
     nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow();             \
     if (win && win->IsInnerWindow()) {                                \
       return win->SetOn##name_(cx, v);                                \
     }                                                                 \
     return NS_OK;                                                     \
   }
 #include "nsEventNameList.h"
 #undef WINDOW_EVENT
+#undef ERROR_EVENT
 #undef FORWARDED_EVENT
+#undef FORWARDED_EVENT_HELPER
 #undef EVENT
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -1955,16 +1955,17 @@ bool nsHTMLMediaElement::ParseAttribute(
 
   // Mappings from 'mozaudiochannel' attribute strings to an enumeration.
   static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = {
     { "normal",             AUDIO_CHANNEL_NORMAL },
     { "content",            AUDIO_CHANNEL_CONTENT },
     { "notification",       AUDIO_CHANNEL_NOTIFICATION },
     { "alarm",              AUDIO_CHANNEL_ALARM },
     { "telephony",          AUDIO_CHANNEL_TELEPHONY },
+    { "ringer",             AUDIO_CHANNEL_RINGER },
     { "publicnotification", AUDIO_CHANNEL_PUBLICNOTIFICATION },
     { 0 }
   };
 
   if (aNamespaceID == kNameSpaceID_None) {
     if (ParseImageAttribute(aAttribute, aValue, aResult)) {
       return true;
     }
@@ -3095,18 +3096,23 @@ void nsHTMLMediaElement::NotifyOwnerDocu
         }
       } else if (mSrcStream) {
         GetSrcMediaStream()->ChangeExplicitBlockerCount(-1);
       }
       DispatchPendingMediaEvents();
     }
   }
 
-  if (mPlayingThroughTheAudioChannel) {
-    UpdateChannelMuteState();
+  if (mPlayingThroughTheAudioChannel && mAudioChannelAgent) {
+    bool hidden = false;
+    nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(ownerDoc);
+    if (domDoc) {
+      domDoc->GetHidden(&hidden);
+      mAudioChannelAgent->SetVisibilityState(!hidden);
+    }
   }
 
   AddRemoveSelfReference();
 }
 
 void nsHTMLMediaElement::AddRemoveSelfReference()
 {
   // XXX we could release earlier here in many situations if we examined
@@ -3505,42 +3511,26 @@ NS_IMETHODIMP nsHTMLMediaElement::SetMoz
 }
 
 ImageContainer* nsHTMLMediaElement::GetImageContainer()
 {
   VideoFrameContainer* container = GetVideoFrameContainer();
   return container ? container->GetImageContainer() : nullptr;
 }
 
-nsresult nsHTMLMediaElement::UpdateChannelMuteState()
+nsresult nsHTMLMediaElement::UpdateChannelMuteState(bool aCanPlay)
 {
   // Only on B2G we mute the nsHTMLMediaElement following the rules of
   // AudioChannelService.
 #ifdef MOZ_B2G
-  bool hidden = false;
-  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(OwnerDoc());
-  if (!domDoc) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsresult rv = domDoc->GetHidden(&hidden);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  bool mute = false;
-
-  nsRefPtr<AudioChannelService> audioChannelService = AudioChannelService::GetAudioChannelService();
-  if (audioChannelService) {
-    mute = audioChannelService->GetMuted(mAudioChannelType, hidden);
-  }
-
   // We have to mute this channel:
-  if (mute && !mChannelMuted) {
+  if (!aCanPlay && !mChannelMuted) {
     mChannelMuted = true;
     DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptbegin"));
-  } else if (!mute && mChannelMuted) {
+  } else if (aCanPlay && mChannelMuted) {
     mChannelMuted = false;
     DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
   }
 
   SetMutedInternal(mMuted);
 #endif
 
   return NS_OK;
@@ -3551,27 +3541,35 @@ void nsHTMLMediaElement::UpdateAudioChan
   // The nsHTMLMediaElement is registered to the AudioChannelService only on B2G.
 #ifdef MOZ_B2G
   bool playingThroughTheAudioChannel =
      (mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
       IsPotentiallyPlaying());
   if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
     mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
 
-    nsRefPtr<AudioChannelService> audioChannelService = AudioChannelService::GetAudioChannelService();
-    if (!audioChannelService) {
-      return;
+    if (!mAudioChannelAgent) {
+      nsresult rv;
+      mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
+      if (!mAudioChannelAgent) {
+        return;
+      }
+      mAudioChannelAgent->Init( mAudioChannelType, this);
     }
 
     if (mPlayingThroughTheAudioChannel) {
-      audioChannelService->RegisterMediaElement(this, mAudioChannelType);
+      bool canPlay;
+      mAudioChannelAgent->StartPlaying(&canPlay);
     } else {
-      audioChannelService->UnregisterMediaElement(this);
+      mAudioChannelAgent->StopPlaying();
+      mAudioChannelAgent = nullptr;
     }
   }
 #endif
 }
 
-nsresult nsHTMLMediaElement::NotifyAudioChannelStateChanged()
+/* void canPlayChanged (in boolean canPlay); */
+NS_IMETHODIMP nsHTMLMediaElement::CanPlayChanged(bool canPlay)
 {
-  return UpdateChannelMuteState();
+  UpdateChannelMuteState(canPlay);
+  return NS_OK;
 }
 
--- a/content/html/content/src/nsHTMLSelectElement.h
+++ b/content/html/content/src/nsHTMLSelectElement.h
@@ -665,19 +665,15 @@ nsHTMLOptionCollection::Add(const HTMLOp
   nsGenericHTMLElement& element =
     aElement.IsHTMLOptionElement() ?
     static_cast<nsGenericHTMLElement&>(*aElement.GetAsHTMLOptionElement()) :
     static_cast<nsGenericHTMLElement&>(*aElement.GetAsHTMLOptGroupElement());
 
   if (aBefore.IsNull()) {
     mSelect->Add(element, (nsGenericHTMLElement*)nullptr, aError);
   } else if (aBefore.Value().IsHTMLElement()) {
-    nsCOMPtr<nsIContent> content =
-      do_QueryInterface(aBefore.Value().GetAsHTMLElement());
-    nsGenericHTMLElement* before =
-      static_cast<nsGenericHTMLElement*>(content.get());
-    mSelect->Add(element, before, aError);
+    mSelect->Add(element, &aBefore.Value().GetAsHTMLElement(), aError);
   } else {
     mSelect->Add(element, aBefore.Value().GetAsLong(), aError);
   }
 }
 
 #endif
--- a/content/media/AbstractMediaDecoder.h
+++ b/content/media/AbstractMediaDecoder.h
@@ -88,18 +88,18 @@ public:
   virtual void SetMediaEndTime(int64_t aTime) = 0;
 
   // Make the decoder state machine update the playback position. Called by
   // the reader on the decoder thread (Assertions for this checked by
   // mDecoderStateMachine). This must be called with the decode monitor
   // held.
   virtual void UpdatePlaybackPosition(int64_t aTime) = 0;
 
-  // Called when the metadata from the media file has been read by the reader.
-  // Call on the decode thread only.
+  // May be called by the reader to notify this decoder that the metadata from
+  // the media file has been read. Call on the decode thread only.
   virtual void OnReadMetadataCompleted() = 0;
 
   // Stack based class to assist in notifying the frame statistics of
   // parsed and decoded frames. Use inside video demux & decode functions
   // to ensure all parsed and decoded frames are reported on all return paths.
   class AutoNotifyDecoded {
   public:
     AutoNotifyDecoded(AbstractMediaDecoder* aDecoder, uint32_t& aParsed, uint32_t& aDecoded)
--- a/content/media/AudioStream.cpp
+++ b/content/media/AudioStream.cpp
@@ -161,16 +161,18 @@ static sa_stream_type_t ConvertChannelTo
     case dom::AUDIO_CHANNEL_CONTENT:
       return SA_STREAM_TYPE_MUSIC;
     case dom::AUDIO_CHANNEL_NOTIFICATION:
       return SA_STREAM_TYPE_NOTIFICATION;
     case dom::AUDIO_CHANNEL_ALARM:
       return SA_STREAM_TYPE_ALARM;
     case dom::AUDIO_CHANNEL_TELEPHONY:
       return SA_STREAM_TYPE_VOICE_CALL;
+    case dom::AUDIO_CHANNEL_RINGER:
+      return SA_STREAM_TYPE_RING;
     case dom::AUDIO_CHANNEL_PUBLICNOTIFICATION:
       return SA_STREAM_TYPE_ENFORCED_AUDIBLE;
     default:
       NS_ERROR("The value of AudioChannelType is invalid");
       return SA_STREAM_TYPE_MAX;
   }
 }
 
new file mode 100644
--- /dev/null
+++ b/content/media/BufferMediaResource.h
@@ -0,0 +1,144 @@
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#if !defined(BufferMediaResource_h_)
+#define BufferMediaResource_h_
+
+#include "MediaResource.h"
+#include "nsISeekableStream.h"
+#include "nsIPrincipal.h"
+#include <algorithm>
+
+namespace mozilla {
+
+// A simple MediaResource based on an in memory buffer.  This class accepts
+// the address and the length of the buffer, and simulates a read/seek API
+// on top of it.  The Read implementation involves copying memory, which is
+// unfortunate, but the MediaResource interface mandates that.
+class BufferMediaResource : public MediaResource
+{
+public:
+  BufferMediaResource(const uint8_t* aBuffer,
+                      uint32_t aLength,
+                      nsIPrincipal* aPrincipal) :
+    mBuffer(aBuffer),
+    mLength(aLength),
+    mOffset(0),
+    mPrincipal(aPrincipal)
+  {
+    MOZ_COUNT_CTOR(BufferMediaResource);
+  }
+
+  virtual ~BufferMediaResource()
+  {
+    MOZ_COUNT_DTOR(BufferMediaResource);
+  }
+
+  virtual nsresult Close() { return NS_OK; }
+  virtual void Suspend(bool aCloseImmediately) {}
+  virtual void Resume() {}
+  // Get the current principal for the channel
+  virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal()
+  {
+    nsCOMPtr<nsIPrincipal> principal = mPrincipal;
+    return principal.forget();
+  }
+  virtual bool CanClone() { return false; }
+  virtual MediaResource* CloneData(MediaDecoder* aDecoder)
+  {
+    return nullptr;
+  }
+
+  // These methods are called off the main thread.
+  // The mode is initially MODE_PLAYBACK.
+  virtual void SetReadMode(MediaCacheStream::ReadMode aMode) {}
+  virtual void SetPlaybackRate(uint32_t aBytesPerSecond) {}
+  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
+  {
+    *aBytes = std::min(mLength - mOffset, aCount);
+    memcpy(aBuffer, mBuffer + mOffset, *aBytes);
+    mOffset += *aBytes;
+    MOZ_ASSERT(mOffset <= mLength);
+    return NS_OK;
+  }
+  virtual nsresult Seek(int32_t aWhence, int64_t aOffset)
+  {
+    MOZ_ASSERT(aOffset <= UINT32_MAX);
+    switch (aWhence) {
+    case nsISeekableStream::NS_SEEK_SET:
+      if (aOffset < 0 || aOffset > mLength) {
+        return NS_ERROR_FAILURE;
+      }
+      mOffset = static_cast<uint32_t> (aOffset);
+      break;
+    case nsISeekableStream::NS_SEEK_CUR:
+      if (aOffset >= mLength - mOffset) {
+        return NS_ERROR_FAILURE;
+      }
+      mOffset += static_cast<uint32_t> (aOffset);
+      break;
+    case nsISeekableStream::NS_SEEK_END:
+      if (aOffset < 0 || aOffset > mLength) {
+        return NS_ERROR_FAILURE;
+      }
+      mOffset = mLength - aOffset;
+      break;
+    }
+
+    return NS_OK;
+  }
+  virtual void StartSeekingForMetadata() {}
+  virtual void EndSeekingForMetadata() {}
+  virtual int64_t Tell() { return mOffset; }
+
+  virtual void Pin() {}
+  virtual void Unpin() {}
+  virtual double GetDownloadRate(bool* aIsReliable) { return 0.; }
+  virtual int64_t GetLength() { return mLength; }
+  virtual int64_t GetNextCachedData(int64_t aOffset) { return aOffset; }
+  virtual int64_t GetCachedDataEnd(int64_t aOffset) { return mLength; }
+  virtual bool IsDataCachedToEndOfResource(int64_t aOffset) { return true; }
+  virtual bool IsSuspendedByCache(MediaResource** aActiveResource) { return false; }
+  virtual bool IsSuspended() { return false; }
+  virtual nsresult ReadFromCache(char* aBuffer,
+                                 int64_t aOffset,
+                                 uint32_t aCount)
+  {
+    if (aOffset < 0) {
+      return NS_ERROR_FAILURE;
+    }
+
+    uint32_t bytes = std::min(mLength - static_cast<uint32_t>(aOffset), aCount);
+    memcpy(aBuffer, mBuffer + aOffset, bytes);
+    return NS_OK;
+  }
+
+  virtual nsresult Open(nsIStreamListener** aStreamListener)
+  {
+    return NS_ERROR_FAILURE;
+  }
+
+  virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
+                                 MediaByteRange const &aByteRange)
+  {
+    return NS_ERROR_FAILURE;
+  }
+
+  virtual nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
+  {
+    aRanges.AppendElement(MediaByteRange(0, mLength));
+    return NS_OK;
+  }
+
+private:
+  const uint8_t * mBuffer;
+  uint32_t mLength;
+  uint32_t mOffset;
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+};
+
+}
+
+#endif
--- a/content/media/Makefile.in
+++ b/content/media/Makefile.in
@@ -13,16 +13,17 @@ include $(DEPTH)/config/autoconf.mk
 MODULE = content
 LIBRARY_NAME = gkconmedia_s
 LIBXUL_LIBRARY = 1
 
 EXPORTS = \
   AbstractMediaDecoder.h \
   AudioSampleFormat.h \
   AudioSegment.h \
+  BufferMediaResource.h \
   DecoderTraits.h \
   FileBlockCache.h \
   MediaDecoderOwner.h \
   MediaResource.h \
   MediaSegment.h \
   MediaStreamGraph.h \
   AudioAvailableEventManager.h \
   MediaDecoder.h \
--- a/content/media/MediaCache.cpp
+++ b/content/media/MediaCache.cpp
@@ -1715,39 +1715,74 @@ MediaCacheStream::NotifyDataReceived(int
 
   // Notify in case there's a waiting reader
   // XXX it would be fairly easy to optimize things a lot more to
   // avoid waking up reader threads unnecessarily
   mon.NotifyAll();
 }
 
 void
+MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
+
+  int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE);
+  if (blockOffset > 0) {
+    LOG(PR_LOG_DEBUG,
+        ("Stream %p writing partial block: [%d] bytes; "
+         "mStreamOffset [%lld] mChannelOffset[%lld] mStreamLength [%lld] "
+         "notifying: [%s]",
+         this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength,
+         aNotifyAll ? "yes" : "no"));
+
+    // Write back the partial block
+    memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
+           BLOCK_SIZE - blockOffset);
+    gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
+        mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
+    if (aNotifyAll) {
+      // Wake up readers who may be waiting for this data
+      mon.NotifyAll();
+    }
+  }
+}
+
+void
+MediaCacheStream::FlushPartialBlock()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+  ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
+
+  // Write the current partial block to memory.
+  // Note: This writes a full block, so if data is not at the end of the
+  // stream, the decoder must subsequently choose correct start and end offsets
+  // for reading/seeking.
+  FlushPartialBlockInternal(false);
+
+  gMediaCache->QueueUpdate();
+}
+
+void
 MediaCacheStream::NotifyDataEnded(nsresult aStatus)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
 
   if (NS_FAILED(aStatus)) {
     // Disconnect from other streams sharing our resource, since they
     // should continue trying to load. Our load might have been deliberately
     // canceled and that shouldn't affect other streams.
     mResourceID = gMediaCache->AllocateResourceID();
   }
 
-  int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE);
-  if (blockOffset > 0) {
-    // Write back the partial block
-    memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
-           BLOCK_SIZE - blockOffset);
-    gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
-        mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
-    // Wake up readers who may be waiting for this data
-    mon.NotifyAll();
-  }
+  FlushPartialBlockInternal(true);
 
   if (!mDidNotifyDataEnded) {
     MediaCache::ResourceStreamIterator iter(mResourceID);
     while (MediaCacheStream* stream = iter.Next()) {
       if (NS_SUCCEEDED(aStatus)) {
         // We read the whole stream, so remember the true length
         stream->mStreamLength = mChannelOffset;
       }
--- a/content/media/MediaCache.h
+++ b/content/media/MediaCache.h
@@ -268,16 +268,19 @@ public:
   // Notifies the cache that data has been received. The stream already
   // knows the offset because data is received in sequence and
   // the starting offset is known via NotifyDataStarted or because
   // the cache requested the offset in
   // ChannelMediaResource::CacheClientSeek, or because it defaulted to 0.
   // We pass in the principal that was used to load this data.
   void NotifyDataReceived(int64_t aSize, const char* aData,
                           nsIPrincipal* aPrincipal);
+  // Notifies the cache that the current bytes should be written to disk.
+  // Called on the main thread.
+  void FlushPartialBlock();
   // Notifies the cache that the channel has closed with the given status.
   void NotifyDataEnded(nsresult aStatus);
 
   // These methods can be called on any thread.
   // Cached blocks associated with this stream will not be evicted
   // while the stream is pinned.
   void Pin();
   void Unpin();
@@ -410,16 +413,21 @@ private:
   // This method assumes that the cache monitor is held and can be called on
   // any thread.
   int64_t GetCachedDataEndInternal(int64_t aOffset);
   // Returns the offset of the first byte of cached data at or after aOffset,
   // or -1 if there is no such cached data.
   // This method assumes that the cache monitor is held and can be called on
   // any thread.
   int64_t GetNextCachedDataInternal(int64_t aOffset);
+  // Writes |mPartialBlock| to disk.
+  // Used by |NotifyDataEnded| and |FlushPartialBlock|.
+  // If |aNotifyAll| is true, this function will wake up readers who may be
+  // waiting on the media cache monitor. Called on the main thread only.
+  void FlushPartialBlockInternal(bool aNotify);
   // A helper function to do the work of closing the stream. Assumes
   // that the cache monitor is held. Main thread only.
   // aReentrantMonitor is the nsAutoReentrantMonitor wrapper holding the cache monitor.
   // This is used to NotifyAll to wake up threads that might be
   // blocked on reading from this stream.
   void CloseInternal(ReentrantMonitorAutoEnter& aReentrantMonitor);
   // Update mPrincipal given that data has been received from aPrincipal
   bool UpdatePrincipal(nsIPrincipal* aPrincipal);
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -650,18 +650,18 @@ public:
    * thread.
    ******/
 
   // Change to a new play state. This updates the mState variable and
   // notifies any thread blocking on this object's monitor of the
   // change. Call on the main thread only.
   void ChangeState(PlayState aState);
 
-  // Called when the metadata from the media file has been read by the reader.
-  // Call on the decode thread only.
+  // May be called by the reader to notify this decoder that the metadata from
+  // the media file has been read. Call on the decode thread only.
   void OnReadMetadataCompleted() MOZ_OVERRIDE { }
 
   // Called when the metadata from the media file has been loaded by the
   // state machine. Call on the main thread only.
   void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, MetadataTags* aTags);
 
   // Called when the first frame has been loaded.
   // Call on the main thread only.
--- a/content/media/MediaDecoderReader.cpp
+++ b/content/media/MediaDecoderReader.cpp
@@ -332,53 +332,53 @@ MediaDecoderReader::~MediaDecoderReader(
   ResetDecode();
   MOZ_COUNT_DTOR(MediaDecoderReader);
 }
 
 nsresult MediaDecoderReader::ResetDecode()
 {
   nsresult res = NS_OK;
 
-  mVideoQueue.Reset();
-  mAudioQueue.Reset();
+  VideoQueue().Reset();
+  AudioQueue().Reset();
 
   return res;
 }
 
 VideoData* MediaDecoderReader::DecodeToFirstVideoData()
 {
   bool eof = false;
-  while (!eof && mVideoQueue.GetSize() == 0) {
+  while (!eof && VideoQueue().GetSize() == 0) {
     {
       ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
       if (mDecoder->IsShutdown()) {
         return nullptr;
       }
     }
     bool keyframeSkip = false;
     eof = !DecodeVideoFrame(keyframeSkip, 0);
   }
   VideoData* d = nullptr;
-  return (d = mVideoQueue.PeekFront()) ? d : nullptr;
+  return (d = VideoQueue().PeekFront()) ? d : nullptr;
 }
 
 AudioData* MediaDecoderReader::DecodeToFirstAudioData()
 {
   bool eof = false;
-  while (!eof && mAudioQueue.GetSize() == 0) {
+  while (!eof && AudioQueue().GetSize() == 0) {
     {
       ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
       if (mDecoder->IsShutdown()) {
         return nullptr;
       }
     }
     eof = !DecodeAudioData();
   }
   AudioData* d = nullptr;
-  return (d = mAudioQueue.PeekFront()) ? d : nullptr;
+  return (d = AudioQueue().PeekFront()) ? d : nullptr;
 }
 
 VideoData* MediaDecoderReader::FindStartTime(int64_t& aOutStartTime)
 {
   NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
                "Should be on state machine or decode thread.");
 
   // Extract the start times of the bitstreams in order to calculate
@@ -411,41 +411,41 @@ VideoData* MediaDecoderReader::FindStart
 nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
 {
   // Decode forward to the target frame. Start with video, if we have it.
   if (HasVideo()) {
     bool eof = false;
     int64_t startTime = -1;
     nsAutoPtr<VideoData> video;
     while (HasVideo() && !eof) {
-      while (mVideoQueue.GetSize() == 0 && !eof) {
+      while (VideoQueue().GetSize() == 0 && !eof) {
         bool skip = false;
         eof = !DecodeVideoFrame(skip, 0);
         {
           ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
           if (mDecoder->IsShutdown()) {
             return NS_ERROR_FAILURE;
           }
         }
       }
-      if (mVideoQueue.GetSize() == 0) {
+      if (VideoQueue().GetSize() == 0) {
         // Hit end of file, we want to display the last frame of the video.
         if (video) {
-          mVideoQueue.PushFront(video.forget());
+          VideoQueue().PushFront(video.forget());
         }
         break;
       }
-      video = mVideoQueue.PeekFront();
+      video = VideoQueue().PeekFront();
       // 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 (video && video->mEndTime <= aTarget) {
         if (startTime == -1) {
           startTime = video->mTime;
         }
-        mVideoQueue.PopFront();
+        VideoQueue().PopFront();
       } else {
         video.forget();
         break;
       }
     }
     {
       ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
       if (mDecoder->IsShutdown()) {
@@ -454,37 +454,37 @@ nsresult MediaDecoderReader::DecodeToTar
     }
     LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", startTime));
   }
 
   if (HasAudio()) {
     // Decode audio forward to the seek target.
     bool eof = false;
     while (HasAudio() && !eof) {
-      while (!eof && mAudioQueue.GetSize() == 0) {
+      while (!eof && AudioQueue().GetSize() == 0) {
         eof = !DecodeAudioData();
         {
           ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
           if (mDecoder->IsShutdown()) {
             return NS_ERROR_FAILURE;
           }
         }
       }
-      const AudioData* audio = mAudioQueue.PeekFront();
+      const AudioData* audio = AudioQueue().PeekFront();
       if (!audio)
         break;
       CheckedInt64 startFrame = UsecsToFrames(audio->mTime, mInfo.mAudioRate);
       CheckedInt64 targetFrame = UsecsToFrames(aTarget, mInfo.mAudioRate);
       if (!startFrame.isValid() || !targetFrame.isValid()) {
         return NS_ERROR_FAILURE;
       }
       if (startFrame.value() + audio->mFrames <= targetFrame.value()) {
         // Our seek target lies after the frames in this AudioData. Pop it
         // off the queue, and keep decoding forwards.
-        delete mAudioQueue.PopFront();
+        delete AudioQueue().PopFront();
         audio = nullptr;
         continue;
       }
       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
@@ -521,18 +521,18 @@ nsresult MediaDecoderReader::DecodeToTar
         return NS_ERROR_FAILURE;
       }
       nsAutoPtr<AudioData> data(new AudioData(audio->mOffset,
                                               aTarget,
                                               duration.value(),
                                               frames,
                                               audioData.forget(),
                                               channels));
-      delete mAudioQueue.PopFront();
-      mAudioQueue.PushFront(data.forget());
+      delete AudioQueue().PopFront();
+      AudioQueue().PushFront(data.forget());
       break;
     }
   }
   return NS_OK;
 }
 
 } // namespace mozilla
 
--- a/content/media/MediaDecoderReader.h
+++ b/content/media/MediaDecoderReader.h
@@ -368,16 +368,19 @@ public:
   virtual nsresult ResetDecode();
 
   // 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() = 0;
 
+  // Steps to carry out at the start of the |DecodeLoop|.
+  virtual void PrepareToDecode() { }
+
   // 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) = 0;
 
   virtual bool HasAudio() = 0;
   virtual bool HasVideo() = 0;
@@ -466,27 +469,16 @@ public:
   // Returns a pointer to the decoder.
   AbstractMediaDecoder* GetDecoder() {
     return mDecoder;
   }
 
   AudioData* DecodeToFirstAudioData();
   VideoData* DecodeToFirstVideoData();
 
-  // Sets range for initialization bytes; used by DASH.
-  virtual void SetInitByteRange(MediaByteRange &aByteRange) { }
-
-  // Sets range for index frame bytes; used by DASH.
-  virtual void SetIndexByteRange(MediaByteRange &aByteRange) { }
-
-  // Returns list of ranges for index frame start/end offsets. Used by DASH.
-  virtual nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
 protected:
   // Pumps the decode until we reach frames required to play at time aTarget
   // (usecs).
   nsresult DecodeToTarget(int64_t aTarget);
 
   // Reference to the owning decoder object.
   AbstractMediaDecoder* mDecoder;
 
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -788,16 +788,18 @@ void MediaDecoderStateMachine::DecodeLoo
 
   // Main decode loop.
   bool videoPlaying = HasVideo();
   bool audioPlaying = HasAudio();
   while ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) &&
          !mStopDecodeThread &&
          (videoPlaying || audioPlaying))
   {
+    mReader->PrepareToDecode();
+
     // We don't want to consider skipping to the next keyframe if we've
     // only just started up the decode loop, so wait until we've decoded
     // some frames before enabling the keyframe skip logic on video.
     if (videoPump &&
         (static_cast<uint32_t>(mReader->VideoQueue().GetSize())
          >= videoPumpThreshold * mPlaybackRate))
     {
       videoPump = false;
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -229,31 +229,33 @@ ChannelMediaResource::OnStartRequest(nsI
     }
 
     // Check response code for byte-range requests (seeking, chunk requests).
     if (!mByteRange.IsNull() && (responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
       // Byte range requests should get partial response codes and should
       // accept ranges.
       if (!acceptsRanges) {
         CMLOG("Error! HTTP_PARTIAL_RESPONSE_CODE received but server says "
-              "range requests are not accepted! Channel[%p]", hc.get());
+              "range requests are not accepted! Channel[%p] decoder[%p]",
+              hc.get(), mDecoder);
         mDecoder->NetworkError();
         CloseChannel();
         return NS_OK;
       }
 
       // Parse Content-Range header.
       int64_t rangeStart = 0;
       int64_t rangeEnd = 0;
       int64_t rangeTotal = 0;
       rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
       if (NS_FAILED(rv)) {
         // Content-Range header text should be parse-able.
         CMLOG("Error processing \'Content-Range' for "
-              "HTTP_PARTIAL_RESPONSE_CODE: rv[%x]channel [%p]", rv, hc.get());
+              "HTTP_PARTIAL_RESPONSE_CODE: rv[%x] channel[%p] decoder[%p]",
+              rv, hc.get(), mDecoder);
         mDecoder->NetworkError();
         CloseChannel();
         return NS_OK;
       }
 
       // Give some warnings if the ranges are unexpected.
       // XXX These could be error conditions.
       NS_WARN_IF_FALSE(mByteRange.mStart == rangeStart,
@@ -384,18 +386,18 @@ ChannelMediaResource::ParseContentRangeH
   if (aRangeTotalText[0] == '*') {
     aRangeTotal = -1;
   } else {
     aRangeTotal = aRangeTotalText.ToInteger64(&rv);
     NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  CMLOG("Received bytes [%d] to [%d] of [%d]",
-        aRangeStart, aRangeEnd, aRangeTotal);
+  CMLOG("Received bytes [%lld] to [%lld] of [%lld] for decoder[%p]",
+        aRangeStart, aRangeEnd, aRangeTotal, mDecoder);
 
   return NS_OK;
 }
 
 nsresult
 ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
 {
   NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
@@ -473,20 +475,33 @@ ChannelMediaResource::CopySegmentToCache
                                          uint32_t aToOffset,
                                          uint32_t aCount,
                                          uint32_t *aWriteCount)
 {
   CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
 
   closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset);
 
-  // Keep track of where we're up to
+  // For byte range downloads controlled by |DASHDecoder|, there are cases in
+  // which the reader's offset is different enough from the channel offset that
+  // |MediaCache| requests a |CacheClientSeek| to the reader's offset. This
+  // can happen between calls to |CopySegmentToCache|. To avoid copying at
+  // incorrect offsets, ensure |MediaCache| copies to the location that
+  // |ChannelMediaResource| expects.
+  if (closure->mResource->mByteRangeDownloads) {
+    closure->mResource->mCacheStream.NotifyDataStarted(closure->mResource->mOffset);
+  }
+
+  // Keep track of where we're up to.
+  LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add "
+      "[%d] bytes for decoder[%p]",
+      closure->mResource, closure->mResource->mOffset, aCount,
+      closure->mResource->mDecoder);
   closure->mResource->mOffset += aCount;
-  LOG("%p [ChannelMediaResource]: CopySegmentToCache new mOffset = %d",
-      closure->mResource, closure->mResource->mOffset);
+
   closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
                                                       closure->mPrincipal);
   *aWriteCount = aCount;
   return NS_OK;
 }
 
 nsresult
 ChannelMediaResource::OnDataAvailable(nsIRequest* aRequest,
@@ -755,16 +770,18 @@ nsresult ChannelMediaResource::Read(char
 
   return mCacheStream.Read(aBuffer, aCount, aBytes);
 }
 
 nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
+  CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
+        aOffset, mDecoder);
   // Remember |aOffset|, because Media Cache may request a diff offset later.
   if (mByteRangeDownloads) {
     ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
     mSeekOffset = aOffset;
   }
 
   return mCacheStream.Seek(aWhence, aOffset);
 }
@@ -966,17 +983,31 @@ ChannelMediaResource::CacheClientNotifyP
   mDecoder->NotifyPrincipalChanged();
 }
 
 nsresult
 ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
 {
   NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
 
-  CloseChannel();
+  CMLOG("CacheClientSeek requested for aOffset [%lld] for decoder [%p]",
+        aOffset, mDecoder);
+
+  // |CloseChannel| immediately for non-byte-range downloads.
+  if (!mByteRangeDownloads) {
+    CloseChannel();
+  } else if (mChannel) {
+    // Only close byte range channels if they are not in pending state.
+    bool isPending = false;
+    nsresult rv = mChannel->IsPending(&isPending);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (!isPending) {
+      CloseChannel();
+    }
+  }
 
   if (aResume) {
     NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
     // No need to mess with the channel, since we're making a new one
     --mSuspendCount;
   }
 
   // Note: For chunked downloads, e.g. DASH, we need to determine which chunk
@@ -996,34 +1027,65 @@ ChannelMediaResource::CacheClientSeek(in
   //
   //   3 - Call |OpenByteRange| requesting |mByteRange| bytes.
 
   if (mByteRangeDownloads) {
     // Query decoder for chunk containing desired offset.
     nsresult rv;
     {
       ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
-      // Ensure that media cache can only request an equal or smaller offset;
-      // it may be trying to include the start of a cache block.
-      NS_ENSURE_TRUE(aOffset <= mSeekOffset, NS_ERROR_ILLEGAL_VALUE);
-      rv = mDecoder->GetByteRangeForSeek(mSeekOffset, mByteRange);
-      mSeekOffset = -1;
+      // Only continue with seek request if a prior call to |Seek| was made.
+      // If |Seek| was not called previously, it means the media cache is
+      // seeking on its own.
+      // E.g. For those WebM files which are encoded with cues at the end of
+      // the file, when the cues are parsed, the reader and media cache
+      // automatically return to the first offset not downloaded, normally the
+      // first byte after init data. This results in |MediaCache| requesting
+      // |aOffset| = 0 (aligning to the start of the cache block. Ignore this
+      // and let |DASHDecoder| decide which bytes to download and when.
+      if (mSeekOffset >= 0) {
+        rv = mDecoder->GetByteRangeForSeek(mSeekOffset, mByteRange);
+        // Cache may try to seek from the next uncached byte: this offset may
+        // be after the byte range being seeked, i.e. the range containing
+        // |mSeekOffset|, which is the offset actually requested by the reader.
+        // This case means that the seeked range is already cached. For byte
+        // range downloads, we do not permit the cache to request bytes outside
+        // the seeked range. Instead, the decoder is responsible for
+        // controlling the sequence of byte range downloads. As such, return
+        // silently, and do NOT request a new download.
+        if (NS_SUCCEEDED(rv) && !mByteRange.IsNull() &&
+            aOffset > mByteRange.mEnd) {
+          rv = NS_ERROR_NOT_AVAILABLE;
+          mByteRange.Clear();
+        }
+        mSeekOffset = -1;
+      } else {
+        LOG("MediaCache [%p] trying to seek independently to offset [%lld].",
+            &mCacheStream, aOffset);
+        rv = NS_ERROR_NOT_AVAILABLE;
+      }
     }
     if (rv == NS_ERROR_NOT_AVAILABLE) {
-      // Assume decoder will request correct bytes when range information
-      // becomes available. Return silently.
+      // Decoder will not make byte ranges available for non-active streams, or
+      // if range information is not yet available, or for metadata bytes if
+      // they have already been downloaded and read. In all cases, it is ok to
+      // return silently and assume that the decoder will request the correct
+      // byte range when range information becomes available.
+      CMLOG("Byte range not available for decoder [%p]; returning "
+            "silently.", mDecoder);
       return NS_OK;
     } else if (NS_FAILED(rv) || mByteRange.IsNull()) {
       // Decoder reported an error we don't want to handle here; just return.
+      CMLOG("Error getting byte range: seek offset[%lld] cache offset[%lld] "
+            "decoder[%p]", mSeekOffset, aOffset, mDecoder);
       mDecoder->NetworkError();
       CloseChannel();
       return rv;
     }
-    // Media cache may decrease offset to start of cache data block.
-    // Adjust start of byte range accordingly.
+    // Adjust the byte range to start where the media cache requested.
     mByteRange.mStart = mOffset = aOffset;
     return OpenByteRange(nullptr, mByteRange);
   }
 
   mOffset = aOffset;
 
   if (mSuspendCount > 0) {
     // Close the existing channel to force the channel to be recreated at
@@ -1037,16 +1099,36 @@ ChannelMediaResource::CacheClientSeek(in
 
   nsresult rv = RecreateChannel();
   if (NS_FAILED(rv))
     return rv;
 
   return OpenChannel(nullptr);
 }
 
+void
+ChannelMediaResource::FlushCache()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
+  // Ensure that data in the cache's partial block is written to disk.
+  mCacheStream.FlushPartialBlock();
+}
+
+void
+ChannelMediaResource::NotifyLastByteRange()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
+  // Tell media cache that the last data has been downloaded.
+  // Note: subsequent seeks will require re-opening the channel etc.
+  mCacheStream.NotifyDataEnded(NS_OK);
+
+}
+
 nsresult
 ChannelMediaResource::CacheClientSuspend()
 {
   Suspend(false);
 
   mDecoder->NotifySuspendedStatusChanged();
   return NS_OK;
 }
--- a/content/media/MediaResource.h
+++ b/content/media/MediaResource.h
@@ -299,16 +299,23 @@ public:
   }
 
   /**
    * Fills aRanges with MediaByteRanges representing the data which is cached
    * in the media cache. Stream should be pinned during call and while
    * aRanges is being used.
    */
   virtual nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges) = 0;
+
+  // Ensure that the media cache writes any data held in its partial block.
+  // Called on the main thread only.
+  virtual void FlushCache() { }
+
+  // Notify that the last data byte range was loaded.
+  virtual void NotifyLastByteRange() { }
 };
 
 class BaseMediaResource : public MediaResource {
 public:
   virtual nsIURI* URI() const { return mURI; }
   virtual void MoveLoadsToBackground();
 
 protected:
@@ -383,16 +390,23 @@ public:
   // MediaCacheStream::NotifyDataReceived/Ended.
   // This can fail.
   nsresult CacheClientSeek(int64_t aOffset, bool aResume);
   // Suspend the current load since data is currently not wanted
   nsresult CacheClientSuspend();
   // Resume the current load since data is wanted again
   nsresult CacheClientResume();
 
+  // Ensure that the media cache writes any data held in its partial block.
+  // Called on the main thread.
+  virtual void FlushCache() MOZ_OVERRIDE;
+
+  // Notify that the last data byte range was loaded.
+  virtual void NotifyLastByteRange() MOZ_OVERRIDE;
+
   // Main thread
   virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
                                  MediaByteRange const & aByteRange);
   virtual nsresult Close();
   virtual void     Suspend(bool aCloseImmediately);
   virtual void     Resume();
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
--- a/content/media/VideoUtils.h
+++ b/content/media/VideoUtils.h
@@ -65,16 +65,55 @@ private:
     ReentrantMonitorAutoExit(const ReentrantMonitorAutoExit&);
     ReentrantMonitorAutoExit& operator =(const ReentrantMonitorAutoExit&);
     static void* operator new(size_t) CPP_THROW_NEW;
     static void operator delete(void*);
 
     ReentrantMonitor* mReentrantMonitor;
 };
 
+/**
+ * ReentrantMonitorConditionallyEnter
+ *
+ * Enters the supplied monitor only if the conditional value |aEnter| is true.
+ * E.g. Used to allow unmonitored read access on the decode thread,
+ * and monitored access on all other threads.
+ */
+class NS_STACK_CLASS ReentrantMonitorConditionallyEnter
+{
+public:
+  ReentrantMonitorConditionallyEnter(bool aEnter,
+                                     ReentrantMonitor &aReentrantMonitor) :
+    mReentrantMonitor(nullptr)
+  {
+    MOZ_COUNT_CTOR(ReentrantMonitorConditionallyEnter);
+    if (aEnter) {
+      mReentrantMonitor = &aReentrantMonitor;
+      NS_ASSERTION(mReentrantMonitor, "null monitor");
+      mReentrantMonitor->Enter();
+    }
+  }
+  ~ReentrantMonitorConditionallyEnter(void)
+  {
+    if (mReentrantMonitor) {
+      mReentrantMonitor->Exit();
+    }
+    MOZ_COUNT_DTOR(ReentrantMonitorConditionallyEnter);
+  }
+private:
+  // Restrict to constructor and destructor defined above.
+  ReentrantMonitorConditionallyEnter();
+  ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter&);
+  ReentrantMonitorConditionallyEnter& operator =(const ReentrantMonitorConditionallyEnter&);
+  static void* operator new(size_t) CPP_THROW_NEW;
+  static void operator delete(void*);
+
+  ReentrantMonitor* mReentrantMonitor;
+};
+
 // Shuts down a thread asynchronously.
 class ShutdownThreadEvent : public nsRunnable 
 {
 public:
   ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
   ~ShutdownThreadEvent() {}
   NS_IMETHOD Run() {
     mThread->Shutdown();
--- a/content/media/dash/DASHDecoder.cpp
+++ b/content/media/dash/DASHDecoder.cpp
@@ -144,18 +144,23 @@ extern PRLogModuleInfo* gMediaDecoderLog
 DASHDecoder::DASHDecoder() :
   MediaDecoder(),
   mNotifiedLoadAborted(false),
   mBuffer(nullptr),
   mBufferLength(0),
   mMPDReaderThread(nullptr),
   mPrincipal(nullptr),
   mDASHReader(nullptr),
-  mAudioRepDecoder(nullptr),
-  mVideoRepDecoder(nullptr)
+  mVideoAdaptSetIdx(-1),
+  mAudioRepDecoderIdx(-1),
+  mVideoRepDecoderIdx(-1),
+  mAudioSubsegmentIdx(0),
+  mVideoSubsegmentIdx(0),
+  mAudioMetadataReadCount(0),
+  mVideoMetadataReadCount(0)
 {
   MOZ_COUNT_CTOR(DASHDecoder);
 }
 
 DASHDecoder::~DASHDecoder()
 {
   MOZ_COUNT_DTOR(DASHDecoder);
 }
@@ -292,16 +297,17 @@ DASHDecoder::ReadMPDBuffer()
 }
 
 void
 DASHDecoder::OnReadMPDBufferCompleted()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   if (mShuttingDown) {
+    LOG1("Shutting down! Ignoring OnReadMPDBufferCompleted().");
     return;
   }
 
   // Shutdown the thread.
   if (!mMPDReaderThread) {
     LOG1("Error: MPD reader thread does not exist!");
     DecodeError();
     return;
@@ -380,16 +386,19 @@ DASHDecoder::CreateRepDecoders()
   int64_t startTime = mMPDManager->GetStartTime();
   mDuration = mMPDManager->GetDuration();
   NS_ENSURE_TRUE(startTime >= 0 && mDuration > 0, NS_ERROR_ILLEGAL_VALUE);
 
   // For each audio/video stream, create a |ChannelMediaResource| object.
 
   for (int i = 0; i < mMPDManager->GetNumAdaptationSets(); i++) {
     IMPDManager::AdaptationSetType asType = mMPDManager->GetAdaptationSetType(i);
+    if (asType == IMPDManager::DASH_VIDEO_STREAM) {
+      mVideoAdaptSetIdx = i;
+    }
     for (int j = 0; j < mMPDManager->GetNumRepresentations(i); j++) {
       // Get URL string.
       nsAutoString segmentUrl;
       nsresult rv = mMPDManager->GetFirstSegmentUrl(i, j, segmentUrl);
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Get segment |nsIURI|; use MPD's base URI in case of relative paths.
       nsCOMPtr<nsIURI> url;
@@ -417,18 +426,18 @@ DASHDecoder::CreateRepDecoders()
         Representation const * rep = mMPDManager->GetRepresentation(i, j);
         NS_ENSURE_TRUE(rep, NS_ERROR_NULL_POINTER);
         rv = CreateAudioRepDecoder(url, rep);
         NS_ENSURE_SUCCESS(rv, rv);
       }
     }
   }
 
-  NS_ENSURE_TRUE(mVideoRepDecoder, NS_ERROR_NOT_INITIALIZED);
-  NS_ENSURE_TRUE(mAudioRepDecoder, NS_ERROR_NOT_INITIALIZED);
+  NS_ENSURE_TRUE(VideoRepDecoder(), NS_ERROR_NOT_INITIALIZED);
+  NS_ENSURE_TRUE(AudioRepDecoder(), NS_ERROR_NOT_INITIALIZED);
 
   return NS_OK;
 }
 
 nsresult
 DASHDecoder::CreateAudioRepDecoder(nsIURI* aUrl,
                                    mozilla::net::Representation const * aRep)
 {
@@ -436,72 +445,80 @@ DASHDecoder::CreateAudioRepDecoder(nsIUR
   NS_ENSURE_ARG(aUrl);
   NS_ENSURE_ARG(aRep);
   NS_ENSURE_TRUE(mOwner, NS_ERROR_NOT_INITIALIZED);
 
   // Create subdecoder and init with media element.
   DASHRepDecoder* audioDecoder = new DASHRepDecoder(this);
   NS_ENSURE_TRUE(audioDecoder->Init(mOwner), NS_ERROR_NOT_INITIALIZED);
 
-  if (!mAudioRepDecoder) {
-    mAudioRepDecoder = audioDecoder;
+  // Set current decoder to the first one created.
+  if (mAudioRepDecoderIdx == -1) {
+    mAudioRepDecoderIdx = 0;
   }
   mAudioRepDecoders.AppendElement(audioDecoder);
 
   // Create sub-reader; attach to DASH reader and sub-decoder.
   WebMReader* audioReader = new WebMReader(audioDecoder);
   if (mDASHReader) {
+    audioReader->SetMainReader(mDASHReader);
     mDASHReader->AddAudioReader(audioReader);
   }
   audioDecoder->SetReader(audioReader);
 
   // Create media resource with URL and connect to sub-decoder.
   MediaResource* audioResource
     = CreateAudioSubResource(aUrl, static_cast<MediaDecoder*>(audioDecoder));
   NS_ENSURE_TRUE(audioResource, NS_ERROR_NOT_INITIALIZED);
 
   audioDecoder->SetResource(audioResource);
   audioDecoder->SetMPDRepresentation(aRep);
 
+  LOG("Created audio DASHRepDecoder [%p]", audioDecoder);
+
   return NS_OK;
 }
 
 nsresult
 DASHDecoder::CreateVideoRepDecoder(nsIURI* aUrl,
                                    mozilla::net::Representation const * aRep)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ENSURE_ARG(aUrl);
   NS_ENSURE_ARG(aRep);
   NS_ENSURE_TRUE(mOwner, NS_ERROR_NOT_INITIALIZED);
 
   // Create subdecoder and init with media element.
   DASHRepDecoder* videoDecoder = new DASHRepDecoder(this);
   NS_ENSURE_TRUE(videoDecoder->Init(mOwner), NS_ERROR_NOT_INITIALIZED);
 
-  if (!mVideoRepDecoder) {
-    mVideoRepDecoder = videoDecoder;
+  // Set current decoder to the first one created.
+  if (mVideoRepDecoderIdx == -1) {
+    mVideoRepDecoderIdx = 0;
   }
   mVideoRepDecoders.AppendElement(videoDecoder);
 
   // Create sub-reader; attach to DASH reader and sub-decoder.
   WebMReader* videoReader = new WebMReader(videoDecoder);
   if (mDASHReader) {
+    videoReader->SetMainReader(mDASHReader);
     mDASHReader->AddVideoReader(videoReader);
   }
   videoDecoder->SetReader(videoReader);
 
   // Create media resource with URL and connect to sub-decoder.
   MediaResource* videoResource
     = CreateVideoSubResource(aUrl, static_cast<MediaDecoder*>(videoDecoder));
   NS_ENSURE_TRUE(videoResource, NS_ERROR_NOT_INITIALIZED);
 
   videoDecoder->SetResource(videoResource);
   videoDecoder->SetMPDRepresentation(aRep);
 
+  LOG("Created video DASHRepDecoder [%p]", videoDecoder);
+
   return NS_OK;
 }
 
 MediaResource*
 DASHDecoder::CreateAudioSubResource(nsIURI* aUrl,
                                     MediaDecoder* aAudioDecoder)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@@ -590,79 +607,154 @@ DASHDecoder::LoadRepresentations()
   nsresult rv;
   {
     // Hold the lock while we do this to set proper lock ordering
     // expectations for dynamic deadlock detectors: decoder lock(s)
     // should be grabbed before the cache lock.
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
 
     // Load the decoders for each |Representation|'s media streams.
-    if (mAudioRepDecoder) {
-      rv = mAudioRepDecoder->Load();
+    // XXX Prob ok to load all audio decoders, since there should only be one
+    //     created, but need to review the rest of the file.
+    if (AudioRepDecoder()) {
+      rv = AudioRepDecoder()->Load();
       NS_ENSURE_SUCCESS(rv, rv);
+      mAudioMetadataReadCount++;
     }
-    if (mVideoRepDecoder) {
-      rv = mVideoRepDecoder->Load();
+    // Load all video decoders.
+    for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
+      rv = mVideoRepDecoders[i]->Load();
       NS_ENSURE_SUCCESS(rv, rv);
+      mVideoMetadataReadCount++;
     }
-    if (NS_FAILED(rv)) {
-      LOG("Failed to open stream! rv [%x].", rv);
-      return rv;
+    if (AudioRepDecoder()) {
+      AudioRepDecoder()->SetStateMachine(mDecoderStateMachine);
+    }
+    for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
+      mVideoRepDecoders[i]->SetStateMachine(mDecoderStateMachine);
     }
   }
-
-  if (mAudioRepDecoder) {
-    mAudioRepDecoder->SetStateMachine(mDecoderStateMachine);
-  }
-  if (mVideoRepDecoder) {
-    mVideoRepDecoder->SetStateMachine(mDecoderStateMachine);
-  }
-
   // Now that subreaders are init'd, it's ok to init state machine.
   return InitializeStateMachine(nullptr);
 }
 
 void
 DASHDecoder::NotifyDownloadEnded(DASHRepDecoder* aRepDecoder,
-                                   nsresult aStatus,
-                                   MediaByteRange &aRange)
+                                 nsresult aStatus,
+                                 int32_t const aSubsegmentIdx)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
+  if (mShuttingDown) {
+    LOG1("Shutting down! Ignoring NotifyDownloadEnded().");
+    return;
+  }
+
   // MPD Manager must exist, indicating MPD has been downloaded and parsed.
   if (!mMPDManager) {
     LOG1("Network Error! MPD Manager must exist, indicating MPD has been "
         "downloaded and parsed");
     NetworkError();
     return;
   }
 
   // Decoder for the media |Representation| must not be null.
   if (!aRepDecoder) {
     LOG1("Decoder for Representation is reported as null.");
     DecodeError();
     return;
   }
 
   if (NS_SUCCEEDED(aStatus)) {
-    // Return error if |aRepDecoder| does not match current audio/video decoder.
-    if (aRepDecoder != mAudioRepDecoder && aRepDecoder != mVideoRepDecoder) {
-      LOG("Error! Decoder [%p] does not match current sub-decoders!",
+    LOG("Byte range downloaded: decoder [%p] subsegmentIdx [%d]",
+        aRepDecoder, aSubsegmentIdx);
+
+    if (aSubsegmentIdx < 0) {
+      LOG("Last subsegment for decoder [%p] was downloaded",
           aRepDecoder);
+      return;
+    }
+
+    nsRefPtr<DASHRepDecoder> decoder = aRepDecoder;
+    {
+      ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+
+      if (!IsDecoderAllowedToDownloadSubsegment(aRepDecoder,
+                                                aSubsegmentIdx)) {
+        NS_WARNING("Decoder downloaded subsegment but it is not allowed!");
+        LOG("Error! Decoder [%p] downloaded subsegment [%d] but it is not "
+            "allowed!", aRepDecoder, aSubsegmentIdx);
+        return;
+      }
+
+      if (aRepDecoder == VideoRepDecoder() &&
+          mVideoSubsegmentIdx == aSubsegmentIdx) {
+        IncrementSubsegmentIndex(aRepDecoder);
+      } else if (aRepDecoder == AudioRepDecoder() &&
+          mAudioSubsegmentIdx == aSubsegmentIdx) {
+        IncrementSubsegmentIndex(aRepDecoder);
+      } else {
+        return;
+      }
+
+      // Do Stream Switching here before loading next bytes.
+      // Audio stream switching not supported.
+      if (aRepDecoder == VideoRepDecoder() &&
+          mVideoSubsegmentIdx < VideoRepDecoder()->GetNumDataByteRanges()) {
+        nsresult rv = PossiblySwitchDecoder(aRepDecoder);
+        if (NS_FAILED(rv)) {
+          LOG("Failed possibly switching decoder rv[0x%x]", rv);
+          DecodeError();
+          return;
+        }
+        decoder = VideoRepDecoder();
+      }
+    }
+
+    // Before loading, note the index of the decoder which will downloaded the
+    // next video subsegment.
+    if (decoder == VideoRepDecoder()) {
+      if (mVideoSubsegmentLoads.IsEmpty() ||
+          (uint32_t)mVideoSubsegmentIdx >= mVideoSubsegmentLoads.Length()) {
+        LOG("Appending decoder [%d] [%p] to mVideoSubsegmentLoads at index "
+            "[%d] before load; mVideoSubsegmentIdx[%d].",
+            mVideoRepDecoderIdx, VideoRepDecoder(),
+            mVideoSubsegmentLoads.Length(), mVideoSubsegmentIdx);
+        mVideoSubsegmentLoads.AppendElement(mVideoRepDecoderIdx);
+      } else {
+        // Change an existing load, and keep subsequent entries to help
+        // determine if subsegments are cached already.
+        LOG("Setting decoder [%d] [%p] in mVideoSubsegmentLoads at index "
+            "[%d] before load; mVideoSubsegmentIdx[%d].",
+            mVideoRepDecoderIdx, VideoRepDecoder(),
+            mVideoSubsegmentIdx, mVideoSubsegmentIdx);
+        mVideoSubsegmentLoads[mVideoSubsegmentIdx] = mVideoRepDecoderIdx;
+      }
+    }
+
+    // Load the next range of data bytes. If the range is already cached,
+    // this function will be called again to adaptively download the next
+    // subsegment.
+#ifdef PR_LOGGING
+    if (decoder.get() == AudioRepDecoder()) {
+      LOG("Requesting load for audio decoder [%p] subsegment [%d].",
+        decoder.get(), mAudioSubsegmentIdx);
+    } else if (decoder.get() == VideoRepDecoder()) {
+      LOG("Requesting load for video decoder [%p] subsegment [%d].",
+        decoder.get(), mVideoSubsegmentIdx);
+    }
+#endif
+    if (!decoder || (decoder != AudioRepDecoder() &&
+                     decoder != VideoRepDecoder())) {
+      LOG("Invalid decoder [%p]: video idx [%d] audio idx [%d]",
+          decoder.get(), AudioRepDecoder(), VideoRepDecoder());
       DecodeError();
       return;
     }
-    LOG("Byte range downloaded: decoder [%p] range requested [%d - %d]",
-        aRepDecoder, aRange.mStart, aRange.mEnd);
-
-    // XXX Do Stream Switching here before loading next bytes, e.g.
-    // decoder = PossiblySwitchDecoder(aRepDecoder);
-    // decoder->LoadNextByteRange();
-    aRepDecoder->LoadNextByteRange();
-    return;
+    decoder->LoadNextByteRange();
   } else if (aStatus == NS_BINDING_ABORTED) {
     LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
     if (mOwner) {
       mOwner->LoadAborted();
     }
     return;
   } else if (aStatus != NS_BASE_STREAM_CLOSED) {
     LOG("Network error trying to download MPD: aStatus [%x].", aStatus);
@@ -726,10 +818,181 @@ DASHDecoder::DecodeError()
       NS_NewRunnableMethod(this, &MediaDecoder::DecodeError);
     nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
     if (NS_FAILED(rv)) {
       LOG("Error dispatching DecodeError event to main thread: rv[%x]", rv);
     }
   }
 }
 
+void
+DASHDecoder::OnReadMetadataCompleted(DASHRepDecoder* aRepDecoder)
+{
+  if (mShuttingDown) {
+    LOG1("Shutting down! Ignoring OnReadMetadataCompleted().");
+    return;
+  }
+
+  NS_ASSERTION(aRepDecoder, "aRepDecoder is null!");
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+
+  LOG("Metadata loaded for decoder[%p]", aRepDecoder);
+
+  // Decrement audio|video metadata read counter and get ref to active decoder.
+  nsRefPtr<DASHRepDecoder> activeDecoder;
+  {
+    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+    for (uint32_t i = 0; i < mAudioRepDecoders.Length(); i++) {
+      if (aRepDecoder == mAudioRepDecoders[i]) {
+        --mAudioMetadataReadCount;
+        break;
+      }
+    }
+    for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
+      if (aRepDecoder == mVideoRepDecoders[i]) {
+        --mVideoMetadataReadCount;
+        break;
+      }
+    }
+  }
+
+  // Once all metadata is downloaded for audio|video decoders, start loading
+  // data for the active decoder.
+  if (mAudioMetadataReadCount == 0 && mVideoMetadataReadCount == 0) {
+    if (AudioRepDecoder()) {
+      LOG("Dispatching load event for audio decoder [%p]", AudioRepDecoder());
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableMethod(AudioRepDecoder(), &DASHRepDecoder::LoadNextByteRange);
+      nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+      if (NS_FAILED(rv)) {
+        LOG("Error dispatching audio decoder [%p] load event to main thread: "
+            "rv[%x]", AudioRepDecoder(), rv);
+        DecodeError();
+        return;
+      }
+    }
+    if (VideoRepDecoder()) {
+      LOG("Dispatching load event for video decoder [%p]", VideoRepDecoder());
+      // Add decoder to subsegment load history.
+      NS_ASSERTION(mVideoSubsegmentLoads.IsEmpty(),
+                   "No subsegment loads should be recorded at this stage!");
+      NS_ASSERTION(mVideoSubsegmentIdx == 0,
+                   "Current subsegment should be 0 at this stage!");
+      LOG("Appending decoder [%d] [%p] to mVideoSubsegmentLoads at index "
+          "[%d] before load; mVideoSubsegmentIdx[%d].",
+          mVideoRepDecoderIdx, VideoRepDecoder(),
+          (uint32_t)mVideoSubsegmentLoads.Length(), mVideoSubsegmentIdx);
+      mVideoSubsegmentLoads.AppendElement(mVideoRepDecoderIdx);
+
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableMethod(VideoRepDecoder(), &DASHRepDecoder::LoadNextByteRange);
+      nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+      if (NS_FAILED(rv)) {
+        LOG("Error dispatching video decoder [%p] load event to main thread: "
+            "rv[%x]", VideoRepDecoder(), rv);
+        DecodeError();
+        return;
+      }
+    }
+  }
+}
+
+nsresult
+DASHDecoder::PossiblySwitchDecoder(DASHRepDecoder* aRepDecoder)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  NS_ENSURE_FALSE(mShuttingDown, NS_ERROR_UNEXPECTED);
+  NS_ENSURE_TRUE(aRepDecoder == VideoRepDecoder(), NS_ERROR_ILLEGAL_VALUE);
+  NS_ASSERTION((uint32_t)mVideoRepDecoderIdx < mVideoRepDecoders.Length(),
+               "Index for video decoder is out of bounds!");
+  NS_ASSERTION((uint32_t)mVideoSubsegmentIdx < VideoRepDecoder()->GetNumDataByteRanges(),
+               "Can't switch to a byte range out of bounds.");
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+
+  // Now, determine if and which decoder to switch to.
+  // XXX This download rate is averaged over time, and only refers to the bytes
+  // downloaded for the given decoder. A running average would be better, and
+  // something that includes all downloads. But this will do for now.
+  NS_ASSERTION(VideoRepDecoder(), "Video decoder should not be null.");
+  NS_ASSERTION(VideoRepDecoder()->GetResource(),
+               "Video resource should not be null");
+  bool reliable = false;
+  double downloadRate = VideoRepDecoder()->GetResource()->GetDownloadRate(&reliable);
+  uint32_t bestRepIdx = UINT32_MAX;
+  bool noRepAvailable = !mMPDManager->GetBestRepForBandwidth(mVideoAdaptSetIdx,
+                                                             downloadRate,
+                                                             bestRepIdx);
+  LOG("downloadRate [%f] reliable [%s] bestRepIdx [%d] noRepAvailable",
+      downloadRate, (reliable ? "yes" : "no"), bestRepIdx,
+      (noRepAvailable ? "yes" : "no"));
+
+  // If there is a higher bitrate stream that can be downloaded with the
+  // current estimated bandwidth, step up to the next stream, for a graceful
+  // increase in quality.
+  uint32_t toDecoderIdx = mVideoRepDecoderIdx;
+  if (bestRepIdx > toDecoderIdx) {
+    toDecoderIdx = NS_MIN(toDecoderIdx+1, mVideoRepDecoders.Length()-1);
+  } else if (toDecoderIdx < bestRepIdx) {
+    // If the bitrate is too much for the current bandwidth, just use that
+    // stream directly.
+    toDecoderIdx = bestRepIdx;
+  }
+  NS_ENSURE_TRUE(toDecoderIdx < mVideoRepDecoders.Length(),
+                 NS_ERROR_ILLEGAL_VALUE);
+
+  // Notify reader and sub decoders and do the switch.
+  if (toDecoderIdx != mVideoRepDecoderIdx) {
+    LOG("*** Switching video decoder from [%d] [%p] to [%d] [%p] at "
+        "subsegment [%d]", mVideoRepDecoderIdx, VideoRepDecoder(),
+        toDecoderIdx, mVideoRepDecoders[toDecoderIdx].get(),
+        mVideoSubsegmentIdx);
+
+    // Tell main reader to switch subreaders at |subsegmentIdx| - equates to
+    // switching data source for reading.
+    mDASHReader->RequestVideoReaderSwitch(mVideoRepDecoderIdx, toDecoderIdx,
+                                          mVideoSubsegmentIdx);
+    // Notify decoder it is about to be switched.
+    mVideoRepDecoders[mVideoRepDecoderIdx]->PrepareForSwitch();
+    // Switch video decoders - equates to switching download source.
+    mVideoRepDecoderIdx = toDecoderIdx;
+  }
+
+  return NS_OK;
+}
+
+bool
+DASHDecoder::IsDecoderAllowedToDownloadData(DASHRepDecoder* aRepDecoder)
+{
+  NS_ASSERTION(aRepDecoder, "DASHRepDecoder pointer is null.");
+
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+  // Only return true if |aRepDecoder| is active and metadata for all
+  // representations has been downloaded.
+  return ((aRepDecoder == AudioRepDecoder() && mAudioMetadataReadCount == 0) ||
+          (aRepDecoder == VideoRepDecoder() && mVideoMetadataReadCount == 0));
+}
+
+bool
+DASHDecoder::IsDecoderAllowedToDownloadSubsegment(DASHRepDecoder* aRepDecoder,
+                                                  int32_t const aSubsegmentIdx)
+{
+  NS_ASSERTION(aRepDecoder, "DASHRepDecoder pointer is null.");
+
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+
+  // Return false if there is still metadata to be downloaded.
+  if (mAudioMetadataReadCount != 0 || mVideoMetadataReadCount != 0) {
+    return false;
+  }
+  // No audio switching; allow the audio decoder to download all subsegments.
+  if (aRepDecoder == AudioRepDecoder()) {
+    return true;
+  }
+
+  int32_t videoDecoderIdx = GetRepIdxForVideoSubsegmentLoad(aSubsegmentIdx);
+  if (aRepDecoder == mVideoRepDecoders[videoDecoderIdx]) {
+    return true;
+  }
+  return false;
+}
+
 } // namespace mozilla
 
--- a/content/media/dash/DASHDecoder.h
+++ b/content/media/dash/DASHDecoder.h
@@ -61,17 +61,105 @@ public:
   // Called on the main thread only.
   void NotifyDownloadEnded(nsresult aStatus);
 
   // Notifies that a byte range download has ended. As per the DASH spec, this
   // allows for stream switching at the boundaries of the byte ranges.
   // Called on the main thread only.
   void NotifyDownloadEnded(DASHRepDecoder* aRepDecoder,
                            nsresult aStatus,
-                           MediaByteRange &aRange);
+                           int32_t const aSubsegmentIdx);
+
+  // Notification from an |MediaDecoderReader| class that metadata has been
+  // read. Declared here to allow overloading.
+  void OnReadMetadataCompleted() MOZ_OVERRIDE { }
+
+  // Notification from |DASHRepDecoder| that a metadata has been read.
+  // |DASHDecoder| will initiate load of data bytes for active audio/video
+  // decoders. Called on the decode thread.
+  void OnReadMetadataCompleted(DASHRepDecoder* aRepDecoder);
+
+  // Refers to downloading data bytes, i.e. non metadata.
+  // Returns true if |aRepDecoder| is an active audio or video sub decoder AND
+  // if metadata for all audio or video decoders has been read.
+  // Could be called from any thread; enters decoder monitor.
+  bool IsDecoderAllowedToDownloadData(DASHRepDecoder* aRepDecoder);
+
+  // Refers to downloading data bytes during SEEKING.
+  // Returns true if |aRepDecoder| is the active audio sub decoder, OR if
+  // it is a video decoder and is allowed to download this subsegment.
+  // Returns false if there is still some metadata to download.
+  // Could be called from any thread; enters decoder monitor.
+  bool IsDecoderAllowedToDownloadSubsegment(DASHRepDecoder* aRepDecoder,
+                                            int32_t const aSubsegmentIdx);
+
+  // Determines if rep/sub decoders should be switched, and if so switches
+  // them. Notifies |DASHReader| if and when it should switch readers.
+  // Returns a pointer to the new active decoder.
+  // Called on the main thread.
+  nsresult PossiblySwitchDecoder(DASHRepDecoder* aRepDecoder);
+
+  // Sets the byte range index for audio|video downloads. Will only increment
+  // for current active decoders. Could be called from any thread.
+  // Requires monitor because of write to |mAudioSubsegmentIdx| or
+  // |mVideoSubsegmentIdx|.
+  void SetSubsegmentIndex(DASHRepDecoder* aRepDecoder,
+                          uint32_t aSubsegmentIdx)
+  {
+    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+    if (aRepDecoder == AudioRepDecoder()) {
+      mAudioSubsegmentIdx = aSubsegmentIdx;
+    } else if (aRepDecoder == VideoRepDecoder()) {
+      mVideoSubsegmentIdx = aSubsegmentIdx;
+    }
+  }
+private:
+  // Increments the byte range index for audio|video downloads. Will only
+  // increment for current active decoders. Could be called from any thread.
+  // Requires monitor because of write to |mAudioSubsegmentIdx| or
+  // |mVideoSubsegmentIdx|.
+  void IncrementSubsegmentIndex(DASHRepDecoder* aRepDecoder)
+  {
+    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+    if (aRepDecoder == AudioRepDecoder()) {
+      mAudioSubsegmentIdx++;
+    } else if (aRepDecoder == VideoRepDecoder()) {
+      mVideoSubsegmentIdx++;
+    }
+  }
+public:
+  // Gets the byte range index for audio|video downloads. Will only increment
+  // for current active decoders. Could be called from any thread. Will enter
+  // monitor for read access off the decode thread.
+  int32_t GetSubsegmentIndex(DASHRepDecoder* aRepDecoder)
+  {
+    ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
+                                           GetReentrantMonitor());
+    if (aRepDecoder == AudioRepDecoder()) {
+      return mAudioSubsegmentIdx;
+    } else if (aRepDecoder == VideoRepDecoder()) {
+      return mVideoSubsegmentIdx;
+    }
+    return (-1);
+  }
+
+  // Returns the index of the rep decoder used to load a subsegment. Will enter
+  // monitor for read access off the decode thread.
+  int32_t GetRepIdxForVideoSubsegmentLoad(int32_t aSubsegmentIdx)
+  {
+    NS_ASSERTION(0 < aSubsegmentIdx, "Subsegment index should not be negative.");
+    ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
+                                           GetReentrantMonitor());
+    if ((uint32_t)aSubsegmentIdx < mVideoSubsegmentLoads.Length()) {
+      return mVideoSubsegmentLoads[aSubsegmentIdx];
+    } else {
+      // If it hasn't been downloaded yet, use the lowest bitrate decoder.
+      return 0;
+    }
+  }
 
   // Drop reference to state machine and tell sub-decoders to do the same.
   // Only called during shutdown dance, on main thread only.
   void ReleaseStateMachine();
 
   // Overridden to forward |Shutdown| to sub-decoders.
   // Called on the main thread only.
   void Shutdown();
@@ -102,16 +190,48 @@ private:
   // On the main thread.
   nsresult CreateRepDecoders();
 
   // Creates audio/video decoders for individual |Representation|s.
   // On the main thread.
   nsresult CreateAudioRepDecoder(nsIURI* aUrl, Representation const * aRep);
   nsresult CreateVideoRepDecoder(nsIURI* aUrl, Representation const * aRep);
 
+  // Get audio sub-decoder for current audio |Representation|. Will return
+  // nullptr for out of range indexes.
+  // Enters monitor for read access off the decode thread.
+  // XXX Note: Although an array of audio decoders is provided, audio stream
+  // switching is not yet supported.
+  DASHRepDecoder* AudioRepDecoder() {
+    ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
+                                           GetReentrantMonitor());
+    NS_ENSURE_TRUE((uint32_t)mAudioRepDecoderIdx < mAudioRepDecoders.Length(),
+                   nullptr);
+    if (mAudioRepDecoderIdx < 0) {
+      return nullptr;
+    } else {
+      return mAudioRepDecoders[mAudioRepDecoderIdx];
+    }
+  }
+
+  // Get video sub-decoder for current video |Representation|. Will return
+  // nullptr for out of range indexes.
+  // Enters monitor for read access off the decode thread.
+  DASHRepDecoder* VideoRepDecoder() {
+    ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
+                                           GetReentrantMonitor());
+    NS_ENSURE_TRUE((uint32_t)mVideoRepDecoderIdx < mVideoRepDecoders.Length(),
+                   nullptr);
+    if (mVideoRepDecoderIdx < 0) {
+      return nullptr;
+    } else {
+      return mVideoRepDecoders[mVideoRepDecoderIdx];
+    }
+  }
+
   // Creates audio/video resources for individual |Representation|s.
   // On the main thread.
   MediaResource* CreateAudioSubResource(nsIURI* aUrl,
                                         MediaDecoder* aAudioDecoder);
   MediaResource* CreateVideoSubResource(nsIURI* aUrl,
                                         MediaDecoder* aVideoDecoder);
 
   // Creates an http channel for a |Representation|.
@@ -136,22 +256,44 @@ private:
 
   // MPD Manager provides access to the MPD information.
   nsAutoPtr<IMPDManager>       mMPDManager;
 
   // Main reader object; manages all sub-readers for |Representation|s. Owned by
   // state machine; destroyed in state machine's destructor.
   DASHReader* mDASHReader;
 
-  // Sub-decoder for current audio |Representation|.
-  nsRefPtr<DASHRepDecoder> mAudioRepDecoder;
-  // Array of pointers for the |Representation|s in the audio |AdaptationSet|.
+  // Sub-decoder vars. Note: For all following members, the decode monitor
+  // should be held for write access on decode thread, and all read/write off
+  // the decode thread.
+
+  // Index of the video |AdaptationSet|.
+  int32_t mVideoAdaptSetIdx;
+
+  // Indexes for the current audio and video decoders.
+  int32_t mAudioRepDecoderIdx;
+  int32_t mVideoRepDecoderIdx;
+
+  // Array of pointers for the |Representation|s in the audio/video
+  // |AdaptationSet|.
   nsTArray<nsRefPtr<DASHRepDecoder> > mAudioRepDecoders;
+  nsTArray<nsRefPtr<DASHRepDecoder> > mVideoRepDecoders;
 
-  // Sub-decoder for current video |Representation|.
-  nsRefPtr<DASHRepDecoder> mVideoRepDecoder;
-  // Array of pointers for the |Representation|s in the video |AdaptationSet|.
-  nsTArray<nsRefPtr<DASHRepDecoder> > mVideoRepDecoders;
+  // Current index of subsegments downloaded for audio/video decoder.
+  int32_t mAudioSubsegmentIdx;
+  int32_t mVideoSubsegmentIdx;
+
+  // Count for the number of readers which have called |OnReadMetadataCompleted|.
+  // Initialised to 0; incremented for every decoder which has |Load| called;
+  // and decremented for every call to |OnReadMetadataCompleted|. When it is
+  // zero again, all metadata has been read for audio or video, and data bytes
+  // can be downloaded.
+  uint32_t mAudioMetadataReadCount;
+  uint32_t mVideoMetadataReadCount;
+
+  // Array records the index of the decoder/Representation which loaded each
+  // subsegment.
+  nsTArray<int32_t> mVideoSubsegmentLoads;
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/media/dash/DASHReader.cpp
+++ b/content/media/dash/DASHReader.cpp
@@ -11,30 +11,56 @@
  * partitioned into one or more segments and delivered to a client using HTTP.
  *
  * see DASHDecoder.cpp for info on DASH interaction with the media engine.*/
 
 #include "nsTimeRanges.h"
 #include "VideoFrameContainer.h"
 #include "AbstractMediaDecoder.h"
 #include "DASHReader.h"
+#include "DASHDecoder.h"
 
 namespace mozilla {
 
 #ifdef PR_LOGGING
-extern PRLogModuleInfo* gMediaDecoderLog;
-#define LOG(msg, ...) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, \
+PRLogModuleInfo* gDASHReaderLog;
+#define LOG(msg, ...) PR_LOG(gDASHReaderLog, PR_LOG_DEBUG, \
                              ("%p [DASHReader] " msg, this, __VA_ARGS__))
-#define LOG1(msg) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, \
+#define LOG1(msg) PR_LOG(gDASHReaderLog, PR_LOG_DEBUG, \
                          ("%p [DASHReader] " msg, this))
 #else
 #define LOG(msg, ...)
 #define LOG1(msg)
 #endif
 
+DASHReader::DASHReader(AbstractMediaDecoder* aDecoder) :
+  MediaDecoderReader(aDecoder),
+  mReadMetadataMonitor("media.dashreader.readmetadata"),
+  mReadyToReadMetadata(false),
+  mDecoderIsShuttingDown(false),
+  mAudioReader(this),
+  mVideoReader(this),
+  mAudioReaders(this),
+  mVideoReaders(this),
+  mSwitchVideoReaders(false),
+  mSwitchCount(-1)
+{
+  MOZ_COUNT_CTOR(DASHReader);
+#ifdef PR_LOGGING
+  if (!gDASHReaderLog) {
+    gDASHReaderLog = PR_NewLogModule("DASHReader");
+  }
+#endif
+}
+
+DASHReader::~DASHReader()
+{
+  MOZ_COUNT_DTOR(DASHReader);
+}
+
 nsresult
 DASHReader::Init(MediaDecoderReader* aCloneDonor)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(mAudioReaders.Length() != 0 && mVideoReaders.Length() != 0,
                "Audio and video readers should exist already.");
@@ -47,57 +73,71 @@ DASHReader::Init(MediaDecoderReader* aCl
   for (uint i = 0; i < mVideoReaders.Length(); i++) {
     rv = mVideoReaders[i]->Init(nullptr);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
 void
-DASHReader::AddAudioReader(MediaDecoderReader* aAudioReader)
+DASHReader::AddAudioReader(DASHRepReader* aAudioReader)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ENSURE_TRUE(aAudioReader, );
 
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   mAudioReaders.AppendElement(aAudioReader);
   // XXX For now, just pick the first reader to be default.
   if (!mAudioReader)
     mAudioReader = aAudioReader;
 }
 
 void
-DASHReader::AddVideoReader(MediaDecoderReader* aVideoReader)
+DASHReader::AddVideoReader(DASHRepReader* aVideoReader)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ENSURE_TRUE(aVideoReader, );
 
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   mVideoReaders.AppendElement(aVideoReader);
   // XXX For now, just pick the first reader to be default.
   if (!mVideoReader)
     mVideoReader = aVideoReader;
 }
 
+bool
+DASHReader::HasAudio()
+{
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  return mAudioReader ? mAudioReader->HasAudio() : false;
+}
+
+bool
+DASHReader::HasVideo()
+{
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  return mVideoReader ? mVideoReader->HasVideo() : false;
+}
+
 int64_t
 DASHReader::VideoQueueMemoryInUse()
 {
   ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
                                          mDecoder->GetReentrantMonitor());
-  return (mVideoReader ? mVideoReader->VideoQueueMemoryInUse() : 0);
+  return VideoQueueMemoryInUse();
 }
 
 int64_t
 DASHReader::AudioQueueMemoryInUse()
 {
   ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
                                          mDecoder->GetReentrantMonitor());
-  return (mAudioReader ? mAudioReader->AudioQueueMemoryInUse() : 0);
+  return AudioQueueMemoryInUse();
 }
 
 bool
 DASHReader::DecodeVideoFrame(bool &aKeyframeSkip,
                                int64_t aTimeThreshold)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   if (mVideoReader) {
@@ -111,39 +151,51 @@ bool
 DASHReader::DecodeAudioData()
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   return (mAudioReader ? mAudioReader->DecodeAudioData() : false);
 }
 
 nsresult
 DASHReader::ReadMetadata(VideoInfo* aInfo,
-                           MetadataTags** aTags)
+                         MetadataTags** aTags)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   // Wait for MPD to be parsed and child readers created.
   LOG1("Waiting for metadata download.");
   nsresult rv = WaitForMetadata();
   // If we get an abort, return silently; the decoder is shutting down.
   if (NS_ERROR_ABORT == rv) {
     return NS_OK;
   }
   // Verify no other errors before continuing.
   NS_ENSURE_SUCCESS(rv, rv);
 
+  NS_ASSERTION(aTags, "Called with null MetadataTags**.");
+  *aTags = nullptr;
+
   // Get metadata from child readers.
   VideoInfo audioInfo, videoInfo;
 
-  if (mVideoReader) {
-    rv = mVideoReader->ReadMetadata(&videoInfo, aTags);
+  // Read metadata for all video streams.
+  for (uint i = 0; i < mVideoReaders.Length(); i++) {
+    // Use an nsAutoPtr here to ensure |tags| memory does not leak.
+    nsAutoPtr<nsHTMLMediaElement::MetadataTags> tags;
+    rv = mVideoReaders[i]->ReadMetadata(&videoInfo, getter_Transfers(tags));
     NS_ENSURE_SUCCESS(rv, rv);
-    mInfo.mHasVideo      = videoInfo.mHasVideo;
-    mInfo.mDisplay       = videoInfo.mDisplay;
+    // Use metadata from current video sub reader to populate aInfo.
+    if (mVideoReaders[i] == mVideoReader) {
+      mInfo.mHasVideo      = videoInfo.mHasVideo;
+      mInfo.mDisplay       = videoInfo.mDisplay;
+    }
   }
+  // Read metadata for audio stream.
+  // Note: Getting metadata tags from audio reader only for now.
+  // XXX Audio stream switching not yet supported.
   if (mAudioReader) {
     rv = mAudioReader->ReadMetadata(&audioInfo, aTags);
     NS_ENSURE_SUCCESS(rv, rv);
     mInfo.mHasAudio      = audioInfo.mHasAudio;
     mInfo.mAudioRate     = audioInfo.mAudioRate;
     mInfo.mAudioChannels = audioInfo.mAudioChannels;
     mInfo.mStereoMode    = audioInfo.mStereoMode;
   }
@@ -298,33 +350,149 @@ DASHReader::FindStartTime(int64_t& aOutS
 }
 
 MediaQueue<AudioData>&
 DASHReader::AudioQueue()
 {
   ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
                                          mDecoder->GetReentrantMonitor());
   NS_ASSERTION(mAudioReader, "mAudioReader is NULL!");
-  return mAudioReader->AudioQueue();
+  return mAudioQueue;
 }
 
 MediaQueue<VideoData>&
 DASHReader::VideoQueue()
 {
   ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
                                          mDecoder->GetReentrantMonitor());
   NS_ASSERTION(mVideoReader, "mVideoReader is NULL!");
-  return mVideoReader->VideoQueue();
+  return mVideoQueue;
 }
 
 bool
 DASHReader::IsSeekableInBufferedRanges()
 {
   ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
                                          mDecoder->GetReentrantMonitor());
   // At least one subreader must exist, and all subreaders must return true.
   return (mVideoReader || mAudioReader) &&
           !((mVideoReader && !mVideoReader->IsSeekableInBufferedRanges()) ||
             (mAudioReader && !mAudioReader->IsSeekableInBufferedRanges()));
 }
 
+void
+DASHReader::RequestVideoReaderSwitch(uint32_t aFromReaderIdx,
+                                       uint32_t aToReaderIdx,
+                                       uint32_t aSubsegmentIdx)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  NS_ASSERTION(aFromReaderIdx < mVideoReaders.Length(),
+               "From index is greater than number of video readers!");
+  NS_ASSERTION(aToReaderIdx < mVideoReaders.Length(),
+               "To index is greater than number of video readers!");
+  NS_ASSERTION(aToReaderIdx != aFromReaderIdx,
+               "Don't request switches to same reader!");
+  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
+
+  if (mSwitchCount < 0) {
+    mSwitchCount = 0;
+  }
+
+  DASHRepReader* fromReader = mVideoReaders[aFromReaderIdx];
+  DASHRepReader* toReader = mVideoReaders[aToReaderIdx];
+
+  LOG("Switch requested from reader [%d] [%p] to reader [%d] [%p] "
+      "at subsegment[%d].",
+      aFromReaderIdx, fromReader, aToReaderIdx, toReader, aSubsegmentIdx);
+
+  // Append the subsegment index to the list of pending switches.
+  mSwitchToVideoSubsegmentIndexes.AppendElement(aSubsegmentIdx);
+
+  // Tell the SWITCH FROM reader when it should stop reading.
+  fromReader->RequestSwitchAtSubsegment(aSubsegmentIdx, toReader);
+
+  // Tell the SWITCH TO reader to seek to the correct offset.
+  toReader->RequestSeekToSubsegment(aSubsegmentIdx);
+
+  mSwitchVideoReaders = true;
+}
+
+void
+DASHReader::PossiblySwitchVideoReaders()
+{
+  NS_ASSERTION(mDecoder, "Decoder should not be null");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+
+  // Flag to switch streams is set in |RequestVideoReaderSwitch|.
+  if (!mSwitchVideoReaders) {
+    return;
+  }
+
+  // Only switch if we reached a switch access point.
+  NS_ENSURE_TRUE(0 <= mSwitchCount, );
+  NS_ENSURE_TRUE((uint32_t)mSwitchCount < mSwitchToVideoSubsegmentIndexes.Length(), );
+  uint32_t switchIdx = mSwitchToVideoSubsegmentIndexes[mSwitchCount];
+  if (!mVideoReader->HasReachedSubsegment(switchIdx)) {
+    return;
+  }
+
+  // Get Representation index to switch to.
+  DASHDecoder* dashDecoder = static_cast<DASHDecoder*>(mDecoder);
+  int32_t toReaderIdx = dashDecoder->GetRepIdxForVideoSubsegmentLoad(switchIdx);
+  NS_ENSURE_TRUE(0 <= toReaderIdx, );
+  NS_ENSURE_TRUE((uint32_t)toReaderIdx < mVideoReaders.Length(), );
+
+  DASHRepReader* fromReader = mVideoReader;
+  DASHRepReader* toReader = mVideoReaders[toReaderIdx];
+  NS_ENSURE_TRUE(fromReader != toReader, );
+
+  LOG("Switching video readers now from [%p] to [%p] at subsegment [%d]: "
+      "mSwitchCount [%d].",
+      fromReader, toReader, switchIdx, mSwitchCount);
+
+  // Switch readers while in the monitor.
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  mVideoReader = toReader;
+
+  // Prep readers for next switch, also while in monitor.
+  if ((uint32_t)++mSwitchCount < mSwitchToVideoSubsegmentIndexes.Length()) {
+    // Get the subsegment at which to switch.
+    switchIdx = mSwitchToVideoSubsegmentIndexes[mSwitchCount];
+
+    // Update from and to reader ptrs for next switch.
+    fromReader = toReader;
+    toReaderIdx = dashDecoder->GetRepIdxForVideoSubsegmentLoad(switchIdx);
+    toReader = mVideoReaders[toReaderIdx];
+    NS_ENSURE_TRUE((uint32_t)toReaderIdx < mVideoReaders.Length(), );
+    NS_ENSURE_TRUE(fromReader != toReader, );
+
+    // Tell the SWITCH FROM reader when it should stop reading.
+    fromReader->RequestSwitchAtSubsegment(switchIdx, toReader);
+
+    // Tell the SWITCH TO reader to seek to the correct offset.
+    toReader->RequestSeekToSubsegment(switchIdx);
+  } else {
+    // If there are no more pending switches, unset the switch readers flag.
+    mSwitchVideoReaders = false;
+  }
+}
+
+void
+DASHReader::PrepareToDecode()
+{
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+
+  // Flag to switch streams is set by |DASHDecoder|.
+  if (!mSwitchVideoReaders) {
+    return;
+  }
+
+  PossiblySwitchVideoReaders();
+
+  // Prepare each sub reader for decoding: includes seeking to the correct
+  // offset if a seek was previously requested.
+  for (uint32_t i = 0; i < mVideoReaders.Length(); i++) {
+    mVideoReaders[i]->PrepareToDecode();
+  }
+}
+
 } // namespace mozilla
 
--- a/content/media/dash/DASHReader.h
+++ b/content/media/dash/DASHReader.h
@@ -11,44 +11,34 @@
  * partitioned into one or more segments and delivered to a client using HTTP.
  *
  * see DASHDecoder.cpp for comments on DASH object interaction
  */
 
 #if !defined(DASHReader_h_)
 #define DASHReader_h_
 
+#include "VideoUtils.h"
 #include "MediaDecoderReader.h"
+#include "DASHRepReader.h"
 
 namespace mozilla {
 
+class DASHRepReader;
+
 class DASHReader : public MediaDecoderReader
 {
 public:
-  DASHReader(AbstractMediaDecoder* aDecoder) :
-    MediaDecoderReader(aDecoder),
-    mReadMetadataMonitor("media.dashreader.readmetadata"),
-    mReadyToReadMetadata(false),
-    mDecoderIsShuttingDown(false),
-    mAudioReader(this),
-    mVideoReader(this),
-    mAudioReaders(this),
-    mVideoReaders(this)
-  {
-    MOZ_COUNT_CTOR(DASHReader);
-  }
-  ~DASHReader()
-  {
-    MOZ_COUNT_DTOR(DASHReader);
-  }
+  DASHReader(AbstractMediaDecoder* aDecoder);
+  ~DASHReader();
 
   // Adds a pointer to a audio/video reader for a media |Representation|.
   // Called on the main thread only.
-  void AddAudioReader(MediaDecoderReader* aAudioReader);
-  void AddVideoReader(MediaDecoderReader* aVideoReader);
+  void AddAudioReader(DASHRepReader* aAudioReader);
+  void AddVideoReader(DASHRepReader* aVideoReader);
 
   // Waits for metadata bytes to be downloaded, then reads and parses them.
   // Called on the decode thread only.
   nsresult ReadMetadata(VideoInfo* aInfo,
                         MetadataTags** aTags);
 
   // Waits for |ReadyToReadMetadata| or |NotifyDecoderShuttingDown|
   // notification, whichever comes first. Ensures no attempt to read metadata
@@ -84,37 +74,36 @@ public:
     ReentrantMonitorAutoEnter metadataMon(mReadMetadataMonitor);
     mDecoderIsShuttingDown = true;
     // Notify |ReadMetadata| of the shutdown if it's waiting.
     metadataMon.NotifyAll();
   }
 
   // Audio/video status are dependent on the presence of audio/video readers.
   // Call on decode thread only.
-  bool HasAudio() {
-    NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-    return mAudioReader ? mAudioReader->HasAudio() : false;
-  }
-  bool HasVideo() {
-    NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-    return mVideoReader ? mVideoReader->HasVideo() : false;
-  }
+  bool HasAudio();
+  bool HasVideo();
 
   // Returns references to the audio/video queues of sub-readers. Called on
   // decode, state machine and audio threads.
-  MediaQueue<AudioData>& AudioQueue();
-  MediaQueue<VideoData>& VideoQueue();
+  MediaQueue<AudioData>& AudioQueue() MOZ_OVERRIDE;
+  MediaQueue<VideoData>& VideoQueue() MOZ_OVERRIDE;
 
   // Called from MediaDecoderStateMachine on the main thread.
   nsresult Init(MediaDecoderReader* aCloneDonor);
 
   // Used by |MediaMemoryReporter|.
   int64_t VideoQueueMemoryInUse();
   int64_t AudioQueueMemoryInUse();
 
+  // Called on the decode thread, at the start of the decode loop, before
+  // |DecodeVideoFrame|.  Carries out video reader switch if previously
+  // requested, and tells sub-readers to |PrepareToDecode|.
+  void PrepareToDecode() MOZ_OVERRIDE;
+
   // Called on the decode thread.
   bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold);
   bool DecodeAudioData();
 
   // Converts seek time to byte offset. Called on the decode thread only.
   nsresult Seek(int64_t aTime,
                 int64_t aStartTime,
                 int64_t aEndTime,
@@ -124,55 +113,30 @@ public:
   nsresult GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime);
 
   // Called on the state machine or decode threads.
   VideoData* FindStartTime(int64_t& aOutStartTime);
 
   // Call by state machine on multiple threads.
   bool IsSeekableInBufferedRanges();
 
+  // Prepares for an upcoming switch of video readers. Called by
+  // |DASHDecoder| when it has switched download streams. Sets the index of
+  // the reader to switch TO and the index of the subsegment to switch AT
+  // (start offset). (Note: Subsegment boundaries are switch access points for
+  // DASH-WebM). Called on the main thread. Must be in the decode monitor.
+  void RequestVideoReaderSwitch(uint32_t aFromReaderIdx,
+                                uint32_t aToReaderIdx,
+                                uint32_t aSubsegmentIdx);
+
 private:
-  // Similar to |ReentrantMonitorAutoEnter|, this class enters the supplied
-  // monitor in its constructor, but only if the conditional value |aEnter| is
-  // true. Used here to allow read access on the sub-readers' owning thread,
-  // i.e. the decode thread, while locking write accesses from all threads,
-  // and read accesses from non-decode threads.
-  class ReentrantMonitorConditionallyEnter
-  {
-  public:
-    ReentrantMonitorConditionallyEnter(bool aEnter,
-                                       ReentrantMonitor &aReentrantMonitor) :
-      mReentrantMonitor(nullptr)
-    {
-      MOZ_COUNT_CTOR(DASHReader::ReentrantMonitorConditionallyEnter);
-      if (aEnter) {
-        mReentrantMonitor = &aReentrantMonitor;
-        NS_ASSERTION(mReentrantMonitor, "null monitor");
-        mReentrantMonitor->Enter();
-      }
-    }
-    ~ReentrantMonitorConditionallyEnter(void)
-    {
-      if (mReentrantMonitor) {
-        mReentrantMonitor->Exit();
-      }
-      MOZ_COUNT_DTOR(DASHReader::ReentrantMonitorConditionallyEnter);
-    }
-  private:
-    // Restrict to constructor and destructor defined above.
-    ReentrantMonitorConditionallyEnter();
-    ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter&);
-    ReentrantMonitorConditionallyEnter& operator =(const ReentrantMonitorConditionallyEnter&);
-    static void* operator new(size_t) CPP_THROW_NEW;
-    static void operator delete(void*);
-
-    // Ptr to the |ReentrantMonitor| object. Null if |aEnter| in constructor
-    // was false.
-    ReentrantMonitor* mReentrantMonitor;
-  };
+  // Switches video subreaders if a stream-switch flag has been set, and the
+  // current reader has read up to the switching subsegment (start offset).
+  // Called on the decode thread only.
+  void PossiblySwitchVideoReaders();
 
   // Monitor and booleans used to wait for metadata bytes to be downloaded, and
   // skip reading metadata if |DASHDecoder|'s shutdown is in progress.
   ReentrantMonitor mReadMetadataMonitor;
   bool mReadyToReadMetadata;
   bool mDecoderIsShuttingDown;
 
   // Wrapper class protecting accesses to sub-readers. Asserts that the
@@ -194,46 +158,46 @@ private:
     // Note: |mSubReader|'s refcount will be decremented in this destructor.
     ~MonitoredSubReader()
     {
       MOZ_COUNT_DTOR(DASHReader::MonitoredSubReader);
     }
 
     // Override '=' to always assert thread is "in monitor" for writes/changes
     // to |mSubReader|.
-    MonitoredSubReader& operator=(MediaDecoderReader* rhs)
+    MonitoredSubReader& operator=(DASHRepReader* rhs)
     {
       NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
       mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
       mSubReader = rhs;
       return *this;
     }
 
     // Override '*' to assert threads other than the decode thread are "in
     // monitor" for ptr reads.
-    operator MediaDecoderReader*() const
+    operator DASHRepReader*() const
     {
       NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
       if (!mReader->GetDecoder()->OnDecodeThread()) {
         mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
       }
       return mSubReader;
     }
 
     // Override '->' to assert threads other than the decode thread are "in
     // monitor" for |mSubReader| function calls.
-    MediaDecoderReader* operator->() const
+    DASHRepReader* operator->() const
     {
       return *this;
     }
   private:
     // Pointer to |DASHReader| object which owns this |MonitoredSubReader|.
     DASHReader* mReader;
     // Ref ptr to the sub reader.
-    nsRefPtr<MediaDecoderReader> mSubReader;
+    nsRefPtr<DASHRepReader> mSubReader;
   };
 
   // Wrapped ref ptrs to current sub-readers of individual media
   // |Representation|s. Decoder monitor must be entered for write access on all
   // threads and read access on all threads that are not the decode thread.
   // Read access on the decode thread does not need to be protected.
   // Note: |MonitoredSubReader| class will assert correct monitor use.
   MonitoredSubReader mAudioReader;
@@ -272,44 +236,56 @@ private:
         mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
       }
       return mSubReaderList.Length();
     }
 
     // Override '[]' to assert threads other than the decode thread are "in
     // monitor" for accessing individual elems. Note: elems returned do not
     // have monitor assertions builtin like |MonitoredSubReader| objects.
-    nsRefPtr<MediaDecoderReader>& operator[](uint32_t i)
+    nsRefPtr<DASHRepReader>& operator[](uint32_t i)
     {
       NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
       if (!mReader->GetDecoder()->OnDecodeThread()) {
         mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
       }
       return mSubReaderList[i];
     }
 
     // Appends a reader to the end of |mSubReaderList|. Will always assert that
     // the thread is "in monitor".
     void
-    AppendElement(MediaDecoderReader* aReader)
+    AppendElement(DASHRepReader* aReader)
     {
       NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
       mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
       mSubReaderList.AppendElement(aReader);
     }
   private:
     // Pointer to |DASHReader| object which owns this |MonitoredSubReader|.
     DASHReader* mReader;
     // Ref ptrs to the sub readers.
-    nsTArray<nsRefPtr<MediaDecoderReader> > mSubReaderList;
+    nsTArray<nsRefPtr<DASHRepReader> > mSubReaderList;
   };
 
   // Ref ptrs to all sub-readers of individual media |Representation|s.
   // Decoder monitor must be entered for write access on all threads and read
   // access on all threads that are not the decode thread. Read acces on the
   // decode thread does not need to be protected.
   MonitoredSubReaderList mAudioReaders;
   MonitoredSubReaderList mVideoReaders;
+
+  // When true, indicates that we should switch reader. Must be in the monitor
+  // for write access and read access off the decode thread.
+  bool mSwitchVideoReaders;
+
+  // Indicates the subsegment index at which the reader should switch. Must be
+  // in the monitor for write access and read access off the decode thread.
+  nsTArray<uint32_t> mSwitchToVideoSubsegmentIndexes;
+
+  // Counts the number of switches that have taken place. Must be in the
+  // monitor for write access and read access off the decode thread.
+  int32_t mSwitchCount;
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/media/dash/DASHRepDecoder.cpp
+++ b/content/media/dash/DASHRepDecoder.cpp
@@ -14,16 +14,17 @@
 
 #include "prlog.h"
 #include "VideoUtils.h"
 #include "SegmentBase.h"
 #include "MediaDecoderStateMachine.h"
 #include "DASHReader.h"
 #include "MediaResource.h"
 #include "DASHRepDecoder.h"
+#include "WebMReader.h"
 
 namespace mozilla {
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define LOG(msg, ...) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, \
                              ("%p [DASHRepDecoder] " msg, this, __VA_ARGS__))
 #define LOG1(msg) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, \
@@ -128,26 +129,34 @@ DASHRepDecoder::NotifyDownloadEnded(nsre
     DecodeError();
     return;
   }
 
   if (NS_SUCCEEDED(aStatus)) {
     // Decrement counter as metadata chunks are downloaded.
     // Note: Reader gets next chunk download via |ChannelMediaResource|:|Seek|.
     if (mMetadataChunkCount > 0) {
-      LOG("Metadata chunk [%d] downloaded: range requested [%d - %d]",
+      LOG("Metadata chunk [%d] downloaded: range requested [%lld - %lld] "
+          "subsegmentIdx [%d]",
           mMetadataChunkCount,
-          mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
+          mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx);
       mMetadataChunkCount--;
     } else {
+      LOG("Byte range downloaded: status [%x] range requested [%lld - %lld] "
+          "subsegmentIdx [%d]",
+          aStatus, mCurrentByteRange.mStart, mCurrentByteRange.mEnd,
+          mSubsegmentIdx);
+      if ((uint32_t)mSubsegmentIdx == mByteRanges.Length()-1) {
+        mResource->NotifyLastByteRange();
+      }
       // Notify main decoder that a DATA byte range is downloaded.
-      LOG("Byte range downloaded: status [%x] range requested [%d - %d]",
-          aStatus, mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
-      mMainDecoder->NotifyDownloadEnded(this, aStatus,
-                                        mCurrentByteRange);
+      // Only notify IF this decoder is allowed to download data.
+      NS_ASSERTION(mMainDecoder->IsDecoderAllowedToDownloadData(this),
+                   "This decoder should not have downloaded data.");
+      mMainDecoder->NotifyDownloadEnded(this, aStatus, mSubsegmentIdx);
     }
   } else if (aStatus == NS_BINDING_ABORTED) {
     LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
     if (mMainDecoder) {
       mMainDecoder->LoadAborted();
     }
     return;
   } else if (aStatus != NS_BASE_STREAM_CLOSED) {
@@ -156,117 +165,185 @@ DASHRepDecoder::NotifyDownloadEnded(nsre
   }
 }
 
 void
 DASHRepDecoder::OnReadMetadataCompleted()
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
 
+  // If shutting down, just return silently.
+  if (mShuttingDown) {
+    LOG1("Shutting down! Ignoring OnReadMetadataCompleted().");
+    return;
+  }
+
   LOG1("Metadata has been read.");
-  nsCOMPtr<nsIRunnable> event =
-    NS_NewRunnableMethod(this, &DASHRepDecoder::LoadNextByteRange);
-  nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-  if (NS_FAILED(rv)) {
-    LOG("Error dispatching parse event to main thread: rv[%x]", rv);
+
+  // Metadata loaded and read for this stream; ok to populate byte ranges.
+  nsresult rv = PopulateByteRanges();
+  if (NS_FAILED(rv) || mByteRanges.IsEmpty()) {
+    LOG("Error populating byte ranges [%x]", rv);
     DecodeError();
     return;
   }
+
+  mMainDecoder->OnReadMetadataCompleted(this);
+}
+
+nsresult
+DASHRepDecoder::PopulateByteRanges()
+{
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+
+  // Should not be called during shutdown.
+  NS_ENSURE_FALSE(mShuttingDown, NS_ERROR_UNEXPECTED);
+
+  if (!mByteRanges.IsEmpty()) {
+    return NS_OK;
+  }
+  NS_ENSURE_TRUE(mReader, NS_ERROR_NULL_POINTER);
+  LOG1("Populating byte range array.");
+  return mReader->GetSubsegmentByteRanges(mByteRanges);
 }
 
 void
 DASHRepDecoder::LoadNextByteRange()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-  if (!mResource) {
-    LOG1("Error: resource is reported as null!");
+  NS_ASSERTION(mResource, "Error: resource is reported as null!");
+
+  // Return silently if shutting down.
+  if (mShuttingDown) {
+    LOG1("Shutting down! Ignoring LoadNextByteRange().");
+    return;
+  }
+
+  NS_ASSERTION(mMainDecoder, "Error: main decoder is null!");
+  NS_ASSERTION(mMainDecoder->IsDecoderAllowedToDownloadData(this),
+               "Should not be called on non-active decoders!");
+
+  // Cannot have empty byte ranges.
+  if (mByteRanges.IsEmpty()) {
+    LOG1("Error getting list of subsegment byte ranges.");
     DecodeError();
     return;
   }
 
-  // Populate the array of subsegment byte ranges if it's empty.
-  nsresult rv;
-  if (mByteRanges.IsEmpty()) {
-    if (!mReader) {
-      LOG1("Error: mReader should not be null!");
-      DecodeError();
-      return;
-    }
-    rv = mReader->GetIndexByteRanges(mByteRanges);
-    // If empty, just fail.
-    if (NS_FAILED(rv) || mByteRanges.IsEmpty()) {
-      LOG1("Error getting list of subsegment byte ranges.");
-      DecodeError();
-      return;
-    }
-  }
-
   // Get byte range for subsegment.
-  if (mSubsegmentIdx < mByteRanges.Length()) {
-    mCurrentByteRange = mByteRanges[mSubsegmentIdx];
+  int32_t subsegmentIdx = mMainDecoder->GetSubsegmentIndex(this);
+  NS_ASSERTION(0 <= subsegmentIdx,
+               "Subsegment index should be >= 0 for active decoders");
+  if (subsegmentIdx >= 0 && (uint32_t)subsegmentIdx < mByteRanges.Length()) {
+    mCurrentByteRange = mByteRanges[subsegmentIdx];
+    mSubsegmentIdx = subsegmentIdx;
   } else {
     mCurrentByteRange.Clear();
-    LOG("End of subsegments: index [%d] out of range.", mSubsegmentIdx);
+    mSubsegmentIdx = -1;
+    LOG("End of subsegments: index [%d] out of range.", subsegmentIdx);
     return;
   }
 
+  // Request a seek for the first reader. Required so that the reader is
+  // primed to start here, and will block subsequent subsegment seeks unless
+  // the subsegment has been read.
+  if (subsegmentIdx == 0) {
+    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+    mReader->RequestSeekToSubsegment(0);
+  }
+
   // Open byte range corresponding to subsegment.
-  rv = mResource->OpenByteRange(nullptr, mCurrentByteRange);
+  nsresult rv = mResource->OpenByteRange(nullptr, mCurrentByteRange);
   if (NS_FAILED(rv)) {
-    LOG("Error opening byte range [%d - %d]: rv [%x].",
-        mCurrentByteRange.mStart, mCurrentByteRange.mEnd, rv);
+    LOG("Error opening byte range [%lld - %lld]: subsegmentIdx [%d] rv [%x].",
+        mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx, rv);
     NetworkError();
     return;
   }
-  // Increment subsegment index for next load.
-  mSubsegmentIdx++;
 }
 
 nsresult
 DASHRepDecoder::GetByteRangeForSeek(int64_t const aOffset,
                                       MediaByteRange& aByteRange)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
-  // Check data ranges, if available.
-  for (int i = 0; i < mByteRanges.Length(); i++) {
-    NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED);
-    if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) {
-      mCurrentByteRange = aByteRange = mByteRanges[i];
-      mSubsegmentIdx = i;
+  // Only check data ranges if they're available and if this decoder is active,
+  // i.e. inactive rep decoders should only load metadata.
+  bool canDownloadData = mMainDecoder->IsDecoderAllowedToDownloadData(this);
+  if (canDownloadData) {
+    for (int i = 0; i < mByteRanges.Length(); i++) {
+      NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED);
+      // Check if |aOffset| lies within the current data range.
+      if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) {
+        mCurrentByteRange = aByteRange = mByteRanges[i];
+        mSubsegmentIdx = i;
+        // XXX Hack: should be setting subsegment outside this function, but
+        // need to review seeking for multiple switches anyhow.
+        mMainDecoder->SetSubsegmentIndex(this, i);
+        LOG("Getting DATA range [%d] for seek offset [%lld]: "
+            "bytes [%lld] to [%lld]",
+            i, aOffset, aByteRange.mStart, aByteRange.mEnd);
+        return NS_OK;
+      }
+    }
+  } else {
+    LOG1("Restricting seekable byte ranges to metadata for this decoder.");
+  }
+  // Don't allow metadata downloads once they're loaded and byte ranges have
+  // been populated.
+  bool canDownloadMetadata = mByteRanges.IsEmpty();
+  if (canDownloadMetadata) {
+    // Check metadata ranges; init range.
+    if (mInitByteRange.mStart <= aOffset && aOffset <= mInitByteRange.mEnd) {
+      mCurrentByteRange = aByteRange = mInitByteRange;
+      mSubsegmentIdx = 0;
+        LOG("Getting INIT range for seek offset [%lld]: bytes [%lld] to "
+            "[%lld]", aOffset, aByteRange.mStart, aByteRange.mEnd);
       return NS_OK;
     }
-  }
-  // Check metadata ranges; init range.
-  if (mInitByteRange.mStart <= aOffset && aOffset <= mInitByteRange.mEnd) {
-    mCurrentByteRange = aByteRange = mInitByteRange;
-    mSubsegmentIdx = 0;
-    return NS_OK;
-  }
-  // ... index range.
-  if (mIndexByteRange.mStart <= aOffset && aOffset <= mIndexByteRange.mEnd) {
-    mCurrentByteRange = aByteRange = mIndexByteRange;
-    mSubsegmentIdx = 0;
-    return NS_OK;
+    // ... index range.
+    if (mIndexByteRange.mStart <= aOffset && aOffset <= mIndexByteRange.mEnd) {
+      mCurrentByteRange = aByteRange = mIndexByteRange;
+      mSubsegmentIdx = 0;
+      LOG("Getting INDEXES range for seek offset [%lld]: bytes [%lld] to "
+          "[%lld]", aOffset, aByteRange.mStart, aByteRange.mEnd);
+      return NS_OK;
+    }
+  } else {
+    LOG1("Metadata should be read; inhibiting further metadata downloads.");
   }
 
+  // If no byte range is found by this stage, clear the parameter and return.
   aByteRange.Clear();
-  if (mByteRanges.IsEmpty()) {
+  if (mByteRanges.IsEmpty() || !canDownloadData || !canDownloadMetadata) {
     // Assume mByteRanges will be populated after metadata is read.
-    LOG("Can't get range for offset [%d].", aOffset);
+    LOG("Data ranges not populated [%s]; data download restricted [%s]; "
+        "metadata download restricted [%s]: offset[%lld].",
+        (mByteRanges.IsEmpty() ? "yes" : "no"),
+        (canDownloadData ? "no" : "yes"),
+        (canDownloadMetadata ? "no" : "yes"), aOffset);
     return NS_ERROR_NOT_AVAILABLE;
   } else {
     // Cannot seek to an unknown offset.
     // XXX Revisit this for dynamic MPD profiles if MPD is regularly updated.
-    LOG("Error! Offset [%d] is in an unknown range!", aOffset);
+    LOG("Error! Offset [%lld] is in an unknown range!", aOffset);
     return NS_ERROR_ILLEGAL_VALUE;
   }
 }
 
 void
+DASHRepDecoder::PrepareForSwitch()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  // Ensure that the media cache writes any data held in its partial block.
+  mResource->FlushCache();
+}
+
+void
 DASHRepDecoder::NetworkError()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mMainDecoder) { mMainDecoder->NetworkError(); }
 }
 
 void
 DASHRepDecoder::SetDuration(double aDuration)
@@ -297,17 +374,17 @@ DASHRepDecoder::Progress(bool aTimer)
 
 void
 DASHRepDecoder::NotifyDataArrived(const char* aBuffer,
                                     uint32_t aLength,
                                     int64_t aOffset)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
-  LOG("Data bytes [%d - %d] arrived via buffer [%p].",
+  LOG("Data bytes [%lld - %lld] arrived via buffer [%p].",
       aOffset, aOffset+aLength, aBuffer);
   // Notify reader directly, since call to |MediaDecoderStateMachine|::
   // |NotifyDataArrived| will go to |DASHReader|::|NotifyDataArrived|, which
   // has no way to forward the notification to the correct sub-reader.
   if (mReader) {
     mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
   }
   // Forward to main decoder which will notify state machine.
--- a/content/media/dash/DASHRepDecoder.h
+++ b/content/media/dash/DASHRepDecoder.h
@@ -20,31 +20,32 @@
 #include "DASHDecoder.h"
 #include "WebMDecoder.h"
 #include "WebMReader.h"
 #include "MediaDecoder.h"
 
 namespace mozilla {
 
 class DASHDecoder;
+class DASHRepReader;
 
 class DASHRepDecoder : public MediaDecoder
 {
 public:
   typedef mozilla::net::Representation Representation;
   typedef mozilla::net::SegmentBase SegmentBase;
   typedef mozilla::layers::ImageContainer ImageContainer;
 
   // Constructor takes a ptr to the main decoder.
   DASHRepDecoder(DASHDecoder* aMainDecoder) :
     mMainDecoder(aMainDecoder),
     mMPDRepresentation(nullptr),
     mMetadataChunkCount(0),
     mCurrentByteRange(),
-    mSubsegmentIdx(0),
+    mSubsegmentIdx(-1),
     mReader(nullptr)
   {
     MOZ_COUNT_CTOR(DASHRepDecoder);
   }
 
   ~DASHRepDecoder()
   {
     MOZ_COUNT_DTOR(DASHRepDecoder);
@@ -123,31 +124,39 @@ public:
   // should stop buffering or otherwise waiting for download progress and
   // start consuming data, if possible, because the cache is full.
   void NotifySuspendedStatusChanged();
 
   // Gets a byte range containing the byte offset. Call on main thread only.
   nsresult GetByteRangeForSeek(int64_t const aOffset,
                                MediaByteRange& aByteRange);
 
+  // Gets the number of data byte ranges (not inc. metadata).
+  uint32_t GetNumDataByteRanges() {
+    return mByteRanges.Length();
+  }
+
+  // Notify that a switch is about to happen. Called on the main thread.
+  void PrepareForSwitch();
+
   // Returns true if the current thread is the state machine thread.
   bool OnStateMachineThread() const;
 
   // Returns true if the current thread is the decode thread.
   bool OnDecodeThread() const;
 
   // Returns main decoder's monitor for synchronised access.
-  ReentrantMonitor& GetReentrantMonitor();
+  ReentrantMonitor& GetReentrantMonitor() MOZ_OVERRIDE;
 
   // Called on the decode thread from WebMReader.
   ImageContainer* GetImageContainer();
 
   // Called when Metadata has been read; notifies that index data is read.
   // Called on the decode thread only.
-  void OnReadMetadataCompleted();
+  void OnReadMetadataCompleted() MOZ_OVERRIDE;
 
   // Overridden to cleanup ref to |DASHDecoder|. Called on main thread only.
   void Shutdown() {
     NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
     // Call parent class shutdown.
     MediaDecoder::Shutdown();
     NS_ENSURE_TRUE(mShuttingDown, );
     // Cleanup ref to main decoder.
@@ -157,16 +166,20 @@ public:
   // Drop reference to state machine and mReader (owned by state machine).
   // Only called during shutdown dance.
   void ReleaseStateMachine();
 
   // Notifies the element that decoding has failed.
   void DecodeError();
 
 private:
+  // Populates |mByteRanges| by calling |GetIndexByteRanges| from |mReader|.
+  // Called on the main thread only.
+  nsresult PopulateByteRanges();
+
   // The main decoder.
   nsRefPtr<DASHDecoder> mMainDecoder;
   // This decoder's MPD |Representation| object.
   Representation const * mMPDRepresentation;
 
   // Countdown var for loading metadata byte ranges.
   uint16_t        mMetadataChunkCount;
 
@@ -174,19 +187,19 @@ private:
   nsTArray<MediaByteRange> mByteRanges;
 
   // Byte range for the init and index bytes.
   MediaByteRange  mInitByteRange;
   MediaByteRange  mIndexByteRange;
 
   // The current byte range being requested.
   MediaByteRange  mCurrentByteRange;
-  // Index of the current byte range.
-  uint64_t        mSubsegmentIdx;
+  // Index of the current byte range. Initialized to -1.
+  int32_t         mSubsegmentIdx;
 
   // Ptr to the reader object for this |Representation|. Owned by state
   // machine.
-  MediaDecoderReader*   mReader;
+  DASHRepReader* mReader;
 };
 
 } // namespace mozilla
 
 #endif //DASHRepDecoder_h_
new file mode 100644
--- /dev/null
+++ b/content/media/dash/DASHRepReader.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+
+/* 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/. */
+
+/* DASH - Dynamic Adaptive Streaming over HTTP
+ *
+ * DASH is an adaptive bitrate streaming technology where a multimedia file is
+ * partitioned into one or more segments and delivered to a client using HTTP.
+ *
+ * see DASHDecoder.cpp for comments on DASH object interaction
+ */
+
+#if !defined(DASHRepReader_h_)
+#define DASHRepReader_h_
+
+#include "VideoUtils.h"
+#include "MediaDecoderReader.h"
+#include "DASHReader.h"
+
+namespace mozilla {
+
+class DASHReader;
+
+class DASHRepReader : public MediaDecoderReader
+{
+public:
+  DASHRepReader(AbstractMediaDecoder* aDecoder)
+    : MediaDecoderReader(aDecoder) { }
+  virtual ~DASHRepReader() { }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DASHRepReader)
+
+  virtual void SetMainReader(DASHReader *aMainReader) = 0;
+
+  // Sets range for initialization bytes; used by DASH.
+  virtual void SetInitByteRange(MediaByteRange &aByteRange) = 0;
+
+  // Sets range for index frame bytes; used by DASH.
+  virtual void SetIndexByteRange(MediaByteRange &aByteRange) = 0;
+
+  // Returns list of ranges for index frame start/end offsets. Used by DASH.
+  virtual nsresult GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges) = 0;
+
+  // Returns true if the reader has reached a DASH switch access point.
+  virtual bool HasReachedSubsegment(uint32_t aSubsegmentIndex) = 0;
+
+  // Requests a seek to the start of a particular DASH subsegment.
+  virtual void RequestSeekToSubsegment(uint32_t aIdx) = 0;
+
+  // Reader should stop reading at the start of the specified subsegment, and
+  // should prepare for the next reader to add data to the video queue.
+  // Should be implemented by a sub-reader, e.g. |nsDASHWebMReader|.
+  virtual void RequestSwitchAtSubsegment(int32_t aCluster,
+                                         MediaDecoderReader* aNextReader) = 0;
+};
+
+}// namespace mozilla
+
+#endif /*DASHRepReader*/
--- a/content/media/dash/Makefile.in
+++ b/content/media/dash/Makefile.in
@@ -18,16 +18,17 @@ include $(DEPTH)/config/autoconf.mk
 MODULE         := content
 LIBRARY_NAME   := gkcondash_s
 LIBXUL_LIBRARY := 1
 
 EXPORTS := \
   DASHDecoder.h \
   DASHRepDecoder.h \
   DASHReader.h \
+  DASHRepReader.h \
   $(NULL)
 
 CPPSRCS := \
   DASHDecoder.cpp \
   DASHRepDecoder.cpp \
   DASHReader.cpp \
   $(NULL)
 
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -98,26 +98,39 @@ static int64_t webm_tell(void *aUserData
   NS_ASSERTION(aUserData, "aUserData must point to a valid AbstractMediaDecoder");
   AbstractMediaDecoder* decoder = reinterpret_cast<AbstractMediaDecoder*>(aUserData);
   MediaResource* resource = decoder->GetResource();
   NS_ASSERTION(resource, "Decoder has no media resource");
   return resource->Tell();
 }
 
 WebMReader::WebMReader(AbstractMediaDecoder* aDecoder)
+#ifdef MOZ_DASH
+  : DASHRepReader(aDecoder),
+#else
   : MediaDecoderReader(aDecoder),
+#endif
   mContext(nullptr),
   mPacketCount(0),
   mChannels(0),
   mVideoTrack(0),
   mAudioTrack(0),
   mAudioStartUsec(-1),
   mAudioFrames(0),
   mHasVideo(false),
   mHasAudio(false)
+#ifdef MOZ_DASH
+  , mMainReader(nullptr),
+  mSwitchingCluster(-1),
+  mNextReader(nullptr),
+  mSeekToCluster(-1),
+  mCurrentOffset(-1),
+  mPushVideoPacketToNextReader(false),
+  mReachedSwitchAccessPoint(false)
+#endif
 {
   MOZ_COUNT_CTOR(WebMReader);
   // Zero these member vars to avoid crashes in VP8 destroy and Vorbis clear
   // functions when destructor is called before |Init|.
   memset(&mVP8, 0, sizeof(vpx_codec_ctx_t));
   memset(&mVorbisBlock, 0, sizeof(vorbis_block));
   memset(&mVorbisDsp, 0, sizeof(vorbis_dsp_state));
   memset(&mVorbisInfo, 0, sizeof(vorbis_info));
@@ -199,17 +212,21 @@ nsresult WebMReader::ReadMetadata(VideoI
                      this, mDecoder,
                      mInitByteRange.mStart, mInitByteRange.mEnd,
                      mCuesByteRange.mStart, mCuesByteRange.mEnd));
   nestegg_io io;
   io.read = webm_read;
   io.seek = webm_seek;
   io.tell = webm_tell;
   io.userdata = mDecoder;
+#ifdef MOZ_DASH
   int64_t maxOffset = mInitByteRange.IsNull() ? -1 : mInitByteRange.mEnd;
+#else
+  int64_t maxOffset = -1;
+#endif
   int r = nestegg_init(&mContext, io, nullptr, maxOffset);
   if (r == -1) {
     return NS_ERROR_FAILURE;
   }
 
   uint64_t duration = 0;
   r = nestegg_duration(mContext, &duration);
   if (r == 0) {
@@ -349,16 +366,17 @@ nsresult WebMReader::ReadMetadata(VideoI
       }
 
       mInfo.mAudioRate = mVorbisDsp.vi->rate;
       mInfo.mAudioChannels = mVorbisDsp.vi->channels;
       mChannels = mInfo.mAudioChannels;
     }
   }
 
+#ifdef MOZ_DASH
   // Byte range for cues has been specified; load them.
   if (!mCuesByteRange.IsNull()) {
     maxOffset = mCuesByteRange.mEnd;
 
     // Iterate through cluster ranges until nestegg returns the last one
     NS_ENSURE_TRUE(mClusterByteRanges.IsEmpty(),
                    NS_ERROR_ALREADY_INITIALIZED);
     int clusterNum = 0;
@@ -381,22 +399,25 @@ nsresult WebMReader::ReadMetadata(VideoI
       if (mClusterByteRanges[clusterNum].mEnd == -1) {
         mClusterByteRanges[clusterNum].mEnd = (mCuesByteRange.mStart-1);
         done = true;
       } else {
         clusterNum++;
       }
     } while (!done);
   }
+#endif
 
   *aInfo = mInfo;
 
   *aTags = nullptr;
 
+#ifdef MOZ_DASH
   mDecoder->OnReadMetadataCompleted();
+#endif
 
   return NS_OK;
 }
 
 ogg_packet WebMReader::InitOggPacket(unsigned char* aData,
                                        size_t aLength,
                                        bool aBOS,
                                        bool aEOS,
@@ -508,33 +529,73 @@ bool WebMReader::DecodeAudioPacket(neste
       CheckedInt64 time = total_duration + tstamp_usecs;
       if (!time.isValid()) {
         NS_WARNING("Int overflow adding total_duration and tstamp_usecs");
         nestegg_free_packet(aPacket);
         return false;
       };
 
       total_frames += frames;
-      mAudioQueue.Push(new AudioData(aOffset,
+      AudioQueue().Push(new AudioData(aOffset,
                                      time.value(),
                                      duration.value(),
                                      frames,
                                      buffer.forget(),
                                      mChannels));
       mAudioFrames += frames;
       if (vorbis_synthesis_read(&mVorbisDsp, frames) != 0) {
         return false;
       }
     }
   }
 
   return true;
 }
 
 nsReturnRef<NesteggPacketHolder> WebMReader::NextPacket(TrackType aTrackType)
+#ifdef MOZ_DASH
+{
+  nsAutoRef<NesteggPacketHolder> holder;
+  // Get packet from next reader if we're at a switching point; most likely we
+  // did not download the next packet for this reader's stream, so we have to
+  // get it from the next one. Note: Switch to next reader only for video;
+  // audio switching is not supported in the DASH-WebM On Demand profile.
+  if (aTrackType == VIDEO &&
+      (uint32_t)mSwitchingCluster < mClusterByteRanges.Length() &&
+      mCurrentOffset == mClusterByteRanges[mSwitchingCluster].mStart) {
+
+    if (mVideoPackets.GetSize() > 0) {
+      holder = NextPacketInternal(VIDEO);
+      LOG(PR_LOG_DEBUG,
+          ("WebMReader[%p] got packet from mVideoPackets @[%lld]",
+           this, holder->mOffset));
+    } else {
+      mReachedSwitchAccessPoint = true;
+      NS_ASSERTION(mNextReader,
+                   "Stream switch has been requested but mNextReader is null");
+      holder = mNextReader->NextPacket(aTrackType);
+      mPushVideoPacketToNextReader = true;
+      // Reset for possible future switches.
+      mSwitchingCluster = -1;
+      LOG(PR_LOG_DEBUG,
+          ("WebMReader[%p] got packet from mNextReader[%p] @[%lld]",
+           this, mNextReader.get(), (holder ? holder->mOffset : 0)));
+    }
+  } else {
+    holder = NextPacketInternal(aTrackType);
+    if (holder) {
+      mCurrentOffset = holder->mOffset;
+    }
+  }
+  return holder.out();
+}
+
+nsReturnRef<NesteggPacketHolder>
+WebMReader::NextPacketInternal(TrackType aTrackType)
+#endif
 {
   // The packet queue that packets will be pushed on if they
   // are not the type we are interested in.
   WebMPacketQueue& otherPackets =
     aTrackType == VIDEO ? mAudioPackets : mVideoPackets;
 
   // The packet queue for the type that we are interested in.
   WebMPacketQueue &packets =
@@ -593,17 +654,17 @@ nsReturnRef<NesteggPacketHolder> WebMRea
 }
 
 bool WebMReader::DecodeAudioData()
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   nsAutoRef<NesteggPacketHolder> holder(NextPacket(AUDIO));
   if (!holder) {
-    mAudioQueue.Finish();
+    AudioQueue().Finish();
     return false;
   }
 
   return DecodeAudioPacket(holder->mPacket, holder->mOffset);
 }
 
 bool WebMReader::DecodeVideoFrame(bool &aKeyframeSkip,
                                       int64_t aTimeThreshold)
@@ -612,17 +673,17 @@ bool WebMReader::DecodeVideoFrame(bool &
 
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   uint32_t parsed = 0, decoded = 0;
   AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
 
   nsAutoRef<NesteggPacketHolder> holder(NextPacket(VIDEO));
   if (!holder) {
-    mVideoQueue.Finish();
+    VideoQueue().Finish();
     return false;
   }
 
   nestegg_packet* packet = holder->mPacket;
   unsigned int track = 0;
   int r = nestegg_packet_track(packet, &track);
   if (r == -1) {
     return false;
@@ -647,17 +708,17 @@ bool WebMReader::DecodeVideoFrame(bool &
   uint64_t next_tstamp = 0;
   {
     nsAutoRef<NesteggPacketHolder> next_holder(NextPacket(VIDEO));
     if (next_holder) {
       r = nestegg_packet_tstamp(next_holder->mPacket, &next_tstamp);
       if (r == -1) {
         return false;
       }
-      mVideoPackets.PushFront(next_holder.disown());
+      PushVideoPacket(next_holder.disown());
     } else {
       ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
       int64_t endTime = mDecoder->GetEndMediaTime();
       if (endTime == -1) {
         return false;
       }
       next_tstamp = endTime * NS_PER_USEC;
     }
@@ -747,29 +808,47 @@ bool WebMReader::DecodeVideoFrame(bool &
                                        picture);
       if (!v) {
         return false;
       }
       parsed++;
       decoded++;
       NS_ASSERTION(decoded <= parsed,
         "Expect only 1 frame per chunk per packet in WebM...");
-      mVideoQueue.Push(v);
+      VideoQueue().Push(v);
     }
   }
 
   return true;
 }
 
+void
+WebMReader::PushVideoPacket(NesteggPacketHolder* aItem)
+{
+#ifdef MOZ_DASH
+  if (mPushVideoPacketToNextReader) {
+    NS_ASSERTION(mNextReader,
+                 "Stream switch has been requested but mNextReader is null");
+    mNextReader->mVideoPackets.PushFront(aItem);
+    mPushVideoPacketToNextReader = false;
+  } else {
+#endif
+    mVideoPackets.PushFront(aItem);
+#ifdef MOZ_DASH
+  }
+#endif
+}
+
 nsresult WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime,
                             int64_t aCurrentTime)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
-  LOG(PR_LOG_DEBUG, ("%p About to seek to %fs", mDecoder, aTarget/1000000.0));
+  LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: About to seek to %fs",
+                     this, mDecoder, aTarget/1000000.0));
   if (NS_FAILED(ResetDecode())) {
     return NS_ERROR_FAILURE;
   }
   uint32_t trackToSeek = mHasVideo ? mVideoTrack : mAudioTrack;
   int r = nestegg_track_seek(mContext, trackToSeek, aTarget * NS_PER_USEC);
   if (r != 0) {
     return NS_ERROR_FAILURE;
   }
@@ -831,24 +910,116 @@ nsresult WebMReader::GetBuffered(nsTimeR
   return NS_OK;
 }
 
 void WebMReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
 {
   mBufferedState->NotifyDataArrived(aBuffer, aLength, aOffset);
 }
 
+#ifdef MOZ_DASH
 nsresult
-WebMReader::GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges)
+WebMReader::GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges)
 {
   NS_ENSURE_TRUE(mContext, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(aByteRanges.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
   NS_ENSURE_FALSE(mClusterByteRanges.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
   NS_ENSURE_FALSE(mCuesByteRange.IsNull(), NS_ERROR_NOT_INITIALIZED);
 
   aByteRanges = mClusterByteRanges;
 
   return NS_OK;
 }
 
+void
+WebMReader::RequestSwitchAtSubsegment(int32_t aSubsegmentIdx,
+                                      MediaDecoderReader* aNextReader)
+{
+  NS_ASSERTION(NS_IsMainThread() || mDecoder->OnDecodeThread(),
+               "Should be on main thread or decode thread.");
+  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
+
+  // Only allow one switch at a time; ignore if one is already requested.
+  if (mSwitchingCluster != -1) {
+    return;
+  }
+  NS_ENSURE_TRUE((uint32_t)aSubsegmentIdx < mClusterByteRanges.Length(), );
+  mSwitchingCluster = aSubsegmentIdx;
+  NS_ENSURE_TRUE(aNextReader != this, );
+  mNextReader = static_cast<WebMReader*>(aNextReader);
+}
+
+void
+WebMReader::RequestSeekToSubsegment(uint32_t aIdx)
+{
+  NS_ASSERTION(NS_IsMainThread() || mDecoder->OnDecodeThread(),
+               "Should be on main thread or decode thread.");
+  NS_ASSERTION(mDecoder, "decoder should not be null!");
+  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
+
+  // Don't seek if we're about to switch to another reader.
+  if (mSwitchingCluster != -1) {
+    return;
+  }
+  // Only allow seeking if a request was not already made.
+  if (mSeekToCluster != -1) {
+    return;
+  }
+  NS_ENSURE_TRUE(aIdx < mClusterByteRanges.Length(), );
+  mSeekToCluster = aIdx;
+
+  // XXX Hack to get the resource to seek to the correct offset if the decode
+  // thread is in shutdown, e.g. if the video is not autoplay.
+  if (mDecoder->IsShutdown()) {
+    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+    mDecoder->GetResource()->Seek(PR_SEEK_SET,
+                                  mClusterByteRanges[mSeekToCluster].mStart);
+  }
+}
+
+void
+WebMReader::PrepareToDecode()
+{
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  if (mSeekToCluster != -1) {
+    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+    SeekToCluster(mSeekToCluster);
+  }
+}
+
+void
+WebMReader::SeekToCluster(uint32_t aIdx)
+{
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(0 <= mSeekToCluster, "mSeekToCluster should be set.");
+  NS_ENSURE_TRUE(aIdx < mClusterByteRanges.Length(), );
+  LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: seeking to "
+                     "subsegment [%lld] at offset [%lld]",
+                     this, mDecoder, aIdx, mClusterByteRanges[aIdx].mStart));
+  int r = nestegg_offset_seek(mContext, mClusterByteRanges[aIdx].mStart);
+  NS_ENSURE_TRUE(r == 0, );
+  mSeekToCluster = -1;
+}
+
+bool
+WebMReader::HasReachedSubsegment(uint32_t aSubsegmentIndex)
+{
+  NS_ASSERTION(mDecoder, "Decoder is null.");
+  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  NS_ENSURE_TRUE(aSubsegmentIndex < mClusterByteRanges.Length(), false);
+
+  NS_ASSERTION(mDecoder->GetResource(), "Decoder has no media resource.");
+  if (mReachedSwitchAccessPoint) {
+    LOG(PR_LOG_DEBUG,
+        ("Reader [%p] for Decoder [%p]: reached switching offset [%lld] = "
+         "mClusterByteRanges[%d].mStart[%lld]",
+         this, mDecoder, mCurrentOffset, aSubsegmentIndex,
+         mClusterByteRanges[aSubsegmentIndex].mStart));
+    mReachedSwitchAccessPoint = false;
+    return true;
+  }
+  return false;
+}
+#endif /* MOZ_DASH */
+
 } // namespace mozilla
 
 
--- a/content/media/webm/WebMReader.h
+++ b/content/media/webm/WebMReader.h
@@ -17,16 +17,20 @@
 #include "vpx/vpx_codec.h"
 
 #ifdef MOZ_TREMOR
 #include "tremor/ivorbiscodec.h"
 #else
 #include "vorbis/codec.h"
 #endif
 
+#ifdef MOZ_DASH
+#include "DASHRepReader.h"
+#endif
+
 namespace mozilla {
 
 class WebMBufferedState;
 
 // Holds a nestegg_packet, and its file offset. This is needed so we
 // know the offset in the file we've played up to, in order to calculate
 // whether it's likely we can play through to the end without needing
 // to stop to buffer, given the current download rate.
@@ -92,17 +96,21 @@ class WebMPacketQueue : private nsDeque 
   
   void Reset() {
     while (GetSize() > 0) {
       delete PopFront();
     }
   }
 };
 
+#ifdef MOZ_DASH
+class WebMReader : public DASHRepReader
+#else
 class WebMReader : public MediaDecoderReader
+#endif
 {
 public:
   WebMReader(AbstractMediaDecoder* aDecoder);
   ~WebMReader();
 
   virtual nsresult Init(MediaDecoderReader* aCloneDonor);
   virtual nsresult ResetDecode();
   virtual bool DecodeAudioData();
@@ -131,42 +139,108 @@ public:
   }
 
   virtual nsresult ReadMetadata(VideoInfo* aInfo,
                                 MetadataTags** aTags);
   virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
   virtual nsresult GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime);
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
+#ifdef MOZ_DASH
+  virtual void SetMainReader(DASHReader *aMainReader) MOZ_OVERRIDE {
+    NS_ASSERTION(aMainReader, "aMainReader is null.");
+    mMainReader = aMainReader;
+  }
+
+  // Called by |DASHReader| on the decode thread so that this reader will
+  // start reading at the appropriate subsegment/cluster. If this is not the
+  // current reader and a switch was previously requested, then it will seek to
+  // starting offset of the subsegment at which it is supposed to switch.
+  // Called on the decode thread, enters the decode monitor.
+  void PrepareToDecode() MOZ_OVERRIDE;
+
+  // Returns a reference to the audio/video queue of the main reader.
+  // Allows for a single audio/video queue to be shared among multiple
+  // |WebMReader|s.
+  MediaQueue<AudioData>& AudioQueue() MOZ_OVERRIDE {
+    if (mMainReader) {
+      return mMainReader->AudioQueue();
+    } else {
+      return MediaDecoderReader::AudioQueue();
+    }
+  }
+
+  MediaQueue<VideoData>& VideoQueue() MOZ_OVERRIDE {
+    if (mMainReader) {
+      return mMainReader->VideoQueue();
+    } else {
+      return MediaDecoderReader::VideoQueue();
+    }
+  }
+
   // Sets byte range for initialization (EBML); used by DASH.
-  void SetInitByteRange(MediaByteRange &aByteRange) {
+  void SetInitByteRange(MediaByteRange &aByteRange) MOZ_OVERRIDE {
     mInitByteRange = aByteRange;
   }
 
   // Sets byte range for cue points, i.e. cluster offsets; used by DASH.
-  void SetIndexByteRange(MediaByteRange &aByteRange) {
+  void SetIndexByteRange(MediaByteRange &aByteRange) MOZ_OVERRIDE {
     mCuesByteRange = aByteRange;
   }
 
   // Returns list of ranges for cluster start and end offsets.
-  nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges);
+  nsresult GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges)
+                                                                  MOZ_OVERRIDE;
+
+  // Called by |DASHReader|::|PossiblySwitchVideoReaders| to check if this
+  // reader has reached a switch access point and it's ok to switch readers.
+  // Called on the decode thread.
+  bool HasReachedSubsegment(uint32_t aSubsegmentIndex) MOZ_OVERRIDE;
 
-private:
+  // Requests that this reader seek to the specified subsegment. Seek will
+  // happen when |PrepareDecodeVideoFrame| is called on the decode
+  // thread.
+  // Called on the main thread or decoder thread. Decode monitor must be held.
+  void RequestSeekToSubsegment(uint32_t aIdx) MOZ_OVERRIDE;
+
+  // Requests that this reader switch to |aNextReader| at the start of the
+  // specified subsegment. This is the reader to switch FROM.
+  // Called on the main thread or decoder thread. Decode monitor must be held.
+  void RequestSwitchAtSubsegment(int32_t aSubsegmentIdx,
+                                 MediaDecoderReader* aNextReader) MOZ_OVERRIDE;
+
+  // Seeks to the beginning of the specified cluster. Called on the decode
+  // thread.
+  void SeekToCluster(uint32_t aIdx);
+#endif
+
+protected:
   // Value passed to NextPacket to determine if we are reading a video or an
   // audio packet.
   enum TrackType {
     VIDEO = 0,
     AUDIO = 1
   };
 
   // Read a packet from the nestegg file. Returns NULL if all packets for
   // the particular track have been read. Pass VIDEO or AUDIO to indicate the
   // type of the packet we want to read.
+#ifdef MOZ_DASH
+  nsReturnRef<NesteggPacketHolder> NextPacketInternal(TrackType aTrackType);
+
+  // Read a packet from the nestegg file. Returns NULL if all packets for
+  // the particular track have been read. Pass VIDEO or AUDIO to indicate the
+  // type of the packet we want to read. If the reader reaches a switch access
+  // point, this function will get a packet from |mNextReader|.
+#endif
   nsReturnRef<NesteggPacketHolder> NextPacket(TrackType aTrackType);
 
+  // Pushes a packet to the front of the video packet queue.
+  virtual void PushVideoPacket(NesteggPacketHolder* aItem);
+
   // Returns an initialized ogg packet with data obtained from the WebM container.
   ogg_packet InitOggPacket(unsigned char* aData,
                            size_t aLength,
                            bool aBOS,
                            bool aEOS,
                            int64_t aGranulepos);
 
   // Decode a nestegg packet of audio data. Push the audio data on the
@@ -222,21 +296,56 @@ private:
 
   // Picture region, as relative to the initial frame size.
   nsIntRect mPicture;
 
   // Booleans to indicate if we have audio and/or video data
   bool mHasVideo;
   bool mHasAudio;
 
+#ifdef MOZ_DASH
   // Byte range for initialisation data; e.g. specified in DASH manifest.
   MediaByteRange mInitByteRange;
 
   // Byte range for cues; e.g. specified in DASH manifest.
   MediaByteRange mCuesByteRange;
 
   // Byte ranges for clusters; set internally, derived from cues.
   nsTArray<MediaByteRange> mClusterByteRanges;
+
+  // Pointer to the main |DASHReader|. Set in the constructor.
+  DASHReader* mMainReader;
+
+  // Index of the cluster to switch to. Monitor must be entered for write
+  // access on all threads, read access off the decode thread.
+  int32_t mSwitchingCluster;
+
+  // Pointer to the next reader. Used in |NextPacket| and |PushVideoPacket| at
+  // switch access points. Monitor must be entered for write access on all
+  // threads, read access off the decode thread.
+  nsRefPtr<WebMReader> mNextReader;
+
+  // Index of the cluster to seek to for a DASH stream request. Monitor must be
+  // entered for write access on all threads, read access off the decode
+  // thread.
+  int32_t mSeekToCluster;
+
+  // Current end offset of the last packet read in |NextPacket|. Used to check
+  // if the reader reached the switch access point. Accessed on the decode
+  // thread only.
+  int64_t mCurrentOffset;
+
+  // Set in |NextPacket| if we read a packet from the next reader. If true in
+  // |PushVideoPacket|, we will push the packet onto the next reader's
+  // video packet queue (not video data queue!). Accessed on decode thread
+  // only.
+  bool mPushVideoPacketToNextReader;
+
+  // Indicates if the reader has reached a switch access point.  Set in
+  // |NextPacket| and read in |HasReachedSubsegment|. Accessed on
+  // decode thread only.
+  bool mReachedSwitchAccessPoint;
+#endif
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/xbl/src/nsXBLBinding.cpp
+++ b/content/xbl/src/nsXBLBinding.cpp
@@ -67,17 +67,17 @@
 //
 // The JS class for XBLBinding
 //
 static void
 XBLFinalize(JSFreeOp *fop, JSObject *obj)
 {
   nsXBLDocumentInfo* docInfo =
     static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
-  NS_RELEASE(docInfo);
+  xpc::DeferredRelease(static_cast<nsIScriptGlobalObjectOwner*>(docInfo));
   
   nsXBLJSClass* c = static_cast<nsXBLJSClass*>(::JS_GetClass(obj));
   c->Drop();
 }
 
 // XBL fields are represented on elements inheriting that field a bit trickily.
 // Initially the element itself won't have a property for the field.  When an
 // attempt is made to access the field, the element's resolve hook won't find
--- a/content/xul/document/src/nsXULDocument.cpp
+++ b/content/xul/document/src/nsXULDocument.cpp
@@ -385,26 +385,16 @@ nsXULDocument::Reset(nsIChannel* aChanne
 
 void
 nsXULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
                           nsIPrincipal* aPrincipal)
 {
     NS_NOTREACHED("ResetToURI");
 }
 
-// Override the nsDocument.cpp method to keep from returning the
-// "cached XUL" type which is completely internal and may confuse
-// people
-NS_IMETHODIMP
-nsXULDocument::GetContentType(nsAString& aContentType)
-{
-    aContentType.AssignLiteral("application/vnd.mozilla.xul+xml");
-    return NS_OK;
-}
-
 void
 nsXULDocument::SetContentType(const nsAString& aContentType)
 {
     NS_ASSERTION(aContentType.EqualsLiteral("application/vnd.mozilla.xul+xml"),
                  "xul-documents always has content-type application/vnd.mozilla.xul+xml");
     // Don't do anything, xul always has the mimetype
     // application/vnd.mozilla.xul+xml
 }
@@ -1877,21 +1867,21 @@ nsXULDocument::RemoveElementFromRefMap(E
     }
 }
 
 //----------------------------------------------------------------------
 //
 // nsIDOMNode interface
 //
 
-NS_IMETHODIMP
-nsXULDocument::CloneNode(bool aDeep, uint8_t aOptionalArgc, nsIDOMNode** aReturn)
+nsresult
+nsXULDocument::Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const
 {
-    // We don't allow cloning of a document
-    *aReturn = nullptr;
+    // We don't allow cloning of a XUL document
+    *aResult = nullptr;
     return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 }
 
 
 //----------------------------------------------------------------------
 //
 // Implementation methods
 //
--- a/content/xul/document/src/nsXULDocument.h
+++ b/content/xul/document/src/nsXULDocument.h
@@ -127,28 +127,26 @@ public:
     NS_IMETHOD RemoveSubtreeFromDocument(nsIContent* aContent);
     NS_IMETHOD SetTemplateBuilderFor(nsIContent* aContent,
                                      nsIXULTemplateBuilder* aBuilder);
     NS_IMETHOD GetTemplateBuilderFor(nsIContent* aContent,
                                      nsIXULTemplateBuilder** aResult);
     NS_IMETHOD OnPrototypeLoadDone(bool aResumeWalk);
     bool OnDocumentParserError();
 
-    // nsIDOMNode interface overrides
-    NS_IMETHOD CloneNode(bool deep, uint8_t aOptionalArgc, nsIDOMNode **_retval)
-        MOZ_OVERRIDE;
+    // nsINode interface overrides
+    virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
 
-    // nsIDOMDocument
-    NS_IMETHOD GetContentType(nsAString& aContentType);
+    // nsIDOMNode interface
+    NS_FORWARD_NSIDOMNODE_TO_NSINODE
+
+    // nsIDOMDocument interface
+    NS_FORWARD_NSIDOMDOCUMENT(nsXMLDocument::)
 
     // nsDocument interface overrides
-    NS_IMETHOD GetElementById(const nsAString& aId, nsIDOMElement** aReturn)
-    {
-        return nsDocument::GetElementById(aId, aReturn);
-    }
     virtual mozilla::dom::Element* GetElementById(const nsAString & elementId);
 
     // nsIDOMXULDocument interface
     NS_DECL_NSIDOMXULDOCUMENT
 
     // nsICSSLoaderObserver
     NS_IMETHOD StyleSheetLoaded(nsCSSStyleSheet* aSheet,
                                 bool aWasAlternate,
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5206,17 +5206,17 @@ nsDocShell::SetIsActive(bool aIsActive)
     pshell->SetIsActive(aIsActive);
 
   // Tell the window about it
   nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mScriptGlobal);
   if (win) {
       win->SetIsBackground(!aIsActive);
       nsCOMPtr<nsIDocument> doc = do_QueryInterface(win->GetExtantDocument());
       if (doc) {
-          doc->PostVisibilityUpdateEvent();
+          doc->UpdateVisibilityState(false);
       }
   }
 
   // Recursively tell all of our children, but don't tell <iframe mozbrowser>
   // children; they handle their state separately.
   int32_t n = mChildList.Count();
   for (int32_t i = 0; i < n; ++i) {
       nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(ChildAt(i));
--- a/docshell/test/chrome/Makefile.in
+++ b/docshell/test/chrome/Makefile.in
@@ -91,16 +91,17 @@ MOCHITEST_CHROME_FILES =	\
 		test_bug311007.xul \
 		bug311007_window.xul \
 		test_principalInherit.xul \
 		test_mozFrameType.xul \
 		mozFrameType_window.xul \
 		test_bug789773.xul \
 		test_bug754029.xul \
 		bug754029_window.xul \
+		test_bug818371.xul \
     docshell_helpers.js \
     generic.html \
     $(NULL)
 
 ifneq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 MOCHITEST_CHROME_FILES += \
 		test_bug454235.xul \
 		$(NULL)
--- a/docshell/test/chrome/test_bug789773.xul
+++ b/docshell/test/chrome/test_bug789773.xul
@@ -34,33 +34,33 @@ https://bugzilla.mozilla.org/show_bug.cg
   var calledListenerForBrowserXUL = false;
   var testProgressListener = {
     START_DOC: Ci.nsIWebProgressListener.STATE_START | Ci.nsIWebProgressListener.STATE_IS_DOCUMENT,
     onStateChange: function(wp, req, stateFlags, status) {
       if (/browser.xul/.test(req.name)) {
         wp.DOMWindow; // Force the lazy creation of a DOM window.
         calledListenerForBrowserXUL = true;
       }
-      if (/aboutHome.xhtml/.test(req.name) && (stateFlags & Ci.nsIWebProgressListener.STATE_STOP))
+      if (/mozilla.xhtml/.test(req.name) && (stateFlags & Ci.nsIWebProgressListener.STATE_STOP))
         finishTest();
     },
     QueryInterface: function(iid) {
       if (iid.equals(Ci.nsISupportsWeakReference) ||
           iid.equals(Ci.nsIWebProgressListener))
         return this;
       throw Cr.NS_ERROR_NO_INTERFACE;
     }
   }
 
    // Add our progress listener
    var webProgress = Cc['@mozilla.org/docloaderservice;1'].getService(Ci.nsIWebProgress);
    webProgress.addProgressListener(testProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
 
    // Open the window.
-   var popup = window.open("about:home", "_blank", "width=640,height=400");
+   var popup = window.open("about:mozilla", "_blank", "width=640,height=400");
 
    // Wait for the window to load.
    function finishTest() {
      webProgress.removeProgressListener(testProgressListener);
      ok(true, "Loaded the popup window without spinning forever in the event loop!");
      ok(calledListenerForBrowserXUL, "Should have called the progress listener for browser.xul");
      popup.close();
      SimpleTest.finish();
new file mode 100644
--- /dev/null
+++ b/docshell/test/chrome/test_bug818371.xul
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=818371
+-->
+<window title="Mozilla Bug 818371"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=818371"
+     target="_blank">Mozilla Bug 818371</a>
+  </body>
+
+  <browser id="b" src="data:text/html,&lt;iframe&gt;&lt;/iframe&gt;"></browser>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+  /** Test for Bug 818371 **/
+  SimpleTest.waitForExplicitFinish();
+  addLoadEvent(function() {
+    function listener(e) {
+      ok(e.target.hidden, "Document should now be hidden");
+      ok(e.target.defaultView.frames[0].document.hidden,
+         "Subdocument should now be hidden");
+      e.target.removeEventListener("visibilitychange", listener);
+      SimpleTest.finish();
+    }
+
+    var doc = frames[0].document;
+    ok(!doc.hidden, "Document should be visible");
+    ok(!frames[0].frames[0].document.hidden,
+       "Subdocument should now be hidden");
+    doc.addEventListener("visibilitychange", listener);
+    $("b").docShell.isActive = false;
+  });
+
+  ]]>
+  </script>
+</window>
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -302,16 +302,26 @@ this.DOMApplicationRegistry = {
 
     this.loadCurrentRegistry((function() {
 #ifdef MOZ_WIDGET_GONK
       // if first run, merge the system apps.
       if (runUpdate)
         this.installSystemApps(onAppsLoaded);
       else
         onAppsLoaded();
+
+      // XXX: To be removed as soon as the app:// protocol is remoted.
+      // See Bug 819061
+      let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+      dir.initWithPath("/data");
+      dir.permissions = parseInt("755", 8);
+      dir.append("local");
+      dir.permissions = parseInt("755", 8);
+      dir.append("webapps");
+      dir.permissions = parseInt("755", 8);
 #else
       onAppsLoaded();
 #endif
     }).bind(this));
   },
 
 #ifdef MOZ_SYS_MSG
   // |aEntryPoint| is either the entry_point name or the null in which case we
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -0,0 +1,116 @@
+/* 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 "AudioChannelAgent.h"
+#include "AudioChannelCommon.h"
+#include "AudioChannelService.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS1(AudioChannelAgent, nsIAudioChannelAgent)
+
+static nsRefPtr<AudioChannelService> gAudioChannelService;
+
+AudioChannelAgent::AudioChannelAgent()
+  : mCallback(nullptr)
+  , mAudioChannelType(AUDIO_AGENT_CHANNEL_ERROR)
+  , mIsRegToService(false)
+  , mVisible(true)
+{
+  gAudioChannelService = AudioChannelService::GetAudioChannelService();
+}
+
+AudioChannelAgent::~AudioChannelAgent()
+{
+  gAudioChannelService = nullptr;
+}
+
+/* readonly attribute long audioChannelType; */
+NS_IMETHODIMP AudioChannelAgent::GetAudioChannelType(int32_t *aAudioChannelType)
+{
+  *aAudioChannelType = mAudioChannelType;
+  return NS_OK;
+}
+
+/* boolean init (in long channelType); */
+NS_IMETHODIMP AudioChannelAgent::Init(int32_t channelType, nsIAudioChannelAgentCallback *callback)
+{
+  // We syncd the enum of channel type between nsIAudioChannelAgent.idl and
+  // AudioChannelCommon.h the same.
+  MOZ_STATIC_ASSERT(static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_NORMAL) ==
+                    AUDIO_CHANNEL_NORMAL &&
+                    static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_CONTENT) ==
+                    AUDIO_CHANNEL_CONTENT &&
+                    static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_NOTIFICATION) ==
+                    AUDIO_CHANNEL_NOTIFICATION &&
+                    static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_ALARM) ==
+                    AUDIO_CHANNEL_ALARM &&
+                    static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_TELEPHONY) ==
+                    AUDIO_CHANNEL_TELEPHONY &&
+                    static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_RINGER) ==
+                    AUDIO_CHANNEL_RINGER &&
+                    static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION) ==
+                    AUDIO_CHANNEL_PUBLICNOTIFICATION,
+                    "Enum of channel on nsIAudioChannelAgent.idl should be the same with AudioChannelCommon.h");
+
+  if (mAudioChannelType != AUDIO_AGENT_CHANNEL_ERROR ||
+      channelType > AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION ||
+      channelType < AUDIO_AGENT_CHANNEL_NORMAL) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mAudioChannelType = channelType;
+  mCallback = callback;
+  return NS_OK;
+}
+
+/* boolean startPlaying (); */
+NS_IMETHODIMP AudioChannelAgent::StartPlaying(bool *_retval)
+{
+  if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
+      gAudioChannelService == nullptr) {
+    return NS_ERROR_FAILURE;
+  }
+
+  gAudioChannelService->RegisterAudioChannelAgent(this,
+    static_cast<AudioChannelType>(mAudioChannelType));
+  *_retval = !gAudioChannelService->GetMuted(static_cast<AudioChannelType>(mAudioChannelType), !mVisible);
+  mIsRegToService = true;
+  return NS_OK;
+}
+
+/* void stopPlaying (); */
+NS_IMETHODIMP AudioChannelAgent::StopPlaying(void)
+{
+  if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
+      mIsRegToService == false) {
+    return NS_ERROR_FAILURE;
+  }
+
+  gAudioChannelService->UnregisterAudioChannelAgent(this);
+  mIsRegToService = false;
+  return NS_OK;
+}
+
+/* void setVisibilityState (in boolean visible); */
+NS_IMETHODIMP AudioChannelAgent::SetVisibilityState(bool visible)
+{
+  bool oldVisibility = mVisible;
+
+  mVisible = visible;
+  if (mIsRegToService && oldVisibility != mVisible && mCallback != nullptr) {
+    mCallback->CanPlayChanged(!gAudioChannelService->GetMuted(static_cast<AudioChannelType>(mAudioChannelType),
+       !mVisible));
+  }
+  return NS_OK;
+}
+
+void AudioChannelAgent::NotifyAudioChannelStateChanged()
+{
+  if (mCallback != nullptr) {
+    mCallback->CanPlayChanged(!gAudioChannelService->GetMuted(static_cast<AudioChannelType>(mAudioChannelType),
+      !mVisible));
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/AudioChannelAgent.h
@@ -0,0 +1,42 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* 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 mozilla_dom_audio_channel_agent_h__
+#define mozilla_dom_audio_channel_agent_h__
+
+#include "nsIAudioChannelAgent.h"
+#include "nsCOMPtr.h"
+
+#define NS_AUDIOCHANNELAGENT_CONTRACTID "@mozilla.org/audiochannelagent;1"
+// f27688e2-3dd7-11e2-904e-10bf48d64bd4
+#define NS_AUDIOCHANNELAGENT_CID {0xf27688e2, 0x3dd7, 0x11e2, \
+      {0x90, 0x4e, 0x10, 0xbf, 0x48, 0xd6, 0x4b, 0xd4}}
+
+namespace mozilla {
+namespace dom {
+
+/* Header file */
+class AudioChannelAgent : public nsIAudioChannelAgent
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIAUDIOCHANNELAGENT
+
+  AudioChannelAgent();
+  virtual void NotifyAudioChannelStateChanged();
+
+private:
+  virtual ~AudioChannelAgent();
+  nsCOMPtr<nsIAudioChannelAgentCallback> mCallback;
+  int32_t mAudioChannelType;
+  bool mIsRegToService;
+  bool mVisible;
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif
+
--- a/dom/audiochannel/AudioChannelCommon.h
+++ b/dom/audiochannel/AudioChannelCommon.h
@@ -13,16 +13,17 @@ namespace dom {
 // The audio channel. Read the nsIHTMLMediaElement.idl for a description
 // about this attribute.
 enum AudioChannelType {
   AUDIO_CHANNEL_NORMAL = 0,
   AUDIO_CHANNEL_CONTENT,
   AUDIO_CHANNEL_NOTIFICATION,
   AUDIO_CHANNEL_ALARM,
   AUDIO_CHANNEL_TELEPHONY,
+  AUDIO_CHANNEL_RINGER,
   AUDIO_CHANNEL_PUBLICNOTIFICATION,
   AUDIO_CHANNEL_LAST
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -15,16 +15,19 @@
 #include "mozilla/Util.h"
 
 #include "mozilla/dom/ContentParent.h"
 
 #include "base/basictypes.h"
 
 #include "nsThreadUtils.h"
 
+#ifdef MOZ_WIDGET_GONK
+#include "nsIAudioManager.h"
+#endif
 using namespace mozilla;
 using namespace mozilla::dom;
 
 StaticRefPtr<AudioChannelService> gAudioChannelService;
 
 // static
 AudioChannelService*
 AudioChannelService::GetAudioChannelService()
@@ -70,62 +73,62 @@ AudioChannelService::AudioChannelService
 
   for (int i = AUDIO_CHANNEL_NORMAL;
        i <= AUDIO_CHANNEL_PUBLICNOTIFICATION;
        ++i) {
     mChannelCounters[i] = 0;
   }
 
   // Creation of the hash table.
-  mMediaElements.Init();
+  mAgents.Init();
 }
 
 AudioChannelService::~AudioChannelService()
 {
   delete [] mChannelCounters;
 }
 
 void
-AudioChannelService::RegisterMediaElement(nsHTMLMediaElement* aMediaElement,
+AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                           AudioChannelType aType)
 {
-  mMediaElements.Put(aMediaElement, aType);
+  mAgents.Put(aAgent, aType);
   RegisterType(aType);
 }
 
 void
 AudioChannelService::RegisterType(AudioChannelType aType)
 {
   mChannelCounters[aType]++;
 
   // In order to avoid race conditions, it's safer to notify any existing
-  // media element any time a new one is registered.
+  // agent any time a new one is registered.
   Notify();
 }
 
 void
-AudioChannelService::UnregisterMediaElement(nsHTMLMediaElement* aMediaElement)
+AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
 {
   AudioChannelType type;
-  if (!mMediaElements.Get(aMediaElement, &type)) {
+  if (!mAgents.Get(aAgent, &type)) {
     return;
   }
 
-  mMediaElements.Remove(aMediaElement);
+  mAgents.Remove(aAgent);
   UnregisterType(type);
 }
 
 void
 AudioChannelService::UnregisterType(AudioChannelType aType)
 {
   mChannelCounters[aType]--;
   MOZ_ASSERT(mChannelCounters[aType] >= 0);
 
   // In order to avoid race conditions, it's safer to notify any existing
-  // media element any time a new one is registered.
+  // agent any time a new one is registered.
   Notify();
 }
 
 bool
 AudioChannelService::GetMuted(AudioChannelType aType, bool aElementHidden)
 {
   // We are not visible, maybe we have to mute:
   if (aElementHidden) {
@@ -137,16 +140,17 @@ AudioChannelService::GetMuted(AudioChann
         // TODO: this should work per apps
         if (mChannelCounters[AUDIO_CHANNEL_CONTENT] > 1)
           return true;
         break;
 
       case AUDIO_CHANNEL_NOTIFICATION:
       case AUDIO_CHANNEL_ALARM:
       case AUDIO_CHANNEL_TELEPHONY:
+      case AUDIO_CHANNEL_RINGER:
       case AUDIO_CHANNEL_PUBLICNOTIFICATION:
         // Nothing to do
         break;
 
       case AUDIO_CHANNEL_LAST:
         MOZ_NOT_REACHED();
         return false;
     }
@@ -156,22 +160,26 @@ AudioChannelService::GetMuted(AudioChann
 
   // Priorities:
   switch (aType) {
     case AUDIO_CHANNEL_NORMAL:
     case AUDIO_CHANNEL_CONTENT:
       muted = !!mChannelCounters[AUDIO_CHANNEL_NOTIFICATION] ||
               !!mChannelCounters[AUDIO_CHANNEL_ALARM] ||
               !!mChannelCounters[AUDIO_CHANNEL_TELEPHONY] ||
+              !!mChannelCounters[AUDIO_CHANNEL_RINGER] ||
               !!mChannelCounters[AUDIO_CHANNEL_PUBLICNOTIFICATION];
+      break;
 
     case AUDIO_CHANNEL_NOTIFICATION:
     case AUDIO_CHANNEL_ALARM:
     case AUDIO_CHANNEL_TELEPHONY:
+    case AUDIO_CHANNEL_RINGER:
       muted = ChannelsActiveWithHigherPriorityThan(aType);
+      break;
 
     case AUDIO_CHANNEL_PUBLICNOTIFICATION:
       break;
 
     case AUDIO_CHANNEL_LAST:
       MOZ_NOT_REACHED();
       return false;
   }
@@ -200,32 +208,32 @@ AudioChannelService::GetMuted(AudioChann
     }
   }
 
   return muted;
 }
 
 
 static PLDHashOperator
-NotifyEnumerator(nsHTMLMediaElement* aElement,
+NotifyEnumerator(AudioChannelAgent* aAgent,
                  AudioChannelType aType, void* aData)
 {
-  if (aElement) {
-    aElement->NotifyAudioChannelStateChanged();
+  if (aAgent) {
+    aAgent->NotifyAudioChannelStateChanged();
   }
   return PL_DHASH_NEXT;
 }
 
 void
 AudioChannelService::Notify()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // Notify any media element for the main process.
-  mMediaElements.EnumerateRead(NotifyEnumerator, nullptr);
+  // Notify any agent for the main process.
+  mAgents.EnumerateRead(NotifyEnumerator, nullptr);
 
   // Notify for the child processes.
   nsTArray<ContentParent*> children;
   ContentParent::GetAll(children);
   for (uint32_t i = 0; i < children.Length(); i++) {
     unused << children[i]->SendAudioChannelNotify();
   }
 }
@@ -254,22 +262,38 @@ AudioChannelService::ChannelName(AudioCh
     int32_t type;
     const char* value;
   } ChannelNameTable[] = {
     { AUDIO_CHANNEL_NORMAL,             "normal" },
     { AUDIO_CHANNEL_CONTENT,            "normal" },
     { AUDIO_CHANNEL_NOTIFICATION,       "notification" },
     { AUDIO_CHANNEL_ALARM,              "alarm" },
     { AUDIO_CHANNEL_TELEPHONY,          "telephony" },
+    { AUDIO_CHANNEL_RINGER,             "ringer" },
     { AUDIO_CHANNEL_PUBLICNOTIFICATION, "publicnotification" },
     { -1,                               "unknown" }
   };
 
   for (int i = AUDIO_CHANNEL_NORMAL; ; ++i) {
     if (ChannelNameTable[i].type == aType ||
         ChannelNameTable[i].type == -1) {
       return ChannelNameTable[i].value;
     }
   }
 
   NS_NOTREACHED("Execution should not reach here!");
   return nullptr;
 }
+
+#ifdef MOZ_WIDGET_GONK
+void
+AudioChannelService::SetPhoneInCall(bool aActive)
+{
+  //while ring tone and in-call mode, mute media element
+  if (aActive) {
+    mChannelCounters[AUDIO_CHANNEL_TELEPHONY] = 1;
+  } else {
+    mChannelCounters[AUDIO_CHANNEL_TELEPHONY] = 0;
+  }
+  Notify();
+}
+#endif
+
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -6,17 +6,18 @@
 
 #ifndef mozilla_dom_audiochannelservice_h__
 #define mozilla_dom_audiochannelservice_h__
 
 #include "nsAutoPtr.h"
 #include "nsISupports.h"
 
 #include "AudioChannelCommon.h"
-#include "nsHTMLMediaElement.h"
+#include "AudioChannelAgent.h"
+#include "nsDataHashtable.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioChannelService : public nsISupports
 {
 public:
   NS_DECL_ISUPPORTS
@@ -29,48 +30,55 @@ public:
   GetAudioChannelService();
 
   /**
    * Shutdown the singleton.
    */
   static void Shutdown();
 
   /**
-   * Any MediaElement that starts playing should register itself to
+   * Any audio channel agent that starts playing should register itself to
    * this service, sharing the AudioChannelType.
    */
-  virtual void RegisterMediaElement(nsHTMLMediaElement* aMediaElement,
-                                    AudioChannelType aType);
+  virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
+                                         AudioChannelType aType);
 
   /**
-   * Any MediaElement that stops playing should unregister itself to
+   * Any  audio channel agent that stops playing should unregister itself to
    * this service.
    */
-  virtual void UnregisterMediaElement(nsHTMLMediaElement* aMediaElement);
+  virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
 
   /**
    * Return true if this type should be muted.
    */
   virtual bool GetMuted(AudioChannelType aType, bool aElementHidden);
 
+  /**
+   * Sync the phone status with telephony
+   */
+#ifdef MOZ_WIDGET_GONK
+  void SetPhoneInCall(bool aActive);
+#endif
+
 protected:
   void Notify();
 
   /* Register/Unregister IPC types: */
   void RegisterType(AudioChannelType aType);
   void UnregisterType(AudioChannelType aType);
 
   AudioChannelService();
   virtual ~AudioChannelService();
 
   bool ChannelsActiveWithHigherPriorityThan(AudioChannelType aType);
 
   const char* ChannelName(AudioChannelType aType);
 
-  nsDataHashtable< nsPtrHashKey<nsHTMLMediaElement>, AudioChannelType > mMediaElements;
+  nsDataHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelType > mAgents;
 
   int32_t* mChannelCounters;
 
   AudioChannelType mCurrentHigherChannel;
 
   // This is needed for IPC comunication between
   // AudioChannelServiceChild and this class.
   friend class ContentParent;
--- a/dom/audiochannel/AudioChannelServiceChild.cpp
+++ b/dom/audiochannel/AudioChannelServiceChild.cpp
@@ -69,35 +69,35 @@ AudioChannelServiceChild::GetMuted(Audio
   if (cc) {
     cc->SendAudioChannelGetMuted(aType, aMozHidden, &muted);
   }
 
   return muted;
 }
 
 void
-AudioChannelServiceChild::RegisterMediaElement(nsHTMLMediaElement* aMediaElement,
+AudioChannelServiceChild::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                                AudioChannelType aType)
 {
-  AudioChannelService::RegisterMediaElement(aMediaElement, aType);
+  AudioChannelService::RegisterAudioChannelAgent(aAgent, aType);
 
   ContentChild *cc = ContentChild::GetSingleton();
   if (cc) {
     cc->SendAudioChannelRegisterType(aType);
   }
 }
 
 void
-AudioChannelServiceChild::UnregisterMediaElement(nsHTMLMediaElement* aMediaElement)
+AudioChannelServiceChild::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
 {
   AudioChannelType type;
-  if (!mMediaElements.Get(aMediaElement, &type)) {
+  if (!mAgents.Get(aAgent, &type)) {
     return;
   }
 
-  AudioChannelService::UnregisterMediaElement(aMediaElement);
+  AudioChannelService::UnregisterAudioChannelAgent(aAgent);
 
   ContentChild *cc = ContentChild::GetSingleton();
   if (cc) {
     cc->SendAudioChannelUnregisterType(type);
   }
 }
 
--- a/dom/audiochannel/AudioChannelServiceChild.h
+++ b/dom/audiochannel/AudioChannelServiceChild.h
@@ -7,17 +7,16 @@
 #ifndef mozilla_dom_audiochannelservicechild_h__
 #define mozilla_dom_audiochannelservicechild_h__
 
 #include "nsAutoPtr.h"
 #include "nsISupports.h"
 
 #include "AudioChannelService.h"
 #include "AudioChannelCommon.h"
-#include "nsHTMLMediaElement.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioChannelServiceChild : public AudioChannelService
 {
 public:
 
@@ -25,19 +24,19 @@ public:
    * Returns the AudioChannelServce singleton. Only to be called from main thread.
    * @return NS_OK on proper assignment, NS_ERROR_FAILURE otherwise.
    */
   static AudioChannelService*
   GetAudioChannelService();
 
   static void Shutdown();
 
-  virtual void RegisterMediaElement(nsHTMLMediaElement* aMediaElement,
-                                    AudioChannelType aType);
-  virtual void UnregisterMediaElement(nsHTMLMediaElement* aMediaElement);
+  virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
+                                         AudioChannelType aType);
+  virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
 
   /**
    * Return true if this type + this mozHidden should be muted.
    */
   virtual bool GetMuted(AudioChannelType aType, bool aMozHidden);
 
 protected:
   AudioChannelServiceChild();
--- a/dom/audiochannel/Makefile.in
+++ b/dom/audiochannel/Makefile.in
@@ -28,19 +28,25 @@ EXPORT_LIBRARY = 1
 FAIL_ON_WARNINGS := 1
 
 EXPORTS_NAMESPACES = \
   mozilla/dom \
   $(NULL)
 
 EXPORTS = AudioChannelService.h \
           AudioChannelServiceChild.h \
-          AudioChannelCommon.h
+          AudioChannelCommon.h \
+          AudioChannelAgent.h
 
 CPPSRCS += \
   AudioChannelService.cpp \
   AudioChannelServiceChild.cpp \
+  AudioChannelAgent.cpp \
+  $(NULL)
+
+XPIDLSRCS = \
+  nsIAudioChannelAgent.idl \
   $(NULL)
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/dom/audiochannel/nsIAudioChannelAgent.idl
@@ -0,0 +1,98 @@
+/* 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 "nsISupports.idl"
+
+[scriptable, uuid(35dddb4c-3d35-11e2-978c-10bf48d64bd4)]
+interface nsIAudioChannelAgentCallback : nsISupports
+{
+  /**
+   * Notified when the playable status of channel is changed.
+   *
+   * @param canPlay
+   *        Callback from agent to notify component of the playable status
+   *        of the channel. If canPlay is false, component SHOULD stop playing
+   *        media associated with this channel as soon as possible.
+   */
+  void canPlayChanged(in boolean canPlay);
+};
+
+/**
+ * This interface provides an agent for gecko components to participate
+ * in the audio channel service. Gecko components are responsible for
+ *   1. Indicating what channel type they are using (via the init() member function).
+ *   2. Before playing, checking the playable status of the channel.
+ *   3. Notifying the agent when they start/stop using this channel.
+ *   4. Notifying the agent of changes to the visibility of the component using
+ *       this channel.
+ *
+ * The agent will invoke a callback to notify Gecko components of
+ *   1. Changes to the playable status of this channel.
+ */
+
+[scriptable, uuid(4d01d4f0-3d16-11e2-a0db-10bf48d64bd4)]
+interface nsIAudioChannelAgent : nsISupports
+{
+  const long AUDIO_AGENT_CHANNEL_NORMAL             = 0;
+  const long AUDIO_AGENT_CHANNEL_CONTENT            = 1;
+  const long AUDIO_AGENT_CHANNEL_NOTIFICATION       = 2;
+  const long AUDIO_AGENT_CHANNEL_ALARM              = 3;
+  const long AUDIO_AGENT_CHANNEL_TELEPHONY          = 4;
+  const long AUDIO_AGENT_CHANNEL_RINGER             = 5;
+  const long AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION = 6;
+
+  const long AUDIO_AGENT_CHANNEL_ERROR              = 1000;
+
+  /**
+   * Before init() is called, this returns AUDIO_AGENT_CHANNEL_ERROR.
+   */
+  readonly attribute long audioChannelType;
+
+  /**
+   * Initialize the agent with a channel type.
+   * Note: This function should only be called once.
+   *
+   * @param channelType
+   *    Audio Channel Type listed as above
+   * @param callback
+   *    1. Once the playable status changes, agent uses this callback function to notify
+   *       Gecko component.
+   *    2. The callback is allowed to be null. Ex: telephony doesn't need to listen change
+   *       of the playable status.
+   */
+  void init(in long channelType, in nsIAudioChannelAgentCallback callback);
+
+  /**
+   * Notify the agent that we want to start playing.
+   * Note: Gecko component SHOULD call this function first then start to
+   *          play audio stream only when return value is true.
+   *
+   *
+   * @return
+   *    true: the agent has registered with audio channel service and the
+   *          component should start playback.
+   *    false: the agent has registered with audio channel service but the
+   *          component should not start playback.
+   */
+  boolean startPlaying();
+
+  /**
+   * Notify the agent we no longer want to play.
+   *
+   * Note : even if startPlaying() returned false, the agent would still be
+   *        registered with the audio channel service and receive callbacks for status changes.
+   *        So stopPlaying must still eventually be called to unregister the agent with the
+   *        channel service.
+   */
+  void stopPlaying();
+
+  /**
+   * Notify the agent of the visibility state of the window using this agent.
+   * @param visible
+   *    True if the window associated with the agent is visible.
+   */
+  void setVisibilityState(in boolean visible);
+
+};
+
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -1842,18 +1842,16 @@ jsid nsDOMClassInfo::sEnumerate_id      
 jsid nsDOMClassInfo::sNavigator_id       = JSID_VOID;
 jsid nsDOMClassInfo::sTop_id             = JSID_VOID;
 jsid nsDOMClassInfo::sDocument_id        = JSID_VOID;
 jsid nsDOMClassInfo::sFrames_id          = JSID_VOID;
 jsid nsDOMClassInfo::sSelf_id            = JSID_VOID;
 jsid nsDOMClassInfo::sOpener_id          = JSID_VOID;
 jsid nsDOMClassInfo::sAll_id             = JSID_VOID;
 jsid nsDOMClassInfo::sTags_id            = JSID_VOID;
-jsid nsDOMClassInfo::sBaseURIObject_id   = JSID_VOID;
-jsid nsDOMClassInfo::sNodePrincipal_id   = JSID_VOID;
 jsid nsDOMClassInfo::sDocumentURIObject_id=JSID_VOID;
 jsid nsDOMClassInfo::sWrappedJSObject_id = JSID_VOID;
 jsid nsDOMClassInfo::sURL_id             = JSID_VOID;
 jsid nsDOMClassInfo::sOnload_id          = JSID_VOID;
 jsid nsDOMClassInfo::sOnerror_id         = JSID_VOID;
 
 static const JSClass *sObjectClass = nullptr;
 
@@ -2114,18 +2112,16 @@ nsDOMClassInfo::DefineStaticJSVals(JSCon
   SET_JSID_TO_STRING(sNavigator_id,       cx, "navigator");
   SET_JSID_TO_STRING(sTop_id,             cx, "top");
   SET_JSID_TO_STRING(sDocument_id,        cx, "document");
   SET_JSID_TO_STRING(sFrames_id,          cx, "frames");
   SET_JSID_TO_STRING(sSelf_id,            cx, "self");
   SET_JSID_TO_STRING(sOpener_id,          cx, "opener");
   SET_JSID_TO_STRING(sAll_id,             cx, "all");
   SET_JSID_TO_STRING(sTags_id,            cx, "tags");
-  SET_JSID_TO_STRING(sBaseURIObject_id,   cx, "baseURIObject");
-  SET_JSID_TO_STRING(sNodePrincipal_id,   cx, "nodePrincipal");
   SET_JSID_TO_STRING(sDocumentURIObject_id,cx,"documentURIObject");
   SET_JSID_TO_STRING(sWrappedJSObject_id, cx, "wrappedJSObject");
   SET_JSID_TO_STRING(sURL_id,             cx, "URL");
   SET_JSID_TO_STRING(sOnload_id,          cx, "onload");
   SET_JSID_TO_STRING(sOnerror_id,         cx, "onerror");
 
   return NS_OK;
 }
@@ -5204,18 +5200,16 @@ nsDOMClassInfo::ShutDown()
   sNavigator_id       = JSID_VOID;
   sTop_id             = JSID_VOID;
   sDocument_id        = JSID_VOID;
   sFrames_id          = JSID_VOID;
   sSelf_id            = JSID_VOID;
   sOpener_id          = JSID_VOID;
   sAll_id             = JSID_VOID;
   sTags_id            = JSID_VOID;
-  sBaseURIObject_id   = JSID_VOID;
-  sNodePrincipal_id   = JSID_VOID;
   sDocumentURIObject_id=JSID_VOID;
   sWrappedJSObject_id = JSID_VOID;
   sOnload_id          = JSID_VOID;
   sOnerror_id         = JSID_VOID;
 
   NS_IF_RELEASE(sXPConnect);
   NS_IF_RELEASE(sSecMan);
   sIsInitialized = false;
@@ -7734,74 +7728,16 @@ GetterShim(JSContext *cx, JSHandleObject
   if (NS_FAILED(rv)) {
     xpc::Throw(cx, rv);
     return JS_FALSE;
   }
 
   return JS_TRUE;  
 }
 
-// Can't be static so GetterShim will compile
-nsresult
-BaseURIObjectGetter(JSContext *cx, JSObject *obj, jsval *vp)
-{
-  // This function duplicates some of the logic in XPC_WN_HelperGetProperty
-  XPCWrappedNative *wrapper =
-    XPCWrappedNative::GetWrappedNativeOfJSObject(cx, obj);
-
-  // The error checks duplicate code in THROW_AND_RETURN_IF_BAD_WRAPPER
-  NS_ENSURE_TRUE(!wrapper || wrapper->IsValid(), NS_ERROR_XPC_HAS_BEEN_SHUTDOWN);
-
-  nsCOMPtr<nsINode> node = do_QueryWrappedNative(wrapper, obj);
-  NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED);
-
-  nsCOMPtr<nsIURI> uri = node->GetBaseURI();
-  return WrapNative(cx, JS_GetGlobalForScopeChain(cx), uri,
-                    &NS_GET_IID(nsIURI), true, vp);
-}
-
-// Can't be static so GetterShim will compile
-nsresult
-NodePrincipalGetter(JSContext *cx, JSObject *obj, jsval *vp)
-{
-  // This function duplicates some of the logic in XPC_WN_HelperGetProperty
-  XPCWrappedNative *wrapper =
-    XPCWrappedNative::GetWrappedNativeOfJSObject(cx, obj);
-
-  // The error checks duplicate code in THROW_AND_RETURN_IF_BAD_WRAPPER
-  NS_ENSURE_TRUE(!wrapper || wrapper->IsValid(), NS_ERROR_XPC_HAS_BEEN_SHUTDOWN);
-
-  nsCOMPtr<nsINode> node = do_QueryWrappedNative(wrapper, obj);
-  NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED);
-
-  return WrapNative(cx, JS_GetGlobalForScopeChain(cx), node->NodePrincipal(),
-                    &NS_GET_IID(nsIPrincipal), true, vp);
-}
-
-NS_IMETHODIMP
-nsNodeSH::PostCreatePrototype(JSContext * cx, JSObject * proto)
-{
-  // set up our proto first
-  nsresult rv = nsDOMGenericSH::PostCreatePrototype(cx, proto);
-
-  if (xpc::AccessCheck::isChrome(js::GetObjectCompartment(proto))) {
-    // Stick nodePrincipal and baseURIObject  properties on there
-    JS_DefinePropertyById(cx, proto, sNodePrincipal_id,
-                          JSVAL_VOID, GetterShim<NodePrincipalGetter>,
-                          nullptr,
-                          JSPROP_READONLY | JSPROP_SHARED);
-    JS_DefinePropertyById(cx, proto, sBaseURIObject_id,
-                          JSVAL_VOID, GetterShim<BaseURIObjectGetter>,
-                          nullptr,
-                          JSPROP_READONLY | JSPROP_SHARED);
-  }
-
-  return rv;
-}
-
 NS_IMETHODIMP
 nsNodeSH::PreCreate(nsISupports *nativeObj, JSContext *cx, JSObject *globalObj,
                     JSObject **parentObj)
 {
   nsINode *node = static_cast<nsINode*>(nativeObj);
   
 #ifdef DEBUG
   {
@@ -8557,16 +8493,17 @@ DocumentURIObjectGetter(JSContext *cx, J
 
   return WrapNative(cx, JS_GetGlobalForScopeChain(cx), doc->GetDocumentURI(),
                     &NS_GET_IID(nsIURI), true, vp);
 }
 
 NS_IMETHODIMP
 nsDocumentSH::PostCreatePrototype(JSContext * cx, JSObject * proto)
 {
+  // XXXbz when this goes away, kill GetterShim as well.
   // set up our proto first
   nsresult rv = nsNodeSH::PostCreatePrototype(cx, proto);
 
   if (xpc::AccessCheck::isChrome(js::GetObjectCompartment(proto))) {
     // Stick a documentURIObject property on there
     JS_DefinePropertyById(cx, proto, sDocumentURIObject_id,
                           JSVAL_VOID, GetterShim<DocumentURIObjectGetter>,
                           nullptr,
--- a/dom/base/nsDOMClassInfo.h
+++ b/dom/base/nsDOMClassInfo.h
@@ -252,18 +252,16 @@ public:
   static jsid sNavigator_id;
   static jsid sTop_id;
   static jsid sDocument_id;
   static jsid sFrames_id;
   static jsid sSelf_id;
   static jsid sOpener_id;
   static jsid sAll_id;
   static jsid sTags_id;
-  static jsid sBaseURIObject_id;
-  static jsid sNodePrincipal_id;
   static jsid sDocumentURIObject_id;
   static jsid sJava_id;
   static jsid sPackages_id;
   static jsid sWrappedJSObject_id;
   static jsid sURL_id;
   static jsid sOnload_id;
   static jsid sOnerror_id;
 
@@ -520,17 +518,16 @@ protected:
 
   virtual ~nsNodeSH()
   {
   }
 
 public:
   NS_IMETHOD PreCreate(nsISupports *nativeObj, JSContext *cx,
                        JSObject *globalObj, JSObject **parentObj);
-  NS_IMETHOD PostCreatePrototype(JSContext * cx, JSObject * proto);
   NS_IMETHOD AddProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
                          JSObject *obj, jsid id, jsval *vp, bool *_retval);
   NS_IMETHOD NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
                         JSObject *obj, jsid id, uint32_t flags,
                         JSObject **objp, bool *_retval);
   NS_IMETHOD GetFlags(uint32_t *aFlags);
 
   virtual void PreserveWrapper(nsISupports *aNative);
--- a/dom/base/nsDOMClassInfoID.h
+++ b/dom/base/nsDOMClassInfoID.h
@@ -46,16 +46,19 @@ enum nsDOMClassInfoID {
  *          the objects that implement these interfaces may use the new DOM
  *          bindings.
  */
 #undef DOMCI_CASTABLE_INTERFACE
 #define DOMCI_CASTABLE_INTERFACES(_extra)                                     \
 DOMCI_CASTABLE_INTERFACE(nsINode, nsINode, 0, _extra)                         \
 DOMCI_CASTABLE_NODECL_INTERFACE(mozilla::dom::Element,  mozilla::dom::Element,\
                                 1, _extra)                                    \
+/* If this is ever removed, the IID for EventTarget can go away */            \
+DOMCI_CASTABLE_NODECL_INTERFACE(mozilla::dom::EventTarget,                    \
+                                mozilla::dom::EventTarget, 2, _extra)         \
 DOMCI_CASTABLE_INTERFACE(nsDocument, nsIDocument, 5, _extra)                  \
 DOMCI_CASTABLE_INTERFACE(nsGenericHTMLElement, nsGenericHTMLElement, 6,       \
                          _extra)                                              \
 DOMCI_CASTABLE_INTERFACE(nsHTMLDocument, nsIDocument, 7, _extra)              \
 DOMCI_CASTABLE_INTERFACE(nsStyledElement, nsStyledElement, 8, _extra)         \
 DOMCI_CASTABLE_INTERFACE(nsSVGStylableElement, nsIContent, 9, _extra)
  
 // Make sure all classes mentioned in DOMCI_CASTABLE_INTERFACES
@@ -63,16 +66,17 @@ DOMCI_CASTABLE_INTERFACE(nsSVGStylableEl
 #define DOMCI_CASTABLE_NODECL_INTERFACE(_interface, _u1, _u2, _u3) /* Nothing */
 #define DOMCI_CASTABLE_INTERFACE(_interface, _u1, _u2, _u3) class _interface;
 DOMCI_CASTABLE_INTERFACES(unused)
 #undef DOMCI_CASTABLE_INTERFACE
 #undef DOMCI_CASTABLE_NODECL_INTERFACE
 namespace mozilla {
 namespace dom {
 class Element;
+class EventTarget;
 } // namespace dom
 } // namespace mozilla
 
 #define DOMCI_CASTABLE_NODECL_INTERFACE DOMCI_CASTABLE_INTERFACE
 
 #ifdef _IMPL_NS_LAYOUT
 
 #define DOMCI_CLASS(_dom_class)                                               \
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11215,49 +11215,40 @@ nsGlobalWindow::DisableNetworkEvent(uint
       break;
   }
 }
 #endif // MOZ_B2G
 
 #define EVENT(name_, id_, type_, struct_)                                    \
   NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx,                  \
                                              jsval *vp) {                    \
-    nsEventListenerManager *elm = GetListenerManager(false);                 \
-    if (elm) {                                                               \
-      EventHandlerNonNull* h = elm->GetEventHandler(nsGkAtoms::on##name_);   \
-      if (h) {                                                               \
-        *vp = JS::ObjectValue(*h->Callable());                               \
-        return NS_OK;                                                        \
-      }                                                                      \
-    }                                                                        \
-    *vp = JSVAL_NULL;                                                        \
+    EventHandlerNonNull* h = GetOn##name_();                                 \
+    vp->setObjectOrNull(h ? h->Callable() : nullptr);                        \
     return NS_OK;                                                            \
   }                                                                          \
   NS_IMETHODIMP nsGlobalWindow::SetOn##name_(JSContext *cx,                  \
                                              const jsval &v) {               \
-    nsEventListenerManager *elm = GetListenerManager(true);                  \
-    if (!elm) {                                                              \
-      return NS_ERROR_OUT_OF_MEMORY;                                         \
-    }                                                                        \
-                                                                             \
     JSObject *obj = mJSObject;                                               \
     if (!obj) {                                                              \
-      return NS_ERROR_UNEXPECTED;                                            \
+      /* Just silently do nothing */                                         \
+      return NS_OK;                                                          \
     }                                                                        \
     nsRefPtr<EventHandlerNonNull> handler;                                   \
     JSObject *callable;                                                      \
     if (v.isObject() &&                                                      \
         JS_ObjectIsCallable(cx, callable = &v.toObject())) {                 \
       bool ok;                                                               \
       handler = new EventHandlerNonNull(cx, obj, callable, &ok);             \
       if (!ok) {                                                             \
         return NS_ERROR_OUT_OF_MEMORY;                                       \
       }                                                                      \
     }                                                                        \
-    return elm->SetEventHandler(nsGkAtoms::on##name_, handler);              \
+    ErrorResult rv;                                                          \
+    SetOn##name_(handler, rv);                                               \
+    return rv.ErrorCode();                                                   \
   }
 #define ERROR_EVENT(name_, id_, type_, struct_)                              \
   NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx,                  \
                                              jsval *vp) {                    \
     nsEventListenerManager *elm = GetListenerManager(false);                 \
     if (elm) {                                                               \
       OnErrorEventHandlerNonNull* h = elm->GetOnErrorEventHandler();         \
       if (h) {                                                               \
@@ -11329,10 +11320,12 @@ nsGlobalWindow::DisableNetworkEvent(uint
     }                                                                        \
     return elm->SetEventHandler(handler);                                    \
   }
 #define WINDOW_ONLY_EVENT EVENT
 #define TOUCH_EVENT EVENT
 #include "nsEventNameList.h"
 #undef TOUCH_EVENT
 #undef WINDOW_ONLY_EVENT
+#undef BEFOREUNLOAD_EVENT
+#undef ERROR_EVENT
 #undef EVENT
 
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -614,16 +614,74 @@ public:
   nsresult HandleIdleActiveEvent();
   bool ContainsIdleObserver(nsIIdleObserver* aIdleObserver, uint32_t timeInS);
   void HandleIdleObserverCallback();
 
   void AllowScriptsToClose()
   {
     mAllowScriptsToClose = true;
   }
+
+#define EVENT(name_, id_, type_, struct_)                                     \
+  mozilla::dom::EventHandlerNonNull* GetOn##name_()                           \
+  {                                                                           \
+    nsEventListenerManager *elm = GetListenerManager(false);                  \
+    return elm ? elm->GetEventHandler(nsGkAtoms::on##name_) : nullptr;        \
+  }                                                                           \
+  void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler,               \
+                    mozilla::ErrorResult& error)                              \
+  {                                                                           \
+    nsEventListenerManager *elm = GetListenerManager(true);                   \
+    if (elm) {                                                                \
+      error = elm->SetEventHandler(nsGkAtoms::on##name_, handler);            \
+    } else {                                                                  \
+      error.Throw(NS_ERROR_OUT_OF_MEMORY);                                    \
+    }                                                                         \
+  }
+#define ERROR_EVENT(name_, id_, type_, struct_)                               \
+  mozilla::dom::OnErrorEventHandlerNonNull* GetOn##name_()                    \
+  {                                                                           \
+    nsEventListenerManager *elm = GetListenerManager(false);                  \
+    return elm ? elm->GetOnErrorEventHandler() : nullptr;                     \
+  }                                                                           \
+  void SetOn##name_(mozilla::dom::OnErrorEventHandlerNonNull* handler,        \
+                    mozilla::ErrorResult& error)                              \
+  {                                                                           \
+    nsEventListenerManager *elm = GetListenerManager(true);                   \
+    if (elm) {                                                                \
+      error = elm->SetEventHandler(handler);                                  \
+    } else {                                                                  \
+      error.Throw(NS_ERROR_OUT_OF_MEMORY);                                    \
+    }                                                                         \
+  }
+#define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_)                        \
+  mozilla::dom::BeforeUnloadEventHandlerNonNull* GetOn##name_()               \
+  {                                                                           \
+    nsEventListenerManager *elm = GetListenerManager(false);                  \
+    return elm ? elm->GetOnBeforeUnloadEventHandler() : nullptr;              \
+  }                                                                           \
+  void SetOn##name_(mozilla::dom::BeforeUnloadEventHandlerNonNull* handler,   \
+                    mozilla::ErrorResult& error)                              \
+  {                                                                           \
+    nsEventListenerManager *elm = GetListenerManager(true);                   \
+    if (elm) {                                                                \
+      error = elm->SetEventHandler(handler);                                  \
+    } else {                                                                  \
+      error.Throw(NS_ERROR_OUT_OF_MEMORY);                                    \
+    }                                                                         \
+  }
+#define WINDOW_ONLY_EVENT EVENT
+#define TOUCH_EVENT EVENT
+#include "nsEventNameList.h"
+#undef TOUCH_EVENT
+#undef WINDOW_ONLY_EVENT
+#undef BEFOREUNLOAD_EVENT
+#undef ERROR_EVENT
+#undef EVENT
+
 protected:
   // Array of idle observers that are notified of idle events.
   nsTObserverArray<IdleObserverHolder> mIdleObservers;
 
   // Idle timer used for function callbacks to notify idle observers.
   nsCOMPtr<nsITimer> mIdleTimer;
 
   // Idle fuzz time added to idle timer callbacks.
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -216,22 +216,28 @@ CreateInterfaceObject(JSContext* cx, JSO
                       JSNativeHolder* constructorNative,
                       unsigned ctorNargs,
                       JSObject* proto,
                       const NativeProperties* properties,
                       const NativeProperties* chromeOnlyProperties,
                       const char* name)
 {
   JSObject* constructor;
+  bool isCallbackInterface = constructorClass == js::Jsvalify(&js::ObjectClass);
   if (constructorClass) {
-    JSObject* functionProto = JS_GetFunctionPrototype(cx, global);
-    if (!functionProto) {
+    JSObject* constructorProto;
+    if (isCallbackInterface) {
+      constructorProto = JS_GetObjectPrototype(cx, global);
+    } else {
+      constructorProto = JS_GetFunctionPrototype(cx, global);
+    }
+    if (!constructorProto) {
       return NULL;
     }
-    constructor = JS_NewObject(cx, constructorClass, functionProto, global);
+    constructor = JS_NewObject(cx, constructorClass, constructorProto, global);
   } else {
     MOZ_ASSERT(constructorNative);
     JSFunction* fun = js::NewFunctionWithReserved(cx, Constructor, ctorNargs,
                                                   JSFUN_CONSTRUCTOR, global,
                                                   name);
     if (!fun) {
       return NULL;
     }
@@ -239,17 +245,19 @@ CreateInterfaceObject(JSContext* cx, JSO
     js::SetFunctionNativeReserved(constructor,
                                   CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT,
                                   js::PrivateValue(constructorNative));
   }
   if (!constructor) {
     return NULL;
   }
 
-  if (constructorClass) {
+  if (constructorClass && !isCallbackInterface) {
+    // Have to shadow Function.prototype.toString, since that throws
+    // on things that are not js::FunctionClass.
     JSFunction* toString = js::DefineFunctionWithReserved(cx, constructor,
                                                           "toString",
                                                           InterfaceObjectToString,
                                                           0, 0);
     if (!toString) {
       return NULL;
     }
 
@@ -565,18 +573,18 @@ JSBool
 ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp)
 {
   return ThrowErrorMessage(cx, MSG_ILLEGAL_CONSTRUCTOR);
 }
 
 inline const NativePropertyHooks*
 GetNativePropertyHooks(JSContext *cx, JSObject *obj, DOMObjectType& type)
 {
-  const DOMClass* domClass;
-  if (GetDOMClass(obj, domClass) != eNonDOMObject) {
+  const DOMClass* domClass = GetDOMClass(obj);
+  if (domClass) {
     type = eInstance;
     return domClass->mNativeHooks;
   }
 
   if (JS_ObjectIsFunction(cx, obj)) {
     MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
     type = eInterface;
     const JS::Value& v =
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -86,97 +86,65 @@ IsDOMIfaceAndProtoClass(const JSClass* c
 }
 
 inline bool
 IsDOMIfaceAndProtoClass(const js::Class* clasp)
 {
   return IsDOMIfaceAndProtoClass(Jsvalify(clasp));
 }
 
-// It's ok for eRegularDOMObject and eProxyDOMObject to be the same, but
-// eNonDOMObject should always be different from the other two. This enum
-// shouldn't be used to differentiate between non-proxy and proxy bindings.
-enum DOMObjectSlot {
-  eNonDOMObject = -1,
-  eRegularDOMObject = DOM_OBJECT_SLOT,
-  eProxyDOMObject = DOM_PROXY_OBJECT_SLOT
-};
-
+MOZ_STATIC_ASSERT(DOM_OBJECT_SLOT == js::JSSLOT_PROXY_PRIVATE,
+                  "JSSLOT_PROXY_PRIVATE doesn't match DOM_OBJECT_SLOT.  "
+                  "Expect bad things");
 template <class T>
 inline T*
-UnwrapDOMObject(JSObject* obj, DOMObjectSlot slot)
+UnwrapDOMObject(JSObject* obj)
 {
-  MOZ_ASSERT(slot != eNonDOMObject,
+  MOZ_ASSERT(IsDOMClass(js::GetObjectClass(obj)) || IsDOMProxy(obj),
              "Don't pass non-DOM objects to this function");
 
-#ifdef DEBUG
-  if (IsDOMClass(js::GetObjectClass(obj))) {
-    MOZ_ASSERT(slot == eRegularDOMObject);
-  } else {
-    MOZ_ASSERT(IsDOMProxy(obj));
-    MOZ_ASSERT(slot == eProxyDOMObject);
-  }
-#endif
-
-  JS::Value val = js::GetReservedSlot(obj, slot);
+  JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
   // XXXbz/khuey worker code tries to unwrap interface objects (which have
   // nothing here).  That needs to stop.
   // XXX We don't null-check UnwrapObject's result; aren't we going to crash
   // anyway?
   if (val.isUndefined()) {
     return NULL;
   }
   
   return static_cast<T*>(val.toPrivate());
 }
 
-// Only use this with a new DOM binding object (either proxy or regular).
 inline const DOMClass*
 GetDOMClass(JSObject* obj)
 {
   js::Class* clasp = js::GetObjectClass(obj);
   if (IsDOMClass(clasp)) {
     return &DOMJSClass::FromJSClass(clasp)->mClass;
   }
 
-  MOZ_ASSERT(IsDOMProxy(obj));
-  js::BaseProxyHandler* handler = js::GetProxyHandler(obj);
-  return &static_cast<DOMProxyHandler*>(handler)->mClass;
-}
-
-inline DOMObjectSlot
-GetDOMClass(JSObject* obj, const DOMClass*& result)
-{
-  js::Class* clasp = js::GetObjectClass(obj);
-  if (IsDOMClass(clasp)) {
-    result = &DOMJSClass::FromJSClass(clasp)->mClass;
-    return eRegularDOMObject;
-  }
-
   if (js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) {
     js::BaseProxyHandler* handler = js::GetProxyHandler(obj);
     if (handler->family() == ProxyFamily()) {
-      result = &static_cast<DOMProxyHandler*>(handler)->mClass;
-      return eProxyDOMObject;
+      return &static_cast<DOMProxyHandler*>(handler)->mClass;
     }
   }
 
-  return eNonDOMObject;
+  return nullptr;
 }
 
 inline bool
 UnwrapDOMObjectToISupports(JSObject* obj, nsISupports*& result)
 {
-  const DOMClass* clasp;
-  DOMObjectSlot slot = GetDOMClass(obj, clasp);
-  if (slot == eNonDOMObject || !clasp->mDOMObjectIsISupports) {
+  const DOMClass* clasp = GetDOMClass(obj);
+  if (!clasp || !clasp->mDOMObjectIsISupports) {
     return false;
   }
  
-  result = UnwrapDOMObject<nsISupports>(obj, slot);
+  result = UnwrapDOMObject<nsISupports>(obj);
   return true;
 }
 
 inline bool
 IsDOMObject(JSObject* obj)
 {
   js::Class* clasp = js::GetObjectClass(obj);
   return IsDOMClass(clasp) || IsDOMProxy(obj, clasp);
@@ -186,43 +154,42 @@ IsDOMObject(JSObject* obj)
 // (for example, overload resolution uses unwrapping to tell what sort
 // of thing it's looking at).
 // U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr<T>).
 template <prototypes::ID PrototypeID, class T, typename U>
 MOZ_ALWAYS_INLINE nsresult
 UnwrapObject(JSContext* cx, JSObject* obj, U& value)
 {
   /* First check to see whether we have a DOM object */
-  const DOMClass* domClass;
-  DOMObjectSlot slot = GetDOMClass(obj, domClass);
-  if (slot == eNonDOMObject) {
+  const DOMClass* domClass = GetDOMClass(obj);
+  if (!domClass) {
     /* Maybe we have a security wrapper or outer window? */
     if (!js::IsWrapper(obj)) {
       /* Not a DOM object, not a wrapper, just bail */
       return NS_ERROR_XPC_BAD_CONVERT_JS;
     }
 
     obj = xpc::Unwrap(cx, obj, false);
     if (!obj) {
       return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
     }
     MOZ_ASSERT(!js::IsWrapper(obj));
-    slot = GetDOMClass(obj, domClass);
-    if (slot == eNonDOMObject) {
+    domClass = GetDOMClass(obj);
+    if (!domClass) {
       /* We don't have a DOM object */
       return NS_ERROR_XPC_BAD_CONVERT_JS;
     }
   }
 
   /* This object is a DOM object.  Double-check that it is safely
      castable to T by checking whether it claims to inherit from the
      class identified by protoID. */
   if (domClass->mInterfaceChain[PrototypeTraits<PrototypeID>::Depth] ==
       PrototypeID) {
-    value = UnwrapDOMObject<T>(obj, slot);
+    value = UnwrapDOMObject<T>(obj);
     return NS_OK;
   }
 
   /* It's the wrong sort of DOM object */
   return NS_ERROR_XPC_BAD_CONVERT_JS;
 }
 
 inline bool
@@ -538,21 +505,20 @@ WrapNewBindingObject(JSContext* cx, JSOb
       // try to communicate triedToWrap to the caller, but in practice
       // callers seem to be testing JS_IsExceptionPending(cx) to
       // figure out whether WrapObject() threw instead.
       return false;
     }
   }
 
 #ifdef DEBUG
-  const DOMClass* clasp = nullptr;
-  DOMObjectSlot slot = GetDOMClass(obj, clasp);
-  // slot can be eNonDOMObject if the cache contained a non-DOM object from a
+  const DOMClass* clasp = GetDOMClass(obj);
+  // clasp can be null if the cache contained a non-DOM object from a
   // different compartment than scope.
-  if (slot != eNonDOMObject) {
+  if (clasp) {
     // Some sanity asserts about our object.  Specifically:
     // 1)  If our class claims we're nsISupports, we better be nsISupports
     //     XXXbz ideally, we could assert that reinterpret_cast to nsISupports
     //     does the right thing, but I don't see a way to do it.  :(
     // 2)  If our class doesn't claim we're nsISupports we better be
     //     reinterpret_castable to nsWrapperCache.
     MOZ_ASSERT(clasp, "What happened here?");
     MOZ_ASSERT_IF(clasp->mDOMObjectIsISupports, IsISupports<T>::Value);
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -216,20 +216,19 @@ DOMInterfaces = {
 
 'EventListener': [
 {
     'workers': True,
 }],
 
 'EventTarget': [
 {
-    'nativeType': 'nsDOMEventTargetHelper',
     'hasInstanceInterface': 'nsIDOMEventTarget',
+    'hasXPConnectImpls': True,
     'concrete': False,
-    'prefable': True,
 },
 {
     'workers': True,
     'concrete': False
 }],
 
 'FileHandle': {
     'nativeType': 'mozilla::dom::file::FileHandle'
@@ -255,30 +254,47 @@ DOMInterfaces = {
     'resultNotAddRefed': [ 'gain' ],
 }],
 
 'HTMLCollection': {
     'nativeType': 'nsIHTMLCollection',
     'resultNotAddRefed': [ 'item' ]
 },
 
+'HTMLElement': {
+    'nativeType': 'nsGenericHTMLElement',
+    'register': False,
+    'hasXPConnectImpls': True,
+    'hasInstanceInterface': 'nsIDOMHTMLElement',
+    'resultNotAddRefed': [
+        'itemType', 'itemRef', 'itemProp', 'properties', 'contextMenu', 'style',
+        'offsetParent'
+    ]
+},
+
 'HTMLOptionsCollection': {
     'nativeType': 'nsHTMLOptionCollection',
     'headerFile': 'nsHTMLSelectElement.h',
     'resultNotAddRefed': [ 'item' ],
     'binaryNames': {
         '__indexedsettercreator': 'SetOption'
     }
 },
 
 'HTMLPropertiesCollection': {
     'headerFile': 'HTMLPropertiesCollection.h',
     'resultNotAddRefed': [ 'item', 'namedItem', 'names' ]
 },
 
+'HTMLUnknownElement': {
+    'nativeType': 'nsHTMLUnknownElement',
+    'register': False,
+    'hasXPConnectImpls': True
+},
+
 'IID': [
 {
     'nativeType': 'nsIJSID',
     'headerFile': 'xpcjsid.h',
 },
 {
     'workers': True,
 }],
@@ -723,32 +739,32 @@ def addExternalIface(iface, nativeType=N
 # macros added for it
 def addExternalHTMLElement(element):
    nativeElement = 'ns' + element
    addExternalIface(element, nativeType=nativeElement,
                     headerFile=nativeElement + '.h')
 
 addExternalHTMLElement('HTMLCanvasElement')
 addExternalHTMLElement('HTMLImageElement')
+addExternalHTMLElement('HTMLMenuElement')
 addExternalHTMLElement('HTMLOptionElement')
 addExternalHTMLElement('HTMLOptGroupElement')
 addExternalHTMLElement('HTMLVideoElement')
 addExternalIface('Attr')
 addExternalIface('CanvasGradient', headerFile='nsIDOMCanvasRenderingContext2D.h')
 addExternalIface('CanvasPattern', headerFile='nsIDOMCanvasRenderingContext2D.h')
 addExternalIface('ClientRect')
 addExternalIface('CSSRule')
 addExternalIface('CSSValue')
 addExternalIface('DocumentType', nativeType='nsIDOMDocumentType')
 addExternalIface('DOMRequest')
 addExternalIface('DOMStringList', nativeType='nsDOMStringList',
                  headerFile='nsDOMLists.h')
 addExternalIface('File')
 addExternalIface('HitRegionOptions', nativeType='nsISupports')
-addExternalIface('HTMLElement')
 addExternalIface('LockedFile')
 addExternalIface('MediaStream')
 addExternalIface('NamedNodeMap')
 addExternalIface('OutputStream', nativeType='nsIOutputStream',
                  notflattened=True)
 addExternalIface('PaintRequest')
 addExternalIface('Principal', nativeType='nsIPrincipal',
                  headerFile='nsIPrincipal.h', notflattened=True)
--- a/dom/bindings/CallbackFunction.h
+++ b/dom/bindings/CallbackFunction.h
@@ -83,16 +83,26 @@ public:
 
   bool HasGrayCallable() const
   {
     // Play it safe in case this gets called after unlink.
     return mCallable && xpc_IsGrayGCThing(mCallable);
   }
 
 protected:
+  explicit CallbackFunction(CallbackFunction* aCallbackFunction)
+    : mCallable(aCallbackFunction->mCallable)
+  {
+    // Set mCallable before we hold, on the off chance that a GC could somehow
+    // happen in there... (which would be pretty odd, granted).
+    // Make sure we'll be able to drop as needed
+    nsLayoutStatics::AddRef();
+    NS_HOLD_JS_OBJECTS(this, CallbackFunction);
+  }
+
   void DropCallback()
   {
     if (mCallable) {
       mCallable = nullptr;
       NS_DROP_JS_OBJECTS(this, CallbackFunction);
       nsLayoutStatics::Release();
     }
   }
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -602,50 +602,55 @@ def UnionTypes(descriptors, dictionaries
                     declarations.add((f.inner.identifier.name, True))
                     implheaders.add(CGHeaders.getDeclarationFilename(f.inner))
 
     callForEachType(descriptors, dictionaries, callbacks, addInfoForType)
 
     return (headers, implheaders, declarations,
             CGList(SortedDictValues(unionStructs), "\n"))
 
-def UnionConversions(descriptors):
+def UnionConversions(descriptors, dictionaries, callbacks, config):
     """
     Returns a CGThing to declare all union argument conversion helper structs.
     """
     # Now find all the things we'll need as arguments because we
     # need to unwrap them.
+    headers = set()
     unionConversions = dict()
-    for d in descriptors:
-        if d.interface.isExternal():
-            continue
-
-        def addUnionTypes(type):
-            if type.isUnion():
-                type = type.unroll()
-                name = str(type)
-                if not name in unionConversions:
-                    unionConversions[name] = CGUnionConversionStruct(type, d)
-
-        members = [m for m in d.interface.members]
-        if d.interface.ctor():
-            members.append(d.interface.ctor())
-        signatures = [s for m in members if m.isMethod() for s in m.signatures()]
-        for s in signatures:
-            assert len(s) == 2
-            (_, arguments) = s
-            for a in arguments:
-                addUnionTypes(a.type)
-
-        for m in members:
-            if m.isAttr() and not m.readonly:
-                addUnionTypes(m.type)
-
-    return CGWrapper(CGList(SortedDictValues(unionConversions), "\n"),
-                     post="\n\n")
+
+    def addInfoForType(t, descriptor=None, dictionary=None):
+        """
+        Add info for the given type.  descriptor and dictionary, if passed, are
+        used to figure out what to do with interface types.
+        """
+        assert not descriptor or not dictionary
+        t = t.unroll()
+        if not t.isUnion():
+            return
+        name = str(t)
+        if not name in unionConversions:
+            providers = getRelevantProviders(descriptor, dictionary,
+                                             config)
+            unionConversions[name] = CGUnionConversionStruct(t, providers[0])
+            for f in t.flatMemberTypes:
+                f = f.unroll()
+                if f.isInterface():
+                    if f.isSpiderMonkeyInterface():
+                        headers.add("jsfriendapi.h")
+                        headers.add("mozilla/dom/TypedArray.h")
+                    elif not f.inner.isExternal():
+                        headers.add(CGHeaders.getDeclarationFilename(f.inner))
+                elif f.isDictionary():
+                    headers.add(CGHeaders.getDeclarationFilename(f.inner))
+
+    callForEachType(descriptors, dictionaries, callbacks, addInfoForType)
+
+    return (headers,
+            CGWrapper(CGList(SortedDictValues(unionConversions), "\n"),
+                      post="\n\n"))
 
 class Argument():
     """
     A class for outputting the type and name of an argument
     """
     def __init__(self, argType, name):
         self.argType = argType
         self.name = name
@@ -739,17 +744,17 @@ class CGAbstractClassHook(CGAbstractStat
     'this' unwrapping as it assumes that the unwrapped type is always known.
     """
     def __init__(self, descriptor, name, returnType, args):
         CGAbstractStaticMethod.__init__(self, descriptor, name, returnType,
                                         args)
 
     def definition_body_prologue(self):
         return """
-  %s* self = UnwrapDOMObject<%s>(obj, eRegularDOMObject);
+  %s* self = UnwrapDOMObject<%s>(obj);
 """ % (self.descriptor.nativeType, self.descriptor.nativeType)
 
     def definition_body(self):
         return self.definition_body_prologue() + self.generate_code()
 
     def generate_code(self):
         # Override me
         assert(False)
@@ -1453,33 +1458,37 @@ class CGCreateInterfaceObjectsMethod(CGA
         else:
             prefCache = None
             
         getParentProto = ("JSObject* parentProto = %s;\n" +
                           "if (!parentProto) {\n" +
                           "  return;\n" +
                           "}\n") % getParentProto
 
-        needConstructor = (needInterfaceObject and
-                           not self.descriptor.hasInstanceInterface)
-        constructHook = "&" + CONSTRUCT_HOOK_NAME + "_holder"
+        if (needInterfaceObject and
+            self.descriptor.needsConstructHookHolder()):
+            constructHookHolder = "&" + CONSTRUCT_HOOK_NAME + "_holder"
+        else:
+            constructHookHolder = "nullptr"
         if self.descriptor.interface.ctor():
             constructArgs = methodLength(self.descriptor.interface.ctor())
         else:
             constructArgs = 0
 
         if needInterfacePrototypeObject:
             protoClass = "&PrototypeClass.mBase"
             protoCache = "&protoAndIfaceArray[prototypes::id::%s]" % self.descriptor.name
         else:
             protoClass = "nullptr"
             protoCache = "nullptr"
         if needInterfaceObject:
             if self.descriptor.hasInstanceInterface:
                 interfaceClass = "&InterfaceObjectClass.mBase"
+            elif self.descriptor.interface.isCallback():
+                interfaceClass = "js::Jsvalify(&js::ObjectClass)"
             else:
                 interfaceClass = "nullptr"
             interfaceCache = "&protoAndIfaceArray[constructors::id::%s]" % self.descriptor.name
         else:
             interfaceClass = "nullptr"
             interfaceCache = "nullptr"
 
         if self.descriptor.concrete:
@@ -1502,17 +1511,17 @@ class CGCreateInterfaceObjectsMethod(CGA
         call = ("dom::CreateInterfaceObjects(aCx, aGlobal, parentProto,\n"
                 "                            %s, %s,\n"
                 "                            %s, %s, %d, %s,\n"
                 "                            %s,\n"
                 "                            %s,\n"
                 "                            %s,\n"
                 "                            %s);" % (
             protoClass, protoCache,
-            interfaceClass, constructHook if needConstructor else "nullptr",
+            interfaceClass, constructHookHolder,
             constructArgs, interfaceCache,
             domClass,
             properties,
             chromeProperties,
             '"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "NULL"))
         functionBody = CGList(
             [CGGeneric(getParentProto), initIds, prefCache, CGGeneric(call)],
             "\n\n")
@@ -4992,32 +5001,38 @@ class ClassConstructor(ClassItem):
     inline should be True if the constructor should be marked inline.
 
     bodyInHeader should be True if the body should be placed in the class
     declaration in the header.
 
     visibility determines the visibility of the constructor (public,
     protected, private), defaults to private.
 
+    explicit should be True if the constructor should be marked explicit.
+
     baseConstructors is a list of strings containing calls to base constructors,
     defaults to None.
 
     body contains a string with the code for the constructor, defaults to None.
     """
     def __init__(self, args, inline=False, bodyInHeader=False,
-                 visibility="private", baseConstructors=None, body=None):
+                 visibility="private", explicit=False, baseConstructors=None,
+                 body=None):
         self.args = args
         self.inline = inline or bodyInHeader
         self.bodyInHeader = bodyInHeader
+        self.explicit = explicit
         self.baseConstructors = baseConstructors
         self.body = body
         ClassItem.__init__(self, None, visibility)
 
     def getDecorators(self, declaring):
         decorators = []
+        if self.explicit:
+            decorators.append('explicit')
         if self.inline and declaring:
             decorators.append('inline')
         if decorators:
             return ' '.join(decorators) + ' '
         return ''
 
     def getInitializationList(self, cgClass):
         items = [str(c) for c in self.baseConstructors]
@@ -6143,17 +6158,18 @@ class CGDescriptor(CGThing):
         cgThings.append(CGNativeProperties(descriptor, properties))
 
         cgThings.append(CGNativePropertyHooks(descriptor, properties))
 
         if descriptor.interface.hasInterfaceObject():
             cgThings.append(CGClassConstructHook(descriptor))
             cgThings.append(CGClassHasInstanceHook(descriptor))
             cgThings.append(CGInterfaceObjectJSClass(descriptor, properties))
-            cgThings.append(CGClassConstructHookHolder(descriptor))
+            if descriptor.needsConstructHookHolder():
+                cgThings.append(CGClassConstructHookHolder(descriptor))
 
         if descriptor.interface.hasInterfacePrototypeObject():
             cgThings.append(CGPrototypeJSClass(descriptor, properties))
 
         cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties))
         if descriptor.interface.hasInterfacePrototypeObject():
             cgThings.append(CGGetProtoObjectMethod(descriptor))
         if descriptor.interface.hasInterfaceObject():
@@ -7368,17 +7384,17 @@ class CGCallbackFunction(CGClass):
             # same return type as what CallCallback generates.  So we want to
             # take advantage of all its CGNativeMember infrastructure, but that
             # infrastructure can't deal with templates and most especially
             # template arguments.  So just cheat and have CallCallback compute
             # all those things for us.
             callCallback = CallCallback(callback, descriptorProvider)
             CGClass.__init__(self, name,
                              bases=[ClassBase("CallbackFunction")],
-                             constructors=[self.getConstructor()],
+                             constructors=self.getConstructors(),
                              methods=self.getCallImpls(callCallback))
             self.generatable = True
         except NoSuchDescriptorError, err:
             if not descriptorProvider.workers:
                 raise err
             self.generatable = False
 
     def define(self):
@@ -7386,28 +7402,37 @@ class CGCallbackFunction(CGClass):
             return ""
         return CGClass.define(self)
 
     def declare(self):
         if not self.generatable:
             return ""
         return CGClass.declare(self)
 
-    def getConstructor(self):
-        return ClassConstructor(
+    def getConstructors(self):
+        return [ClassConstructor(
             [Argument("JSContext*", "cx"),
              Argument("JSObject*", "aOwner"),
              Argument("JSObject*", "aCallable"),
              Argument("bool*", "aInited")],
             bodyInHeader=True,
             visibility="public",
             baseConstructors=[
                 "CallbackFunction(cx, aOwner, aCallable, aInited)"
                 ],
-            body="")
+            body=""),
+            ClassConstructor(
+            [Argument("CallbackFunction*", "aOther")],
+            bodyInHeader=True,
+            visibility="public",
+            explicit=True,
+            baseConstructors=[
+                "CallbackFunction(aOther)"
+                ],
+            body="")]
 
     def getCallImpls(self, callCallback):
         args = list(callCallback.args)
         # Strip out the JSContext*/JSObject* args
         # that got added.
         assert args[0].name == "cx" and args[0].argType == "JSContext*"
         assert args[1].name == "aThisObj" and args[1].argType == "JSObject*"
         args = args[2:]
@@ -7647,18 +7672,31 @@ class GlobalGenRoots():
     @staticmethod
     def PrototypeList(config):
 
         # Prototype ID enum.
         protos = [d.name for d in config.getDescriptors(hasInterfacePrototypeObject=True)]
         idEnum = CGNamespacedEnum('id', 'ID', ['_ID_Start'] + protos,
                                   [0, '_ID_Start'])
         idEnum = CGList([idEnum])
-        idEnum.append(CGGeneric(declare="const unsigned MaxProtoChainLength = " +
-                                str(config.maxProtoChainLength) + ";\n\n"))
+
+        # This is only used by DOM worker code, once there are no more consumers
+        # of INTERFACE_CHAIN_* this code should be removed.
+        def ifaceChainMacro(ifaceCount):
+            supplied = [CGGeneric(declare="_iface_" + str(i + 1)) for i in range(ifaceCount)]
+            remaining = [CGGeneric(declare="prototypes::id::_ID_Count")] * (config.maxProtoChainLength - ifaceCount)
+            macro = CGWrapper(CGList(supplied, ", "),
+                              pre="#define INTERFACE_CHAIN_" + str(ifaceCount) + "(",
+                              post=") \\\n")
+            macroContent = CGIndenter(CGList(supplied + remaining, ", \\\n"))
+            macroContent = CGIndenter(CGWrapper(macroContent, pre="{ \\\n",
+                                                post=" \\\n}"))
+            return CGWrapper(CGList([macro, macroContent]), post="\n\n")
+
+        idEnum.append(ifaceChainMacro(1))
 
         # Wrap all of that in our namespaces.
         idEnum = CGNamespace.build(['mozilla', 'dom', 'prototypes'],
                                    CGWrapper(idEnum, pre='\n'))
         idEnum = CGWrapper(idEnum, post='\n')
 
         curr = CGList([idEnum])
 
@@ -7771,22 +7809,26 @@ struct PrototypeIDMap;
         curr = CGIncludeGuard('UnionTypes', curr)
 
         # Done.
         return curr
 
     @staticmethod
     def UnionConversions(config):
 
-        unions = UnionConversions(config.getDescriptors())
+        (headers, unions) = UnionConversions(config.getDescriptors(),
+                                             config.getDictionaries(),
+                                             config.getCallbacks(),
+                                             config)
 
         # Wrap all of that in our namespaces.
         curr = CGNamespace.build(['mozilla', 'dom'], unions)
 
         curr = CGWrapper(curr, post='\n')
 
-        curr = CGHeaders([], [], [], ["nsDebug.h", "mozilla/dom/UnionTypes.h", "nsDOMQS.h", "XPCWrapper.h"], [], curr)
+        headers.update(["nsDebug.h", "mozilla/dom/UnionTypes.h", "nsDOMQS.h", "XPCWrapper.h"])
+        curr = CGHeaders([], [], [], headers, [], curr)
 
         # Add include guards.
         curr = CGIncludeGuard('UnionConversions', curr)
 
         # Done.
         return curr
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -385,8 +385,12 @@ class Descriptor(DescriptorProvider):
         maybeAppendInfallibleToAttrs(attrs, throws)
         return attrs
 
     def supportsIndexedProperties(self):
         return self.operations['IndexedGetter'] is not None
 
     def supportsNamedProperties(self):
         return self.operations['NamedGetter'] is not None
+
+    def needsConstructHookHolder(self):
+        assert self.interface.hasInterfaceObject()
+        return not self.hasInstanceInterface and not self.interface.isCallback()
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -62,17 +62,17 @@ DOMProxyHandler::EnsureExpandoObject(JSC
     }
 
     XPCWrappedNativeScope* scope = xpc::GetObjectScope(obj);
     if (!scope->RegisterDOMExpandoObject(obj)) {
       return NULL;
     }
 
     nsWrapperCache* cache;
-    CallQueryInterface(UnwrapDOMObject<nsISupports>(obj, eProxyDOMObject), &cache);
+    CallQueryInterface(UnwrapDOMObject<nsISupports>(obj), &cache);
     cache->SetPreservingWrapper(true);
 
     js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando));
   }
   return expando;
 }
 
 bool
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -246,42 +246,52 @@ ContactDB.prototype = {
         for (let i = 0; i <= aContact.properties[field].length; i++) {
           if (aContact.properties[field][i]) {
             if (field == "tel") {
               // Special case telephone number.
               // "+1-234-567" should also be found with 1234, 234-56, 23456
 
               // Chop off the first characters
               let number = aContact.properties[field][i].value;
+              let search = {};
               if (number) {
                 for (let i = 0; i < number.length; i++) {
-                  contact.search[field].push(number.substring(0, number.length - i));
+                  search[number.substring(i, number.length)] = 1;
                 }
                 // Store +1-234-567 as ["1234567", "234567"...]
                 let digits = number.match(/\d/g);
                 if (digits && number.length != digits.length) {
                   digits = digits.join('');
                   for(let i = 0; i < digits.length; i++) {
-                    contact.search[field].push(digits.substring(0, digits.length - i));
+                    search[digits.substring(i, digits.length)] = 1;
                   }
                 }
                 if (DEBUG) debug("lookup: " + JSON.stringify(contact.search[field]));
                 let parsedNumber = PhoneNumberUtils.parse(number.toString());
                 if (parsedNumber) {
                   debug("InternationalFormat: " + parsedNumber.internationalFormat);
                   debug("InternationalNumber: " + parsedNumber.internationalNumber);
                   debug("NationalNumber: " + parsedNumber.nationalNumber);
                   debug("NationalFormat: " + parsedNumber.nationalFormat);
                   if (number.toString() !== parsedNumber.internationalNumber) {
-                    contact.search[field].push(parsedNumber.internationalNumber);
+                    let digits = parsedNumber.internationalNumber.match(/\d/g);
+                    if (digits) {
+                      digits = digits.join('');
+                      for(let i = 0; i < digits.length; i++) {
+                        search[digits.substring(i, digits.length)] = 1;
+                      }
+                    }
                   }
                 } else {
                   dump("Warning: No international number found for " + number + "\n");
                 }
               }
+              for (let num in search) {
+                contact.search[field].push(num);
+              }
             } else if (field == "email") {
               let address = aContact.properties[field][i].value;
               if (address && typeof address == "string") {
                 contact.search[field].push(address.toLowerCase());
               }
             } else if (field == "impp") {
               let value = aContact.properties[field][i].value;
               if (value && typeof value == "string") {
@@ -434,16 +444,22 @@ ContactDB.prototype = {
         // case sensitive
         let index = store.index(key);
         request = index.mozGetAll(options.filterValue, limit);
       } else {
         // not case sensitive
         let tmp = typeof options.filterValue == "string"
                   ? options.filterValue.toLowerCase()
                   : options.filterValue.toString().toLowerCase();
+        if (key === 'tel') {
+          let digits = tmp.match(/\d/g);
+          if (digits) {
+            tmp = digits.join('');
+          }
+        }
         let range = this._global.IDBKeyRange.bound(tmp, tmp + "\uFFFF");
         let index = store.index(key + "LowerCase");
         request = index.mozGetAll(range, limit);
       }
       if (!txn.result)
         txn.result = {};
 
       request.onsuccess = function (event) {
--- a/dom/icc/Makefile.in
+++ b/dom/icc/Makefile.in
@@ -6,9 +6,11 @@ DEPTH            = @DEPTH@
 topsrcdir        = @top_srcdir@
 srcdir           = @srcdir@
 VPATH            = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 PARALLEL_DIRS = interfaces src
 
+TEST_DIRS += tests
+
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/icc/tests/Makefile.in
@@ -0,0 +1,16 @@
+# 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/.
+
+DEPTH            = @DEPTH@
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+relativesrcdir   = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = test_dom_icc
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/icc/tests/marionette/manifest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+b2g = true
+browser = false
+qemu = true
+
+[test_stk_proactive_command.js]
new file mode 100644
--- /dev/null
+++ b/dom/icc/tests/marionette/test_stk_proactive_command.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 30000;
+
+SpecialPowers.addPermission("mobileconnection", true, document);
+
+let icc = navigator.mozMobileConnection.icc;
+ok(icc instanceof MozIccManager, "icc is instanceof " + icc.constructor);
+
+function testDisplayTextGsm7BitEncoding(cmd) {
+  log("STK CMD " + JSON.stringify(cmd));
+  is(cmd.typeOfCommand, icc.STK_CMD_DISPLAY_TEXT);
+  is(cmd.options.userClear, true);
+  is(cmd.options.text, "Saldo 2.04 E. Validez 20/05/13. ");
+
+  runNextTest();
+}
+
+let tests = [
+  {command: "d0288103012180820281020d1d00d3309bfc06c95c301aa8e80259c3ec34b9ac07c9602f58ed159bb940",
+   func: testDisplayTextGsm7BitEncoding},
+];
+
+let pendingEmulatorCmdCount = 0;
+function sendStkPduToEmulator(cmd, func) {
+  ++pendingEmulatorCmdCount;
+
+  runEmulatorCmd(cmd, function (result) {
+    --pendingEmulatorCmdCount;
+    is(result[0], "OK");
+  });
+
+  icc.onstkcommand = function (evt) {
+    func(evt.command);
+  }
+}
+
+function runNextTest() {
+  let test = tests.pop();
+  if (!test) {
+    cleanUp();
+    return;
+  }
+
+  let cmd = "stk pdu " + test.command;
+  sendStkPduToEmulator(cmd, test.func)
+}
+
+function cleanUp() {
+  if (pendingEmulatorCmdCount) {
+    window.setTimeout(cleanUp, 100);
+    return;
+  }
+
+  SpecialPowers.removePermission("mobileconnection", document);
+  finish();
+}
+
+runNextTest();
--- a/dom/interfaces/html/nsIDOMHTMLAudioElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLAudioElement.idl
@@ -15,17 +15,17 @@
  * <audio> element.
  *
  * For more information on this interface, please see
  * http://www.whatwg.org/specs/web-apps/current-work/#audio
  *
  * @status UNDER_DEVELOPMENT
  */
 
-[scriptable, uuid(5215662f-9ab5-4219-adb1-672b870b08ba)]
+[scriptable, uuid(ee5df17c-3928-11e2-8808-10bf48d64bd4)]
 interface nsIDOMHTMLAudioElement : nsIDOMHTMLMediaElement
 {
   // Setup the audio stream for writing
   void mozSetup(in uint32_t channels, in uint32_t rate);
 
   // Write audio to the audio stream
   [implicit_jscontext]
   unsigned long mozWriteAudio(in jsval data);
--- a/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
@@ -22,17 +22,17 @@ interface nsIDOMMediaStream;
 
 // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
 %{C++
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 %}
 
-[scriptable, uuid(75ccaaec-6920-43ae-a56f-ee7a693f3d31)]
+[scriptable, uuid(d9331886-3928-11e2-b0e1-10bf48d64bd4)]
 interface nsIDOMHTMLMediaElement : nsIDOMHTMLElement
 {
   // error state
   readonly attribute nsIDOMMediaError error;
 
   // network state
            attribute DOMString src;
   [implicit_jscontext] attribute jsval mozSrcObject;
@@ -131,20 +131,25 @@ interface nsIDOMHTMLMediaElement : nsIDO
    // * notification
    //   Automatically paused if "alarm" or higher priority channel is played.
    //   Use case: New email, incoming SMS
    // * alarm
    //   Automatically paused if "telephony" or higher priority channel is
    //   played.
    //   User case: Alarm clock, calendar alarms
    // * telephony
+   //   Automatically paused if "ringer" or higher priority
+   //   channel is played.
+   //   Use case: dialer, voip
+   // * ringer
    //   Automatically paused if "publicnotification" or higher priority
    //   channel is played.
    //   Use case: dialer, voip
    // * publicnotification
    //   Always plays in speaker, even when headphones are plugged in.
+   //   Use case: Camera shutter sound.
    attribute DOMString mozAudioChannelType;
 
   // In addition the media element has this new events:
   // * onmozinterruptbegin - called when the media element is interrupted
   //   because of the audiochannel manager.
   // * onmozinterruptend - called when the interruption is concluded
 };
--- a/dom/interfaces/html/nsIDOMHTMLVideoElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLVideoElement.idl
@@ -11,17 +11,17 @@
  * <video> element.
  *
  * For more information on this interface, please see
  * http://www.whatwg.org/specs/web-apps/current-work/#video
  *
  * @status UNDER_DEVELOPMENT
  */
 
-[scriptable, uuid(5f33df40-25d7-4d7b-9b2f-0b9acd9e5ad7)]
+[scriptable, uuid(fe914e4a-3928-11e2-bea2-10bf48d64bd4)]
 interface nsIDOMHTMLVideoElement : nsIDOMHTMLMediaElement
 {
            attribute long width; 
            attribute long height;
   readonly attribute unsigned long videoWidth;
   readonly attribute unsigned long videoHeight;
            attribute DOMString poster;
            
--- a/dom/interfaces/xul/nsIDOMXULDocument.idl
+++ b/dom/interfaces/xul/nsIDOMXULDocument.idl
@@ -1,21 +1,22 @@
 /* -*- Mode: IDL; 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 "domstubs.idl"
+#include "nsIDOMDocument.idl"
 
 interface nsIDOMXULCommandDispatcher;
 interface nsIObserver;
 interface nsIBoxObject;
 
-[scriptable, uuid(b16d13c3-837d-445d-8f56-05d83d9b9eae)]
-interface nsIDOMXULDocument : nsISupports
+[scriptable, uuid(9230f88f-a61f-4fc2-b0a3-79e65d58f94f)]
+interface nsIDOMXULDocument : nsIDOMDocument
 {
   attribute nsIDOMNode                          popupNode;
 
   /**
    * These attributes correspond to trustedGetPopupNode().rangeOffset and
    * rangeParent. They will help you find where in the DOM the popup is
    * happening. Can be accessed from chrome only, and only during a popup
    * event. Accessing any other time will be an error.
--- a/dom/payment/Payment.jsm
+++ b/dom/payment/Payment.jsm
@@ -11,16 +11,17 @@ Cu.import("resource://gre/modules/Servic
 
 this.EXPORTED_SYMBOLS = [];
 
 const PAYMENT_IPC_MSG_NAMES = ["Payment:Pay",
                                "Payment:Success",
                                "Payment:Failed"];
 
 const PREF_PAYMENTPROVIDERS_BRANCH = "dom.payment.provider.";
+const PREF_PAYMENT_BRANCH = "dom.payment.";
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageListenerManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "prefService",
                                    "@mozilla.org/preferences-service;1",
                                    "nsIPrefService");
@@ -31,16 +32,27 @@ function debug (s) {
 
 let PaymentManager =  {
   init: function init() {
     // Payment providers data are stored as a preference.
     this.registeredProviders = null;
 
     this.messageManagers = {};
 
+    // The dom.payment.skipHTTPSCheck pref is supposed to be used only during
+    // development process. This preference should not be active for a
+    // production build.
+    let paymentPrefs = prefService.getBranch(PREF_PAYMENT_BRANCH);
+    this.checkHttps = true;
+    try {
+      if (paymentPrefs.getPrefType("skipHTTPSCheck")) {
+        this.checkHttps = !paymentPrefs.getBoolPref("skipHTTPSCheck");
+      }
+    } catch(e) {}
+
     for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
       ppmm.addMessageListener(msgname, this);
     }
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
   },
 
   /**
@@ -298,17 +310,17 @@ let PaymentManager =  {
 
     if (!provider.uri || !provider.name) {
       this.paymentFailed(aRequestId,
                          "INTERNAL_ERROR_WRONG_REGISTERED_PAY_PROVIDER");
       return true;
     }
 
     // We only allow https for payment providers uris.
-    if (!/^https/.exec(provider.uri.toLowerCase())) {
+    if (this.checkHttps && !/^https/.exec(provider.uri.toLowerCase())) {
       // We should never get this far.
       debug("Payment provider uris must be https: " + provider.uri);
       this.paymentFailed(aRequestId,
                          "INTERNAL_ERROR_NON_HTTPS_PROVIDER_URI");
       return true;
     }
 
     let pldRequest = payloadObject.request;
--- a/dom/permission/PermissionSettings.jsm
+++ b/dom/permission/PermissionSettings.jsm
@@ -103,15 +103,20 @@ this.PermissionSettingsModule = {
   receiveMessage: function receiveMessage(aMessage) {
     debug("PermissionSettings::receiveMessage " + aMessage.name);
     let mm = aMessage.target;
     let msg = aMessage.data;
 
     let result;
     switch (aMessage.name) {
       case "PermissionSettings:AddPermission":
+        if (!aMessage.target.assertPermission("permissions")) {
+          Cu.reportError("PermissionSettings message " + msg.name +
+                         " from a content process with no 'permissions' privileges.");
+          return null;
+        }
         this.addPermission(msg);
         break;
     }
   }
 }
 
 PermissionSettingsModule.init();
--- a/dom/sms/src/ril/SmsDatabaseService.js
+++ b/dom/sms/src/ril/SmsDatabaseService.js
@@ -3,23 +3,24 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
 
 const RIL_SMSDATABASESERVICE_CONTRACTID = "@mozilla.org/sms/rilsmsdatabaseservice;1";
 const RIL_SMSDATABASESERVICE_CID = Components.ID("{a1fa610c-eb6c-4ac2-878f-b005d5e89249}");
 
 const DEBUG = false;
 const DB_NAME = "sms";
-const DB_VERSION = 5;
+const DB_VERSION = 6;
 const STORE_NAME = "sms";
 const MOST_RECENT_STORE_NAME = "most-recent";
 
 const DELIVERY_SENT = "sent";
 const DELIVERY_RECEIVED = "received";
 
 const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
 const DELIVERY_STATUS_SUCCESS = "success";
@@ -184,16 +185,20 @@ SmsDatabaseService.prototype = {
           case 3:
             if (DEBUG) debug("Upgrade to version 4. Add quick threads view.")
             self.upgradeSchema3(db, event.target.transaction);
             break;
           case 4:
             if (DEBUG) debug("Upgrade to version 5. Populate quick threads view.")
             self.upgradeSchema4(event.target.transaction);
             break;
+          case 5:
+            if (DEBUG) debug("Upgrade to version 6. Use PhonenumberJS.")
+            self.upgradeSchema5(event.target.transaction);
+            break;
           default:
             event.target.transaction.abort();
             callback("Old database version: " + event.oldVersion, null);
             break;
         }
         currentVersion++;
       }
     }
@@ -352,16 +357,86 @@ SmsDatabaseService.prototype = {
           body: message.body,
           unreadCount: message.read ? 0 : 1
         }
       }
       cursor.continue();
     }
   },
 
+  upgradeSchema5: function upgradeSchema5(transaction) {
+    let smsStore = transaction.objectStore(STORE_NAME);
+    let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME);
+
+    smsStore.openCursor().onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (!cursor) {
+        return;
+      }
+
+      let needsUpdate = false;
+      let message = cursor.value;
+      if (message.receiver) {
+        if (message.receiver !== "undefined") {
+          if (DEBUG) debug("upgrade message.receiver from: " + message.receiver + "\n");
+          let parsedNumber = PhoneNumberUtils.parse(message.receiver.toString());
+          if (parsedNumber && parsedNumber.internationalNumber) {
+            message.receiver = parsedNumber.internationalNumber;
+            needsUpdate = true;
+          }
+
+          if (DEBUG) debug("upgrade message.receiver to: " + message.receiver + "\n");
+        } else {
+          message.receiver = null;
+          needsUpdate = true;
+        }
+      }
+
+      if (message.sender) {
+        if (message.sender !== "undefined") {
+          if (DEBUG) debug("upgrade message.sender from: " + message.sender + "\n");
+          let parsedNumber = PhoneNumberUtils.parse(message.sender.toString());
+          if (parsedNumber && parsedNumber.internationalNumber) {
+            message.sender = parsedNumber.internationalNumber;
+            needsUpdate = true;
+            if (DEBUG) debug("upgrade message.sender to: " + message.sender + "\n");
+          }
+        } else {
+          message.sender = null;
+          needsUpdate = true;
+        }
+      }
+
+      if (needsUpdate) {
+        cursor.update(message);
+      }
+      cursor.continue();
+    }
+
+    mostRecentStore.openCursor().onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (!cursor) {
+        return;
+      }
+
+      let entry = cursor.value;
+      if (entry.senderOrReceiver) {
+        if (DEBUG) debug("upgrade mostRecentStore from: " + entry.senderOrReceiver + "\n");
+        let parsedNumber = PhoneNumberUtils.parse(entry.senderOrReceiver);
+        if (parsedNumber && parsedNumber.internationalNumber) {
+          entry.senderOrReceiver = parsedNumber.internationalNumber;
+          cursor.update(entry);
+          if (DEBUG) debug("upgrade mostRecentStore to: " + entry.senderOrReceiver + "\n");
+        }
+      }
+
+      cursor.continue();
+    }
+  },
+
   /**
    * Helper function to make the intersection of the partial result arrays
    * obtained within createMessageList.
    *
    * @param keys
    *        Object containing the partial result arrays.
    * @param fiter
    *        Object containing the filter search criteria used to retrieved the
@@ -488,52 +563,82 @@ SmsDatabaseService.prototype = {
     return message.id;
   },
 
 
   /**
    * nsISmsDatabaseService API
    */
 
-  saveReceivedMessage: function saveReceivedMessage(sender, body, messageClass, date) {
+  saveReceivedMessage: function saveReceivedMessage(aSender, aBody, aMessageClass, aDate) {
     let receiver = this.mRIL.rilContext.icc ? this.mRIL.rilContext.icc.msisdn : null;
 
     // Workaround an xpconnect issue with undefined string objects.
     // See bug 808220
     if (receiver === undefined || receiver === "undefined") {
       receiver = null;
     }
 
+    if (receiver) {
+      let parsedNumber = PhoneNumberUtils.parse(receiver);
+      receiver = (parsedNumber && parsedNumber.internationalNumber)
+                 ? parsedNumber.internationalNumber
+                 : receiver;
+    }
+
+    let sender = aSender;
+    if (sender) {
+      let parsedNumber = PhoneNumberUtils.parse(sender);
+      sender = (parsedNumber && parsedNumber.internationalNumber)
+               ? parsedNumber.internationalNumber
+               : sender;
+    }
+
     let message = {delivery:       DELIVERY_RECEIVED,
                    deliveryStatus: DELIVERY_STATUS_SUCCESS,
                    sender:         sender,
                    receiver:       receiver,
-                   body:           body,
-                   messageClass:   messageClass,
-                   timestamp:      date,
+                   body:           aBody,
+                   messageClass:   aMessageClass,
+                   timestamp:      aDate,
                    read:           FILTER_READ_UNREAD};
     return this.saveMessage(message);
   },
 
-  saveSentMessage: function saveSentMessage(receiver, body, date) {
+  saveSentMessage: function saveSentMessage(aReceiver, aBody, aDate) {
     let sender = this.mRIL.rilContext.icc ? this.mRIL.rilContext.icc.msisdn : null;
 
     // Workaround an xpconnect issue with undefined string objects.
     // See bug 808220
     if (sender === undefined || sender === "undefined") {
       sender = null;
     }
 
+    let receiver = aReceiver
+    if (receiver) {
+      let parsedNumber = PhoneNumberUtils.parse(receiver.toString());
+      receiver = (parsedNumber && parsedNumber.internationalNumber)
+                 ? parsedNumber.internationalNumber
+                 : receiver;
+    }
+
+    if (sender) {
+      let parsedNumber = PhoneNumberUtils.parse(sender.toString());
+      sender = (parsedNumber && parsedNumber.internationalNumber)
+               ? parsedNumber.internationalNumber
+               : sender;
+    }
+
     let message = {delivery:       DELIVERY_SENT,
                    deliveryStatus: DELIVERY_STATUS_PENDING,
                    sender:         sender,
                    receiver:       receiver,
-                   body:           body,
+                   body:           aBody,
                    messageClass:   MESSAGE_CLASS_NORMAL,
-                   timestamp:      date,
+                   timestamp:      aDate,
                    read:           FILTER_READ_READ};
     return this.saveMessage(message);
   },
 
   setMessageDeliveryStatus: function setMessageDeliveryStatus(messageId, deliveryStatus) {
     if ((deliveryStatus != DELIVERY_STATUS_SUCCESS)
         && (deliveryStatus != DELIVERY_STATUS_ERROR)) {
       if (DEBUG) {
@@ -620,17 +725,20 @@ SmsDatabaseService.prototype = {
                                                    data.body,
                                                    data.messageClass,
                                                    data.timestamp,
                                                    data.read);
         aRequest.notifyMessageGot(message);
       };
 
       txn.onerror = function onerror(event) {
-        if (DEBUG) debug("Caught error on transaction", event.target.errorCode);
+        if (DEBUG) {
+          if (event.target)
+            debug("Caught error on transaction", event.target.errorCode);
+        }
         //TODO look at event.target.errorCode, pick appropriate error constant
         aRequest.notifyGetMessageFailed(Ci.nsISmsRequest.INTERNAL_ERROR);
       };
     });
   },
 
   deleteMessage: function deleteMessage(messageId, aRequest) {
     let deleted = false;
--- a/dom/sms/tests/marionette/manifest.ini
+++ b/dom/sms/tests/marionette/manifest.ini
@@ -10,17 +10,17 @@ qemu = true
 [test_incoming_delete.js]
 [test_outgoing_delete.js]
 [test_getmessage.js]
 [test_getmessage_notfound.js]
 [test_incoming_multipart.js]
 [test_getmessages.js]
 [test_filter_date.js]
 [test_filter_date_notfound.js]
-[test_filter_number_single.js]
-[test_filter_number_multiple.js]
+;[test_filter_number_single.js]
+;[test_filter_number_multiple.js]
 [test_filter_received.js]
 [test_filter_sent.js]
 [test_filter_read.js]
 [test_filter_unread.js]
 [test_number_of_messages.js]
 [test_mark_msg_read.js]
 [test_mark_msg_read_error.js]
--- a/dom/sms/tests/marionette/test_filter_number_single.js
+++ b/dom/sms/tests/marionette/test_filter_number_single.js
@@ -5,16 +5,17 @@ MARIONETTE_TIMEOUT = 20000;
 
 SpecialPowers.addPermission("sms", true, document);
 SpecialPowers.setBoolPref("dom.sms.enabled", true);
 
 let sms = window.navigator.mozSms;
 let numberMsgs = 10;
 let smsList = new Array();
 let defaultRemoteNumber = "5552227777";
+let defaultRemoteNumberFormats = ["5552227777", "+15552227777"];
 
 function verifyInitialState() {
   log("Verifying initial state.");
   ok(sms, "mozSms");
   // Ensure test is starting clean with no existing sms messages
   deleteAllMsgs(simulateIncomingSms);
 }
 
@@ -138,17 +139,17 @@ function nextRep() {
 
 function getMsgs() {
   // Set the filter and test it via getMessages
   var filter = new MozSmsFilter();
   let foundSmsList = new Array();
 
   // Going to filter for one number only, so set our expected SMS array
   smsList = smsList.filter(function(i) {
-    return i.sender != defaultRemoteNumber ? false: true;
+    return defaultRemoteNumberFormats.indexOf(i.sender) >= 0 ? true : false;
   });
 
   // Set filter for default remote number
   filter.numbers = new Array(defaultRemoteNumber);
 
   log("Getting the SMS messages from sender " + defaultRemoteNumber + ".");
   let request = sms.getMessages(filter, false);
   ok(request instanceof MozSmsRequest,
--- a/dom/sms/tests/marionette/test_getmessage.js
+++ b/dom/sms/tests/marionette/test_getmessage.js
@@ -3,48 +3,54 @@
 
 MARIONETTE_TIMEOUT = 10000;
 
 SpecialPowers.setBoolPref("dom.sms.enabled", true);
 SpecialPowers.addPermission("sms", true, document);
 
 let sms = window.navigator.mozSms;
 let myNumber = "15555215554";
+let myNumberFormats = ["15555215554", "+15555215554"];
 let inText = "Incoming SMS message. Mozilla Firefox OS!";
 let remoteNumber = "5559997777";
+let remoteNumberFormats = ["5559997777", "+15559997777"];
 let outText = "Outgoing SMS message. Mozilla Firefox OS!";
 let gotSmsOnsent = false;
 let gotReqOnsuccess = false;
 let inSmsid = 0;
 let outSmsid = 0;
 let inSmsTimeStamp;
 let outSmsTimeStamp;
 
 function verifyInitialState() {
   log("Verifying initial state.");
   ok(sms, "mozSms");
   simulateIncomingSms();  
 }
 
+function isIn(aVal, aArray, aMsg) {
+  ok(aArray.indexOf(aVal) >= 0, aMsg);
+}
+
 function simulateIncomingSms() {
   log("Simulating incoming SMS.");
 
   sms.onreceived = function onreceived(event) {
     log("Received 'onreceived' smsmanager event.");
     let incomingSms = event.message;
     ok(incomingSms, "incoming sms");
     ok(incomingSms.id, "sms id");
     inSmsId = incomingSms.id;
     log("Received SMS (id: " + inSmsId + ").");
     is(incomingSms.body, inText, "msg body");
     is(incomingSms.delivery, "received", "delivery");
     is(incomingSms.deliveryStatus, "success", "deliveryStatus");
     is(incomingSms.read, false, "read");
     is(incomingSms.receiver, null, "receiver");
-    is(incomingSms.sender, remoteNumber, "sender");
+    isIn(incomingSms.sender, remoteNumberFormats, "sender");
     is(incomingSms.messageClass, "normal", "messageClass");
     ok(incomingSms.timestamp instanceof Date, "timestamp is instanceof date");
     inSmsTimeStamp = incomingSms.timestamp;
     sendSms();
   };
   // Simulate incoming sms sent from remoteNumber to our emulator
   runEmulatorCmd("sms send " + remoteNumber + " " + inText, function(result) {
     is(result[0], "OK", "emulator output");
@@ -60,17 +66,17 @@ function sendSms() {
     ok(sentSms, "outgoing sms");
     ok(sentSms.id, "sms id");
     outSmsId = sentSms.id;
     log("Sent SMS (id: " + outSmsId + ").");
     is(sentSms.body, outText, "msg body");
     is(sentSms.delivery, "sent", "delivery");
     is(sentSms.deliveryStatus, "pending", "deliveryStatus");
     is(sentSms.read, true, "read");
-    is(sentSms.receiver, remoteNumber, "receiver");
+    isIn(sentSms.receiver, remoteNumberFormats, "receiver");
     is(sentSms.sender, null, "sender");
     is(sentSms.messageClass, "normal", "messageClass");
     ok(sentSms.timestamp instanceof Date, "timestamp is instanceof date");  
     outSmsTimeStamp = sentSms.timestamp;
 
     if (gotSmsOnsent && gotReqOnsuccess) { getReceivedSms(); }
   };
 
@@ -109,18 +115,18 @@ function getReceivedSms() {
     ok(event.target.result, "smsrequest event.target.result");
     let foundSms = event.target.result;
     is(foundSms.id, inSmsId, "SMS id matches");
     log("Got SMS (id: " + foundSms.id + ").");
     is(foundSms.body, inText, "SMS msg text matches");
     is(foundSms.delivery, "received", "delivery");
     is(foundSms.deliveryStatus, "success", "deliveryStatus");
     is(foundSms.read, false, "read");
-    is(foundSms.receiver, myNumber, "receiver");
-    is(foundSms.sender, remoteNumber, "sender");
+    isIn(foundSms.receiver, myNumberFormats, "receiver");
+    isIn(foundSms.sender, remoteNumberFormats, "sender");
     is(foundSms.messageClass, "normal", "messageClass");
     ok(foundSms.timestamp instanceof Date, "timestamp is instanceof date");
     is(foundSms.timestamp.getTime(), inSmsTimeStamp.getTime(), "timestamp matches");
     getSentSms();
   };
 
   requestRet.onerror = function(event) {
     log("Received 'onerror' smsrequest event.");
@@ -142,18 +148,18 @@ function getSentSms() {
     ok(event.target.result, "smsrequest event.target.result");
     let foundSms = event.target.result;
     is(foundSms.id, outSmsId, "SMS id matches");
     log("Got SMS (id: " + foundSms.id + ").");
     is(foundSms.body, outText, "SMS msg text matches");
     is(foundSms.delivery, "sent", "delivery");
     is(foundSms.deliveryStatus, "pending", "deliveryStatus");
     is(foundSms.read, true, "read");
-    is(foundSms.receiver, remoteNumber, "receiver");
-    is(foundSms.sender, myNumber, "sender");
+    isIn(foundSms.receiver, remoteNumberFormats, "receiver");
+    isIn(foundSms.sender, myNumberFormats, "sender");
     is(foundSms.messageClass, "normal", "messageClass");
     ok(foundSms.timestamp instanceof Date, "timestamp is instanceof date");
     is(foundSms.timestamp.getTime(), outSmsTimeStamp.getTime(), "timestamp matches");
     deleteMsgs();
   };
 
   requestRet.onerror = function(event) {
     log("Received 'onerror' smsrequest event.");
--- a/dom/sms/tests/marionette/test_getmessages.js
+++ b/dom/sms/tests/marionette/test_getmessages.js
@@ -12,16 +12,20 @@ let smsList = new Array();
 
 function verifyInitialState() {
   log("Verifying initial state.");
   ok(sms, "mozSms");
   // Ensure test is starting clean with no existing sms messages
   deleteAllMsgs(simulateIncomingSms);
 }
 
+function isIn(aVal, aArray, aMsg) {
+  ok(aArray.indexOf(aVal) >= 0, aMsg);
+}
+
 function deleteAllMsgs(nextFunction) {
   let msgList = new Array();
   let smsFilter = new MozSmsFilter;
 
   let request = sms.getMessages(smsFilter, false);
   ok(request instanceof MozSmsRequest,
       "request is instanceof " + request.constructor);
 
@@ -188,22 +192,22 @@ function verifyFoundMsgs(foundSmsList, r
     is(foundSmsList[x].id, smsList[x].id, "id");
     is(foundSmsList[x].body, smsList[x].body, "body");
     is(foundSmsList[x].delivery, smsList[x].delivery, "delivery");
     is(foundSmsList[x].read, smsList[x].read, "read");
 
     // Bug 805799: receiver null when onreceived event is fired, until do a
     // getMessage. Default emulator (receiver) phone number is 15555215554
     if (!smsList[x].receiver) {
-      is(foundSmsList[x].receiver, "15555215554", "receiver");
+      isIn(foundSmsList[x].receiver, ["15555215554", "+15555215554"], "receiver");
     } else {
-      is(foundSmsList[x].receiver, smsList[x].receiver, "receiver");
+      isIn(foundSmsList[x].receiver, [smsList[x].receiver, "+15555215554"], "receiver");
     }
 
-    is(foundSmsList[x].sender, smsList[x].sender, "sender");
+    isIn(foundSmsList[x].sender, [smsList[x].sender, "+15552229797"], "sender");
     is(foundSmsList[x].timestamp.getTime(), smsList[x].timestamp.getTime(),
         "timestamp");
   }
 
   log("Content in all of the returned SMS messages is correct.");
 
   if (!reverse) {
     // Now get messages in reverse
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -15,16 +15,17 @@
 
 #include <android/log.h>
 
 #include "mozilla/Hal.h"
 #include "AudioManager.h"
 #include "gonk/AudioSystem.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
+#include "AudioChannelService.h"
 
 using namespace mozilla::dom::gonk;
 using namespace android;
 using namespace mozilla::hal;
 using namespace mozilla;
 
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AudioManager" , ## args)
 
@@ -293,16 +294,22 @@ AudioManager::GetPhoneState(int32_t* aSt
 NS_IMETHODIMP
 AudioManager::SetPhoneState(int32_t aState)
 {
   if (AudioSystem::setPhoneState(aState)) {
     return NS_ERROR_FAILURE;
   }
 
   mPhoneState = aState;
+
+  nsRefPtr<AudioChannelService> audioChannelService = AudioChannelService::GetAudioChannelService();
+  if (!audioChannelService) {
+    return NS_ERROR_FAILURE;
+  }
+  audioChannelService->SetPhoneInCall(aState == nsIAudioManager::PHONE_STATE_IN_CALL);
   return NS_OK;
 }
 
 //
 // Kids, don't try this at home.  We want this to link and work on
 // both GB and ICS.  Problem is, the symbol exported by audioflinger
 // is different on the two gonks.
 //
--- a/dom/system/gonk/NetworkManager.js
+++ b/dom/system/gonk/NetworkManager.js
@@ -596,17 +596,17 @@ NetworkManager.prototype = {
     }
     if (securityType != WIFI_SECURITY_TYPE_NONE &&
         securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
         securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
 
       debug("Invalid security type.");
       return null;
     }
-    if (!securityId) {
+    if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
       debug("Invalid security password.");
       return null;
     }
     // Using the default values here until application supports these settings.
     if (interfaceIp == "" || prefix == "" ||
         dhcpStartIp == "" || dhcpEndIp == "") {
       debug("Invalid subnet information.");
       return null;
--- a/dom/system/gonk/nsVolumeService.cpp
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -96,19 +96,23 @@ NS_IMETHODIMP nsVolumeService::GetVolume
     nsAutoCString volMountPointSlash = NS_ConvertUTF16toUTF8(vol->MountPoint());
     volMountPointSlash.Append(NS_LITERAL_CSTRING("/"));
     nsDependentCSubstring testStr(realPathBuf, volMountPointSlash.Length());
     if (volMountPointSlash.Equals(testStr)) {
       NS_ADDREF(*aResult = vol);
       return NS_OK;
     }
   }
-  ERR("GetVolumeByPath: Unable to find volume by path: '%s'",
-      NS_LossyConvertUTF16toASCII(aPath).get());
-  return NS_ERROR_NOT_AVAILABLE;
+
+  // In order to support queries by DeviceStorage and the updater, we will fabricate
+  // a volume from the pathname, so that the caller can determine the volume size
+  nsRefPtr<nsVolume> vol = new nsVolume(NS_LITERAL_STRING("fake"),
+  aPath, nsIVolume::STATE_MOUNTED);
+  NS_ADDREF(*aResult = vol);
+  return NS_OK;
 }
 
 already_AddRefed<nsVolume> nsVolumeService::FindVolumeByName(const nsAString &aName)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
   MOZ_ASSERT(NS_IsMainThread());
 
   nsVolume::Array::size_type  numVolumes = mVolumeArray.Length();
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -8292,17 +8292,17 @@ let StkProactiveCmdHelper = {
 
     let text = {
       codingScheme: GsmPDUHelper.readHexOctet()
     };
 
     length--; // -1 for the codingScheme.
     switch (text.codingScheme & 0x0f) {
       case STK_TEXT_CODING_GSM_7BIT_PACKED:
-        text.textString = GsmPDUHelper.readSeptetsToString(length / 7, 0, 0, 0);
+        text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0);
         break;
       case STK_TEXT_CODING_GSM_8BIT:
         text.textString = GsmPDUHelper.read8BitUnpackedToString(length);
         break;
       case STK_TEXT_CODING_UCS2:
         text.textString = GsmPDUHelper.readUCS2String(length);
         break;
     }
--- a/dom/system/gonk/tests/test_ril_worker_icc.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc.js
@@ -431,16 +431,47 @@ add_test(function test_stk_proactive_com
 
   tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs);
   do_check_eq(tlv.value.timeUnit, STK_TIME_UNIT_SECOND);
   do_check_eq(tlv.value.timeInterval, 0x14);
 
   run_next_test();
 });
 
+/**
+ * Verify Proactive Command: Display Text
+ */
+add_test(function test_read_septets_to_string() {
+  let worker = newUint8Worker();
+  let pduHelper = worker.GsmPDUHelper;
+  let berHelper = worker.BerTlvHelper;
+  let stkHelper = worker.StkProactiveCmdHelper;
+
+  let display_text_1 = [
+    0xd0,
+    0x28,
+    0x81, 0x03, 0x01, 0x21, 0x80,
+    0x82, 0x02, 0x81, 0x02,
+    0x0d, 0x1d, 0x00, 0xd3, 0x30, 0x9b, 0xfc, 0x06, 0xc9, 0x5c, 0x30, 0x1a,
+    0xa8, 0xe8, 0x02, 0x59, 0xc3, 0xec, 0x34, 0xb9, 0xac, 0x07, 0xc9, 0x60,
+    0x2f, 0x58, 0xed, 0x15, 0x9b, 0xb9, 0x40,
+  ];
+
+  for (let i = 0; i < display_text_1.length; i++) {
+    pduHelper.writeHexOctet(display_text_1[i]);
+  }
+
+  let berTlv = berHelper.decode(display_text_1.length);
+  let ctlvs = berTlv.value;
+  let tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
+  do_check_eq(tlv.value.textString, "Saldo 2.04 E. Validez 20/05/13. ");
+
+  run_next_test();
+});
+
 add_test(function test_stk_proactive_command_event_list() {
   let worker = newUint8Worker();
   let pduHelper = worker.GsmPDUHelper;
   let berHelper = worker.BerTlvHelper;
   let stkHelper = worker.StkProactiveCmdHelper;
 
   let event_1 = [
     0xD0,
--- a/dom/tests/mochitest/chrome/Makefile.in
+++ b/dom/tests/mochitest/chrome/Makefile.in
@@ -48,16 +48,17 @@ MOCHITEST_CHROME_FILES = \
 		test_selectAtPoint.html \
 		selectAtPoint.html \
 		test_bug799299.xul \
 		file_bug799299.xul \
 		test_bug800817.xul \
 		file_bug800817.xul \
 		test_subscript_bindings.xul \
 		file_subscript_bindings.js \
+		test_sandbox_eventhandler.xul \
 		$(NULL)
 
 ifeq (WINNT,$(OS_ARCH))
 MOCHITEST_CHROME_FILES += \
 		test_sizemode_attribute.xul \
 		sizemode_attribute.xul \
 		$(NULL)
 endif
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/chrome/test_sandbox_eventhandler.xul
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=817284
+-->
+<window title="Mozilla Bug 817284"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=817284"
+     target="_blank">Mozilla Bug 817284</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+
+  /** Test for Bug 817284 **/
+  var cu = Components.utils;
+  var sb = cu.Sandbox("http://example.com", {wantXHRConstructor: true});
+
+  // Test event handler calls
+  var xhr = cu.evalInSandbox(
+      'var xhr = new XMLHttpRequest();\
+       var called = false;\
+       xhr.onload = function() { called = true; };\
+       xhr', sb);
+
+  var e = document.createEvent("Events");
+  e.initEvent("load", false, false);
+  xhr.dispatchEvent(e);
+  is(cu.evalInSandbox('called', sb), true, "Event handler should have been called");
+  ]]>
+  </script>
+</window>
--- a/dom/webidl/HTMLOptionsCollection.webidl
+++ b/dom/webidl/HTMLOptionsCollection.webidl
@@ -5,17 +5,16 @@
  *
  * The origin of this IDL file is
  * http://www.w3.org/TR/2012/WD-html5-20120329/
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
-interface HTMLElement;
 interface HTMLOptionElement;
 interface HTMLOptGroupElement;
 
 interface HTMLOptionsCollection : HTMLCollection {
            attribute unsigned long length;
   [Throws]
   getter object? namedItem(DOMString name);
   [Throws]
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -34,16 +34,17 @@ webidl_files = \
   EventListener.webidl \
   EventTarget.webidl \
   FileHandle.webidl \
   FileList.webidl \
   FileReaderSync.webidl \
   Function.webidl \
   GainNode.webidl \
   HTMLCollection.webidl \
+  HTMLElement.webidl \
   HTMLOptionsCollection.webidl \
   HTMLPropertiesCollection.webidl \
   ImageData.webidl \
   Node.webidl \
   NodeList.webidl \
   PaintRequestList.webidl \
   PannerNode.webidl \
   Performance.webidl \
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -1936,17 +1936,21 @@ function WifiWorker() {
 
       // We start the connection information timer when we associate, but
       // don't have our IP address until here. Make sure that we fire a new
       // connectionInformation event with the IP address the next time the
       // timer fires.
       self._lastConnectionInfo = null;
       self._fireEvent("onconnect", { network: netToDOM(self.currentNetwork) });
     } else {
-      WifiManager.reassociate(function(){});
+      // NB: We have to call disconnect first. Otherwise, we only reauth with
+      // the existing AP and don't retrigger DHCP.
+      WifiManager.disconnect(function() {
+        WifiManager.reassociate(function(){});
+      });
     }
   };
 
   WifiManager.onscanresultsavailable = function() {
     if (self._scanStuckTimer) {
       // We got scan results! We must not be stuck for now, try again.
       self._scanStuckTimer.cancel();
       self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
--- a/dom/workers/Worker.cpp
+++ b/dom/workers/Worker.cpp
@@ -212,29 +212,27 @@ private:
   {
     return ConstructInternal(aCx, aArgc, aVp, false, Class());
   }
 
   static void
   Finalize(JSFreeOp* aFop, JSObject* aObj)
   {
     JS_ASSERT(JS_GetClass(aObj) == Class());
-    WorkerPrivate* worker =
-      UnwrapDOMObject<WorkerPrivate>(aObj, eRegularDOMObject);
+    WorkerPrivate* worker = UnwrapDOMObject<WorkerPrivate>(aObj);
     if (worker) {
       worker->_finalize(aFop);
     }
   }
 
   static void
   Trace(JSTracer* aTrc, JSObject* aObj)
   {
     JS_ASSERT(JS_GetClass(aObj) == Class());
-    WorkerPrivate* worker =
-      UnwrapDOMObject<WorkerPrivate>(aObj, eRegularDOMObject);
+    WorkerPrivate* worker = UnwrapDOMObject<WorkerPrivate>(aObj);
     if (worker) {
       worker->_trace(aTrc);
     }
   }
 
   static JSBool
   Terminate(JSContext* aCx, unsigned aArgc, jsval* aVp)
   {
@@ -272,31 +270,27 @@ private:
                              &message, &transferable)) {
       return false;
     }
 
     return worker->PostMessage(aCx, message, transferable);
   }
 };
 
-MOZ_STATIC_ASSERT(prototypes::MaxProtoChainLength == 3,
-                  "The MaxProtoChainLength must match our manual DOMJSClasses");
-
 DOMJSClass Worker::sClass = {
   {
     "Worker",
     JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2) |
     JSCLASS_IMPLEMENTS_BARRIERS,
     JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Finalize,
     NULL, NULL, NULL, NULL, Trace
   },
   {
-    { prototypes::id::EventTarget_workers, prototypes::id::_ID_Count,
-      prototypes::id::_ID_Count },
+    INTERFACE_CHAIN_1(prototypes::id::EventTarget_workers),
     false,
     &sWorkerNativePropertyHooks
   }
 };
 
 JSPropertySpec Worker::sProperties[] = {
   { sEventStrings[STRING_onerror], STRING_onerror, PROPERTY_FLAGS,
     JSOP_WRAPPER(GetEventListener), JSOP_WRAPPER(SetEventListener) },
@@ -360,78 +354,72 @@ private:
   ~ChromeWorker();
 
   static WorkerPrivate*
   GetInstancePrivate(JSContext* aCx, JSObject* aObj, const char* aFunctionName)
   {
     if (aObj) {
       JSClass* classPtr = JS_GetClass(aObj);
       if (classPtr == Class()) {
-        return UnwrapDOMObject<WorkerPrivate>(aObj, eRegularDOMObject);
+        return UnwrapDOMObject<WorkerPrivate>(aObj);
       }
     }
 
     return Worker::GetInstancePrivate(aCx, aObj, aFunctionName);
   }
 
   static JSBool
   Construct(JSContext* aCx, unsigned aArgc, jsval* aVp)
   {
     return ConstructInternal(aCx, aArgc, aVp, true, Class());
   }
 
   static void
   Finalize(JSFreeOp* aFop, JSObject* aObj)
   {
     JS_ASSERT(JS_GetClass(aObj) == Class());
-    WorkerPrivate* worker =
-      UnwrapDOMObject<WorkerPrivate>(aObj, eRegularDOMObject);
+    WorkerPrivate* worker = UnwrapDOMObject<WorkerPrivate>(aObj);
     if (worker) {
       worker->_finalize(aFop);
     }
   }
 
   static void
   Trace(JSTracer* aTrc, JSObject* aObj)
   {
     JS_ASSERT(JS_GetClass(aObj) == Class());
-    WorkerPrivate* worker =
-      UnwrapDOMObject<WorkerPrivate>(aObj, eRegularDOMObject);
+    WorkerPrivate* worker = UnwrapDOMObject<WorkerPrivate>(aObj);
     if (worker) {
       worker->_trace(aTrc);
     }
   }
 };
 
-MOZ_STATIC_ASSERT(prototypes::MaxProtoChainLength == 3,
-                  "The MaxProtoChainLength must match our manual DOMJSClasses");
-
 DOMJSClass ChromeWorker::sClass = {
   { "ChromeWorker",
     JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2) |
     JSCLASS_IMPLEMENTS_BARRIERS,
     JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Finalize,
     NULL, NULL, NULL, NULL, Trace,
   },
   {
-    { prototypes::id::EventTarget_workers, prototypes::id::_ID_Count,
-      prototypes::id::_ID_Count },
+    INTERFACE_CHAIN_1(prototypes::id::EventTarget_workers),
     false,
     &sWorkerNativePropertyHooks
   }
 };
 
 WorkerPrivate*
 Worker::GetInstancePrivate(JSContext* aCx, JSObject* aObj,
                            const char* aFunctionName)
 {
   JSClass* classPtr = JS_GetClass(aObj);
   if (classPtr == Class() || classPtr == ChromeWorker::Class()) {
-    return UnwrapDOMObject<WorkerPrivate>(aObj, eRegularDOMObject);
+    return UnwrapDOMObject<WorkerPrivate>(aObj);
   }
 
   JS_ReportErrorNumber(aCx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
                        Class()->name, aFunctionName, classPtr->name);
   return NULL;
 }
 
 } // anonymous namespace
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -48,17 +48,17 @@
   JSPROP_ENUMERATE
 
 using namespace mozilla;
 using namespace mozilla::dom;
 USING_WORKERS_NAMESPACE
 
 namespace {
 
-class WorkerGlobalScope : public EventTarget
+class WorkerGlobalScope : public workers::EventTarget
 {
   static JSClass sClass;
   static JSPropertySpec sProperties[];
   static JSFunctionSpec sFunctions[];
 
   enum
   {
     SLOT_wrappedScope = 0,
@@ -765,18 +765,17 @@ private:
     return true;
   }
 
   static DedicatedWorkerGlobalScope*
   GetInstancePrivate(JSContext* aCx, JSObject* aObj, const char* aFunctionName)
   {
     JSClass* classPtr = JS_GetClass(aObj);
     if (classPtr == Class()) {
-      return UnwrapDOMObject<DedicatedWorkerGlobalScope>(aObj,
-                                                         eRegularDOMObject);
+      return UnwrapDOMObject<DedicatedWorkerGlobalScope>(aObj);
     }
 
     JS_ReportErrorNumber(aCx, js_GetErrorMessage, NULL,
                          JSMSG_INCOMPATIBLE_PROTO, Class()->name, aFunctionName,
                          classPtr->name);
     return NULL;
   }
 
@@ -801,29 +800,29 @@ private:
     return true;
   }
 
   static void
   Finalize(JSFreeOp* aFop, JSObject* aObj)
   {
     JS_ASSERT(JS_GetClass(aObj) == Class());
     DedicatedWorkerGlobalScope* scope =
-      UnwrapDOMObject<DedicatedWorkerGlobalScope>(aObj, eRegularDOMObject);
+      UnwrapDOMObject<DedicatedWorkerGlobalScope>(aObj);
     if (scope) {
       DestroyProtoAndIfaceCache(aObj);
       scope->_finalize(aFop);
     }
   }
 
   static void
   Trace(JSTracer* aTrc, JSObject* aObj)
   {
     JS_ASSERT(JS_GetClass(aObj) == Class());
     DedicatedWorkerGlobalScope* scope =
-      UnwrapDOMObject<DedicatedWorkerGlobalScope>(aObj, eRegularDOMObject);
+      UnwrapDOMObject<DedicatedWorkerGlobalScope>(aObj);
     if (scope) {
       TraceProtoAndIfaceCache(aTrc, aObj);
       scope->_trace(aTrc);
     }
   }
 
   static JSBool
   PostMessage(JSContext* aCx, unsigned aArgc, jsval* aVp)
@@ -845,33 +844,29 @@ private:
                              &message, &transferable)) {
       return false;
     }
 
     return scope->mWorker->PostMessageToParent(aCx, message, transferable);
   }
 };
 
-MOZ_STATIC_ASSERT(prototypes::MaxProtoChainLength == 3,
-                  "The MaxProtoChainLength must match our manual DOMJSClasses");
-
 DOMJSClass DedicatedWorkerGlobalScope::sClass = {
   {
     // We don't have to worry about Xray expando slots here because we'll never
     // have an Xray wrapper to a worker global scope.
     "DedicatedWorkerGlobalScope",
     JSCLASS_DOM_GLOBAL | JSCLASS_IS_DOMJSCLASS | JSCLASS_IMPLEMENTS_BARRIERS |
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(3) | JSCLASS_NEW_RESOLVE,
     JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
     JS_EnumerateStub, reinterpret_cast<JSResolveOp>(Resolve), JS_ConvertStub,
     Finalize, NULL, NULL, NULL, NULL, Trace
   },
   {
-    { prototypes::id::EventTarget_workers, prototypes::id::_ID_Count,
-      prototypes::id::_ID_Count },
+    INTERFACE_CHAIN_1(prototypes::id::EventTarget_workers),
     false,
     &sWorkerNativePropertyHooks
   }
 };
 
 JSPropertySpec DedicatedWorkerGlobalScope::sProperties[] = {
   { sEventStrings[STRING_onmessage], STRING_onmessage, PROPERTY_FLAGS,
     JSOP_WRAPPER(GetEventListener), JSOP_WRAPPER(SetEventListener) },
@@ -893,17 +888,17 @@ WorkerGlobalScope::GetInstancePrivate(JS
 {
   JSClass* classPtr = JS_GetClass(aObj);
 
   // We can only make DedicatedWorkerGlobalScope, not WorkerGlobalScope, so this
   // should never happen.
   JS_ASSERT(classPtr != Class());
 
   if (classPtr == DedicatedWorkerGlobalScope::Class()) {
-    return UnwrapDOMObject<DedicatedWorkerGlobalScope>(aObj, eRegularDOMObject);
+    return UnwrapDOMObject<DedicatedWorkerGlobalScope>(aObj);
   }
 
   JS_ReportErrorNumber(aCx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
                        sClass.name, aFunctionName, classPtr->name);
   return NULL;
 }
 
 } /* anonymous namespace */
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -678,21 +678,24 @@ SampleValue(float aPortion, Animation& a
     return;
   }
 
   nsCSSValueList* interpolatedList = interpolatedValue.GetCSSValueListValue();
 
   TransformData& data = aAnimation.data().get_TransformData();
   nsPoint origin = data.origin();
   int32_t auPerCSSPixel = nsDeviceContext::AppUnitsPerCSSPixel();
+  nsDisplayTransform::FrameTransformProperties props(interpolatedList,
+                                                     data.mozOrigin(),
+                                                     data.perspectiveOrigin(),
+                                                     data.perspective());
   gfx3DMatrix transform =
-    nsDisplayTransform::GetResultingTransformMatrix(
-      nullptr, origin, auPerCSSPixel,
-      &data.bounds(), interpolatedList, &data.mozOrigin(),
-      &data.perspectiveOrigin(), &data.perspective());
+    nsDisplayTransform::GetResultingTransformMatrix(props, data.origin(),
+                                                    nsDeviceContext::AppUnitsPerCSSPixel(),
+                                                    &data.bounds());
   // NB: See nsDisplayTransform::GetTransform().
   gfxPoint3D newOrigin =
     gfxPoint3D(NS_round(NSAppUnitsToFloatPixels(origin.x, auPerCSSPixel)),
                NS_round(NSAppUnitsToFloatPixels(origin.y, auPerCSSPixel)),
                0.0f);
   transform.Translate(newOrigin);
 
   InfallibleTArray<TransformFunction>* functions = new InfallibleTArray<TransformFunction>();
--- a/ipc/chromium/src/base/histogram.cc
+++ b/ipc/chromium/src/base/histogram.cc
@@ -425,37 +425,37 @@ size_t Histogram::SampleSet::SizeOfExclu
 {
   // We're not allowed to do deep dives into STL data structures.  This
   // is as close as we can get to measuring this array.
   return aMallocSizeOf(&counts_[0]);
 }
 
 Histogram::Histogram(const std::string& name, Sample minimum,
                      Sample maximum, size_t bucket_count)
-  : histogram_name_(name),
+  : sample_(),
+    histogram_name_(name),
     declared_min_(minimum),
     declared_max_(maximum),
     bucket_count_(bucket_count),
     flags_(kNoFlags),
     ranges_(bucket_count + 1, 0),
-    range_checksum_(0),
-    sample_() {
+    range_checksum_(0) {
   Initialize();
 }
 
 Histogram::Histogram(const std::string& name, TimeDelta minimum,
                      TimeDelta maximum, size_t bucket_count)
-  : histogram_name_(name),
+  : sample_(),
+    histogram_name_(name),
     declared_min_(static_cast<int> (minimum.InMilliseconds())),
     declared_max_(static_cast<int> (maximum.InMilliseconds())),
     bucket_count_(bucket_count),
     flags_(kNoFlags),
     ranges_(bucket_count + 1, 0),
-    range_checksum_(0),
-    sample_() {
+    range_checksum_(0) {
   Initialize();
 }
 
 Histogram::~Histogram() {
   if (StatisticsRecorder::dump_on_exit()) {
     std::string output;
     WriteAscii(true, "\n", &output);
     LOG(INFO) << output;
@@ -554,17 +554,17 @@ const std::string Histogram::GetAsciiBuc
   else
     StringAppendF(&result, "%d", ranges(i));
   return result;
 }
 
 // Update histogram data with new sample.
 void Histogram::Accumulate(Sample value, Count count, size_t index) {
   // Note locking not done in this version!!!
-  sample_.Accumulate(value, count, index);
+  sample_.AccumulateWithExponentialStats(value, count, index);
 }
 
 void Histogram::SetBucketRange(size_t i, Sample value) {
   DCHECK_GT(bucket_count_, i);
   ranges_[i] = value;
 }
 
 bool Histogram::ValidateBucketRanges() const {
@@ -701,42 +701,58 @@ void Histogram::WriteAsciiBucketGraph(do
 
 //------------------------------------------------------------------------------
 // Methods for the Histogram::SampleSet class
 //------------------------------------------------------------------------------
 
 Histogram::SampleSet::SampleSet()
     : counts_(),
       sum_(0),
+      sum_squares_(0),
+      log_sum_(0),
+      log_sum_squares_(0),
       redundant_count_(0) {
 }
 
 Histogram::SampleSet::~SampleSet() {
 }
 
 void Histogram::SampleSet::Resize(const Histogram& histogram) {
   counts_.resize(histogram.bucket_count(), 0);
 }
 
 void Histogram::SampleSet::CheckSize(const Histogram& histogram) const {
   DCHECK_EQ(histogram.bucket_count(), counts_.size());
 }
 
 
-void Histogram::SampleSet::Accumulate(Sample value,  Count count,
-                                      size_t index) {
+void Histogram::SampleSet::AccumulateWithLinearStats(Sample value,
+                                                     Count count,
+                                                     size_t index) {
   DCHECK(count == 1 || count == -1);
   counts_[index] += count;
-  sum_ += count * value;
+  int64_t amount = static_cast<int64_t>(count) * value;
+  sum_ += amount;
+  sum_squares_ += amount * value;
   redundant_count_ += count;
   DCHECK_GE(counts_[index], 0);
   DCHECK_GE(sum_, 0);
   DCHECK_GE(redundant_count_, 0);
 }
 
+void Histogram::SampleSet::AccumulateWithExponentialStats(Sample value,
+                                                          Count count,
+                                                          size_t index) {
+  AccumulateWithLinearStats(value, count, index);
+  DCHECK_GE(value, 0);
+  double value_log = log(static_cast<double>(value) + 1);
+  log_sum_ += count * value_log;
+  log_sum_squares_ += count * value_log * value_log;
+}
+
 Count Histogram::SampleSet::TotalCount() const {
   Count total = 0;
   for (Counts::const_iterator it = counts_.begin();
        it != counts_.end();
        ++it) {
     total += *it;
   }
   return total;
@@ -846,16 +862,20 @@ Histogram* LinearHistogram::FactoryTimeG
   return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(),
                     bucket_count, flags);
 }
 
 Histogram::ClassType LinearHistogram::histogram_type() const {
   return LINEAR_HISTOGRAM;
 }
 
+void LinearHistogram::Accumulate(Sample value, Count count, size_t index) {
+  sample_.AccumulateWithLinearStats(value, count, index);
+}
+
 void LinearHistogram::SetRangeDescriptions(
     const DescriptionPair descriptions[]) {
   for (int i =0; descriptions[i].description; ++i) {
     bucket_description_[descriptions[i].sample] = descriptions[i].description;
   }
 }
 
 LinearHistogram::LinearHistogram(const std::string& name,
@@ -950,17 +970,17 @@ FlagHistogram::FactoryGet(const std::str
   Histogram *h(nullptr);
 
   if (!StatisticsRecorder::FindHistogram(name, &h)) {
     // To avoid racy destruction at shutdown, the following will be leaked.
     FlagHistogram *fh = new FlagHistogram(name);
     fh->InitializeBucketRange();
     fh->SetFlags(flags);
     size_t zero_index = fh->BucketIndex(0);
-    fh->Histogram::Accumulate(0, 1, zero_index);
+    fh->LinearHistogram::Accumulate(0, 1, zero_index);
     h = StatisticsRecorder::RegisterOrDeleteDuplicate(fh);
   }
 
   return h;
 }
 
 FlagHistogram::FlagHistogram(const std::string &name)
   : BooleanHistogram(name), mSwitched(false) {
@@ -976,19 +996,19 @@ void
 FlagHistogram::Accumulate(Sample value, Count count, size_t index)
 {
   if (mSwitched) {
     return;
   }
 
   mSwitched = true;
   DCHECK_EQ(value, 1);
-  Histogram::Accumulate(value, 1, index);
+  LinearHistogram::Accumulate(value, 1, index);
   size_t zero_index = BucketIndex(0);
-  Histogram::Accumulate(0, -1, zero_index);
+  LinearHistogram::Accumulate(0, -1, zero_index);
 }
 
 void
 FlagHistogram::AddSampleSet(const SampleSet& sample) {
   DCHECK_EQ(bucket_count(), sample.size());
   // We can't be sure the SampleSet provided came from another FlagHistogram,
   // so we take the following steps:
   //  - If our flag has already been set do nothing.
--- a/ipc/chromium/src/base/histogram.h
+++ b/ipc/chromium/src/base/histogram.h
@@ -329,22 +329,27 @@ class Histogram {
     explicit SampleSet();
     ~SampleSet();
 
     // Adjust size of counts_ for use with given histogram.
     void Resize(const Histogram& histogram);
     void CheckSize(const Histogram& histogram) const;
 
     // Accessor for histogram to make routine additions.
-    void Accumulate(Sample value, Count count, size_t index);
+    void AccumulateWithLinearStats(Sample value, Count count, size_t index);
+    // Alternate routine for exponential histograms.
+    void AccumulateWithExponentialStats(Sample value, Count count, size_t index);
 
     // Accessor methods.
     Count counts(size_t i) const { return counts_[i]; }
     Count TotalCount() const;
     int64_t sum() const { return sum_; }
+    uint64_t sum_squares() const { return sum_squares_; }
+    double log_sum() const { return log_sum_; }
+    double log_sum_squares() const { return log_sum_squares_; }
     int64_t redundant_count() const { return redundant_count_; }
     size_t size() const { return counts_.size(); }
 
     // Arithmetic manipulation of corresponding elements of the set.
     void Add(const SampleSet& other);
     void Subtract(const SampleSet& other);
 
     bool Serialize(Pickle* pickle) const;
@@ -355,16 +360,23 @@ class Histogram {
    protected:
     // Actual histogram data is stored in buckets, showing the count of values
     // that fit into each bucket.
     Counts counts_;
 
     // Save simple stats locally.  Note that this MIGHT get done in base class
     // without shared memory at some point.
     int64_t sum_;         // sum of samples.
+    uint64_t sum_squares_; // sum of squares of samples.
+
+    // These fields may or may not be updated at the discretion of the
+    // histogram.  We use the natural log and compute ln(sample+1) so that
+    // zeros are handled sanely.
+    double log_sum_;      // sum of logs of samples.
+    double log_sum_squares_; // sum of squares of logs of samples
 
    private:
     // Allow tests to corrupt our innards for testing purposes.
     FRIEND_TEST(HistogramTest, CorruptSampleCounts);
 
     // To help identify memory corruption, we reduntantly save the number of
     // samples we've accumulated into all of our buckets.  We can compare this
     // count to the sum of the counts in all buckets, and detect problems.  Note
@@ -506,16 +518,20 @@ class Histogram {
   void SetBucketRange(size_t i, Sample value);
 
   // Validate that ranges_ was created sensibly (top and bottom range
   // values relate properly to the declared_min_ and declared_max_)..
   bool ValidateBucketRanges() const;
 
   virtual uint32_t CalculateRangeChecksum() const;
 
+  // Finally, provide the state that changes with the addition of each new
+  // sample.
+  SampleSet sample_;
+
  private:
   // Allow tests to corrupt our innards for testing purposes.
   FRIEND_TEST(HistogramTest, CorruptBucketBounds);
   FRIEND_TEST(HistogramTest, CorruptSampleCounts);
   FRIEND_TEST(HistogramTest, Crc32SampleHash);
   FRIEND_TEST(HistogramTest, Crc32TableTest);
 
   friend class StatisticsRecorder;  // To allow it to delete duplicates.
@@ -573,20 +589,16 @@ class Histogram {
   // The dimension of ranges_ is bucket_count + 1.
   Ranges ranges_;
 
   // For redundancy, we store a checksum of all the sample ranges when ranges
   // are generated.  If ever there is ever a difference, then the histogram must
   // have been corrupted.
   uint32_t range_checksum_;
 
-  // Finally, provide the state that changes with the addition of each new
-  // sample.
-  SampleSet sample_;
-
   DISALLOW_COPY_AND_ASSIGN(Histogram);
 };
 
 //------------------------------------------------------------------------------
 
 // LinearHistogram is a more traditional histogram, with evenly spaced
 // buckets.
 class LinearHistogram : public Histogram {
@@ -604,16 +616,18 @@ class LinearHistogram : public Histogram
                                    TimeDelta minimum,
                                    TimeDelta maximum,
                                    size_t bucket_count,
                                    Flags flags);
 
   // Overridden from Histogram:
   virtual ClassType histogram_type() const;
 
+  virtual void Accumulate(Sample value, Count count, size_t index);
+
   // Store a list of number/text values for use in rendering the histogram.
   // The last element in the array has a null in its "description" slot.
   virtual void SetRangeDescriptions(const DescriptionPair descriptions[]);
 
  protected:
   LinearHistogram(const std::string& name, Sample minimum,
                   Sample maximum, size_t bucket_count);
 
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -33,63 +33,130 @@ const size_t ArenaShift = PageShift;
 const size_t PageSize = size_t(1) << PageShift;
 const size_t ArenaSize = size_t(1) << ArenaShift;
 const size_t ArenaMask = ArenaSize - 1;
 
 const size_t ChunkShift = 20;
 const size_t ChunkSize = size_t(1) << ChunkShift;
 const size_t ChunkMask = ChunkSize - 1;
 
+const size_t CellShift = 3;
+const size_t CellSize = size_t(1) << CellShift;
+const size_t CellMask = CellSize - 1;
+
+/* These are magic constants derived from actual offsets in gc/Heap.h. */
+const size_t ChunkMarkBitmapOffset = 1032376;
+const size_t ChunkMarkBitmapBits = 129024;
+
+/*
+ * Live objects are marked black. How many other additional colors are available
+ * depends on the size of the GCThing. Objects marked gray are eligible for
+ * cycle collection.
+ */
+static const uint32_t BLACK = 0;
+static const uint32_t GRAY = 1;
+
 } /* namespace gc */
 } /* namespace js */
 
 namespace JS {
-
 namespace shadow {
 
 struct ArenaHeader
 {
     JSCompartment *compartment;
 };
 
+struct Compartment
+{
+    bool needsBarrier_;
+
+    Compartment() : needsBarrier_(false) {}
+};
+
 } /* namespace shadow */
+} /* namespace JS */
+
+namespace js {
+namespace gc {
 
-static inline shadow::ArenaHeader *
+static JS_ALWAYS_INLINE uintptr_t *
+GetGCThingMarkBitmap(const void *thing)
+{
+    uintptr_t addr = uintptr_t(thing);
+    addr &= ~js::gc::ChunkMask;
+    addr |= js::gc::ChunkMarkBitmapOffset;
+    return reinterpret_cast<uintptr_t *>(addr);
+}
+
+static JS_ALWAYS_INLINE void
+GetGCThingMarkWordAndMask(const void *thing, uint32_t color,
+                          uintptr_t **wordp, uintptr_t *maskp)
+{
+    uintptr_t addr = uintptr_t(thing);
+    size_t bit = (addr & js::gc::ChunkMask) / js::gc::CellSize + color;
+    JS_ASSERT(bit < js::gc::ChunkMarkBitmapBits);
+    uintptr_t *bitmap = GetGCThingMarkBitmap(thing);
+    *maskp = uintptr_t(1) << (bit % JS_BITS_PER_WORD);
+    *wordp = &bitmap[bit / JS_BITS_PER_WORD];
+}
+
+static JS_ALWAYS_INLINE JS::shadow::ArenaHeader *
 GetGCThingArena(void *thing)
 {
     uintptr_t addr = uintptr_t(thing);
     addr &= ~js::gc::ArenaMask;
-    return reinterpret_cast<shadow::ArenaHeader *>(addr);
+    return reinterpret_cast<JS::shadow::ArenaHeader *>(addr);
 }
 
-static inline JSCompartment *
+} /* namespace gc */
+} /* namespace js */
+
+namespace JS {
+
+static JS_ALWAYS_INLINE JSCompartment *
 GetGCThingCompartment(void *thing)
 {
     JS_ASSERT(thing);
-    return GetGCThingArena(thing)->compartment;
+    return js::gc::GetGCThingArena(thing)->compartment;
 }
 
-static inline JSCompartment *
+static JS_ALWAYS_INLINE JSCompartment *
 GetObjectCompartment(JSObject *obj)
 {
     return GetGCThingCompartment(obj);
 }
 
+static JS_ALWAYS_INLINE bool
+GCThingIsMarkedGray(void *thing)
+{
+    uintptr_t *word, mask;
+    js::gc::GetGCThingMarkWordAndMask(thing, js::gc::GRAY, &word, &mask);
+    return *word & mask;
+}
+
+static JS_ALWAYS_INLINE bool
+IsIncrementalBarrierNeededOnGCThing(void *thing, JSGCTraceKind kind)
+{
+    JSCompartment *comp = GetGCThingCompartment(thing);
+    return reinterpret_cast<shadow::Compartment *>(comp)->needsBarrier_;
+}
+
 /*
  * This should be called when an object that is marked gray is exposed to the JS
  * engine (by handing it to running JS code or writing it into live JS
  * data). During incremental GC, since the gray bits haven't been computed yet,
  * we conservatively mark the object black.
  */
 static JS_ALWAYS_INLINE void
 ExposeGCThingToActiveJS(void *thing, JSGCTraceKind kind)
 {
     JS_ASSERT(kind != JSTRACE_SHAPE);
 
-    if (js::GCThingIsMarkedGray(thing))
+    if (GCThingIsMarkedGray(thing))
         js::UnmarkGrayGCThingRecursively(thing, kind);
-    else if (js::IsIncrementalBarrierNeededOnGCThing(thing, kind))
+    else if (IsIncrementalBarrierNeededOnGCThing(thing, kind))
         js::IncrementalReferenceBarrier(thing);
 }
 
 } /* namespace JS */
 
 #endif /* js_heap_api_h___ */
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -443,16 +443,42 @@ GCSlice(JSContext *cx, unsigned argc, js
     }
 
     GCDebugSlice(cx->runtime, limit, budget);
     *vp = JSVAL_VOID;
     return JS_TRUE;
 }
 
 static JSBool
+GCState(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (argc != 0) {
+        RootedObject callee(cx, &args.callee());
+        ReportUsageError(cx, callee, "Too many arguments");
+        return false;
+    }
+
+    const char *state;
+    gc::State globalState = cx->runtime->gcIncrementalState;
+    if (globalState == gc::NO_INCREMENTAL)
+        state = "none";
+    else if (globalState == gc::MARK)
+        state = "mark";
+    else if (globalState == gc::SWEEP)
+        state = "sweep";
+    else
+        JS_NOT_REACHED("Unobserveable global GC state");
+
+    *vp = StringValue(js_NewStringCopyZ(cx, state));
+    return true;
+}
+
+static JSBool
 GCPreserveCode(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (argc != 0) {
         RootedObject callee(cx, &args.callee());
         ReportUsageError(cx, callee, "Wrong number of arguments");
         return JS_FALSE;
@@ -899,16 +925,20 @@ static JSFunctionSpecWithHelp TestingFun
     JS_FN_HELP("verifypostbarriers", VerifyPostBarriers, 0, 0,
 "verifypostbarriers()",
 "  Start or end a run of the post-write barrier verifier."),
 
     JS_FN_HELP("gcslice", GCSlice, 1, 0,
 "gcslice(n)",
 "  Run an incremental GC slice that marks about n objects."),
 
+    JS_FN_HELP("gcstate", GCState, 0, 0,
+"gcstate()",
+"  Report the global GC state."),
+
     JS_FN_HELP("gcPreserveCode", GCPreserveCode, 0, 0,
 "gcPreserveCode()",
 "  Preserve JIT code during garbage collections."),
 
     JS_FN_HELP("deterministicgc", DeterministicGC, 1, 0,
 "deterministicgc(true|false)",
 "  If true, only allow determinstic GCs to run."),
 #endif
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -6745,18 +6745,18 @@ GrowSrcNotes(JSContext *cx, BytecodeEmit
 
 jssrcnote *
 frontend::AddToSrcNoteDelta(JSContext *cx, BytecodeEmitter *bce, jssrcnote *sn, ptrdiff_t delta)
 {
     ptrdiff_t base, limit, newdelta, diff;
     int index;
 
     /*
-     * Called only from OptimizeSpanDeps and FinishTakingSrcNotes to add to
-     * main script note deltas, and only by a small positive amount.
+     * Called only from FinishTakingSrcNotes to add to main script note
+     * deltas, and only by a small positive amount.
      */
     JS_ASSERT(bce->current == &bce->main);
     JS_ASSERT((unsigned) delta < (unsigned) SN_XDELTA_LIMIT);
 
     base = SN_DELTA(sn);
     limit = SN_IS_XDELTA(sn) ? SN_XDELTA_LIMIT : SN_DELTA_LIMIT;
     newdelta = base + delta;
     if (newdelta < limit) {
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -29,24 +29,16 @@ namespace js {
 class FreeOp;
 
 namespace gc {
 
 struct Arena;
 struct ArenaHeader;
 struct Chunk;
 
-/*
- * Live objects are marked black. How many other additional colors are available
- * depends on the size of the GCThing. Objects marked gray are eligible for
- * cycle collection.
- */
-static const uint32_t BLACK = 0;
-static const uint32_t GRAY = 1;
-
 /* The GC allocation kinds. */
 enum AllocKind {
     FINALIZE_OBJECT0,
     FINALIZE_OBJECT0_BACKGROUND,
     FINALIZE_OBJECT2,
     FINALIZE_OBJECT2_BACKGROUND,
     FINALIZE_OBJECT4,
     FINALIZE_OBJECT4_BACKGROUND,
@@ -80,20 +72,16 @@ static const unsigned FINALIZE_OBJECT_LI
  */
 static const size_t MAX_BACKGROUND_FINALIZE_KINDS = FINALIZE_LIMIT - FINALIZE_OBJECT_LIMIT / 2;
 
 /*
  * A GC cell is the base class for all GC things.
  */
 struct Cell
 {
-    static const size_t CellShift = 3;
-    static const size_t CellSize = size_t(1) << CellShift;
-    static const size_t CellMask = CellSize - 1;
-
     inline uintptr_t address() const;
     inline ArenaHeader *arenaHeader() const;
     inline Chunk *chunk() const;
     inline AllocKind getAllocKind() const;
     MOZ_ALWAYS_INLINE bool isMarked(uint32_t color = BLACK) const;
     MOZ_ALWAYS_INLINE bool markIfUnmarked(uint32_t color = BLACK) const;
     MOZ_ALWAYS_INLINE void unmark(uint32_t color) const;
 
@@ -111,17 +99,17 @@ struct Cell
 const static uint32_t FreeCommittedArenasThreshold = (32 << 20) / ArenaSize;
 
 /*
  * The mark bitmap has one bit per each GC cell. For multi-cell GC things this
  * wastes space but allows to avoid expensive devisions by thing's size when
  * accessing the bitmap. In addition this allows to use some bits for colored
  * marking during the cycle GC.
  */
-const size_t ArenaCellCount = size_t(1) << (ArenaShift - Cell::CellShift);
+const size_t ArenaCellCount = size_t(1) << (ArenaShift - CellShift);
 const size_t ArenaBitmapBits = ArenaCellCount;
 const size_t ArenaBitmapBytes = ArenaBitmapBits / 8;
 const size_t ArenaBitmapWords = ArenaBitmapBits / JS_BITS_PER_WORD;
 
 /*
  * A FreeSpan represents a contiguous sequence of free cells in an Arena.
  * |first| is the address of the first free cell in the span. |last| is the
  * address of the last free cell in the span. This last cell holds a FreeSpan
@@ -140,17 +128,17 @@ const size_t ArenaBitmapWords = ArenaBit
  * next span. So to allocate from it we need to update the span list head
  * with a copy of the span stored at |last| address so the following
  * allocations will use that span.
  *
  * |first| > |last| implies that we have an empty last span and the arena is
  * fully used.
  *
  * Also only for the last span (|last| & 1)! = 0 as all allocation sizes are
- * multiples of Cell::CellSize.
+ * multiples of CellSize.
  */
 struct FreeSpan
 {
     uintptr_t   first;
     uintptr_t   last;
 
   public:
     FreeSpan() {}
@@ -257,17 +245,17 @@ struct FreeSpan
          * first == ArenaMask + 1 for an empty span.
          */
         uintptr_t arenaAddr = arenaAddress();
         return encodeOffsets(first - arenaAddr, last & ArenaMask);
     }
 
     /* See comments before FreeSpan for details. */
     MOZ_ALWAYS_INLINE void *allocate(size_t thingSize) {
-        JS_ASSERT(thingSize % Cell::CellSize == 0);
+        JS_ASSERT(thingSize % CellSize == 0);
         checkSpan();
         uintptr_t thing = first;
         if (thing < last) {
             /* Bump-allocate from the current span. */
             first = thing + thingSize;
         } else if (JS_LIKELY(thing == last)) {
             /*
              * Move to the next span. We use JS_LIKELY as without PGO
@@ -278,17 +266,17 @@ struct FreeSpan
             return NULL;
         }
         checkSpan();
         return reinterpret_cast<void *>(thing);
     }
 
     /* A version of allocate when we know that the span is not empty. */
     MOZ_ALWAYS_INLINE void *infallibleAllocate(size_t thingSize) {
-        JS_ASSERT(thingSize % Cell::CellSize == 0);
+        JS_ASSERT(thingSize % CellSize == 0);
         checkSpan();
         uintptr_t thing = first;
         if (thing < last) {
             first = thing + thingSize;
         } else {
             JS_ASSERT(thing == last);
             *this = *reinterpret_cast<FreeSpan *>(thing);
         }
@@ -326,37 +314,37 @@ struct FreeSpan
 
             if (first - 1 == last) {
                 /* The span is last and empty. The above start != 0 check
                  * implies that we are not at the end of the address space.
                  */
                 return;
             }
             size_t spanLength = last - first + 1;
-            JS_ASSERT(spanLength % Cell::CellSize == 0);
+            JS_ASSERT(spanLength % CellSize == 0);
 
             /* Start and end must belong to the same arena. */
             JS_ASSERT((first & ~ArenaMask) == arenaAddr);
             return;
         }
 
         /* The span is not the last and we have more spans to follow. */
         JS_ASSERT(first <= last);
         size_t spanLengthWithoutOneThing = last - first;
-        JS_ASSERT(spanLengthWithoutOneThing % Cell::CellSize == 0);
+        JS_ASSERT(spanLengthWithoutOneThing % CellSize == 0);
 
         JS_ASSERT((first & ~ArenaMask) == arenaAddr);
 
         /*
          * If there is not enough space before the arena end to allocate one
          * more thing, then the span must be marked as the last one to avoid
          * storing useless empty span reference.
          */
         size_t beforeTail = ArenaSize - (last & ArenaMask);
-        JS_ASSERT(beforeTail >= sizeof(FreeSpan) + Cell::CellSize);
+        JS_ASSERT(beforeTail >= sizeof(FreeSpan) + CellSize);
 
         FreeSpan *next = reinterpret_cast<FreeSpan *>(last);
 
         /*
          * The GC things on the list of free spans come from one arena
          * and the spans are linked in ascending address order with
          * at least one non-free thing between spans.
          */
@@ -544,17 +532,17 @@ struct Arena
         return ThingSizes[kind];
     }
 
     static size_t firstThingOffset(AllocKind kind) {
         return FirstThingOffsets[kind];
     }
 
     static size_t thingsPerArena(size_t thingSize) {
-        JS_ASSERT(thingSize % Cell::CellSize == 0);
+        JS_ASSERT(thingSize % CellSize == 0);
 
         /* We should be able to fit FreeSpan in any GC thing. */
         JS_ASSERT(thingSize >= sizeof(FreeSpan));
 
         return (ArenaSize - sizeof(ArenaHeader)) / thingSize;
     }
 
     static size_t thingsSpan(size_t thingSize) {
@@ -594,16 +582,24 @@ ArenaHeader::getThingSize() const
 struct ChunkInfo
 {
     Chunk           *next;
     Chunk           **prevp;
 
     /* Free arenas are linked together with aheader.next. */
     ArenaHeader     *freeArenasHead;
 
+#if JS_BITS_PER_WORD == 32
+    /*
+     * Calculating sizes and offsets is simpler if sizeof(ChunkInfo) is
+     * architecture-independent.
+     */
+    char            padding[12];
+#endif
+
     /*
      * Decommitted arenas are tracked by a bitmap in the chunk header. We use
      * this offset to start our search iteration close to a decommitted arena
      * that we can allocate.
      */
     uint32_t        lastDecommittedArenaOffset;
 
     /* Number of free arenas, either committed or decommitted. */
@@ -651,17 +647,20 @@ const size_t ChunkBytesAvailable = Chunk
 const size_t ArenasPerChunk = ChunkBytesAvailable / BytesPerArenaWithHeader;
 
 /* A chunk bitmap contains enough mark bits for all the cells in a chunk. */
 struct ChunkBitmap
 {
     uintptr_t bitmap[ArenaBitmapWords * ArenasPerChunk];
 
     MOZ_ALWAYS_INLINE void getMarkWordAndMask(const Cell *cell, uint32_t color,
-                                             uintptr_t **wordp, uintptr_t *maskp);
+                                              uintptr_t **wordp, uintptr_t *maskp)
+    {
+        GetGCThingMarkWordAndMask(cell, color, wordp, maskp);
+    }
 
     MOZ_ALWAYS_INLINE bool isMarked(const Cell *cell, uint32_t color) {
         uintptr_t *word, mask;
         getMarkWordAndMask(cell, color, &word, &mask);
         return *word & mask;
     }
 
     MOZ_ALWAYS_INLINE bool markIfUnmarked(const Cell *cell, uint32_t color) {
@@ -703,16 +702,17 @@ struct ChunkBitmap
 
         uintptr_t *word, unused;
         getMarkWordAndMask(reinterpret_cast<Cell *>(aheader->address()), BLACK, &word, &unused);
         return word;
     }
 };
 
 JS_STATIC_ASSERT(ArenaBitmapBytes * ArenasPerChunk == sizeof(ChunkBitmap));
+JS_STATIC_ASSERT(js::gc::ChunkMarkBitmapBits == ArenaBitmapBits * ArenasPerChunk);
 
 typedef BitArray<ArenasPerChunk> PerArenaBitmap;
 
 const size_t ChunkPadSize = ChunkSize
                             - (sizeof(Arena) * ArenasPerChunk)
                             - sizeof(ChunkBitmap)
                             - sizeof(PerArenaBitmap)
                             - sizeof(ChunkInfo);
@@ -805,22 +805,23 @@ struct Chunk
   public:
     /* Unlink and return the freeArenasHead. */
     inline ArenaHeader* fetchNextFreeArena(JSRuntime *rt);
 
     inline void addArenaToFreeList(JSRuntime *rt, ArenaHeader *aheader);
 };
 
 JS_STATIC_ASSERT(sizeof(Chunk) == ChunkSize);
+JS_STATIC_ASSERT(js::gc::ChunkMarkBitmapOffset == offsetof(Chunk, bitmap));
 
 inline uintptr_t
 Cell::address() const
 {
     uintptr_t addr = uintptr_t(this);
-    JS_ASSERT(addr % Cell::CellSize == 0);
+    JS_ASSERT(addr % CellSize == 0);
     JS_ASSERT(Chunk::withinArenasRange(addr));
     return addr;
 }
 
 inline uintptr_t
 ArenaHeader::address() const
 {
     uintptr_t addr = reinterpret_cast<uintptr_t>(this);
@@ -914,48 +915,38 @@ ArenaHeader::setNextAllocDuringSweep(Are
 inline void
 ArenaHeader::unsetAllocDuringSweep()
 {
     JS_ASSERT(allocatedDuringIncremental);
     allocatedDuringIncremental = 0;
     auxNextLink = 0;
 }
 
-JS_ALWAYS_INLINE void
-ChunkBitmap::getMarkWordAndMask(const Cell *cell, uint32_t color,
-                                uintptr_t **wordp, uintptr_t *maskp)
-{
-    size_t bit = (cell->address() & ChunkMask) / Cell::CellSize + color;
-    JS_ASSERT(bit < ArenaBitmapBits * ArenasPerChunk);
-    *maskp = uintptr_t(1) << (bit % JS_BITS_PER_WORD);
-    *wordp = &bitmap[bit / JS_BITS_PER_WORD];
-}
-
 static void
 AssertValidColor(const void *thing, uint32_t color)
 {
 #ifdef DEBUG
     ArenaHeader *aheader = reinterpret_cast<const Cell *>(thing)->arenaHeader();
-    JS_ASSERT_IF(color, color < aheader->getThingSize() / Cell::CellSize);
+    JS_ASSERT_IF(color, color < aheader->getThingSize() / CellSize);
 #endif
 }
 
 inline ArenaHeader *
 Cell::arenaHeader() const
 {
     uintptr_t addr = address();
     addr &= ~ArenaMask;
     return reinterpret_cast<ArenaHeader *>(addr);
 }
 
 Chunk *
 Cell::chunk() const
 {
     uintptr_t addr = uintptr_t(this);
-    JS_ASSERT(addr % Cell::CellSize == 0);
+    JS_ASSERT(addr % CellSize == 0);
     addr &= ~(ChunkSize - 1);
     return reinterpret_cast<Chunk *>(addr);
 }
 
 AllocKind
 Cell::getAllocKind() const
 {
     return arenaHeader()->getAllocKind();
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1229,17 +1229,17 @@ GCMarker::restoreValueArray(JSObject *ob
 }
 
 void
 GCMarker::processMarkStackOther(SliceBudget &budget, uintptr_t tag, uintptr_t addr)
 {
     if (tag == TypeTag) {
         ScanTypeObject(this, reinterpret_cast<types::TypeObject *>(addr));
     } else if (tag == SavedValueArrayTag) {
-        JS_ASSERT(!(addr & Cell::CellMask));
+        JS_ASSERT(!(addr & CellMask));
         JSObject *obj = reinterpret_cast<JSObject *>(addr);
         HeapValue *vp, *end;
         if (restoreValueArray(obj, (void **)&vp, (void **)&end))
             pushValueArray(obj, vp, end);
         else
             pushObject(obj);
     } else if (tag == IonCodeTag) {
         MarkChildren(this, reinterpret_cast<ion::IonCode *>(addr));
@@ -1294,17 +1294,17 @@ GCMarker::processMarkStackTop(SliceBudge
     JSObject *obj;
 
     uintptr_t addr = stack.pop();
     uintptr_t tag = addr & StackTagMask;
     addr &= ~StackTagMask;
 
     if (tag == ValueArrayTag) {
         JS_STATIC_ASSERT(ValueArrayTag == 0);
-        JS_ASSERT(!(addr & Cell::CellMask));
+        JS_ASSERT(!(addr & CellMask));
         obj = reinterpret_cast<JSObject *>(addr);
         uintptr_t addr2 = stack.pop();
         uintptr_t addr3 = stack.pop();
         JS_ASSERT(addr2 <= addr3);
         JS_ASSERT((addr3 - addr2) % sizeof(Value) == 0);
         vp = reinterpret_cast<HeapSlot *>(addr2);
         end = reinterpret_cast<HeapSlot *>(addr3);
         goto scan_value_array;
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -36,22 +36,23 @@ CodeGenerator::CodeGenerator(MIRGenerato
 }
 
 bool
 CodeGenerator::visitValueToInt32(LValueToInt32 *lir)
 {
     ValueOperand operand = ToValue(lir, LValueToInt32::Input);
     Register output = ToRegister(lir->output());
 
+    Register tag = masm.splitTagForTest(operand);
+
     Label done, simple, isInt32, isBool, notDouble;
-
     // Type-check switch.
-    masm.branchTestInt32(Assembler::Equal, operand, &isInt32);
-    masm.branchTestBoolean(Assembler::Equal, operand, &isBool);
-    masm.branchTestDouble(Assembler::NotEqual, operand, &notDouble);
+    masm.branchTestInt32(Assembler::Equal, tag, &isInt32);
+    masm.branchTestBoolean(Assembler::Equal, tag, &isBool);
+    masm.branchTestDouble(Assembler::NotEqual, tag, &notDouble);
 
     // If the value is a double, see if it fits in a 32-bit int. We need to ask
     // the platform-specific codegenerator to do this.
     FloatRegister temp = ToFloatRegister(lir->tempFloat());
     masm.unboxDouble(operand, temp);
 
     Label fails;
     switch (lir->mode()) {
@@ -66,22 +67,22 @@ CodeGenerator::visitValueToInt32(LValueT
     }
     masm.jump(&done);
 
     masm.bind(&notDouble);
 
     if (lir->mode() == LValueToInt32::NORMAL) {
         // If the value is not null, it's a string, object, or undefined,
         // which we can't handle here.
-        masm.branchTestNull(Assembler::NotEqual, operand, &fails);
+        masm.branchTestNull(Assembler::NotEqual, tag, &fails);
     } else {
         // Test for string or object - then fallthrough to null, which will
         // also handle undefined.
-        masm.branchTestObject(Assembler::Equal, operand, &fails);
-        masm.branchTestString(Assembler::Equal, operand, &fails);
+        masm.branchTestObject(Assembler::Equal, tag, &fails);
+        masm.branchTestString(Assembler::Equal, tag, &fails);
     }
 
     if (fails.used() && !bailoutFrom(&fails, lir->snapshot()))
         return false;
 
     // The value is null - just emit 0.
     masm.mov(Imm32(0), output);
     masm.jump(&done);
@@ -103,25 +104,26 @@ CodeGenerator::visitValueToInt32(LValueT
 static const double DoubleZero = 0.0;
 
 bool
 CodeGenerator::visitValueToDouble(LValueToDouble *lir)
 {
     ValueOperand operand = ToValue(lir, LValueToDouble::Input);
     FloatRegister output = ToFloatRegister(lir->output());
 
+    Register tag = masm.splitTagForTest(operand);
+
     Label isDouble, isInt32, isBool, isNull, done;
-
     // Type-check switch.
-    masm.branchTestDouble(Assembler::Equal, operand, &isDouble);
-    masm.branchTestInt32(Assembler::Equal, operand, &isInt32);
-    masm.branchTestBoolean(Assembler::Equal, operand, &isBool);
-    masm.branchTestNull(Assembler::Equal, operand, &isNull);
-
-    Assembler::Condition cond = masm.testUndefined(Assembler::NotEqual, operand);
+    masm.branchTestDouble(Assembler::Equal, tag, &isDouble);
+    masm.branchTestInt32(Assembler::Equal, tag, &isInt32);
+    masm.branchTestBoolean(Assembler::Equal, tag, &isBool);
+    masm.branchTestNull(Assembler::Equal, tag, &isNull);
+
+    Assembler::Condition cond = masm.testUndefined(Assembler::NotEqual, tag);
     if (!bailoutIf(cond, lir->snapshot()))
         return false;
     masm.loadStaticDouble(&js_NaN, output);
     masm.jump(&done);
 
     masm.bind(&isNull);
     masm.loadStaticDouble(&DoubleZero, output);
     masm.jump(&done);
@@ -163,17 +165,17 @@ CodeGenerator::visitDoubleToInt32(LDoubl
 bool
 CodeGenerator::visitTestVAndBranch(LTestVAndBranch *lir)
 {
     const ValueOperand value = ToValue(lir, LTestVAndBranch::Input);
     masm.branchTestValueTruthy(value, lir->ifTrue(), ToFloatRegister(lir->tempFloat()));
     masm.jump(lir->ifFalse());
     return true;
 }
- 
+
 bool
 CodeGenerator::visitPolyInlineDispatch(LPolyInlineDispatch *lir)
 {
     MPolyInlineDispatch *mir = lir->mir();
     Register inputReg = ToRegister(lir->input());
 
     InlinePropertyTable *inlinePropTable = mir->inlinePropertyTable();
     if (inlinePropTable) {
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -46,17 +46,17 @@
 
 using namespace js;
 using namespace js::ion;
 
 // Global variables.
 IonOptions ion::js_IonOptions;
 
 // Assert that IonCode is gc::Cell aligned.
-JS_STATIC_ASSERT(sizeof(IonCode) % gc::Cell::CellSize == 0);
+JS_STATIC_ASSERT(sizeof(IonCode) % gc::CellSize == 0);
 
 #ifdef JS_THREADSAFE
 static bool IonTLSInitialized = false