Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 05 Aug 2016 12:15:20 +0200
changeset 308324 b97dd7dd3cb9c679cd5ab95ece0aa933988d0840
parent 308192 9f0d24c5330d484bc6c5d2903d754684f087b2fd (current diff)
parent 308323 d320ef56876f52db9bc0eb79554c7332d4793769 (diff)
child 308325 fc48f8d8b574782d39ad27fc5d8a88c93fbe9104
push id31092
push usercbook@mozilla.com
push dateFri, 05 Aug 2016 10:16:59 +0000
treeherderautoland@b97dd7dd3cb9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone51.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 mozilla-central to autoland
dom/media/MediaDecoder.cpp
gfx/gl/AndroidNativeWindow.cpp
gfx/gl/AndroidNativeWindow.h
testing/web-platform/meta/WebCryptoAPI/digest/digest.worker.js.ini
testing/web-platform/meta/WebCryptoAPI/digest/test_digest.html.ini
testing/web-platform/meta/fetch/api/cors/cors-preflight-worker.html.ini
testing/web-platform/meta/shadow-dom/untriaged/user-interaction/focus-navigation/test-001.html.ini
testing/web-platform/meta/shadow-dom/untriaged/user-interaction/focus-navigation/test-002.html.ini
testing/web-platform/meta/shadow-dom/untriaged/user-interaction/focus-navigation/test-003.html.ini
testing/web-platform/meta/shadow-dom/untriaged/user-interaction/focus-navigation/test-004.html.ini
testing/web-platform/meta/workers/constructors/Worker/expected-self-properties.html.ini
testing/web-platform/tests/XMLHttpRequest/XMLHttpRequest-withCredentials.html
testing/web-platform/tests/XMLHttpRequest/XMLHttpRequest-withCredentials.js
testing/web-platform/tests/XMLHttpRequest/XMLHttpRequest-withCredentials.worker.js
testing/web-platform/tests/encrypted-media/Google/test-encrypted-different-av-keys.webm
testing/web-platform/tests/encrypted-media/Google/test-encrypted.webm
testing/web-platform/tests/fetch/api/request/resources/get-host-info.sub.js
testing/web-platform/tests/pointerevents/pointerevent_button_attribute_mouse-manual.html
testing/web-platform/tests/pointerevents/pointerevent_properties_mouse-manual.html
testing/web-platform/tests/shadow-dom/untriaged/user-interaction/focus-navigation/test-001.html
testing/web-platform/tests/shadow-dom/untriaged/user-interaction/focus-navigation/test-002.html
testing/web-platform/tests/shadow-dom/untriaged/user-interaction/focus-navigation/test-003.html
testing/web-platform/tests/shadow-dom/untriaged/user-interaction/focus-navigation/test-004.html
testing/web-platform/tests/web-animations/interfaces/DocumentTimeline/idlharness.html
testing/web-platform/tests/web-animations/timing-model/timelines/default-document-timeline.html
testing/web-platform/tests/workers/constructors/Worker/DedicatedWorkerGlobalScope-members.html
testing/web-platform/tests/workers/constructors/Worker/expected-self-properties.html
testing/web-platform/tests/workers/constructors/Worker/unexpected-self-properties.html
testing/web-platform/tests/workers/workers.js
toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html
widget/android/AndroidDirectTexture.cpp
widget/android/AndroidGraphicBuffer.cpp
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -759,16 +759,21 @@ DocAccessible::AttributeChanged(nsIDocum
   Accessible* accessible = GetAccessible(aElement);
   if (!accessible) {
     if (mContent != aElement)
       return;
 
     accessible = this;
   }
 
+  if (!accessible->IsBoundToParent()) {
+    MOZ_ASSERT_UNREACHABLE("DOM attribute change on accessible detached from tree");
+    return;
+  }
+
   // Fire accessible events iff there's an accessible, otherwise we consider
   // the accessible state wasn't changed, i.e. its state is initial state.
   AttributeChangedImpl(accessible, aNameSpaceID, aAttribute);
 
   // Update dependent IDs cache. Take care of accessible elements because no
   // accessible element means either the element is not accessible at all or
   // its accessible will be created later. It doesn't make sense to keep
   // dependent IDs for non accessible elements. For the second case we'll update
--- a/browser/base/content/browser-ctrlTab.js
+++ b/browser/base/content/browser-ctrlTab.js
@@ -465,17 +465,17 @@ var ctrlTab = {
       setTimeout(function (selected) {
         selected.focus();
       }, 0, this.selected);
     }
   },
 
   handleEvent: function ctrlTab_handleEvent(event) {
     switch (event.type) {
-      case "SSWindowStateReady":
+      case "SSWindowRestored":
         this._initRecentlyUsedTabs();
         break;
       case "TabAttrModified":
         // tab attribute modified (e.g. label, crop, busy, image, selected)
         for (let i = this.previews.length - 1; i >= 0; i--) {
           if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
             this.updatePreview(this.previews[i], event.target);
             break;
@@ -526,17 +526,17 @@ var ctrlTab = {
     this._recentlyUsedTabs =
       Array.filter(gBrowser.tabs, tab => !tab.closing)
            .sort((tab1, tab2) => tab2.lastAccessed - tab1.lastAccessed);
   },
 
   _init: function ctrlTab__init(enable) {
     var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
 
-    window[toggleEventListener]("SSWindowStateReady", this, false);
+    window[toggleEventListener]("SSWindowRestored", this, false);
 
     var tabContainer = gBrowser.tabContainer;
     tabContainer[toggleEventListener]("TabOpen", this, false);
     tabContainer[toggleEventListener]("TabAttrModified", this, false);
     tabContainer[toggleEventListener]("TabSelect", this, false);
     tabContainer[toggleEventListener]("TabClose", this, false);
 
     document[toggleEventListener]("keypress", this, false);
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -233,17 +233,17 @@ SocialUI = {
     if (shareButton) {
       if (canShare) {
         shareButton.removeAttribute("disabled")
       } else {
         shareButton.setAttribute("disabled", "true")
       }
     }
     // update the disabled state of the button based on the command
-    for (let node of SocialMarks.nodes) {
+    for (let node of SocialMarks.nodes()) {
       if (canShare) {
         node.removeAttribute("disabled")
       } else {
         node.setAttribute("disabled", "true")
       }
     }
   },
 
@@ -1295,31 +1295,31 @@ var SocialMarksWidgetListener = {
 }
 
 /**
  * SocialMarks
  *
  * Handles updates to toolbox and signals all buttons to update when necessary.
  */
 SocialMarks = {
-  get nodes() {
+  *nodes() {
     for (let p of Social.providers.filter(p => p.markURL)) {
       let widgetId = SocialMarks._toolbarHelper.idFromOrigin(p.origin);
       let widget = CustomizableUI.getWidget(widgetId);
       if (!widget)
         continue;
       let node = widget.forWindow(window).node;
       if (node)
         yield node;
     }
   },
   update: function() {
     // querySelectorAll does not work on the menu panel, so we have to do this
     // the hard way.
-    for (let node of this.nodes) {
+    for (let node of this.nodes()) {
       // xbl binding is not complete on startup when buttons are not in toolbar,
       // verify update is available
       if (node.update) {
         node.update();
       }
     }
   },
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7589,18 +7589,22 @@ var TabContextMenu = {
     let disabled = gBrowser.tabs.length == 1;
 
     var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
     for (let menuItem of menuItems)
       menuItem.disabled = disabled;
 
     if (AppConstants.E10S_TESTING_ONLY) {
       menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-remote");
-      for (let menuItem of menuItems)
+      for (let menuItem of menuItems) {
         menuItem.hidden = !gMultiProcessBrowser;
+        if (menuItem.id == "context_openNonRemoteWindow") {
+          menuItem.disabled = !!parseInt(this.contextTab.getAttribute("usercontextid"));
+        }
+      }
     }
 
     disabled = gBrowser.visibleTabs.length == 1;
     menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
     for (let menuItem of menuItems)
       menuItem.disabled = disabled;
 
     // Session store
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1113,19 +1113,19 @@
                                           [listener.mStateFlags, listener.mStatus,
                                            listener.mMessage, listener.mTotalProgress],
                                           true, false);
             }
 
             if (!this._previewMode) {
               this._recordTabAccess(this.mCurrentTab);
 
-              this.mCurrentTab.lastAccessed = Infinity;
+              this.mCurrentTab.updateLastAccessed();
               this.mCurrentTab.removeAttribute("unread");
-              oldTab.lastAccessed = Date.now();
+              oldTab.updateLastAccessed();
 
               let oldFindBar = oldTab._findBar;
               if (oldFindBar &&
                   oldFindBar.findMode == oldFindBar.FIND_NORMAL &&
                   !oldFindBar.hidden)
                 this._lastFindValue = oldFindBar._findField.value;
 
               this.updateTitlebar();
@@ -2015,17 +2015,16 @@
             this.tabContainer.appendChild(t);
 
             // If this new tab is owned by another, assert that relationship
             if (aOwner)
               t.owner = aOwner;
 
             var position = this.tabs.length - 1;
             t._tPos = position;
-            t.lastAccessed = Date.now();
             t.permanentKey = {};
             this.tabContainer._setPositionalAttributes();
 
             this.tabContainer.updateVisibility();
 
             // Currently in this incarnation of bug 906076, we are forcing the
             // browser to immediately be linked.  In future incarnations of this
             // bug this will be removed so we can leave the tab in its "lazy"
@@ -4474,17 +4473,16 @@
           window.addEventListener("sizemodechange", this, false);
 
           var uniqueId = this._generateUniquePanelID();
           this.mPanelContainer.childNodes[0].id = uniqueId;
           this.mCurrentTab.linkedPanel = uniqueId;
           this.mCurrentTab.permanentKey = this.mCurrentBrowser.permanentKey;
           this.mCurrentTab._tPos = 0;
           this.mCurrentTab._fullyOpen = true;
-          this.mCurrentTab.lastAccessed = Infinity;
           this.mCurrentTab.cachePosition = 0;
           this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
           this.mCurrentTab.hasBrowser = true;
           this._tabForBrowser.set(this.mCurrentBrowser, this.mCurrentTab);
 
           // set up the shared autoscroll popup
           this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
           this._autoScrollPopup.id = "autoscroller";
@@ -6277,16 +6275,21 @@
           <xul:toolbarbutton anonid="close-button"
                              xbl:inherits="fadein,pinned,selected,visuallyselected"
                              class="tab-close-button close-icon"/>
         </xul:hbox>
       </xul:stack>
     </content>
 
     <implementation>
+      <constructor><![CDATA[
+        if (!("_lastAccessed" in this)) {
+          this.updateLastAccessed();
+        }
+      ]]></constructor>
 
       <property name="_visuallySelected">
         <setter>
           <![CDATA[
           if (val)
             this.setAttribute("visuallyselected", "true");
           else
             this.removeAttribute("visuallyselected");
@@ -6352,20 +6355,23 @@
           return this.getAttribute("soundplaying") == "true";
         </getter>
       </property>
 
       <property name="lastAccessed">
         <getter>
           return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
         </getter>
-        <setter>
-          this._lastAccessed = val;
-        </setter>
       </property>
+      <method name="updateLastAccessed">
+        <parameter name="aDate"/>
+        <body><![CDATA[
+          this._lastAccessed = this.selected ? Infinity : (aDate || Date.now());
+        ]]></body>
+      </method>
 
       <field name="cachePosition">Infinity</field>
 
       <field name="mOverCloseButton">false</field>
       <property name="_overPlayingIcon" readonly="true">
         <getter><![CDATA[
           let iconVisible = this.hasAttribute("soundplaying") ||
                             this.hasAttribute("muted");
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -877,17 +877,17 @@ add_task(function* test_plaintext_sendpa
                     "---",                  null,
                     "context-viewbgimage",  false,
                     "context-selectall",    true,
                     "---",                  null,
                     "context-viewsource",   true,
                     "context-viewinfo",     true
                    ];
   yield test_contextmenu("#test-text", plainTextItems, {
-      onContextMenuShown() {
+      *onContextMenuShown() {
         yield openMenuItemSubmenu("context-sendpagetodevice");
       }
     });
 
   restoreRemoteClients(oldGetter);
 });
 
 add_task(function* test_link_sendlinktodevice() {
@@ -913,17 +913,17 @@ add_task(function* test_link_sendlinktod
      "---",                  null,
      "context-sendlinktodevice", true,
       ["*Foo", true,
        "*Bar", true,
        "---", null,
        "*All Devices", true], null,
     ],
     {
-      onContextMenuShown() {
+      *onContextMenuShown() {
         yield openMenuItemSubmenu("context-sendlinktodevice");
       }
     });
 
   restoreRemoteClients(oldGetter);
 });
 
 add_task(function* test_cleanup() {
--- a/browser/base/content/test/general/browser_ctrlTab.js
+++ b/browser/base/content/test/general/browser_ctrlTab.js
@@ -36,20 +36,32 @@ add_task(function* () {
     is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes the selected tab");
     yield releaseCtrl();
   }
 
   gBrowser.addTab();
   checkTabs(3);
   yield ctrlTabTest([2, 1, 0], 7, 1);
 
-  gBrowser.addTab();
-  checkTabs(4);
+  { // test for bug 1292049
+    let tabToClose = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:buildconfig");
+    checkTabs(4);
+    selectTabs([0, 1, 2, 3]);
+
+    yield BrowserTestUtils.removeTab(tabToClose);
+    checkTabs(3);
+    undoCloseTab();
+    checkTabs(4);
+    is(gBrowser.tabContainer.selectedIndex, 3, "tab is selected after closing and restoring it");
+
+    yield ctrlTabTest([], 1, 2);
+  }
 
   { // test for bug 445369
+    checkTabs(4);
     selectTabs([1, 2, 0]);
 
     let selectedTab = gBrowser.selectedTab;
     let tabToRemove = gBrowser.tabs[1];
 
     yield pressCtrlTab();
     yield pressCtrlTab();
     yield synthesizeCtrlW();
@@ -122,22 +134,17 @@ add_task(function* () {
     return ctrlTab.isOpen;
   }
 
   function canOpen() {
     return gPrefService.getBoolPref("browser.ctrlTab.previews") && gBrowser.tabs.length > 2;
   }
 
   function checkTabs(aTabs) {
-    var tabs = gBrowser.tabs.length;
-    if (tabs != aTabs) {
-      while (gBrowser.tabs.length > 1)
-        gBrowser.removeCurrentTab();
-      throw "expected " + aTabs + " open tabs, got " + tabs;
-    }
+    is(gBrowser.tabs.length, aTabs, "number of open tabs should be " + aTabs);
   }
 
   function selectTabs(tabs) {
     tabs.forEach(function (index) {
       gBrowser.selectedTab = gBrowser.tabs[index];
     });
   }
 
--- a/browser/components/extensions/.eslintrc
+++ b/browser/components/extensions/.eslintrc
@@ -1,18 +1,18 @@
 {
   "extends": "../../../toolkit/components/extensions/.eslintrc",
 
   "globals": {
     "AllWindowEvents": true,
     "currentWindow": true,
     "EventEmitter": true,
+    "IconDetails": true,
     "makeWidgetId": true,
     "pageActionFor": true,
-    "IconDetails": true,
     "PanelPopup": true,
     "TabContext": true,
     "ViewPopup": true,
     "WindowEventManager": true,
     "WindowListManager": true,
     "WindowManager": true,
   },
 }
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -1,15 +1,19 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 
+XPCOMUtils.defineLazyGetter(this, "colorUtils", () => {
+  return require("devtools/shared/css-color").colorUtils;
+});
+
 Cu.import("resource://devtools/shared/event-emitter.js");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
   IconDetails,
 } = ExtensionUtils;
 
@@ -137,18 +141,18 @@ BrowserAction.prototype = {
     } else {
       node.setAttribute("disabled", "true");
     }
 
     let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
                                         "class", "toolbarbutton-badge");
     if (badgeNode) {
       let color = tabData.badgeBackgroundColor;
-      if (Array.isArray(color)) {
-        color = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
+      if (color) {
+        color = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3] / 255})`;
       }
       badgeNode.style.backgroundColor = color || "";
     }
 
     const LEGACY_CLASS = "toolbarbutton-legacy-addon";
     node.classList.remove(LEGACY_CLASS);
 
     let baseSize = 16;
@@ -328,19 +332,24 @@ extensions.registerSchemaAPI("browserAct
       getPopup: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         let popup = BrowserAction.for(extension).getProperty(tab, "popup");
         return Promise.resolve(popup);
       },
 
       setBadgeBackgroundColor: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
-        BrowserAction.for(extension).setProperty(tab, "badgeBackgroundColor", details.color);
+        let color = details.color;
+        if (!Array.isArray(color)) {
+          let col = colorUtils.colorToRGBA(color);
+          color = col && [col.r, col.g, col.b, Math.round(col.a * 255)];
+        }
+        BrowserAction.for(extension).setProperty(tab, "badgeBackgroundColor", color);
       },
 
       getBadgeBackgroundColor: function(details, callback) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         let color = BrowserAction.for(extension).getProperty(tab, "badgeBackgroundColor");
-        return Promise.resolve(color);
+        return Promise.resolve(color || [0xd9, 0, 0, 255]);
       },
     },
   };
 });
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -39,16 +39,17 @@ support-files =
 [browser_ext_lastError.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
 [browser_ext_pageAction_popup.js]
 [browser_ext_pageAction_popup_resize.js]
 [browser_ext_pageAction_simple.js]
 [browser_ext_popup_api_injection.js]
 [browser_ext_runtime_openOptionsPage.js]
+[browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_runtime_setUninstallURL.js]
 [browser_ext_simple.js]
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_tabs_audio.js]
 [browser_ext_tabs_captureVisibleTab.js]
 [browser_ext_tabs_create.js]
 [browser_ext_tabs_create_invalid_url.js]
 [browser_ext_tabs_detectLanguage.js]
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -166,39 +166,41 @@ add_task(function* testTabSwitchContext(
 
       "default.png": imageBuffer,
       "default-2.png": imageBuffer,
       "1.png": imageBuffer,
       "2.png": imageBuffer,
     },
 
     getTests(tabs, expectDefaults) {
+      const DEFAULT_BADGE_COLOR = [0xd9, 0, 0, 255];
+
       let details = [
         {"icon": browser.runtime.getURL("default.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default Title",
          "badge": "",
-         "badgeBackgroundColor": null},
+         "badgeBackgroundColor": DEFAULT_BADGE_COLOR},
         {"icon": browser.runtime.getURL("1.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default Title",
          "badge": "",
-         "badgeBackgroundColor": null},
+         "badgeBackgroundColor": DEFAULT_BADGE_COLOR},
         {"icon": browser.runtime.getURL("2.png"),
          "popup": browser.runtime.getURL("2.html"),
          "title": "Title 2",
          "badge": "2",
          "badgeBackgroundColor": [0xff, 0, 0, 0xff],
-          "disabled": true},
+         "disabled": true},
         {"icon": browser.runtime.getURL("1.png"),
          "popup": browser.runtime.getURL("default-2.html"),
          "title": "Default Title 2",
          "badge": "d2",
          "badgeBackgroundColor": [0, 0xff, 0, 0xff],
-          "disabled": true},
+         "disabled": true},
         {"icon": browser.runtime.getURL("1.png"),
          "popup": browser.runtime.getURL("default-2.html"),
          "title": "Default Title 2",
          "badge": "d2",
          "badgeBackgroundColor": [0, 0xff, 0, 0xff],
          "disabled": false},
         {"icon": browser.runtime.getURL("default-2.png"),
          "popup": browser.runtime.getURL("default-2.html"),
@@ -232,17 +234,17 @@ add_task(function* testTabSwitchContext(
         },
         expect => {
           browser.test.log("Change properties. Expect new properties.");
           let tabId = tabs[1];
           browser.browserAction.setIcon({tabId, path: "2.png"});
           browser.browserAction.setPopup({tabId, popup: "2.html"});
           browser.browserAction.setTitle({tabId, title: "Title 2"});
           browser.browserAction.setBadgeText({tabId, text: "2"});
-          browser.browserAction.setBadgeBackgroundColor({tabId, color: [0xff, 0, 0, 0xff]});
+          browser.browserAction.setBadgeBackgroundColor({tabId, color: "#ff0000"});
           browser.browserAction.disable(tabId);
 
           expectDefaults(details[0]).then(() => {
             expect(details[2]);
           });
         },
         expect => {
           browser.test.log("Navigate to a new page. Expect no changes.");
@@ -327,36 +329,38 @@ add_task(function* testDefaultTitle() {
       "permissions": ["tabs"],
     },
 
     files: {
       "icon.png": imageBuffer,
     },
 
     getTests(tabs, expectDefaults) {
+      const DEFAULT_BADGE_COLOR = [0xd9, 0, 0, 255];
+
       let details = [
         {"title": "Foo Extension",
          "popup": "",
          "badge": "",
-         "badgeBackgroundColor": null,
+         "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
          "icon": browser.runtime.getURL("icon.png")},
         {"title": "Foo Title",
          "popup": "",
          "badge": "",
-         "badgeBackgroundColor": null,
+         "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
          "icon": browser.runtime.getURL("icon.png")},
         {"title": "Bar Title",
          "popup": "",
          "badge": "",
-         "badgeBackgroundColor": null,
+         "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
          "icon": browser.runtime.getURL("icon.png")},
         {"title": "",
          "popup": "",
          "badge": "",
-         "badgeBackgroundColor": null,
+         "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
          "icon": browser.runtime.getURL("icon.png")},
       ];
 
       return [
         expect => {
           browser.test.log("Initial state. Expect extension title as default title.");
           expectDefaults(details[0]).then(() => {
             expect(details[0]);
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -252,71 +252,8 @@ add_task(function* test_options_no_manif
         browser.test.notifyFail("options-no-manifest");
       });
     },
   });
 
   yield extension.awaitFinish("options-no-manifest");
   yield extension.unload();
 });
-
-add_task(function* test_inline_options_uninstall() {
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
-
-  let extension = yield loadExtension({
-    manifest: {
-      "options_ui": {
-        "page": "options.html",
-      },
-    },
-
-    background: function() {
-      let _optionsPromise;
-      let awaitOptions = () => {
-        browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
-
-        return new Promise(resolve => {
-          _optionsPromise = {resolve};
-        });
-      };
-
-      browser.runtime.onMessage.addListener((msg, sender) => {
-        if (msg == "options.html") {
-          if (_optionsPromise) {
-            _optionsPromise.resolve(sender.tab);
-            _optionsPromise = null;
-          } else {
-            browser.test.fail("Saw unexpected options page load");
-          }
-        }
-      });
-
-      let firstTab;
-      browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
-        firstTab = tabs[0].id;
-
-        browser.test.log("Open options page. Expect fresh load.");
-        return Promise.all([
-          browser.runtime.openOptionsPage(),
-          awaitOptions(),
-        ]);
-      }).then(([, tab]) => {
-        browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
-        browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
-
-        browser.test.sendMessage("options-ui-open");
-      }).catch(error => {
-        browser.test.fail(`Error: ${error} :: ${error.stack}`);
-      });
-    },
-  });
-
-  yield extension.awaitMessage("options-ui-open");
-  yield extension.unload();
-
-  is(gBrowser.selectedBrowser.currentURI.spec, "about:addons",
-     "Add-on manager tab should still be open");
-
-  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
-
-  yield BrowserTestUtils.removeTab(tab);
-});
copy from browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
copy to browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage_uninstall.js
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage_uninstall.js
@@ -32,237 +32,16 @@ function* loadExtension(options) {
     background: options.background,
   });
 
   yield extension.startup();
 
   return extension;
 }
 
-add_task(function* test_inline_options() {
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
-
-  let extension = yield loadExtension({
-    manifest: {
-      "options_ui": {
-        "page": "options.html",
-      },
-    },
-
-    background: function() {
-      let _optionsPromise;
-      let awaitOptions = () => {
-        browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
-
-        return new Promise(resolve => {
-          _optionsPromise = {resolve};
-        });
-      };
-
-      browser.runtime.onMessage.addListener((msg, sender) => {
-        if (msg == "options.html") {
-          if (_optionsPromise) {
-            _optionsPromise.resolve(sender.tab);
-            _optionsPromise = null;
-          } else {
-            browser.test.fail("Saw unexpected options page load");
-          }
-        }
-      });
-
-      let firstTab, optionsTab;
-      browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
-        firstTab = tabs[0].id;
-
-        browser.test.log("Open options page. Expect fresh load.");
-        return Promise.all([
-          browser.runtime.openOptionsPage(),
-          awaitOptions(),
-        ]);
-      }).then(([, tab]) => {
-        browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
-        browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
-
-        optionsTab = tab.id;
-
-        browser.test.log("Switch tabs.");
-        return browser.tabs.update(firstTab, {active: true});
-      }).then(() => {
-        browser.test.log("Open options page again. Expect tab re-selected, no new load.");
-
-        return browser.runtime.openOptionsPage();
-      }).then(() => {
-        return browser.tabs.query({currentWindow: true, active: true});
-      }).then(([tab]) => {
-        browser.test.assertEq(optionsTab, tab.id, "Tab is the same as the previous options tab");
-        browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
-
-        browser.test.log("Ping options page.");
-        return browser.runtime.sendMessage("ping");
-      }).then((pong) => {
-        browser.test.assertEq("pong", pong, "Got pong.");
-
-        browser.test.log("Remove options tab.");
-        return browser.tabs.remove(optionsTab);
-      }).then(() => {
-        browser.test.log("Open options page again. Expect fresh load.");
-        return Promise.all([
-          browser.runtime.openOptionsPage(),
-          awaitOptions(),
-        ]);
-      }).then(([, tab]) => {
-        browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
-        browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != optionsTab, "Tab is a new tab");
-
-        return browser.tabs.remove(tab.id);
-      }).then(() => {
-        browser.test.notifyPass("options-ui");
-      }).catch(error => {
-        browser.test.log(`Error: ${error} :: ${error.stack}`);
-        browser.test.notifyFail("options-ui");
-      });
-    },
-  });
-
-  yield extension.awaitFinish("options-ui");
-  yield extension.unload();
-
-  yield BrowserTestUtils.removeTab(tab);
-});
-
-add_task(function* test_tab_options() {
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
-
-  let extension = yield loadExtension({
-    manifest: {
-      "options_ui": {
-        "page": "options.html",
-        "open_in_tab": true,
-      },
-    },
-
-    background: function() {
-      let _optionsPromise;
-      let awaitOptions = () => {
-        browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
-
-        return new Promise(resolve => {
-          _optionsPromise = {resolve};
-        });
-      };
-
-      browser.runtime.onMessage.addListener((msg, sender) => {
-        if (msg == "options.html") {
-          if (_optionsPromise) {
-            _optionsPromise.resolve(sender.tab);
-            _optionsPromise = null;
-          } else {
-            browser.test.fail("Saw unexpected options page load");
-          }
-        }
-      });
-
-      let optionsURL = browser.extension.getURL("options.html");
-
-      let firstTab, optionsTab;
-      browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
-        firstTab = tabs[0].id;
-
-        browser.test.log("Open options page. Expect fresh load.");
-        return Promise.all([
-          browser.runtime.openOptionsPage(),
-          awaitOptions(),
-        ]);
-      }).then(([, tab]) => {
-        browser.test.assertEq(optionsURL, tab.url, "Tab contains options.html");
-        browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
-
-        optionsTab = tab.id;
-
-        browser.test.log("Switch tabs.");
-        return browser.tabs.update(firstTab, {active: true});
-      }).then(() => {
-        browser.test.log("Open options page again. Expect tab re-selected, no new load.");
-
-        return browser.runtime.openOptionsPage();
-      }).then(() => {
-        return browser.tabs.query({currentWindow: true, active: true});
-      }).then(([tab]) => {
-        browser.test.assertEq(optionsTab, tab.id, "Tab is the same as the previous options tab");
-        browser.test.assertEq(optionsURL, tab.url, "Tab contains options.html");
-
-        // Unfortunately, we can't currently do this, since onMessage doesn't
-        // currently support responses when there are multiple listeners.
-        //
-        // browser.test.log("Ping options page.");
-        // return new Promise(resolve => browser.runtime.sendMessage("ping", resolve));
-
-        browser.test.log("Remove options tab.");
-        return browser.tabs.remove(optionsTab);
-      }).then(() => {
-        browser.test.log("Open options page again. Expect fresh load.");
-        return Promise.all([
-          browser.runtime.openOptionsPage(),
-          awaitOptions(),
-        ]);
-      }).then(([, tab]) => {
-        browser.test.assertEq(optionsURL, tab.url, "Tab contains options.html");
-        browser.test.assertTrue(tab.active, "Tab is active");
-        browser.test.assertTrue(tab.id != optionsTab, "Tab is a new tab");
-
-        return browser.tabs.remove(tab.id);
-      }).then(() => {
-        browser.test.notifyPass("options-ui-tab");
-      }).catch(error => {
-        browser.test.log(`Error: ${error} :: ${error.stack}`);
-        browser.test.notifyFail("options-ui-tab");
-      });
-    },
-  });
-
-  yield extension.awaitFinish("options-ui-tab");
-  yield extension.unload();
-
-  yield BrowserTestUtils.removeTab(tab);
-});
-
-add_task(function* test_options_no_manifest() {
-  let extension = yield loadExtension({
-    manifest: {},
-
-    background: function() {
-      browser.test.log("Try to open options page when not specified in the manifest.");
-
-      browser.runtime.openOptionsPage().then(
-        () => {
-          browser.test.fail("Opening options page without one specified in the manifest generated an error");
-          browser.test.notifyFail("options-no-manifest");
-        },
-        error => {
-          let expected = "No `options_ui` declared";
-          browser.test.assertTrue(
-            error.message.includes(expected),
-            `Got expected error (got: '${error.message}', expected: '${expected}'`);
-        }
-      ).then(() => {
-        browser.test.notifyPass("options-no-manifest");
-      }).catch(error => {
-        browser.test.log(`Error: ${error} :: ${error.stack}`);
-        browser.test.notifyFail("options-no-manifest");
-      });
-    },
-  });
-
-  yield extension.awaitFinish("options-no-manifest");
-  yield extension.unload();
-});
-
 add_task(function* test_inline_options_uninstall() {
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
   let extension = yield loadExtension({
     manifest: {
       "options_ui": {
         "page": "options.html",
       },
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3068,16 +3068,18 @@ var SessionStoreInternal = {
 
     // set smoothScroll back to the original value
     tabstrip.smoothScroll = smoothScroll;
 
     TelemetryStopwatch.finish("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
 
     this._setWindowStateReady(aWindow);
 
+    this._sendWindowRestoredNotification(aWindow);
+
     Services.obs.notifyObservers(aWindow, NOTIFY_SINGLE_WINDOW_RESTORED, "");
 
     this._sendRestoreCompletedNotifications();
   },
 
   /**
    * Restore multiple windows using the provided state.
    * @param aWindow
@@ -3250,17 +3252,17 @@ var SessionStoreInternal = {
       tabbrowser.showTab(tab);
     }
 
     if (!!tabData.muted != browser.audioMuted) {
       tab.toggleMuteAudio(tabData.muteReason);
     }
 
     if (tabData.lastAccessed) {
-      tab.lastAccessed = tabData.lastAccessed;
+      tab.updateLastAccessed(tabData.lastAccessed);
     }
 
     if ("attributes" in tabData) {
       // Ensure that we persist tab attributes restored from previous sessions.
       Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
     }
 
     if (!tabData.entries) {
@@ -4094,16 +4096,27 @@ var SessionStoreInternal = {
    */
   _sendWindowStateEvent: function ssi_sendWindowStateEvent(aWindow, aType) {
     let event = aWindow.document.createEvent("Events");
     event.initEvent("SSWindowState" + aType, true, false);
     aWindow.dispatchEvent(event);
   },
 
   /**
+   * Dispatch the SSWindowRestored event for the given window.
+   * @param aWindow
+   *        The window which has been restored
+   */
+  _sendWindowRestoredNotification(aWindow) {
+    let event = aWindow.document.createEvent("Events");
+    event.initEvent("SSWindowRestored", true, false);
+    aWindow.dispatchEvent(event);
+  },
+
+  /**
    * Dispatch the SSTabRestored event for the given tab.
    * @param aTab
    *        The tab which has been restored
    * @param aIsRemotenessUpdate
    *        True if this tab was restored due to flip from running from
    *        out-of-main-process to in-main-process or vice-versa.
    */
   _sendTabRestoredNotification(aTab, aIsRemotenessUpdate) {
--- a/browser/config/tooltool-manifests/macosx64/releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/releng.manifest
@@ -3,18 +3,19 @@
 "version": "clang 3.8.0",
 "size": 133060926,
 "digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2",
 "unpack": true
 },
 {
-"size": 93295855,
-"digest": "2b8fd0c1ba337a7035090c420305a7892e663ce6781569b100b36fa21cc26146e67f44a34c7715f0004f48bbe46c232bbbf2928c9d0595243d2584530770b504",
+"version": "rust 1.10 repack",
+"size": 150726627,
+"digest": "a30476113212895a837b2c4c18eb34d767c7192c3e327fba84c0138eaf7e671e84d5294e75370af3fe7e527a61e0938cd6cce20fba0aec94537070eb0094e27e",
 "algorithm": "sha512",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
 "version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
 "size": 2571167,
 "digest": "b2616459fbf15c75b54628a6bfe8cf89c0841ea08431f5096e72be4fac4c685785dfc7a2f18a03a5f7bd377e78d3c108e5029b12616842cbbd0497ff7363fdaf",
--- a/config/system-headers
+++ b/config/system-headers
@@ -711,16 +711,18 @@ mapiguid.h
 mapi.h
 mapitags.h
 mapiutil.h
 mapix.h
 Math64.h
 math.h
 mbstring.h
 #ifdef ANDROID
+android/native_window.h
+android/native_window_jni.h
 media/AudioEffect.h
 media/AudioSystem.h
 media/ICrypto.h
 media/IOMX.h
 media/MediaProfiles.h
 media/MediaRecorderBase.h
 media/openmax/OMX_Audio.h
 media/stagefright/AACWriter.h
index 946422b6f188b1c597d7d59236b076c3949706ac..e1c42376e4cf665dd2cc3662037e3e95dbec92e5
GIT binary patch
literal 4246
zc${@tS5y;P7EK7fL+Blm5+DJjh!p89p&6Qr3WQz*0i+{HQK}%#1}dS5NUs3|r1zpo
zkpPN7=)D&i-psu9W?b*>yUxeC>)f;Uxo7`uV+4qp2><|)1A;<{^dM(I`4egYz>ppQ
z_~X_<%Tx`jXQ(aVjt+EobF+bAylti|oaIN(IOO7DKCquCkjMN`*-r|YsYrvz^V_la
z-sC{ABh+mZ>;^xlLM?Nmr_0hIPsa?i%cIo0kdG8LCWEGTegrFw;fi<G9-P0PKR8hy
z+rba|f{4w*Y-d~Y*jfXjA%0K(a5EadQqc%CJ|>VV9X1UW9Twp142ZTee4|eE(>6?X
zl9-+^DvDl`oH{yu)gb8Yb-<U`_xPgE-n>iX)#JNw%*N&B7%@xcPuFZCkLQXz*xlC_
z@F@d^jss){z$<Z2Ns`FSNox;z&E35bk!FBVS#t01VWnF>2Yb7|I^(q@#6c*$v54wE
zAAm?@;UFJ;Wiubl7?G}4Uxx|-*$0O@q<kW+#WDeKDkZvfvrJT{&qe8D_NY>im(Hc{
ztvnH-9wnonL#ac6qG4QchA8#!*Yl{S8fzLOD*Ry@Wgs_|jR9-s?+a^7KbPWYYfnp_
z5QxX=n)&5ZuE}+=KHA^a8V{<AdUJEXa33ooxyDO4XV4-eTXDhq1B!Xkc8<Oj7@clN
zFV>W)($gcnRy7R3a^(6+tx|@b=vkD*`MHfGkUIOhJDo(Sg|F-2Pck=N#Wt!PNj{5R
z$SkTSxk86{F5Lr@r8gs;7A`iju7H^mYUET7k#mbt@9BGsp{kHzQzQ&1!i3C@j!N1}
z_}qk5^&P7}NF8XDNEC`$0#Vk(!Y^wO3S_6;M(t`V!!r@-TuVU<txv}EZ~N81^6+Zk
zO>K6Xw5p^<Z^WcXncsW=YF}CW?6XS%&kFMWR&|`%1S7X$%brri7>3jTHWz=?Ft)iU
z;eBqRl<i0D7~L;XLIp+hcwt#2bgv}MSnXp%-C^F68N$qWnc_}AqHy{uB+9XHt+mNx
zF0uZzZYuj@?uvdgn~Fng3)B#bpRw%wAj_?cv0{ZyWhPm_VUgr=mDi0wX5O){kG+)l
z##h-RS%Z+#v&T{`deU=!AuB-Js7osQiX-U3E>1ten?jK9do}9|ZJ#UxEnu4ac&I)H
zw%~+Iei6lmap_Ie#xcEf^g434<qQhd{Cu#qp;qynr)=q1IQ2HVOY>01;#F~{?X5_&
z`XcQ85vE7>f+vqN@8_@VjuxL4Z`r$0h7^yFW0WQw>tQb92?S!$58REoq)MrBk9)bW
zGZHt`R6F>lzr=I?nVgTXrv5eP7_7GHMW$(ortk9Unq|O*{?z3Rs-F1bmuZV%dQGMD
za?8MLJHy?V#j0<lF>p3gDGP?~E0WkpzO*KPOuAc>9LXi4-G?~5(;D?H#i94%i^LuB
z3d(jJ8<OG)Zi-H<M#JO!DXyt2L=A5<c~AkJsM|$#*qLs`-ri*(13#2*{oN(D(gs7;
z=zMC38m8TfkbYgkz3d=M+4A{FPRg=#G31T%mFzOCQpJmM-O!ypo9j<n$#!IiCM-50
zWsORL)*r!9k&BnNhKZSdpe5@<W2C>d&yf-GdO!kM$-7!p-Hs`l&w{$yvb3Ea4p^~H
z<1kM)7)cf?%emL8t`W6;IaXeC*(7u6S<JeRd0g*X-uSUX4-fuZ4AX=RrhrbNL^GBU
zspc8vu(Vy%c{Q`8$GJ~u@KUP_1m;*km4!{7mU%FusCB*-tLjAPT~h4B_$_|weCESV
z&Ayr4Wpu61rqqsMWskTj)6s}^57#Z2>osN8rp#KEUG7Bujq2{I0pmL7Qv>Z!j2yi9
zbv8e@7sd+d;9gf;s~r9#cX6VpljzW0^wnV5rMCK@E2WS)=rT!@;ijn>g`nz_^=CNG
zxtGOdB*%^vnU<F;?;4v6gTAh4b}^QokW>$?@@9j04+IqHF_((mPWEK+WJ*5HWndY@
zbU~a(|ADBI8I4QHLMjQdd*i)gqZNp)DNMfMY#m#tF^xdelqcA-qJ=`e4DvdR)kH{3
zCT}a63*g08-+2A(@5Hmr4rgWu2;gk$CMwvDVPSwtAo<Yq*LWJ6T;O>Xg6$wiaki;v
zQa!BMXnKLV8Rd1NpKwa`02@-Oa%j$wqyy>g7VCj@ac`~xO%H`nn<m80V?IGlkCGpq
zx}oXfo}zcG;RLthEm8;F(1FwOg)GNPPQ8$p2?Il1ZGoqe^<qEvI2N&RkO}SLuei4G
zm8pO=;!;e!Z8xXFQAPdY<nk@+XnA`tFdUk2OzSTBzQX{y%vnGsRtMsKL??Y6T74JH
z;$!0_&O?n;6K~@TVOgA$s_F$V_Bi7RQAJr6I+6Fm79}-!pqUojVsPRaF8>6zDxkEy
z+vUAZANBAJ5GOO?u>YZyN?U1qW}$2}*LC)>CVRJkF($@hg;P4E?B&|e4iB)AU{%-5
zae}sy7W~9lLhQ3~t|?N2gD+8CMpt}4N#-DpEc-Apsr4<}Y1aI6>mQAU8(2FXD_P~i
z>pLyb_Y>Ye{zjXWQ4f+PMR`1gnRcp<=3Mb^zgob;^G02!`;rO}t%kX`dw^Oe=Un8O
zc5w6NhF55a$CA)B*32AM0p#;m%|Ky-D0PN3J6Oo9CP=_TyYQ}f$^Cckhw5@YsZ(-Q
z+gHtKolU%HU$<@EcfQQ;(QRFF{Ms8?Te$9s_W$(@P3=QE5fPzJp1G2_ki-Gr+U;n`
z`5y2xF_}+_t9Qk3%3@?jNa*HOxx}Om*($$6(ob{|;@Kt#vAGXHgGue{dlf$0CMT`v
zetG^x3kOj_>hv{6E(^=OxSR7^u80}t=r@5cAKze5Fnyo&3-t)dNF6=_VFjxS1Bi4P
ztnp|#P(GC<EKGF<xBzM^KPQP=e<DTwU8xyG_KZQE8k_Pk+{u|vjZa0}AdJ0-!oD*~
zu`I(dBd>qts9Cp+2p)JxeM^;TsToF@AWEFQU0gn<d`mt;?7<3WU_-T%g=tb`@_>tY
zY^*45TP2T}h@7~qbusg2ysDR_0-uQKtDSdbw1j+7v6GE2B5`WHD53`YQ+)6*Z8w!s
zt9fJBw#{)yIKND};!(_+=|b&NnKXE<<-P^4XL(L=Q{==lcpJ%5mBkau=9KMixc#6_
z_`%R6Xhf6u4Ov^CmE-yK?V>HQ_Z*MZci+D^?J2Svqvlu4ldmk**^3O3nHeywcrWR?
ztu$6J?_!zM2%7g`z}>eCn#0wZR?p8zcfc1DTwHow@Wp=eN3~(!EjX#*lCOIJV#J)H
z9?57yPd}%Q*$R#D)!dU$CbCuo%1DDQ&K%Ff+x!~S^JdJgvAL4zkxvcRZ?w!Wwnk5<
zD|!VBcI%KY43_m4NzCtbl)l!cY@58|B8=FGW$^Q3yT$y~aA}P5T2YNoH9RED-bh0e
zIUCE<;mam7CbEE0s{u>Y-WBJqO1|@|$S!hCs;nB{b?aa#g~#CDkbV3w;<}j;;y}-n
z<hE|e^<NXt+msu~r6pRO<-NdY9tYa8T{po8+!@|?OjkHK=uppA8IU4B2?AF6NmPEi
zR}nia#Q`zHMPB?oJC5k_jI?y!ov6Ke;KLMB=9Cv&W_l2qYr4r@q1%IsNd|uF6uHBS
zGD>E-jRnYWIT9QRkH0{}`ZCAAJx|OJDPRc}v`S^n@zV5Oofz%vB$8WtEa9!I_^FdS
zam{s#`)!s|W3}bS;h$>3BrU4=IRTpSyCU_d2bv~g7%9goirvyO{e)*WTg?N!a`Bf`
z4L1<1FGCVQntNwW;wdV!;}XN>i{zf?aALJHvsU0~lX#^_dJ_eg>TU*e>^B1KHHIF$
zqihuufBQ0pacbBd$85*_ny(~+Va|A$=_d}~>*Wh5gd7H8$LL8W@o&c4pc)^#AA_>(
z9Ko~sdX+=3KvsEJblj^^?ATE;tiPNy?jEl4ps24fG_z?PvmNLgzoWrCM+1wUE5gP(
zo8`<qfuFYL-H4kD<aJ!Vqm7(dj&890<|;g_S&<(`AKv+vkS!e*bMs!za9-y8T8J!t
zbnvPr<-5|Yd=fI0bNk6E=O|?+lSJv{V=w#*S0v8&<29+$DiR6HNpaoIYiYM%w2ZbG
zmK19A>sHS?x976A`B|aHUf0X3KAZI@7C#U@dbQIiB>SZ=fS}9$(>Yi<6?%IhoV|6v
zT~nR$dQq;qKHIU+mEQB{E=um9dW>S+VCz=6k7@bGr|DzFYgxn7I%~=q{+a5F-Z7jK
z5ok1G1+g}<p4McSG|Y4Vvk+6r_^hs=5O~$3K}V}xLkl54O0l6oW`Jc=f0d?PfVg*1
zss4pOnrK=`TjPm-VI!J`yk-4*nh!6}K_bMCudC=5!ii+q>wu2dGxT8KjY<1%zpmqV
zj>?-c?t6DBI~&Lce%{&Wy%iAx=YkM|?i990iuaDj%N6E#m{2K9KMfgb?M-dl`P8vR
z%DdX1RjJp+T;9akBeUB-F=rfC&)z8whL$^v#?!KX{N_(pqDyW=ORSYCMwvX4N3(Vn
zeVtL+NxXh>aJj_QY`=ZLYnIpe!Yok&{(!$ZksjcGz<3dU_9&>mzYo*~<KygLW9#EB
z@n=2xzw4#kYLcBUvh<6fxd?X~4`&B^Zy%V8H`=3c0cmX9W6;;DkAT8rBoI*1fxbbL
za-`&Epx&I|LLSjIxL)oOWwiD}M>LBN+?pa%T~I?A^!O%9P0i2&gQ75QU=z7P!vu=W
z%NKGawBl65`<ULv7=yUsVREcu;km9j)!K9_fbswNy@c+4uZW8*1Ms`w`;(iy!_I$6
z4b%LGR0UFUMEd*eHloj<3fUC}U$$o^5WH}9c@azs3poT<`*gcG7E8!s-7`_*AbAc9
zkiCvRK#b(c(C%kl!X?nvn$2JV+d)ZhP{sDPkEJCn30^BYqizMx2;T6Fuv`1ETiLR$
zFKiTosEBVHfm>O^V84;ZdM<^#Tx0<k0scFa>g_-^pg)KZhd|+9|Bv*xuhduHZhfPR
zVfOx*3UuWI(-m%$20UJ1J5~czfrMhXltZQ>;z922xQEJIG*W4-NAlWvkI0x=(=zOi
z1#|tQBXige11@u1Sle$HFW?0=AQ2PjpB?m{2m=EC!B79Y1K^)!^&ecc13+p1USxm1
z|9hGJg}XGriSBpuzsun-@|!e&FOJ_k{JxvNI^@#+?WT+oB&2^7kX#(qi$7AO`y=}o
DfnbJV
--- a/devtools/client/inspector/breadcrumbs.js
+++ b/devtools/client/inspector/breadcrumbs.js
@@ -478,17 +478,17 @@ HTMLBreadcrumbs.prototype = {
    * @param {DOMEvent} event.
    */
   handleEvent: function (event) {
     if (event.type == "click" && event.button == 0) {
       this.handleClick(event);
     } else if (event.type == "mouseover") {
       this.handleMouseOver(event);
     } else if (event.type == "mouseout") {
-      this.handleMouseLeave(event);
+      this.handleMouseOut(event);
     } else if (event.type == "focus") {
       this.handleFocus(event);
     }
   },
 
   /**
    * Focus event handler. When breadcrumbs container gets focus, if there is an
    * already selected breadcrumb, move focus to it.
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_highlight_hover.js
@@ -21,16 +21,23 @@ add_task(function* () {
   yield onNodeHighlighted;
 
   let isVisible = yield testActor.isHighlighting();
   ok(isVisible, "The highlighter is shown on a markup container hover");
 
   ok((yield testActor.assertHighlightedNode("body")),
      "The highlighter highlights the right node");
 
+  let onNodeUnhighlighted = toolbox.once("node-unhighlight");
+  // move outside of the breadcrumb trail to trigger unhighlight
+  EventUtils.synthesizeMouseAtCenter(inspector.addNodeButton,
+    {type: "mousemove"},
+    inspector.addNodeButton.ownerDocument.defaultView);
+  yield onNodeUnhighlighted;
+
   onNodeHighlighted = toolbox.once("node-highlight");
   button = bcButtons.childNodes[2];
   EventUtils.synthesizeMouseAtCenter(button, {type: "mousemove"},
     button.ownerDocument.defaultView);
   yield onNodeHighlighted;
 
   isVisible = yield testActor.isHighlighting();
   ok(isVisible, "The highlighter is shown on a markup container hover");
--- a/devtools/client/shared/components/frame.js
+++ b/devtools/client/shared/components/frame.js
@@ -205,36 +205,37 @@ module.exports = createClass({
         attributes["data-column"] = column;
       }
 
       sourceElements.push(dom.span({ className: "frame-link-line" }, lineInfo));
     }
 
     // Inner el is useful for achieving ellipsis on the left and correct LTR/RTL
     // ordering. See CSS styles for frame-link-source-[inner] and bug 1290056.
-    let sourceInnerEl = dom.span({ className: "frame-link-source-inner" },
-      sourceElements);
+    let sourceInnerEl = dom.span({
+      className: "frame-link-source-inner",
+      title: isLinkable ?
+        l10n.getFormatStr("frame.viewsourceindebugger", tooltip) : tooltip,
+    }, sourceElements);
 
     // If source is not a URL (self-hosted, eval, etc.), don't make
     // it an anchor link, as we can't link to it.
     if (isLinkable) {
       sourceEl = dom.a({
         onClick: e => {
           e.preventDefault();
           onClick(this.getSource(frame));
         },
         href: source,
         className: "frame-link-source",
         draggable: false,
-        title: l10n.getFormatStr("frame.viewsourceindebugger", tooltip)
       }, sourceInnerEl);
     } else {
       sourceEl = dom.span({
         className: "frame-link-source",
-        title: tooltip,
       }, sourceInnerEl);
     }
     elements.push(sourceEl);
 
     if (showHost && host) {
       elements.push(dom.span({ className: "frame-link-host" }, host));
     }
 
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -144,23 +144,24 @@ var TEST_TREE = {
  * Frame
  */
 function checkFrameString({ frame, file, line, column, source, functionName, shouldLink, tooltip }) {
   let el = frame.getDOMNode();
   let $ = selector => el.querySelector(selector);
 
   let $func = $(".frame-link-function-display-name");
   let $source = $(".frame-link-source");
+  let $sourceInner = $(".frame-link-source-inner");
   let $filename = $(".frame-link-filename");
   let $line = $(".frame-link-line");
 
   is($filename.textContent, file, "Correct filename");
   is(el.getAttribute("data-line"), line ? `${line}` : null, "Expected `data-line` found");
   is(el.getAttribute("data-column"), column ? `${column}` : null, "Expected `data-column` found");
-  is($source.getAttribute("title"), tooltip, "Correct tooltip");
+  is($sourceInner.getAttribute("title"), tooltip, "Correct tooltip");
   is($source.tagName, shouldLink ? "A" : "SPAN", "Correct linkable status");
   if (shouldLink) {
     is($source.getAttribute("href"), source, "Correct source");
   }
 
   if (line != null) {
     let lineText = `:${line}`;
     if (column != null) {
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -59,17 +59,17 @@
 }
 
 .property-view-hidden,
 .property-content-hidden {
   display: none;
 }
 
 .property-view {
-  padding: 2px 0 2px 17px;
+  padding: 2px 17px;
   display: flex;
   flex-wrap: wrap;
 }
 
 .property-name-container {
   width: 202px;
 }
 
@@ -128,17 +128,21 @@
 
 .theme-firebug .property-view {
   border-bottom: 1px solid rgba(0, 0, 0, 0.1);
 }
 
 /* From skin */
 .expander {
   visibility: hidden;
-  margin-left: -12px !important;
+  margin-inline-start: -12px !important;
+}
+
+.expander:-moz-locale-dir(rtl) {
+  transform: scaleX(-1);
 }
 
 .expandable {
   visibility: visible;
 }
 
 .match {
   visibility: hidden;
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -344,25 +344,29 @@
   background-image: var(--command-measure-image);
 }
 
 #command-button-frames::before {
   background-image: var(--command-frames-image);
 }
 
 #command-button-frames {
-  background: url("chrome://devtools/skin/images/dropmarker.svg") no-repeat right;
+  background: url("chrome://devtools/skin/images/dropmarker.svg") no-repeat;
 
   /* Override background-size from the command-button.
    The drop down arrow is smaller */
   background-size: 8px 4px !important;
   min-width: 32px;
 }
 
-#command-button-frames:-moz-dir(rtl) {
+#command-button-frames:-moz-locale-dir(ltr) {
+  background-position: right;
+}
+
+#command-button-frames:-moz-locale-dir(rtl) {
   background-position: left;
 }
 
 /* Toolbox panels */
 
 .toolbox-panel {
   display: -moz-box;
   -moz-box-flex: 1;
--- a/dom/base/CustomElementsRegistry.cpp
+++ b/dom/base/CustomElementsRegistry.cpp
@@ -21,17 +21,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 /* static */ bool
 CustomElementsRegistry::IsCustomElementsEnabled(JSContext* aCx, JSObject* aObject)
 {
   JS::Rooted<JSObject*> obj(aCx, aObject);
   if (Preferences::GetBool("dom.webcomponents.customelements.enabled") ||
-      nsDocument::IsWebComponentsEnabled(aCx, obj)) {
+      Preferences::GetBool("dom.webcomponents.enabled")) {
     return true;
   }
 
   return false;
 }
 
 /* static */ already_AddRefed<CustomElementsRegistry>
 CustomElementsRegistry::Create(nsPIDOMWindowInner* aWindow)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -299,27 +299,20 @@ int32_t gTimeoutCnt                     
 
 // The default shortest interval/timeout we permit
 #define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms
 #define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms
 static int32_t gMinTimeoutValue;
 static int32_t gMinBackgroundTimeoutValue;
 inline int32_t
 nsGlobalWindow::DOMMinTimeoutValue() const {
-  bool isBackground = !mOuterWindow || mOuterWindow->IsBackground();
-  if (isBackground) {
-    // Don't use the background timeout value when there are audio contexts with
-    // active nodes, so that background audio can keep running smoothly.
-    for (const AudioContext* ctx : mAudioContexts) {
-      if (ctx->ActiveNodeCount() > 0) {
-        isBackground = false;
-        break;
-      }
-    }
-  }
+  // Don't use the background timeout value when there are audio contexts
+  // present, so that baackground audio can keep running smoothly. (bug 1181073)
+  bool isBackground = mAudioContexts.IsEmpty() &&
+    (!mOuterWindow || mOuterWindow->IsBackground());
   return
     std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, 0);
 }
 
 // The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
 // uses 5.
 #define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
 
--- a/dom/base/nsNodeUtils.cpp
+++ b/dom/base/nsNodeUtils.cpp
@@ -565,17 +565,17 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNod
     if (aReparentScope) {
       AutoJSContext cx;
       JS::Rooted<JSObject*> wrapper(cx);
       if ((wrapper = aNode->GetWrapper())) {
         MOZ_ASSERT(IsDOMObject(wrapper));
         JSAutoCompartment ac(cx, wrapper);
         rv = ReparentWrapper(cx, wrapper);
         if (NS_FAILED(rv)) {
-          aNode->mNodeInfo.swap(nodeInfo);
+          aNode->mNodeInfo.swap(newNodeInfo);
 
           return rv;
         }
       }
     }
   }
 
   if (aDeep && (!aClone || !aNode->IsNodeOfType(nsINode::eATTRIBUTE))) {
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -3366,17 +3366,17 @@ nsObjectLoadingContent::GetRunID(uint32_
 
 static bool sPrefsInitialized;
 static uint32_t sSessionTimeoutMinutes;
 static uint32_t sPersistentTimeoutDays;
 
 bool
 nsObjectLoadingContent::ShouldBlockContent()
 {
-  if (mContentBlockingDisabled)
+  if (mContentBlockingDisabled || !mURI)
     return false;
 
   if (!IsFlashMIME(mContentType) || !Preferences::GetBool(kPrefBlockURIs)) {
     mContentBlockingDisabled = true;
     return false;
   }
 
   return true;
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -634,16 +634,17 @@ NS_IMETHODIMP JSStackFrame::GetFormatted
     mFormattedStackInitialized = true;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP JSStackFrame::GetNativeSavedFrame(JS::MutableHandle<JS::Value> aSavedFrame)
 {
+  JS::ExposeObjectToActiveJS(mStack);
   aSavedFrame.setObjectOrNull(mStack);
   return NS_OK;
 }
 
 NS_IMETHODIMP JSStackFrame::ToString(JSContext* aCx, nsACString& _retval)
 {
   _retval.Truncate();
 
--- a/dom/camera/CameraPreviewMediaStream.cpp
+++ b/dom/camera/CameraPreviewMediaStream.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "CameraPreviewMediaStream.h"
 #include "CameraCommon.h"
 #include "MediaStreamListener.h"
+#include "VideoFrameContainer.h"
 
 /**
  * Maximum number of outstanding invalidates before we start to drop frames;
  * if we hit this threshold, it is an indicator that the main thread is
  * either very busy or the device is busy elsewhere (e.g. encoding or
  * persisting video data).
  */
 #define MAX_INVALIDATE_PENDING 4
@@ -54,28 +55,28 @@ CameraPreviewMediaStream::SetAudioOutput
 }
 
 void
 CameraPreviewMediaStream::RemoveAudioOutput(void* aKey)
 {
 }
 
 void
-CameraPreviewMediaStream::AddVideoOutput(VideoFrameContainer* aContainer)
+CameraPreviewMediaStream::AddVideoOutput(MediaStreamVideoSink* aSink, TrackID aID)
 {
   MutexAutoLock lock(mMutex);
-  RefPtr<VideoFrameContainer> container = aContainer;
-  AddVideoOutputImpl(container.forget());
+  RefPtr<MediaStreamVideoSink> sink = aSink;
+  AddVideoOutputImpl(sink.forget(), aID);
 }
 
 void
-CameraPreviewMediaStream::RemoveVideoOutput(VideoFrameContainer* aContainer)
+CameraPreviewMediaStream::RemoveVideoOutput(MediaStreamVideoSink* aSink, TrackID aID)
 {
   MutexAutoLock lock(mMutex);
-  RemoveVideoOutputImpl(aContainer);
+  RemoveVideoOutputImpl(aSink, aID);
 }
 
 void
 CameraPreviewMediaStream::AddListener(MediaStreamListener* aListener)
 {
   MutexAutoLock lock(mMutex);
 
   MediaStreamListener* listener = *mListeners.AppendElement() = aListener;
@@ -120,18 +121,21 @@ CameraPreviewMediaStream::Destroy()
   DestroyImpl();
 }
 
 void
 CameraPreviewMediaStream::Invalidate()
 {
   MutexAutoLock lock(mMutex);
   --mInvalidatePending;
-  for (nsTArray<RefPtr<VideoFrameContainer> >::size_type i = 0; i < mVideoOutputs.Length(); ++i) {
-    VideoFrameContainer* output = mVideoOutputs[i];
+  for (const TrackBound<MediaStreamVideoSink>& sink : mVideoOutputs) {
+    VideoFrameContainer* output = sink.mListener->AsVideoFrameContainer();
+    if (!output) {
+      continue;
+    }
     output->Invalidate();
   }
 }
 
 void
 CameraPreviewMediaStream::ProcessInput(GraphTime aFrom, GraphTime aTo,
                                        uint32_t aFlags)
 {
@@ -159,32 +163,38 @@ CameraPreviewMediaStream::SetCurrentFram
       }
 
       DOM_CAMERA_LOGI("Update preview frame, %d invalidation(s) pending",
         mInvalidatePending);
     }
     mDiscardedFrames = 0;
 
     TimeStamp now = TimeStamp::Now();
-    for (nsTArray<RefPtr<VideoFrameContainer> >::size_type i = 0; i < mVideoOutputs.Length(); ++i) {
-      VideoFrameContainer* output = mVideoOutputs[i];
+    for (const TrackBound<MediaStreamVideoSink>& sink : mVideoOutputs) {
+      VideoFrameContainer* output = sink.mListener->AsVideoFrameContainer();
+      if (!output) {
+        continue;
+      }
       output->SetCurrentFrame(aIntrinsicSize, aImage, now);
     }
 
     ++mInvalidatePending;
   }
 
   NS_DispatchToMainThread(NewRunnableMethod(this, &CameraPreviewMediaStream::Invalidate));
 }
 
 void
 CameraPreviewMediaStream::ClearCurrentFrame()
 {
   MutexAutoLock lock(mMutex);
 
-  for (nsTArray<RefPtr<VideoFrameContainer> >::size_type i = 0; i < mVideoOutputs.Length(); ++i) {
-    VideoFrameContainer* output = mVideoOutputs[i];
+  for (const TrackBound<MediaStreamVideoSink>& sink : mVideoOutputs) {
+    VideoFrameContainer* output = sink.mListener->AsVideoFrameContainer();
+    if (!output) {
+      continue;
+    }
     output->ClearCurrentFrame();
     NS_DispatchToMainThread(NewRunnableMethod(output, &VideoFrameContainer::Invalidate));
   }
 }
 
 } // namespace mozilla
--- a/dom/camera/CameraPreviewMediaStream.h
+++ b/dom/camera/CameraPreviewMediaStream.h
@@ -1,21 +1,22 @@
 /* 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 DOM_CAMERA_CAMERAPREVIEWMEDIASTREAM_H
 #define DOM_CAMERA_CAMERAPREVIEWMEDIASTREAM_H
 
-#include "VideoFrameContainer.h"
 #include "MediaStreamGraph.h"
 #include "mozilla/Mutex.h"
 
 namespace mozilla {
 
+class MediaStreamVideoSink;
+
 class FakeMediaStreamGraph : public MediaStreamGraph
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FakeMediaStreamGraph)
 public:
   FakeMediaStreamGraph()
     : MediaStreamGraph(16000)
   {
   }
@@ -37,26 +38,26 @@ protected:
  */
 class CameraPreviewMediaStream : public ProcessedMediaStream
 {
   typedef mozilla::layers::Image Image;
 
 public:
   CameraPreviewMediaStream();
 
-  virtual void AddAudioOutput(void* aKey) override;
-  virtual void SetAudioOutputVolume(void* aKey, float aVolume) override;
-  virtual void RemoveAudioOutput(void* aKey) override;
-  virtual void AddVideoOutput(VideoFrameContainer* aContainer) override;
-  virtual void RemoveVideoOutput(VideoFrameContainer* aContainer) override;
-  virtual void Suspend() override {}
-  virtual void Resume() override {}
-  virtual void AddListener(MediaStreamListener* aListener) override;
-  virtual void RemoveListener(MediaStreamListener* aListener) override;
-  virtual void Destroy() override;
+  void AddAudioOutput(void* aKey) override;
+  void SetAudioOutputVolume(void* aKey, float aVolume) override;
+  void RemoveAudioOutput(void* aKey) override;
+  void AddVideoOutput(MediaStreamVideoSink* aSink, TrackID aID) override;
+  void RemoveVideoOutput(MediaStreamVideoSink* aSink, TrackID aID) override;
+  void Suspend() override {}
+  void Resume() override {}
+  void AddListener(MediaStreamListener* aListener) override;
+  void RemoveListener(MediaStreamListener* aListener) override;
+  void Destroy() override;
   void OnPreviewStateChange(bool aActive);
 
   void Invalidate();
 
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
   // Call these on any thread.
   void SetCurrentFrame(const gfx::IntSize& aIntrinsicSize, Image* aImage);
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -798,32 +798,47 @@ WebGLTexture::GetTexParameter(TexTarget 
 {
     mContext->MakeContextCurrent();
 
     GLint i = 0;
     GLfloat f = 0.0f;
 
     switch (pname) {
     case LOCAL_GL_TEXTURE_MIN_FILTER:
+        return JS::NumberValue(uint32_t(mMinFilter.get()));
+
     case LOCAL_GL_TEXTURE_MAG_FILTER:
+        return JS::NumberValue(uint32_t(mMagFilter.get()));
+
     case LOCAL_GL_TEXTURE_WRAP_S:
+        return JS::NumberValue(uint32_t(mWrapS.get()));
+
     case LOCAL_GL_TEXTURE_WRAP_T:
+        return JS::NumberValue(uint32_t(mWrapT.get()));
+
     case LOCAL_GL_TEXTURE_BASE_LEVEL:
-    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
+        return JS::NumberValue(mBaseMipmapLevel);
+
     case LOCAL_GL_TEXTURE_COMPARE_MODE:
+        return JS::NumberValue(uint32_t(mTexCompareMode));
+
+    case LOCAL_GL_TEXTURE_MAX_LEVEL:
+        return JS::NumberValue(mMaxMipmapLevel);
+
+    case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
+        return JS::BooleanValue(mImmutable);
+
     case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS:
-    case LOCAL_GL_TEXTURE_MAX_LEVEL:
+        return JS::NumberValue(uint32_t(mImmutableLevelCount));
+
+    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
     case LOCAL_GL_TEXTURE_WRAP_R:
         mContext->gl->fGetTexParameteriv(texTarget.get(), pname, &i);
         return JS::NumberValue(uint32_t(i));
 
-    case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
-        mContext->gl->fGetTexParameteriv(texTarget.get(), pname, &i);
-        return JS::BooleanValue(bool(i));
-
     case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
     case LOCAL_GL_TEXTURE_MAX_LOD:
     case LOCAL_GL_TEXTURE_MIN_LOD:
         mContext->gl->fGetTexParameterfv(texTarget.get(), pname, &f);
         return JS::NumberValue(float(f));
 
     default:
         MOZ_CRASH("GFX: Unhandled pname.");
@@ -994,21 +1009,23 @@ WebGLTexture::TexParameter(TexTarget tex
 
     ////////////////
     // Store any needed values
 
     switch (pname) {
     case LOCAL_GL_TEXTURE_BASE_LEVEL:
         mBaseMipmapLevel = intParam;
         ClampLevelBaseAndMax();
+        intParam = mBaseMipmapLevel;
         break;
 
     case LOCAL_GL_TEXTURE_MAX_LEVEL:
         mMaxMipmapLevel = intParam;
         ClampLevelBaseAndMax();
+        intParam = mMaxMipmapLevel;
         break;
 
     case LOCAL_GL_TEXTURE_MIN_FILTER:
         mMinFilter = intParam;
         break;
 
     case LOCAL_GL_TEXTURE_MAG_FILTER:
         mMagFilter = intParam;
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -1224,16 +1224,17 @@ WebGLTexture::TexStorage(const char* fun
     const bool isDataInitialized = false;
     const WebGLTexture::ImageInfo newInfo(dstUsage, width, height, depth,
                                           isDataInitialized);
     SetImageInfosAtLevel(0, newInfo);
 
     PopulateMipChain(0, levels-1);
 
     mImmutable = true;
+    mImmutableLevelCount = levels;
 }
 
 ////////////////////////////////////////
 // Tex(Sub)Image
 
 void
 WebGLTexture::TexImage(const char* funcName, TexImageTarget target, GLint level,
                        GLenum internalFormat, const webgl::PackingInfo& pi,
new file mode 100644
--- /dev/null
+++ b/dom/canvas/crashtests/1290628-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<body id='test_body'>
+<canvas id='i0'></canvas>
+<script>
+var c=document.getElementById('i0').getContext('2d');
+c.shadowOffsetX=73;
+c.scale(5,55);
+c.setLineDash([48,81,88,41,32767,8]);
+c.shadowColor='hsl(1.0,1%,1%)';
+c.miterLimit=Number.MIN_VALUE;
+c.strokeRect(0,58,8589934591,0);
+</script>
\ No newline at end of file
--- a/dom/canvas/crashtests/crashtests.list
+++ b/dom/canvas/crashtests/crashtests.list
@@ -29,9 +29,10 @@ skip-if(azureCairo) load 1229983-1.html
 load 1229932-1.html
 load 1244850-1.html
 load 1246775-1.html
 load 1284356-1.html
 load 1284578-1.html
 skip-if(d2d) load 1287515-1.html
 load 1287652-1.html
 load 1288872-1.html
+load 1290628-1.html
 
--- a/dom/canvas/test/webgl-conf/checkout/js/tests/gl-object-get-calls.js
+++ b/dom/canvas/test/webgl-conf/checkout/js/tests/gl-object-get-calls.js
@@ -102,57 +102,66 @@ for (var run = 0; run < testCases.length
 
   if (testCases[run].contextStencil) {
     gl = wtu.create3DContext(null, {stencil: true}, contextVersion);
   } else {
     gl = wtu.create3DContext(null, {stencil: false}, contextVersion);
   }
 
   var texture = gl.createTexture();
+  var anotherTexture = gl.createTexture();
   gl.bindTexture(gl.TEXTURE_2D, texture);
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE,
                 new Uint8Array([
                     0, 0, 0, 255,
                     255, 255, 255, 255,
                     255, 255, 255, 255,
                     0, 0, 0, 255]));
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
   gl.bindTexture(gl.TEXTURE_2D, null);
   var framebuffer = gl.createFramebuffer();
   gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
   gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
   var colorAttachmentsNum = 1;
   if (contextVersion > 1) {
+    gl.bindTexture(gl.TEXTURE_2D, anotherTexture);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE,
+                  new Uint8Array([
+                      0, 0, 0, 255,
+                      255, 255, 255, 255,
+                      255, 255, 255, 255,
+                      0, 0, 0, 255]));
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+    gl.bindTexture(gl.TEXTURE_2D, null);
     colorAttachmentsNum = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);
-    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + colorAttachmentsNum - 1, gl.TEXTURE_2D, texture, 0);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + colorAttachmentsNum - 1, gl.TEXTURE_2D, anotherTexture, 0);
   }
   var renderbuffer = gl.createRenderbuffer();
   wtu.glErrorShouldBe(gl, gl.NO_ERROR);
   gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
   wtu.glErrorShouldBe(gl, gl.NO_ERROR);
   if (contextVersion == 1)
     gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 2, 2);
   else
     gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, 2, 2);
   wtu.glErrorShouldBe(gl, gl.NO_ERROR);
   gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
   if (contextVersion > 1)
     gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
-  // FIXME: on some machines (in particular the WebKit commit bots) the
-  // framebuffer status is FRAMEBUFFER_UNSUPPORTED; more investigation
-  // is needed why this is the case, because the FBO allocated
-  // internally by the WebKit implementation has almost identical
-  // parameters to this one. See https://bugs.webkit.org/show_bug.cgi?id=31843.
   shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
   // The for loop tests two color attachments for WebGL 2: the first one (gl.COLOR_ATTACHMENT0)
   // and the last one (gl.COLOR_ATTACHMENT0 + gl.MAX_COLOR_ATTACHMENTS - 1).
   for (var ii = 0; ii < colorAttachmentsNum; ii += (colorAttachmentsNum > 1 ? colorAttachmentsNum - 1 : 1)) {
     shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)', 'gl.TEXTURE');
-    shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)', 'texture');
+    if (ii == 0)
+      shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)', 'texture');
+    else
+      shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)', 'anotherTexture');
     shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL)', '0');
     shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE)', '0');
     if (contextVersion > 1) {
       shouldBeNonZero('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_RED_SIZE)');
       shouldBeNonZero('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_GREEN_SIZE)');
       shouldBeNonZero('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_BLUE_SIZE)');
       shouldBeNonZero('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE)');
       shouldBeNonZero('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ' + ii + ', gl.FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE)');
@@ -852,17 +861,29 @@ if (contextVersion > 1) {
         function(pname) {
 	    return gl.getSamplerParameter(sampler, pname);
     });
 
     debug("");
     debug("Test getSyncParameter");
     var sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
     shouldBe('gl.getSyncParameter(sync, gl.OBJECT_TYPE)', 'gl.SYNC_FENCE');
-    shouldBe('gl.getSyncParameter(sync, gl.SYNC_STATUS)', 'gl.UNSIGNALED');
+    var sync_status = gl.getSyncParameter(sync, gl.SYNC_STATUS);
+    switch (sync_status) {
+      case gl.UNSIGNALED:
+        testPassed('gl.getSyncParameter(sync, gl.SYNC_CONDITION) is gl.UNSIGNALED');
+        break;
+      case gl.SIGNALED:
+        testPassed('gl.getSyncParameter(sync, gl.SYNC_CONDITION) is gl.SIGNALED');
+        break;
+      default:
+        testFailed('gl.getSyncParameter(sync, gl.SYNC_CONDITION) was ' + sync_status +
+                   ', expected gl.UNSIGNALED or gl.SIGNALED');
+        break;
+    }
     shouldBe('gl.getSyncParameter(sync, gl.SYNC_CONDITION)', 'gl.SYNC_GPU_COMMANDS_COMPLETE');
     shouldBe('gl.getSyncParameter(sync, gl.SYNC_FLAGS)', '0');
     var validArrayForSyncParameter = new Array(
         gl.OBJECT_TYPE,
         gl.SYNC_STATUS,
         gl.SYNC_CONDITION,
         gl.SYNC_FLAGS
     );
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -4587,17 +4587,17 @@ skip-if = (os == 'android' || os == 'lin
 [generated/test_2_conformance2__state__gl-enum-tests.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__state__gl-get-calls.html]
 fail-if = (os == 'mac')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__state__gl-getstring.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__state__gl-object-get-calls.html]
-skip-if = (os == 'mac') || (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
+skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__transform_feedback__transform_feedback.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__vertex_arrays__vertex-array-object.html]
 fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__attribs__gl-bindAttribLocation-aliasing.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__attribs__gl-bindAttribLocation-matrix.html]
@@ -5894,16 +5894,17 @@ skip-if = (os == 'android' || os == 'lin
 [generated/test_2_conformance__typedarrays__array-large-array-tests.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__typedarrays__array-unit-tests.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__typedarrays__data-view-crash.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__typedarrays__data-view-test.html]
 fail-if = 1
+skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__typedarrays__typed-arrays-in-workers.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__uniforms__gl-uniform-arrays.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__uniforms__gl-uniform-bool.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__uniforms__gl-uniformmatrix4fv.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -165,19 +165,16 @@ fail-if = (os == 'android') || (os == 'l
 
 [generated/test_conformance__textures__misc__texture-size-limit.html]
 fail-if = (os == 'linux') || (os == 'android')
 skip-if = (os == 'linux' && asan)
 [generated/test_2_conformance2__reading__read-pixels-from-fbo-test.html]
 skip-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__misc__uninitialized-test-2.html]
 skip-if = (os == 'mac') || (os == 'win')
-[generated/test_2_conformance2__state__gl-object-get-calls.html]
-# Hit MOZ_GL_DEBUG_ABORT_ON_ERROR on debug build
-skip-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance__misc__bad-arguments-test.html]
 # skip because some result logged after SimpleTest.finish()
 skip-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__misc__bad-arguments-test.html]
 # skip because some result logged after SimpleTest.finish()
 skip-if = (os == 'mac') || (os == 'win') || (os == 'linux') || (os == 'android')
 [generated/test_2_conformance__glsl__constructors__glsl-construct-vec-mat-index.html]
 # skip this test because finish() was called multiple times
--- a/dom/gamepad/android/AndroidGamepad.cpp
+++ b/dom/gamepad/android/AndroidGamepad.cpp
@@ -1,23 +1,91 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "GeneratedJNIWrappers.h"
+#include "GeneratedJNINatives.h"
+#include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
+class AndroidGamepadManager final
+  : public java::AndroidGamepadManager::Natives<AndroidGamepadManager>
+  , public jni::UsesNativeCallProxy
+{
+  AndroidGamepadManager() = delete;
+
+public:
+  template<class Functor>
+  static void OnNativeCall(Functor&& aCall)
+  {
+    NS_DispatchToMainThread(NS_NewRunnableFunction(Move(aCall)));
+  }
+
+  static void
+  OnGamepadChange(int32_t aID, bool aAdded)
+  {
+    RefPtr<GamepadPlatformService> service =
+        GamepadPlatformService::GetParentService();
+    if (!service) {
+      return;
+    }
+
+    if (aAdded) {
+      const int svc_id = service->AddGamepad(
+          "android", GamepadMappingType::Standard,
+          kStandardGamepadButtons, kStandardGamepadAxes);
+      java::AndroidGamepadManager::OnGamepadAdded(aID, svc_id);
+
+    } else {
+      service->RemoveGamepad(aID);
+    }
+  }
+
+  static void
+  OnButtonChange(int32_t aID, int32_t aButton, bool aPressed, float aValue)
+  {
+    RefPtr<GamepadPlatformService> service =
+        GamepadPlatformService::GetParentService();
+    if (!service) {
+      return;
+    }
+
+    service->NewButtonEvent(aID, aButton, aPressed, aValue);
+  }
+
+  static void
+  OnAxisChange(int32_t aID, jni::BooleanArray::Param aValid,
+               jni::FloatArray::Param aValues)
+  {
+    RefPtr<GamepadPlatformService> service =
+        GamepadPlatformService::GetParentService();
+    if (!service) {
+      return;
+    }
+
+    const auto& valid = aValid->GetElements();
+    const auto& values = aValues->GetElements();
+    MOZ_ASSERT(valid.Length() == values.Length());
+
+    for (size_t i = 0; i < values.Length(); i++) {
+      service->NewAxisMoveEvent(aID, i, values[i]);
+    }
+  }
+};
+
 void StartGamepadMonitoring()
 {
-  java::GeckoAppShell::StartMonitoringGamepad();
+  AndroidGamepadManager::Init();
+  java::AndroidGamepadManager::Start();
 }
 
 void StopGamepadMonitoring()
 {
-  java::GeckoAppShell::StopMonitoringGamepad();
+  java::AndroidGamepadManager::Stop();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -203,16 +203,17 @@ static const nsAttrValue::EnumTable kInp
 };
 
 // Default inputmode value is "auto".
 static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0];
 
 const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
 const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
 const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
+const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
 const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
 const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
 const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
 const Decimal HTMLInputElement::kStepAny = Decimal(0);
 
 const double HTMLInputElement::kMaximumYear = 275760;
 const double HTMLInputElement::kMinimumYear = 1;
 
@@ -2279,16 +2280,17 @@ HTMLInputElement::GetMaximum() const
 }
 
 Decimal
 HTMLInputElement::GetStepBase() const
 {
   MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER ||
              mType == NS_FORM_INPUT_DATE ||
              mType == NS_FORM_INPUT_TIME ||
+             mType == NS_FORM_INPUT_MONTH ||
              mType == NS_FORM_INPUT_RANGE,
              "Check that kDefaultStepBase is correct for this new type");
 
   Decimal stepBase;
 
   // Do NOT use GetMinimum here - the spec says to use "the min content
   // attribute", not "the minimum".
   nsAutoString minStr;
@@ -6888,17 +6890,17 @@ HTMLInputElement::GetStep() const
   }
 
   Decimal step = StringToDecimal(stepStr);
   if (!step.isFinite() || step <= Decimal(0)) {
     step = GetDefaultStep();
   }
 
   // For input type=date, we round the step value to have a rounded day.
-  if (mType == NS_FORM_INPUT_DATE) {
+  if (mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_MONTH) {
     step = std::max(step.round(), Decimal(1));
   }
 
   return step * GetStepScaleFactor();
 }
 
 // nsIConstraintValidation
 
@@ -7965,29 +7967,32 @@ HTMLInputElement::GetStepScaleFactor() c
   switch (mType) {
     case NS_FORM_INPUT_DATE:
       return kStepScaleFactorDate;
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_RANGE:
       return kStepScaleFactorNumberRange;
     case NS_FORM_INPUT_TIME:
       return kStepScaleFactorTime;
+    case NS_FORM_INPUT_MONTH:
+      return kStepScaleFactorMonth;
     default:
       MOZ_ASSERT(false, "Unrecognized input type");
       return Decimal::nan();
   }
 }
 
 Decimal
 HTMLInputElement::GetDefaultStep() const
 {
   MOZ_ASSERT(DoesStepApply());
 
   switch (mType) {
     case NS_FORM_INPUT_DATE:
+    case NS_FORM_INPUT_MONTH:
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_RANGE:
       return kDefaultStep;
     case NS_FORM_INPUT_TIME:
       return kDefaultStepTime;
     default:
       MOZ_ASSERT(false, "Unrecognized input type");
       return Decimal::nan();
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -1018,21 +1018,17 @@ protected:
   /**
    * Returns if the min and max attributes apply for the current type.
    */
   bool DoesMinMaxApply() const;
 
   /**
    * Returns if the step attribute apply for the current type.
    */
-  bool DoesStepApply() const
-  {
-    // TODO: this is temporary until bug 888324 is fixed.
-    return DoesMinMaxApply() && mType != NS_FORM_INPUT_MONTH;
-  }
+  bool DoesStepApply() const { return DoesMinMaxApply(); }
 
   /**
    * Returns if stepDown and stepUp methods apply for the current type.
    */
   bool DoStepDownStepUpApply() const { return DoesStepApply(); }
 
   /**
    * Returns if valueAsNumber attribute applies for the current type.
@@ -1423,16 +1419,17 @@ protected:
    * nsTextEditorState cannot do its job.
    */
   nsTextEditorState::SelectionProperties mSelectionProperties;
 
   // Step scale factor values, for input types that have one.
   static const Decimal kStepScaleFactorDate;
   static const Decimal kStepScaleFactorNumberRange;
   static const Decimal kStepScaleFactorTime;
+  static const Decimal kStepScaleFactorMonth;
 
   // Default step base value when a type do not have specific one.
   static const Decimal kDefaultStepBase;
 
   // Default step used when there is no specified step.
   static const Decimal kDefaultStep;
   static const Decimal kDefaultStepTime;
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -70,16 +70,17 @@
 #include "MediaMetadataManager.h"
 #include "MediaSourceDecoder.h"
 #include "MediaStreamListener.h"
 #include "DOMMediaStream.h"
 #include "AudioStreamTrack.h"
 #include "VideoStreamTrack.h"
 #include "MediaTrackList.h"
 #include "MediaStreamError.h"
+#include "VideoFrameContainer.h"
 
 #include "AudioChannelService.h"
 
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/WakeLock.h"
 
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
@@ -3632,29 +3633,43 @@ void HTMLMediaElement::UpdateSrcMediaStr
 
     mWatchManager.Watch(*mMediaStreamListener,
         &HTMLMediaElement::UpdateReadyStateInternal);
 
     stream->AddAudioOutput(this);
     SetVolumeInternal();
 
     VideoFrameContainer* container = GetVideoFrameContainer();
-    if (container) {
-      stream->AddVideoOutput(container);
+    if (mSelectedVideoStreamTrack && container) {
+      mSelectedVideoStreamTrack->AddVideoOutput(container);
+    }
+    VideoTrack* videoTrack = VideoTracks()->GetSelectedTrack();
+    if (videoTrack) {
+      VideoStreamTrack* videoStreamTrack = videoTrack->GetVideoStreamTrack();
+      if (videoStreamTrack && container) {
+        videoStreamTrack->AddVideoOutput(container);
+      }
     }
   } else {
     if (stream) {
       mSrcStreamPausedCurrentTime = CurrentTime();
 
       stream->RemoveListener(mMediaStreamListener);
 
       stream->RemoveAudioOutput(this);
       VideoFrameContainer* container = GetVideoFrameContainer();
-      if (container) {
-        stream->RemoveVideoOutput(container);
+      if (mSelectedVideoStreamTrack && container) {
+        mSelectedVideoStreamTrack->RemoveVideoOutput(container);
+      }
+      VideoTrack* videoTrack = VideoTracks()->GetSelectedTrack();
+      if (videoTrack) {
+        VideoStreamTrack* videoStreamTrack = videoTrack->GetVideoStreamTrack();
+        if (videoStreamTrack && container) {
+          videoStreamTrack->RemoveVideoOutput(container);
+        }
       }
     }
     // If stream is null, then DOMMediaStream::Destroy must have been
     // called and that will remove all listeners/outputs.
 
     mWatchManager.Unwatch(*mMediaStreamListener,
         &HTMLMediaElement::UpdateReadyStateInternal);
 
@@ -3782,16 +3797,19 @@ void HTMLMediaElement::ConstructMediaTra
     // must be selected.
     int index = firstEnabledVideo >= 0 ? firstEnabledVideo : 0;
     (*VideoTracks())[index]->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
     VideoTrack* track = (*VideoTracks())[index];
     VideoStreamTrack* streamTrack = track->GetVideoStreamTrack();
     mMediaStreamSizeListener = new StreamSizeListener(this);
     streamTrack->AddDirectListener(mMediaStreamSizeListener);
     mSelectedVideoStreamTrack = streamTrack;
+    if (GetVideoFrameContainer()) {
+      mSelectedVideoStreamTrack->AddVideoOutput(GetVideoFrameContainer());
+    }
   }
 }
 
 void
 HTMLMediaElement::NotifyMediaStreamTrackAdded(const RefPtr<MediaStreamTrack>& aTrack)
 {
   MOZ_ASSERT(aTrack);
 
@@ -3814,16 +3832,20 @@ HTMLMediaElement::NotifyMediaStreamTrack
     // New MediaStreamTrack added, set the new added video track as selected
     // video track when there is no selected track.
     if (selectedIndex == -1) {
       MOZ_ASSERT(!mSelectedVideoStreamTrack);
       videoTrack->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
       mMediaStreamSizeListener = new StreamSizeListener(this);
       t->AddDirectListener(mMediaStreamSizeListener);
       mSelectedVideoStreamTrack = t;
+      VideoFrameContainer* container = GetVideoFrameContainer();
+      if (mSrcStreamIsPlaying && container) {
+        mSelectedVideoStreamTrack->AddVideoOutput(container);
+      }
     }
 
   }
 }
 
 void
 HTMLMediaElement::NotifyMediaStreamTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack)
 {
@@ -3843,16 +3865,20 @@ HTMLMediaElement::NotifyMediaStreamTrack
     // If the removed media stream track is selected video track and there are
     // still video tracks, change the selected video track to the first
     // remaining track.
     if (aTrack == mSelectedVideoStreamTrack) {
       // The mMediaStreamSizeListener might already reset to nullptr.
       if (mMediaStreamSizeListener) {
         mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
       }
+      VideoFrameContainer* container = GetVideoFrameContainer();
+      if (mSrcStreamIsPlaying && container) {
+        mSelectedVideoStreamTrack->RemoveVideoOutput(container);
+      }
       mSelectedVideoStreamTrack = nullptr;
       MOZ_ASSERT(mSrcStream);
       nsTArray<RefPtr<VideoStreamTrack>> tracks;
       mSrcStream->GetVideoTracks(tracks);
 
       for (const RefPtr<VideoStreamTrack>& track : tracks) {
         if (track->Ended()) {
           continue;
@@ -3866,16 +3892,19 @@ HTMLMediaElement::NotifyMediaStreamTrack
         MediaTrack* videoTrack = VideoTracks()->GetTrackById(trackId);
         MOZ_ASSERT(videoTrack);
 
         videoTrack->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
         if (mMediaStreamSizeListener) {
           track->AddDirectListener(mMediaStreamSizeListener);
         }
         mSelectedVideoStreamTrack = track;
+        if (container) {
+          mSelectedVideoStreamTrack->AddVideoOutput(container);
+        }
         return;
       }
 
       // There is no enabled video track existing, clean the
       // mMediaStreamSizeListener.
       if (mMediaStreamSizeListener) {
         mMediaStreamSizeListener->Forget();
         mMediaStreamSizeListener = nullptr;
--- a/dom/html/HTMLVideoElement.cpp
+++ b/dom/html/HTMLVideoElement.cpp
@@ -12,16 +12,17 @@
 #include "nsSize.h"
 #include "nsError.h"
 #include "nsNodeInfoManager.h"
 #include "plbase64.h"
 #include "nsXPCOMStrings.h"
 #include "prlock.h"
 #include "nsThreadUtils.h"
 #include "ImageContainer.h"
+#include "VideoFrameContainer.h"
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 
 #include "nsITimer.h"
 
 #include "MediaError.h"
 #include "MediaDecoder.h"
--- a/dom/html/test/forms/test_step_attribute.html
+++ b/dom/html/test/forms/test_step_attribute.html
@@ -24,18 +24,17 @@ var data = [
   { type: 'text', apply: false },
   { type: 'search', apply: false },
   { type: 'tel', apply: false },
   { type: 'url', apply: false },
   { type: 'email', apply: false },
   { type: 'password', apply: false },
   { type: 'datetime', apply: true, todo: true },
   { type: 'date', apply: true },
-  // TODO: temporary set to false until bug 888324 is fixed.
-  { type: 'month', apply: false },
+  { type: 'month', apply: true },
   { type: 'week', apply: true, todo: true },
   { type: 'time', apply: true },
   { type: 'datetime-local', apply: true, todo: true },
   { type: 'number', apply: true },
   { type: 'range', apply: true },
   { type: 'color', apply: false },
   { type: 'checkbox', apply: false },
   { type: 'radio', apply: false },
@@ -717,17 +716,132 @@ for (var test of data) {
         } else {
           checkValidity(input, false, apply,
                         { low: test.low, high: test.high });
         }
       }
 
       break;
     case 'month':
-      // TODO: this is temporary until bug 888324 is fixed.
+      // When step is invalid, every date is valid
+      input.step = 0;
+      input.value = '2016-07';
+      checkValidity(input, true, apply);
+
+      input.step = 'foo';
+      input.value = '1970-01';
+      checkValidity(input, true, apply);
+
+      input.step = '-1';
+      input.value = '1970-01';
+      checkValidity(input, true, apply);
+
+      input.removeAttribute('step');
+      input.value = '1500-01';
+      checkValidity(input, true, apply);
+
+      input.step = 'any';
+      input.value = '1966-12';
+      checkValidity(input, true, apply);
+
+      input.step = 'ANY';
+      input.value = '2013-02';
+      checkValidity(input, true, apply);
+
+      // When min is set to a valid month, there is a step base.
+      input.min = '2000-01';
+      input.step = '2';
+      input.value = '2000-03';
+      checkValidity(input, true, apply);
+
+      input.value = '2000-02';
+      checkValidity(input, false, apply, { low: "2000-01", high: "2000-03" });
+
+      input.min = '2012-12';
+      input.value = '2013-01';
+      checkValidity(input, false, apply, { low: "2012-12", high: "2013-02" });
+
+      input.min = '2010-10';
+      input.value = '2010-11';
+      checkValidity(input, false, apply, { low: "2010-10", high: "2010-12" });
+
+      input.min = '2010-01';
+      input.step = '1.1';
+      input.value = '2010-02';
+      checkValidity(input, true, apply);
+
+      input.min = '2010-05';
+      input.step = '1.9';
+      input.value = '2010-06';
+      checkValidity(input, false, apply, { low: "2010-05", high: "2010-07" });
+
+      // Without any step attribute the date is valid
+      input.removeAttribute('step');
+      checkValidity(input, true, apply);
+
+      input.min = '1950-01';
+      input.step = '13';
+      input.value = '1951-01';
+      checkValidity(input, false, apply, { low: "1950-01", high: "1951-02" });
+
+      input.min = '1951-01';
+      input.step = '12';
+      input.value = '1952-01';
+      checkValidity(input, true, apply);
+
+      input.step = '0.9';
+      input.value = '1951-02';
+      checkValidity(input, true, apply);
+
+      input.step = '1.5';
+      input.value = '1951-04';
+      checkValidity(input, false, apply, { low: "1951-03", high: "1951-05" });
+
+      input.value = '1951-08';
+      checkValidity(input, false, apply, { low: "1951-07", high: "1951-09" });
+
+      input.step = '300';
+      input.min= '1968-01';
+      input.value = '1968-05';
+      checkValidity(input, false, apply, { low: "1968-01", high: "1993-01" });
+
+      input.value = '1971-01';
+      checkValidity(input, false, apply, { low: "1968-01", high: "1993-01" });
+
+      input.value = '1994-01';
+      checkValidity(input, false, apply, { low: "1993-01", high: "2018-01" });
+
+      input.value = '2018-01';
+      checkValidity(input, true, apply);
+
+      input.value = '2043-01';
+      checkValidity(input, true, apply);
+
+      input.step = '2.1';
+      input.min = '1991-01';
+      input.value = '1991-01';
+      checkValidity(input, true, apply);
+
+      input.value = '1991-02';
+      checkValidity(input, false, apply, { low: "1991-01", high: "1991-03" });
+
+      input.value = '1991-03';
+      checkValidity(input, true, apply);
+
+      input.step = '2.1';
+      input.min = '1969-12';
+      input.value = '1969-12';
+      checkValidity(input, true, apply);
+
+      input.value = '1970-01';
+      checkValidity(input, false, apply, { low: "1969-12", high: "1970-02" });
+
+      input.value = '1970-02';
+      checkValidity(input, true, apply);
+
       break;
     default:
       ok(false, "Implement the tests for <input type='" + test.type + " >");
       break;
   }
 }
 
 </script>
--- a/dom/html/test/forms/test_stepup_stepdown.html
+++ b/dom/html/test/forms/test_stepup_stepdown.html
@@ -45,23 +45,23 @@ function checkAvailability()
     ["submit", false],
     ["image", false],
     ["reset", false],
     ["button", false],
     ["number", true],
     ["range", true],
     ["date", true],
     ["time", true],
+    ["month", true],
     ["color", false],
   ];
 
   var todoList =
   [
     ["datetime", true],
-    ["month", true],
     ["week", true],
     ["datetime-local", true],
   ];
 
   var element = document.createElement("input");
   element.setAttribute('value', '0');
 
   for (data of testData) {
@@ -376,16 +376,80 @@ function checkStepDown()
     // value = "" (NaN).
     [ '',   null,   null,  null,  null, '23:59',    false ],
     // With step = 'any'.
     [ '17:26',  'any',  null,  null,  1,  null,  true ],
     [ '17:26',  'ANY',  null,  null,  1,  null,  true ],
     [ '17:26',  'AnY',  null,  null,  1,  null,  true ],
     [ '17:26',  'aNy',  null,  null,  1,  null,  true ],
   ]},
+  { type: 'month', data: [
+    // Regular case.
+    [ '2016-08',  null,  null,  null,  null, '2016-07',   false ],
+    // Argument testing.
+    [ '2016-08',  null,  null,  null,  1,    '2016-07',   false ],
+    [ '2016-08',  null,  null,  null,  5,    '2016-03',   false ],
+    [ '2016-08',  null,  null,  null,  -1,   '2016-09',   false ],
+    [ '2016-08',  null,  null,  null,  0,    '2016-08',   false ],
+    // Month/Year wrapping.
+    [ '2016-01',  null,  null,  null,  1,    '2015-12',   false ],
+    [ '1969-02',  null,  null,  null,  4,    '1968-10',   false ],
+    [ '1969-01',  null,  null,  null,  -12,  '1970-01',   false ],
+    // Float values are rounded to integer (1.1 -> 1).
+    [ '2016-08',  null,  null,  null,  1.1,  '2016-07',   false ],
+    [ '2016-01',  null,  null,  null,  1.9,  '2015-12',   false ],
+    // With step values.
+    [ '2016-03',  '0.5', null,  null,  null,      '2016-02',   false ],
+    [ '2016-03',  '2',   null,  null,  null,      '2016-01',   false ],
+    [ '2016-03',  '0.25',null,  null,  4,         '2015-11',   false ],
+    [ '2016-12',  '1.1',  '2016-01', null,  1,    '2016-11',   false ],
+    [ '2016-12',  '1.1',  '2016-01', null,  2,    '2016-10',   false ],
+    [ '2016-12',  '1.1',  '2016-01', null,  10,   '2016-02',   false ],
+    [ '2016-12',  '1.1',  '2016-01', null,  12,   '2016-01',   false ],
+    [ '1968-12',  '1.1',  '1968-01', null,  8,    '1968-04',   false ],
+    // step = 0 isn't allowed (-> step = 1).
+    [ '2016-02',  '0',   null,  null,  null, '2016-01',   false ],
+    // step < 0 isn't allowed (-> step = 1).
+    [ '2016-02',  '-1',  null,  null,  null, '2016-01',   false ],
+    // step = NaN isn't allowed (-> step = 1).
+    [ '2016-02',  'foo', null,  null,  null, '2016-01',   false ],
+    // Min values testing.
+    [ '2016-03',  '1',    'foo',     null,      2,     '2016-01',  false ],
+    [ '2016-02',  '1',    '2016-01', null,      null,  '2016-01',  false ],
+    [ '2016-01',  '1',    '2016-01', null,      null,  '2016-01',  false ],
+    [ '2016-01',  '1',    '2016-01', null,      1,     '2016-01',  false ],
+    [ '2016-05',  '3',    '2016-01', null,      null,  '2016-04',  false ],
+    [ '1969-01',  '5',    '1969-01', '1969-02', null,  '1969-01',  false ],
+    // Max values testing.
+    [ '2016-02',  '1',    null,  'foo',      null,  '2016-01',  false ],
+    [ '2016-02',  null,   null,  '2016-05',  null,  '2016-01',  false ],
+    [ '2016-03',  null,   null,  '2016-03',  null,  '2016-02',  false ],
+    [ '2016-07',  null,   null,  '2016-04',  4,     '2016-03',  false ],
+    [ '2016-07',  '2',    null,  '2016-04',  3,     '2016-01',  false ],
+    // Step mismatch.
+    [ '2016-04',  '2',    '2016-01',  null,         null,  '2016-03',  false ],
+    [ '2016-06',  '2',    '2016-01',  null,         2,     '2016-03',  false ],
+    [ '2016-05',  '2',    '2016-04',  '2016-08',    null,  '2016-04',  false ],
+    [ '1970-04',  '2',    null,       null,         null,  '1970-02',  false ],
+    [ '1970-09',  '3',    null,       null,         null,  '1970-06',  false ],
+    // Clamping.
+    [ '2016-05',  null,   null,       '2016-01',    null,  '2016-01',  false ],
+    [ '1970-05',  '2',    '1970-02',  '1970-05',    null,  '1970-04',  false ],
+    [ '1970-01',  '5',    '1970-02',  '1970-09',    10,    '1970-01',  false ],
+    [ '1970-07',  '5',    '1969-12',  '1970-10',    2,     '1969-12',  false ],
+    [ '1970-08',  '3',    '1970-01',  '1970-07',    15,    '1970-01',  false ],
+    [ '1970-10',  '3',    '1970-01',  '1970-06',    2,     '1970-04',  false ],
+    // value = "" (NaN).
+    [ '',   null,   null,  null,  null, '1969-12',    false ],
+    // With step = 'any'.
+    [ '2016-01',  'any',  null,  null,  1,  null,  true ],
+    [ '2016-01',  'ANY',  null,  null,  1,  null,  true ],
+    [ '2016-01',  'AnY',  null,  null,  1,  null,  true ],
+    [ '2016-01',  'aNy',  null,  null,  1,  null,  true ],
+  ]},
   ];
 
   for (var test of testData) {
     for (var data of test.data) {
       var element = document.createElement("input");
       element.type = test.type;
 
       if (data[1] != null) {
@@ -589,17 +653,16 @@ function checkStepUp()
     // With step values.
     [ '2012-01-01',  '0.5',  null,         null,  null, '2012-01-02',   false ],
     [ '2012-01-01',  '2',    null,         null,  null, '2012-01-03',   false ],
     [ '2012-01-01',  '0.25', null,         null,  4,    '2012-01-05',   false ],
     [ '2012-01-01',  '1.1',  '2012-01-01', null,  1,    '2012-01-02',   false ],
     [ '2012-01-01',  '1.1',  '2012-01-01', null,  2,    '2012-01-03',   false ],
     [ '2012-01-01',  '1.1',  '2012-01-01', null,  10,   '2012-01-11',   false ],
     [ '2012-01-01',  '1.1',  '2012-01-01', null,  11,   '2012-01-12',   false ],
-    [ '1968-01-01',  '1.1',  '1968-01-01', null,  8,    '1968-01-09',   false ],
     // step = 0 isn't allowed (-> step = 1).
     [ '2012-01-01',  '0',   null,  null,  null, '2012-01-02',   false ],
     // step < 0 isn't allowed (-> step = 1).
     [ '2012-01-01',  '-1',  null,  null,  null, '2012-01-02',   false ],
     // step = NaN isn't allowed (-> step = 1).
     [ '2012-01-01',  'foo', null,  null,  null, '2012-01-02',   false ],
     // Min values testing.
     [ '2012-01-01',  '1',   'foo',         null,  null,  '2012-01-02',  false ],
@@ -608,17 +671,16 @@ function checkStepUp()
     [ '2012-01-01',  null,  '2012-01-01',  null,  null,  '2012-01-02',  false ],
     [ '2012-01-01',  null,  '2012-01-04',  null,  4,     '2012-01-05',  false ],
     [ '2012-01-01',  '2',   '2012-01-04',  null,  3,     '2012-01-06',  false ],
     // Max values testing.
     [ '2012-01-01',  '1',    null,  'foo',        2,     '2012-01-03',  false ],
     [ '2012-01-01',  '1',    null,  '2012-01-10', 1,     '2012-01-02',  false ],
     [ '2012-01-02',  null,   null,  '2012-01-01', null,  '2012-01-02',  false ],
     [ '2012-01-02',  null,   null,  '2012-01-02', null,  '2012-01-02',  false ],
-    [ '2012-01-02',  null,   null,  '2012-01-02', null,  '2012-01-02',  false ],
     [ '1969-01-02',  '5',    '1969-01-01',  '1969-01-02', null,  '1969-01-02',  false ],
     // Step mismatch.
     [ '2012-01-02',  '2',    '2012-01-01',  null,         null,  '2012-01-03',  false ],
     [ '2012-01-02',  '2',    '2012-01-01',  null,         2,     '2012-01-05',  false ],
     [ '2012-01-05',  '2',    '2012-01-01',  '2012-01-06', null,  '2012-01-05',  false ],
     [ '1970-01-02',  '2',    null,          null,         null,  '1970-01-04',  false ],
     [ '1970-01-05',  '3',    null,          null,         null,  '1970-01-08',  false ],
     [ '1970-01-03',  '3',    null,          null,         null,  '1970-01-06',  false ],
@@ -697,16 +759,81 @@ function checkStepUp()
     // value = "" (NaN).
     [ '',   null,   null,  null,  null, '00:01',    false ],
     // With step = 'any'.
     [ '17:26',  'any',  null,  null,  1,  null,  true ],
     [ '17:26',  'ANY',  null,  null,  1,  null,  true ],
     [ '17:26',  'AnY',  null,  null,  1,  null,  true ],
     [ '17:26',  'aNy',  null,  null,  1,  null,  true ],
   ]},
+  { type: 'month', data: [
+    // Regular case.
+    [ '2016-08',  null,  null,  null,  null, '2016-09',   false ],
+    // Argument testing.
+    [ '2016-08',  null,  null,  null,  1,    '2016-09',   false ],
+    [ '2016-08',  null,  null,  null,  9,    '2017-05',   false ],
+    [ '2016-08',  null,  null,  null,  -1,   '2016-07',   false ],
+    [ '2016-08',  null,  null,  null,  0,    '2016-08',   false ],
+    // Month/Year wrapping.
+    [ '2015-12',  null,  null,  null,  1,    '2016-01',   false ],
+    [ '1968-12',  null,  null,  null,  4,    '1969-04',   false ],
+    [ '1970-01',  null,  null,  null,  -12,  '1969-01',   false ],
+    // Float values are rounded to integer (1.1 -> 1).
+    [ '2016-01',  null,  null,  null,  1.1,  '2016-02',   false ],
+    [ '2016-01',  null,  null,  null,  1.9,  '2016-02',   false ],
+    // With step values.
+    [ '2016-01',  '0.5',  null,         null,  null, '2016-02',   false ],
+    [ '2016-01',  '2',    null,         null,  null, '2016-03',   false ],
+    [ '2016-01',  '0.25', null,         null,  4,    '2016-05',   false ],
+    [ '2016-01',  '1.1',  '2016-01',    null,  1,    '2016-02',   false ],
+    [ '2016-01',  '1.1',  '2016-01',    null,  2,    '2016-03',   false ],
+    [ '2016-01',  '1.1',  '2016-01',    null,  10,   '2016-11',   false ],
+    [ '2016-01',  '1.1',  '2016-01',    null,  11,   '2016-12',   false ],
+    // step = 0 isn't allowed (-> step = 1).
+    [ '2016-01',  '0',   null,  null,  null, '2016-02',   false ],
+    // step < 0 isn't allowed (-> step = 1).
+    [ '2016-01',  '-1',  null,  null,  null, '2016-02',   false ],
+    // step = NaN isn't allowed (-> step = 1).
+    [ '2016-01',  'foo', null,  null,  null, '2016-02',   false ],
+    // Min values testing.
+    [ '2016-01',  '1',   'foo',      null,  null,  '2016-02',  false ],
+    [ '2016-01',  null,  '2015-12',  null,  null,  '2016-02',  false ],
+    [ '2016-01',  null,  '2016-02',  null,  null,  '2016-02',  false ],
+    [ '2016-01',  null,  '2016-01',  null,  null,  '2016-02',  false ],
+    [ '2016-01',  null,  '2016-04',  null,  4,     '2016-05',  false ],
+    [ '2016-01',  '2',   '2016-04',  null,  3,     '2016-06',  false ],
+    // Max values testing.
+    [ '2016-01',  '1',    null,       'foo',        2,     '2016-03',  false ],
+    [ '2016-01',  '1',    null,       '2016-02',    1,     '2016-02',  false ],
+    [ '2016-02',  null,   null,       '2016-01',    null,  '2016-02',  false ],
+    [ '2016-02',  null,   null,       '2016-02',    null,  '2016-02',  false ],
+    [ '1969-02',  '5',    '1969-01',  '1969-02',    null,  '1969-02',  false ],
+    // Step mismatch.
+    [ '2016-02',  '2',    '2016-01',  null,         null,  '2016-03',  false ],
+    [ '2016-02',  '2',    '2016-01',  null,         2,     '2016-05',  false ],
+    [ '2016-05',  '2',    '2016-01',  '2016-06',    null,  '2016-05',  false ],
+    [ '1970-02',  '2',    null,       null,         null,  '1970-04',  false ],
+    [ '1970-05',  '3',    null,       null,         null,  '1970-08',  false ],
+    [ '1970-03',  '3',    null,       null,         null,  '1970-06',  false ],
+    [ '1970-03',  '3',    '1970-02',  null,         null,  '1970-05',  false ],
+    // Clamping.
+    [ '2016-01',  null,   '2016-12',  null,         null,  '2016-12',  false ],
+    [ '1970-02',  '2',    '1970-01',  '1970-04',    null,  '1970-03',  false ],
+    [ '1970-01',  '5',    '1970-02',  '1970-09',    10,    '1970-07',  false ],
+    [ '1969-11',  '5',    '1969-12',  '1970-06',    3,     '1970-05',  false ],
+    [ '1970-01',  '3',    '1970-02',  '1971-07',    15,    '1971-05',  false ],
+    [ '1970-01',  '3',    '1970-01',  '1970-06',    2,     '1970-04',  false ],
+    // value = "" (NaN).
+    [ '',   null,   null,  null,  null, '1970-02',    false ],
+    // With step = 'any'.
+    [ '2016-01',  'any',  null,  null,  1,  null,  true ],
+    [ '2016-01',  'ANY',  null,  null,  1,  null,  true ],
+    [ '2016-01',  'AnY',  null,  null,  1,  null,  true ],
+    [ '2016-01',  'aNy',  null,  null,  1,  null,  true ],
+  ]},
   ];
 
   for (var test of testData) {
     for (var data of test.data) {
       var element = document.createElement("input");
       element.type = test.type;
 
       if (data[1] != null) {
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -30,16 +30,17 @@
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/FlyWebPublishedServerIPC.h"
 #include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/PCrashReporterChild.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/dom/nsIContentChild.h"
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "mozilla/ipc/PSendStreamChild.h"
@@ -2271,16 +2272,23 @@ ContentChild::RecvSystemMemoryAvailable(
 bool
 ContentChild::RecvPreferenceUpdate(const PrefSetting& aPref)
 {
   Preferences::SetPreference(aPref);
   return true;
 }
 
 bool
+ContentChild::RecvVarUpdate(const GfxVarUpdate& aVar)
+{
+  gfx::gfxVars::ApplyUpdate(aVar);
+  return true;
+}
+
+bool
 ContentChild::RecvDataStoragePut(const nsString& aFilename,
                                  const DataStorageItem& aItem)
 {
   RefPtr<DataStorage> storage = DataStorage::GetIfExists(aFilename);
   if (storage) {
     storage->Put(aItem.key(), aItem.value(), aItem.type());
   }
   return true;
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -404,16 +404,17 @@ public:
 
   // auto remove when alertfinished is received.
   nsresult AddRemoteAlertObserver(const nsString& aData, nsIObserver* aObserver);
 
   virtual bool RecvSystemMemoryAvailable(const uint64_t& aGetterId,
                                          const uint32_t& aMemoryAvailable) override;
 
   virtual bool RecvPreferenceUpdate(const PrefSetting& aPref) override;
+  virtual bool RecvVarUpdate(const GfxVarUpdate& pref) override;
 
   virtual bool RecvDataStoragePut(const nsString& aFilename,
                                   const DataStorageItem& aItem) override;
 
   virtual bool RecvDataStorageRemove(const nsString& aFilename,
                                      const nsCString& aKey,
                                      const DataStorageType& aType) override;
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -69,16 +69,17 @@
 #include "mozilla/dom/PresentationParent.h"
 #include "mozilla/dom/PPresentationParent.h"
 #include "mozilla/dom/FlyWebPublishedServerIPC.h"
 #include "mozilla/dom/quota/QuotaManagerService.h"
 #include "mozilla/dom/telephony/TelephonyParent.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
 #include "mozilla/dom/voicemail/VoicemailParent.h"
 #include "mozilla/embedding/printingui/PrintingParent.h"
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/FileDescriptorSetParent.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/PFileDescriptorSetParent.h"
 #include "mozilla/ipc/PSendStreamParent.h"
@@ -1845,16 +1846,17 @@ ContentParent::ActorDestroy(ActorDestroy
     for (size_t i = 0; i < length; ++i) {
       obs->RemoveObserver(static_cast<nsIObserver*>(this),
                           sObserverTopics[i]);
     }
   }
 
   // remove the global remote preferences observers
   Preferences::RemoveObserver(this, "");
+  gfxVars::RemoveReceiver(this);
 
   RecvRemoveGeolocationListener();
 
   mConsoleService = nullptr;
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
   if (mGatherer && !mProfile.IsEmpty()) {
     mGatherer->OOPExitProfile(mProfile);
@@ -2394,16 +2396,39 @@ ContentParent::Pid() const
 bool
 ContentParent::RecvReadPrefsArray(InfallibleTArray<PrefSetting>* aPrefs)
 {
   Preferences::GetPreferences(aPrefs);
   return true;
 }
 
 bool
+ContentParent::RecvGetGfxVars(InfallibleTArray<GfxVarUpdate>* aVars)
+{
+  // Ensure gfxVars is initialized (for xpcshell tests).
+  gfxVars::Initialize();
+
+  *aVars = gfxVars::FetchNonDefaultVars();
+
+  // Now that content has initialized gfxVars, we can start listening for
+  // updates.
+  gfxVars::AddReceiver(this);
+  return true;
+}
+
+void
+ContentParent::OnVarChanged(const GfxVarUpdate& aVar)
+{
+  if (!mIPCOpen) {
+    return;
+  }
+  Unused << SendVarUpdate(aVar);
+}
+
+bool
 ContentParent::RecvReadFontList(InfallibleTArray<FontListEntry>* retValue)
 {
 #ifdef ANDROID
   gfxAndroidPlatform::GetPlatform()->GetSystemFontList(retValue);
 #endif
   return true;
 }
 
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -4,16 +4,17 @@
  * 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_ContentParent_h
 #define mozilla_dom_ContentParent_h
 
 #include "mozilla/dom/PContentParent.h"
 #include "mozilla/dom/nsIContentParent.h"
+#include "mozilla/gfx/gfxVarReceiver.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/UniquePtr.h"
 
@@ -84,16 +85,17 @@ class TabContext;
 class ContentBridgeParent;
 class GetFilesHelper;
 
 class ContentParent final : public PContentParent
                           , public nsIContentParent
                           , public nsIObserver
                           , public nsIDOMGeoPositionCallback
                           , public nsIDOMGeoPositionErrorCallback
+                          , public gfx::gfxVarReceiver
                           , public mozilla::LinkedListElement<ContentParent>
 {
   typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost;
   typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
   typedef mozilla::ipc::PFileDescriptorSetParent PFileDescriptorSetParent;
   typedef mozilla::ipc::TestShellParent TestShellParent;
   typedef mozilla::ipc::URIParams URIParams;
   typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
@@ -563,16 +565,18 @@ public:
 
 protected:
   void OnChannelConnected(int32_t pid) override;
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   bool ShouldContinueFromReplyTimeout() override;
 
+  void OnVarChanged(const GfxVarUpdate& aVar) override;
+
 private:
   static nsDataHashtable<nsStringHashKey, ContentParent*> *sAppContentParents;
   static nsTArray<ContentParent*>* sNonAppContentParents;
   static nsTArray<ContentParent*>* sPrivateContent;
   static StaticAutoPtr<LinkedList<ContentParent> > sContentParents;
 
   static void JoinProcessesIOThread(const nsTArray<ContentParent*>* aProcesses,
                                     Monitor* aMonitor, bool* aDone);
@@ -903,16 +907,17 @@ private:
   virtual PWebBrowserPersistDocumentParent*
   AllocPWebBrowserPersistDocumentParent(PBrowserParent* aBrowser,
                                         const uint64_t& aOuterWindowID) override;
 
   virtual bool
   DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor) override;
 
   virtual bool RecvReadPrefsArray(InfallibleTArray<PrefSetting>* aPrefs) override;
+  virtual bool RecvGetGfxVars(InfallibleTArray<GfxVarUpdate>* aVars) override;
 
   virtual bool RecvReadFontList(InfallibleTArray<FontListEntry>* retValue) override;
 
   virtual bool RecvReadDataStorageArray(const nsString& aFilename,
                                         InfallibleTArray<DataStorageItem>* aValues) override;
 
   virtual bool RecvReadPermissions(InfallibleTArray<IPC::Permission>* aPermissions) override;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -514,16 +514,17 @@ child:
     async SetOffline(bool offline);
     async SetConnectivity(bool connectivity);
 
     async NotifyVisited(URIParams uri);
 
     async SystemMemoryAvailable(uint64_t getterId, uint32_t memoryAvailable);
 
     async PreferenceUpdate(PrefSetting pref);
+    async VarUpdate(GfxVarUpdate var);
 
     async DataStoragePut(nsString aFilename, DataStorageItem aItem);
     async DataStorageRemove(nsString aFilename, nsCString aKey, DataStorageType aType);
     async DataStorageClear(nsString aFilename);
 
     async NotifyAlertsObserver(nsCString topic, nsString data);
 
     async GeolocationUpdate(GeoPosition somewhere);
@@ -875,16 +876,17 @@ parent:
     async VisitURI(URIParams uri, OptionalURIParams referrer, uint32_t flags);
     async SetURITitle(URIParams uri, nsString title);
 
     async LoadURIExternal(URIParams uri, PBrowser windowContext);
     async ExtProtocolChannelConnectParent(uint32_t registrarId);
 
     // PrefService message
     sync ReadPrefsArray() returns (PrefSetting[] prefs) verify;
+    sync GetGfxVars() returns (GfxVarUpdate[] vars);
 
     sync ReadFontList() returns (FontListEntry[] retValue);
 
     sync ReadDataStorageArray(nsString aFilename)
       returns (DataStorageItem[] retValue);
 
     sync SyncMessage(nsString aMessage, ClonedMessageData aData,
                      CpowEntry[] aCpows, Principal aPrincipal)
--- a/dom/media/MP3Demuxer.cpp
+++ b/dom/media/MP3Demuxer.cpp
@@ -364,17 +364,17 @@ MP3TrackDemuxer::StreamLength() const {
 TimeUnit
 MP3TrackDemuxer::Duration() const {
   if (!mNumParsedFrames) {
     return TimeUnit::FromMicroseconds(-1);
   }
 
   int64_t numFrames = 0;
   const auto numAudioFrames = mParser.VBRInfo().NumAudioFrames();
-  if (mParser.VBRInfo().IsValid()) {
+  if (mParser.VBRInfo().IsValid() && numAudioFrames.valueOr(0) + 1 > 1) {
     // VBR headers don't include the VBR header frame.
     numFrames = numAudioFrames.value() + 1;
   } else {
     const int64_t streamLen = StreamLength();
     if (streamLen < 0) {
       // Unknown length, we can't estimate duration.
       return TimeUnit::FromMicroseconds(-1);
     }
@@ -577,17 +577,16 @@ MP3TrackDemuxer::GetNextFrame(const Medi
   frame->mTimecode = frame->mTime;
   frame->mKeyframe = true;
 
   MOZ_ASSERT(frame->mTime >= 0);
   MOZ_ASSERT(frame->mDuration > 0);
 
   if (mNumParsedFrames == 1) {
     // First frame parsed, let's read VBR info if available.
-    // TODO: read info that helps with seeking (bug 1163667).
     ByteReader reader(frame->Data(), frame->Size());
     mParser.ParseVBRHeader(&reader);
     reader.DiscardRemaining();
     mFirstFrameOffset = frame->mOffset;
   }
 
   MP3LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
           " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
@@ -598,33 +597,33 @@ MP3TrackDemuxer::GetNextFrame(const Medi
   return frame.forget();
 }
 
 int64_t
 MP3TrackDemuxer::OffsetFromFrameIndex(int64_t aFrameIndex) const {
   int64_t offset = 0;
   const auto& vbr = mParser.VBRInfo();
 
-  if (vbr.IsValid()) {
+  if (vbr.IsComplete()) {
     offset = mFirstFrameOffset + aFrameIndex * vbr.NumBytes().value() /
              vbr.NumAudioFrames().value();
   } else if (AverageFrameLength() > 0) {
     offset = mFirstFrameOffset + aFrameIndex * AverageFrameLength();
   }
 
   MP3LOGV("OffsetFromFrameIndex(%" PRId64 ") -> %" PRId64, aFrameIndex, offset);
   return std::max<int64_t>(mFirstFrameOffset, offset);
 }
 
 int64_t
 MP3TrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const {
   int64_t frameIndex = 0;
   const auto& vbr = mParser.VBRInfo();
 
-  if (vbr.IsValid()) {
+  if (vbr.IsComplete()) {
     frameIndex = static_cast<float>(aOffset - mFirstFrameOffset) /
                  vbr.NumBytes().value() * vbr.NumAudioFrames().value();
     frameIndex = std::min<int64_t>(vbr.NumAudioFrames().value(), frameIndex);
   } else if (AverageFrameLength() > 0) {
     frameIndex = (aOffset - mFirstFrameOffset) / AverageFrameLength();
   }
 
   MP3LOGV("FrameIndexFromOffset(%" PRId64 ") -> %" PRId64, aOffset, frameIndex);
@@ -690,17 +689,17 @@ MP3TrackDemuxer::Read(uint8_t* aBuffer, 
 }
 
 double
 MP3TrackDemuxer::AverageFrameLength() const {
   if (mNumParsedFrames) {
     return static_cast<double>(mTotalFrameLen) / mNumParsedFrames;
   }
   const auto& vbr = mParser.VBRInfo();
-  if (vbr.IsValid() && vbr.NumAudioFrames().value() + 1) {
+  if (vbr.IsComplete() && vbr.NumAudioFrames().value() + 1) {
     return static_cast<double>(vbr.NumBytes().value()) /
            (vbr.NumAudioFrames().value() + 1);
   }
   return 0.0;
 }
 
 // FrameParser
 
@@ -1031,17 +1030,22 @@ FrameParser::VBRHeader::Scale() const {
 
 bool
 FrameParser::VBRHeader::IsTOCPresent() const {
   return mTOC.size() == vbr_header::TOC_SIZE;
 }
 
 bool
 FrameParser::VBRHeader::IsValid() const {
-  return mType != NONE &&
+  return mType != NONE;
+}
+
+bool
+FrameParser::VBRHeader::IsComplete() const {
+  return IsValid() &&
          mNumAudioFrames.valueOr(0) > 0 &&
          mNumBytes.valueOr(0) > 0 &&
          // We don't care about the scale for any computations here.
          // mScale < 101 &&
          true;
 }
 
 int64_t
--- a/dom/media/MP3Demuxer.h
+++ b/dom/media/MP3Demuxer.h
@@ -223,19 +223,22 @@ public:
     const Maybe<uint32_t>& NumBytes() const;
 
     // Returns the VBR scale factor (0: best quality, 100: lowest quality).
     const Maybe<uint32_t>& Scale() const;
 
     // Returns true iff Xing/Info TOC (table of contents) is present.
     bool IsTOCPresent() const;
 
-    // Returns whether the header is valid (containing reasonable field values).
+    // Returns whether the header is valid (type XING or VBRI).
     bool IsValid() const;
 
+    // Returns whether the header is valid and contains reasonable non-zero field values.
+    bool IsComplete() const;
+
     // Returns the byte offset for the given duration percentage as a factor
     // (0: begin, 1.0: end).
     int64_t Offset(float aDurationFac) const;
 
     // Parses contents of given ByteReader for a valid VBR header.
     // The offset of the passed ByteReader needs to point to an MPEG frame begin,
     // as a VBRI-style header is searched at a fixed offset relative to frame begin.
     // Returns whether a valid VBR header was found in the range.
--- a/dom/media/MediaData.cpp
+++ b/dom/media/MediaData.cpp
@@ -250,37 +250,36 @@ bool VideoData::SetVideoDataToImage(Plan
     return aVideoImage->CopyData(data);
   } else {
     return aVideoImage->AdoptData(data);
   }
 }
 
 /* static */
 already_AddRefed<VideoData>
-VideoData::Create(const VideoInfo& aInfo,
-                  ImageContainer* aContainer,
-                  Image* aImage,
-                  int64_t aOffset,
-                  int64_t aTime,
-                  int64_t aDuration,
-                  const YCbCrBuffer& aBuffer,
-                  bool aKeyframe,
-                  int64_t aTimecode,
-                  const IntRect& aPicture)
+VideoData::CreateAndCopyData(const VideoInfo& aInfo,
+                             ImageContainer* aContainer,
+                             int64_t aOffset,
+                             int64_t aTime,
+                             int64_t aDuration,
+                             const YCbCrBuffer& aBuffer,
+                             bool aKeyframe,
+                             int64_t aTimecode,
+                             const IntRect& aPicture)
 {
-  if (!aImage && !aContainer) {
+  if (!aContainer) {
     // Create a dummy VideoData with no image. This gives us something to
     // send to media streams if necessary.
     RefPtr<VideoData> v(new VideoData(aOffset,
-                                        aTime,
-                                        aDuration,
-                                        aKeyframe,
-                                        aTimecode,
-                                        aInfo.mDisplay,
-                                        0));
+                                      aTime,
+                                      aDuration,
+                                      aKeyframe,
+                                      aTimecode,
+                                      aInfo.mDisplay,
+                                      0));
     return v.forget();
   }
 
   // The following situation should never happen unless there is a bug
   // in the decoder
   if (aBuffer.mPlanes[1].mWidth != aBuffer.mPlanes[2].mWidth ||
       aBuffer.mPlanes[1].mHeight != aBuffer.mPlanes[2].mHeight) {
     NS_ERROR("C planes with different sizes");
@@ -308,156 +307,104 @@ VideoData::Create(const VideoInfo& aInfo
   {
     // The specified picture dimensions can't be contained inside the video
     // frame, we'll stomp memory if we try to copy it. Fail.
     NS_WARNING("Overflowing picture rect");
     return nullptr;
   }
 
   RefPtr<VideoData> v(new VideoData(aOffset,
-                                      aTime,
-                                      aDuration,
-                                      aKeyframe,
-                                      aTimecode,
-                                      aInfo.mDisplay,
-                                      0));
+                                    aTime,
+                                    aDuration,
+                                    aKeyframe,
+                                    aTimecode,
+                                    aInfo.mDisplay,
+                                    0));
 #ifdef MOZ_WIDGET_GONK
   const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0];
   const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1];
   const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2];
 #endif
 
-  if (!aImage) {
-    // Currently our decoder only knows how to output to ImageFormat::PLANAR_YCBCR
-    // format.
+  // Currently our decoder only knows how to output to ImageFormat::PLANAR_YCBCR
+  // format.
 #ifdef MOZ_WIDGET_GONK
-    if (IsYV12Format(Y, Cb, Cr) && !IsInEmulator()) {
-      v->mImage = new layers::GrallocImage();
-    }
+  if (IsYV12Format(Y, Cb, Cr) && !IsInEmulator()) {
+    v->mImage = new layers::GrallocImage();
+  }
 #endif
-    if (!v->mImage) {
-      v->mImage = aContainer->CreatePlanarYCbCrImage();
-    }
-  } else {
-    v->mImage = aImage;
+  if (!v->mImage) {
+    v->mImage = aContainer->CreatePlanarYCbCrImage();
   }
 
   if (!v->mImage) {
     return nullptr;
   }
   NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::PLANAR_YCBCR ||
                v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR,
                "Wrong format?");
   PlanarYCbCrImage* videoImage = v->mImage->AsPlanarYCbCrImage();
   MOZ_ASSERT(videoImage);
 
-  bool shouldCopyData = (aImage == nullptr);
   if (!VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture,
-                                      shouldCopyData)) {
+                                      true /* aCopyData */)) {
     return nullptr;
   }
 
 #ifdef MOZ_WIDGET_GONK
   if (!videoImage->IsValid() && !aImage && IsYV12Format(Y, Cb, Cr)) {
     // Failed to allocate gralloc. Try fallback.
     v->mImage = aContainer->CreatePlanarYCbCrImage();
     if (!v->mImage) {
       return nullptr;
     }
     videoImage = v->mImage->AsPlanarYCbCrImage();
-    if(!VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture,
-                                       true /* aCopyData */)) {
+    if (!VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture,
+                                        true /* aCopyData */)) {
       return nullptr;
     }
   }
 #endif
   return v.forget();
 }
 
 /* static */
 already_AddRefed<VideoData>
-VideoData::Create(const VideoInfo& aInfo,
-                  ImageContainer* aContainer,
-                  int64_t aOffset,
-                  int64_t aTime,
-                  int64_t aDuration,
-                  const YCbCrBuffer& aBuffer,
-                  bool aKeyframe,
-                  int64_t aTimecode,
-                  const IntRect& aPicture)
-{
-  return Create(aInfo, aContainer, nullptr, aOffset, aTime, aDuration, aBuffer,
-                aKeyframe, aTimecode, aPicture);
-}
-
-/* static */
-already_AddRefed<VideoData>
-VideoData::Create(const VideoInfo& aInfo,
-                  Image* aImage,
-                  int64_t aOffset,
-                  int64_t aTime,
-                  int64_t aDuration,
-                  const YCbCrBuffer& aBuffer,
-                  bool aKeyframe,
-                  int64_t aTimecode,
-                  const IntRect& aPicture)
-{
-  return Create(aInfo, nullptr, aImage, aOffset, aTime, aDuration, aBuffer,
-                aKeyframe, aTimecode, aPicture);
-}
-
-/* static */
-already_AddRefed<VideoData>
 VideoData::CreateFromImage(const VideoInfo& aInfo,
-                           ImageContainer* aContainer,
                            int64_t aOffset,
                            int64_t aTime,
                            int64_t aDuration,
                            const RefPtr<Image>& aImage,
                            bool aKeyframe,
                            int64_t aTimecode,
                            const IntRect& aPicture)
 {
   RefPtr<VideoData> v(new VideoData(aOffset,
-                                      aTime,
-                                      aDuration,
-                                      aKeyframe,
-                                      aTimecode,
-                                      aInfo.mDisplay,
-                                      0));
+                                    aTime,
+                                    aDuration,
+                                    aKeyframe,
+                                    aTimecode,
+                                    aInfo.mDisplay,
+                                    0));
   v->mImage = aImage;
   return v.forget();
 }
 
 #ifdef MOZ_OMX_DECODER
 /* static */
 already_AddRefed<VideoData>
-VideoData::Create(const VideoInfo& aInfo,
-                  ImageContainer* aContainer,
-                  int64_t aOffset,
-                  int64_t aTime,
-                  int64_t aDuration,
-                  mozilla::layers::TextureClient* aBuffer,
-                  bool aKeyframe,
-                  int64_t aTimecode,
-                  const IntRect& aPicture)
+VideoData::CreateAndCopyIntoTextureClient(const VideoInfo& aInfo,
+                                          int64_t aOffset,
+                                          int64_t aTime,
+                                          int64_t aDuration,
+                                          mozilla::layers::TextureClient* aBuffer,
+                                          bool aKeyframe,
+                                          int64_t aTimecode,
+                                          const IntRect& aPicture)
 {
-  if (!aContainer) {
-    // Create a dummy VideoData with no image. This gives us something to
-    // send to media streams if necessary.
-    RefPtr<VideoData> v(new VideoData(aOffset,
-                                        aTime,
-                                        aDuration,
-                                        aKeyframe,
-                                        aTimecode,
-                                        aInfo.mDisplay,
-                                        0));
-    return v.forget();
-  }
-
   // The following situations could be triggered by invalid input
   if (aPicture.width <= 0 || aPicture.height <= 0) {
     NS_WARNING("Empty picture rect");
     return nullptr;
   }
 
   // Ensure the picture size specified in the headers can be extracted out of
   // the frame we've been supplied without indexing out of bounds.
@@ -467,22 +414,22 @@ VideoData::Create(const VideoInfo& aInfo
   {
     // The specified picture dimensions can't be contained inside the video
     // frame, we'll stomp memory if we try to copy it. Fail.
     NS_WARNING("Overflowing picture rect");
     return nullptr;
   }
 
   RefPtr<VideoData> v(new VideoData(aOffset,
-                                      aTime,
-                                      aDuration,
-                                      aKeyframe,
-                                      aTimecode,
-                                      aInfo.mDisplay,
-                                      0));
+                                    aTime,
+                                    aDuration,
+                                    aKeyframe,
+                                    aTimecode,
+                                    aInfo.mDisplay,
+                                    0));
 
   RefPtr<layers::GrallocImage> image = new layers::GrallocImage();
   image->AdoptData(aBuffer, aPicture.Size());
   v->mImage = image;
 
   return v.forget();
 }
 #endif  // MOZ_OMX_DECODER
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -454,61 +454,40 @@ public:
   // Constructs a VideoData object. If aImage is nullptr, creates a new Image
   // holding a copy of the YCbCr data passed in aBuffer. If aImage is not
   // nullptr, it's stored as the underlying video image and aBuffer is assumed
   // to point to memory within aImage so no copy is made. aTimecode is a codec
   // specific number representing the timestamp of the frame of video data.
   // Returns nsnull if an error occurs. This may indicate that memory couldn't
   // be allocated to create the VideoData object, or it may indicate some
   // problem with the input data (e.g. negative stride).
-  static already_AddRefed<VideoData> Create(const VideoInfo& aInfo,
-                                            ImageContainer* aContainer,
-                                            Image* aImage,
-                                            int64_t aOffset,
-                                            int64_t aTime,
-                                            int64_t aDuration,
-                                            const YCbCrBuffer &aBuffer,
-                                            bool aKeyframe,
-                                            int64_t aTimecode,
-                                            const IntRect& aPicture);
+
 
-  // Variant that always makes a copy of aBuffer
-  static already_AddRefed<VideoData> Create(const VideoInfo& aInfo,
-                                            ImageContainer* aContainer,
-                                            int64_t aOffset,
-                                            int64_t aTime,
-                                            int64_t aDuration,
-                                            const YCbCrBuffer &aBuffer,
-                                            bool aKeyframe,
-                                            int64_t aTimecode,
-                                            const IntRect& aPicture);
+  // Creates a new VideoData containing a deep copy of aBuffer. May use aContainer
+  // to allocate an Image to hold the copied data.
+  static already_AddRefed<VideoData> CreateAndCopyData(const VideoInfo& aInfo,
+                                                       ImageContainer* aContainer,
+                                                       int64_t aOffset,
+                                                       int64_t aTime,
+                                                       int64_t aDuration,
+                                                       const YCbCrBuffer &aBuffer,
+                                                       bool aKeyframe,
+                                                       int64_t aTimecode,
+                                                       const IntRect& aPicture);
 
-  // Variant to create a VideoData instance given an existing aImage
-  static already_AddRefed<VideoData> Create(const VideoInfo& aInfo,
-                                            Image* aImage,
-                                            int64_t aOffset,
-                                            int64_t aTime,
-                                            int64_t aDuration,
-                                            const YCbCrBuffer &aBuffer,
-                                            bool aKeyframe,
-                                            int64_t aTimecode,
-                                            const IntRect& aPicture);
-
-  static already_AddRefed<VideoData> Create(const VideoInfo& aInfo,
-                                            ImageContainer* aContainer,
-                                            int64_t aOffset,
-                                            int64_t aTime,
-                                            int64_t aDuration,
-                                            layers::TextureClient* aBuffer,
-                                            bool aKeyframe,
-                                            int64_t aTimecode,
-                                            const IntRect& aPicture);
+  static already_AddRefed<VideoData> CreateAndCopyIntoTextureClient(const VideoInfo& aInfo,
+                                                                    int64_t aOffset,
+                                                                    int64_t aTime,
+                                                                    int64_t aDuration,
+                                                                    layers::TextureClient* aBuffer,
+                                                                    bool aKeyframe,
+                                                                    int64_t aTimecode,
+                                                                    const IntRect& aPicture);
 
   static already_AddRefed<VideoData> CreateFromImage(const VideoInfo& aInfo,
-                                                     ImageContainer* aContainer,
                                                      int64_t aOffset,
                                                      int64_t aTime,
                                                      int64_t aDuration,
                                                      const RefPtr<Image>& aImage,
                                                      bool aKeyframe,
                                                      int64_t aTimecode,
                                                      const IntRect& aPicture);
 
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/MathAlgorithms.h"
 #include <limits>
 #include "nsIObserver.h"
 #include "nsTArray.h"
 #include "VideoUtils.h"
 #include "MediaDecoderStateMachine.h"
 #include "ImageContainer.h"
 #include "MediaResource.h"
+#include "VideoFrameContainer.h"
 #include "nsError.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "nsIMemoryReporter.h"
 #include "nsComponentManagerUtils.h"
 #include <algorithm>
 #include "MediaShutdownManager.h"
 #include "AudioChannelService.h"
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -12,16 +12,17 @@
 #include "nsSize.h"
 #include "Layers.h"
 #include "MediaData.h"
 #include "MediaInfo.h"
 #include "MediaFormatReader.h"
 #include "MediaResource.h"
 #include "mozilla/SharedThreadPool.h"
 #include "VideoUtils.h"
+#include "VideoFrameContainer.h"
 
 #include <algorithm>
 
 #ifdef MOZ_EME
 #include "mozilla/CDMProxy.h"
 #endif
 
 using namespace mozilla::media;
@@ -1653,25 +1654,23 @@ MediaFormatReader::SetSeekTarget(const S
   mFallbackSeekTime = mPendingSeekTime = Some(target.GetTime());
 }
 
 TimeUnit
 MediaFormatReader::DemuxStartTime()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(!ForceZeroStartTime());
-  MOZ_ASSERT((!HasAudio() || mAudio.mFirstDemuxedSampleTime.isSome()) &&
-             (!HasVideo() || mVideo.mFirstDemuxedSampleTime.isSome()));
+  MOZ_ASSERT(HasAudio() || HasVideo());
 
-  return std::min(HasAudio()
-                  ? mAudio.mFirstDemuxedSampleTime.ref()
-                  : TimeUnit::FromInfinity(),
-                  HasVideo()
-                  ? mVideo.mFirstDemuxedSampleTime.ref()
-                  : TimeUnit::FromInfinity());
+  const TimeUnit startTime =
+    std::min(mAudio.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()),
+             mVideo.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()));
+
+  return startTime.IsInfinite() ? TimeUnit::FromMicroseconds(0) : startTime;
 }
 
 void
 MediaFormatReader::ScheduleSeek()
 {
   if (mSeekScheduled) {
     return;
   }
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -290,18 +290,20 @@ class MediaRecorder::Session: public nsI
   private:
     RefPtr<Session> mSession;
   };
 
   // For Ensure recorder has tracks to record.
   class TracksAvailableCallback : public OnTracksAvailableCallback
   {
   public:
-    explicit TracksAvailableCallback(Session *aSession)
-     : mSession(aSession) {}
+    explicit TracksAvailableCallback(Session *aSession, TrackRate aTrackRate)
+     : mSession(aSession)
+     , mTrackRate(aTrackRate) {}
+
     virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
     {
       if (mSession->mStopIssued) {
         return;
       }
 
       MOZ_RELEASE_ASSERT(aStream);
       mSession->MediaStreamReady(*aStream);
@@ -342,20 +344,21 @@ class MediaRecorder::Session: public nsI
       // Check that we may access the tracks' content.
       if (!mSession->MediaStreamTracksPrincipalSubsumes()) {
         LOG(LogLevel::Warning, ("Session.NotifyTracksAvailable MediaStreamTracks principal check failed"));
         mSession->DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
         return;
       }
 
       LOG(LogLevel::Debug, ("Session.NotifyTracksAvailable track type = (%d)", trackTypes));
-      mSession->InitEncoder(trackTypes);
+      mSession->InitEncoder(trackTypes, mTrackRate);
     }
   private:
     RefPtr<Session> mSession;
+    TrackRate mTrackRate;
   };
   // Main thread task.
   // To delete RecordingSession object.
   class DestroyRunnable : public Runnable
   {
   public:
     explicit DestroyRunnable(Session* aSession)
       : mSession(aSession) {}
@@ -407,16 +410,17 @@ class MediaRecorder::Session: public nsI
 public:
   Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
     : mRecorder(aRecorder)
     , mTimeSlice(aTimeSlice)
     , mStopIssued(false)
     , mIsStartEventFired(false)
     , mIsRegisterProfiler(false)
     , mNeedSessionEndTask(true)
+    , mSelectedVideoTrackID(TRACK_NONE)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_COUNT_CTOR(MediaRecorder::Session);
 
     uint32_t maxMem = Preferences::GetUint("media.recorder.max_memory",
                                            MAX_ALLOW_MEMORY_BUFFER);
     mEncodedBufferCache = new EncodedBufferCache(maxMem);
     mLastBlobTimeStamp = TimeStamp::Now();
@@ -462,43 +466,44 @@ public:
 
   void Start()
   {
     LOG(LogLevel::Debug, ("Session.Start %p", this));
     MOZ_ASSERT(NS_IsMainThread());
 
     // Create a Track Union Stream
     MediaStreamGraph* gm = mRecorder->GetSourceMediaStream()->Graph();
+    TrackRate trackRate = gm->GraphRate();
     mTrackUnionStream = gm->CreateTrackUnionStream();
     MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
 
     mTrackUnionStream->SetAutofinish(true);
 
     DOMMediaStream* domStream = mRecorder->Stream();
     if (domStream) {
       // Get the available tracks from the DOMMediaStream.
       // The callback will report back tracks that we have to connect to
       // mTrackUnionStream and listen to principal changes on.
-      TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(this);
+      TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(this, trackRate);
       domStream->OnTracksAvailable(tracksAvailableCallback);
     } else {
       // Check that we may access the audio node's content.
       if (!AudioNodePrincipalSubsumes()) {
         LOG(LogLevel::Warning, ("Session.Start AudioNode principal check failed"));
         DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
         return;
       }
       // Bind this Track Union Stream with Source Media.
       RefPtr<MediaInputPort> inputPort =
         mTrackUnionStream->AllocateInputPort(mRecorder->GetSourceMediaStream());
       mInputPorts.AppendElement(inputPort.forget());
       MOZ_ASSERT(mInputPorts[mInputPorts.Length()-1]);
 
       // Web Audio node has only audio.
-      InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK);
+      InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate);
     }
   }
 
   void Stop()
   {
     LOG(LogLevel::Debug, ("Session.Stop %p", this));
     MOZ_ASSERT(NS_IsMainThread());
     mStopIssued = true;
@@ -729,17 +734,17 @@ private:
       return false;
     }
 
     uint32_t perm = nsIPermissionManager::DENY_ACTION;
     pm->TestExactPermissionFromPrincipal(doc->NodePrincipal(), aType, &perm);
     return perm == nsIPermissionManager::ALLOW_ACTION;
   }
 
-  void InitEncoder(uint8_t aTrackTypes)
+  void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate)
   {
     LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mRecorder) {
       LOG(LogLevel::Debug, ("Session.InitEncoder failure, mRecorder is null %p", this));
       return;
     }
@@ -747,52 +752,64 @@ private:
     // At this stage, the API doesn't allow UA to choose the output mimeType format.
 
     // Make sure the application has permission to assign AUDIO_3GPP
     if (mRecorder->mMimeType.EqualsLiteral(AUDIO_3GPP) && CheckPermission("audio-capture:3gpp")) {
       mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(AUDIO_3GPP),
                                              mRecorder->GetAudioBitrate(),
                                              mRecorder->GetVideoBitrate(),
                                              mRecorder->GetBitrate(),
-                                             aTrackTypes);
+                                             aTrackTypes, aTrackRate);
     } else if (mRecorder->mMimeType.EqualsLiteral(AUDIO_3GPP2) && CheckPermission("audio-capture:3gpp2")) {
       mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(AUDIO_3GPP2),
                                              mRecorder->GetAudioBitrate(),
                                              mRecorder->GetVideoBitrate(),
                                              mRecorder->GetBitrate(),
-                                             aTrackTypes);
+                                             aTrackTypes, aTrackRate);
     } else {
       mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""),
                                              mRecorder->GetAudioBitrate(),
                                              mRecorder->GetVideoBitrate(),
                                              mRecorder->GetBitrate(),
-                                             aTrackTypes);
+                                             aTrackTypes, aTrackRate);
     }
 
     if (!mEncoder) {
       LOG(LogLevel::Debug, ("Session.InitEncoder !mEncoder %p", this));
       DoSessionEndTask(NS_ERROR_ABORT);
       return;
     }
 
     // Media stream is ready but UA issues a stop method follow by start method.
     // The Session::stop would clean the mTrackUnionStream. If the AfterTracksAdded
     // comes after stop command, this function would crash.
     if (!mTrackUnionStream) {
       LOG(LogLevel::Debug, ("Session.InitEncoder !mTrackUnionStream %p", this));
       DoSessionEndTask(NS_OK);
       return;
     }
-    mTrackUnionStream->AddListener(mEncoder);
+    mTrackUnionStream->AddListener(mEncoder.get());
+
+    nsTArray<RefPtr<mozilla::dom::VideoStreamTrack>> videoTracks;
+    DOMMediaStream* domStream = mRecorder->Stream();
+    if (domStream) {
+      domStream->GetVideoTracks(videoTracks);
+      if (!videoTracks.IsEmpty()) {
+        // Right now, the MediaRecorder hasn't dealt with multiple video track
+        // issues. So we just bind with the first video track. Bug 1276928 is
+        // the following.
+        videoTracks[0]->AddDirectListener(mEncoder->GetVideoSink());
+      }
+    }
+
     // Try to use direct listeners if possible
-    DOMMediaStream* domStream = mRecorder->Stream();
     if (domStream && domStream->GetInputStream()) {
       mInputStream = domStream->GetInputStream()->AsSourceStream();
       if (mInputStream) {
-        mInputStream->AddDirectListener(mEncoder);
+        mInputStream->AddDirectListener(mEncoder.get());
         mEncoder->SetDirectConnect(true);
       }
     }
 
     // Create a thread to read encode media data from MediaEncoder.
     if (!mReadThread) {
       nsresult rv = NS_NewNamedThread("Media_Encoder", getter_AddRefs(mReadThread));
       if (NS_FAILED(rv)) {
@@ -842,29 +859,34 @@ private:
       MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
     }
     mNeedSessionEndTask = false;
   }
   void CleanupStreams()
   {
     if (mInputStream) {
       if (mEncoder) {
-        mInputStream->RemoveDirectListener(mEncoder);
+        mInputStream->RemoveDirectListener(mEncoder.get());
       }
       mInputStream = nullptr;
     }
     for (RefPtr<MediaInputPort>& inputPort : mInputPorts) {
       MOZ_ASSERT(inputPort);
       inputPort->Destroy();
     }
     mInputPorts.Clear();
 
     if (mTrackUnionStream) {
+      // Sometimes the MediaEncoder might be initialized fail and go to
+      // |CleanupStreams|. So the mEncoder might be a nullptr in this case.
+      if (mEncoder && mSelectedVideoTrackID != TRACK_NONE) {
+        mTrackUnionStream->RemoveVideoOutput(mEncoder->GetVideoSink(), mSelectedVideoTrackID);
+      }
       if (mEncoder) {
-        mTrackUnionStream->RemoveListener(mEncoder);
+        mTrackUnionStream->RemoveListener(mEncoder.get());
       }
       mTrackUnionStream->Destroy();
       mTrackUnionStream = nullptr;
     }
 
     if (mMediaStream) {
       mMediaStream->UnregisterTrackListener(this);
       mMediaStream = nullptr;
@@ -943,16 +965,17 @@ private:
   // Indicate the session had fire start event. Encoding thread only.
   bool mIsStartEventFired;
   // The register flag for "Media_Encoder" thread to profiler
   bool mIsRegisterProfiler;
   // False if the InitEncoder called successfully, ensure the
   // ExtractRunnable/DestroyRunnable will end the session.
   // Main thread only.
   bool mNeedSessionEndTask;
+  TrackID mSelectedVideoTrackID;
 };
 
 NS_IMPL_ISUPPORTS(MediaRecorder::Session, nsIObserver)
 
 MediaRecorder::~MediaRecorder()
 {
   if (mPipeStream != nullptr) {
     mInputPort->Destroy();
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -18,20 +18,22 @@
 #include "mozilla/Attributes.h"
 #include "TrackUnionStream.h"
 #include "ImageContainer.h"
 #include "AudioCaptureStream.h"
 #include "AudioChannelService.h"
 #include "AudioNodeStream.h"
 #include "AudioNodeExternalInputStream.h"
 #include "MediaStreamListener.h"
+#include "MediaStreamVideoSink.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include "mozilla/media/MediaUtils.h"
 #include <algorithm>
 #include "GeckoProfiler.h"
+#include "VideoFrameContainer.h"
 #include "mozilla/unused.h"
 #include "mozilla/media/MediaUtils.h"
 #ifdef MOZ_WEBRTC
 #include "AudioOutputObserver.h"
 #endif
 #include "mtransport/runnable_utils.h"
 
 #include "webaudio/blink/HRTFDatabaseLoader.h"
@@ -102,16 +104,25 @@ MediaStreamGraphImpl::FinishStream(Media
 
   SetStreamOrderDirty();
 }
 
 void
 MediaStreamGraphImpl::AddStreamGraphThread(MediaStream* aStream)
 {
   aStream->mTracksStartTime = mProcessedTime;
+
+  if (aStream->AsSourceStream()) {
+    SourceMediaStream* source = aStream->AsSourceStream();
+    TimeStamp currentTimeStamp = CurrentDriver()->GetCurrentTimeStamp();
+    TimeStamp processedTimeStamp = currentTimeStamp +
+      TimeDuration::FromSeconds(MediaTimeToSeconds(mProcessedTime - IterationEnd()));
+    source->SetStreamTracksStartTimeStamp(processedTimeStamp);
+  }
+
   if (aStream->IsSuspended()) {
     mSuspendedStreams.AppendElement(aStream);
     STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph, in the suspended stream array", aStream));
   } else {
     mStreams.AppendElement(aStream);
     STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to graph %p, count %lu", aStream, this, mStreams.Length()));
     LIFECYCLE_LOG("Adding media stream %p to graph %p, count %lu", aStream, this, mStreams.Length());
   }
@@ -225,26 +236,16 @@ MediaStreamGraphImpl::ExtractPendingInpu
       // Video case.
       if (data->mData->GetType() == MediaSegment::VIDEO) {
         if (data->mCommands) {
           MOZ_ASSERT(!(data->mCommands & SourceMediaStream::TRACK_UNUSED));
           for (MediaStreamListener* l : aStream->mListeners) {
             l->NotifyQueuedTrackChanges(this, data->mID,
                                         offset, static_cast<TrackEventCommand>(data->mCommands), *data->mData);
           }
-        } else {
-          // Fixme: This part will be removed in the bug 1201363. It will be
-          // removed in changeset "Do not copy video segment to StreamTracks in
-          // TrackUnionStream."
-
-          // Dealing with video and not TRACK_CREATE and TRACK_END case.
-          for (MediaStreamListener* l : aStream->mListeners) {
-            l->NotifyQueuedTrackChanges(this, data->mID,
-                                        offset, static_cast<TrackEventCommand>(data->mCommands), *data->mData);
-          }
         }
       }
 
       for (TrackBound<MediaStreamTrackListener>& b : aStream->mTrackListeners) {
         if (b.mTrackID != data->mID) {
           continue;
         }
         b.mListener->NotifyQueuedChanges(this, offset, *data->mData);
@@ -921,203 +922,16 @@ MediaStreamGraphImpl::PlayAudio(MediaStr
     // Need unique id for stream & track - and we want it to match the inserter
     output.WriteTo(LATENCY_STREAM_ID(aStream, track->GetID()),
                                      mMixer, AudioChannelCount(),
                                      mSampleRate);
   }
   return ticksWritten;
 }
 
-static void
-SetImageToBlackPixel(PlanarYCbCrImage* aImage)
-{
-  uint8_t blackPixel[] = { 0x10, 0x80, 0x80 };
-
-  PlanarYCbCrData data;
-  data.mYChannel = blackPixel;
-  data.mCbChannel = blackPixel + 1;
-  data.mCrChannel = blackPixel + 2;
-  data.mYStride = data.mCbCrStride = 1;
-  data.mPicSize = data.mYSize = data.mCbCrSize = IntSize(1, 1);
-  aImage->CopyData(data);
-}
-
-class VideoFrameContainerInvalidateRunnable : public Runnable {
-public:
-  explicit VideoFrameContainerInvalidateRunnable(VideoFrameContainer* aVideoFrameContainer)
-    : mVideoFrameContainer(aVideoFrameContainer)
-  {}
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    mVideoFrameContainer->Invalidate();
-
-    return NS_OK;
-  }
-private:
-  RefPtr<VideoFrameContainer> mVideoFrameContainer;
-};
-
-void
-MediaStreamGraphImpl::PlayVideo(MediaStream* aStream)
-{
-  MOZ_ASSERT(mRealtime, "Should only attempt to play video in realtime mode");
-
-  if (aStream->mVideoOutputs.IsEmpty())
-    return;
-
-  TimeStamp currentTimeStamp = CurrentDriver()->GetCurrentTimeStamp();
-
-  // Collect any new frames produced in this iteration.
-  AutoTArray<ImageContainer::NonOwningImage,4> newImages;
-  PrincipalHandle lastPrincipalHandle = PRINCIPAL_HANDLE_NONE;
-  RefPtr<Image> blackImage;
-
-  MOZ_ASSERT(mProcessedTime >= aStream->mTracksStartTime, "frame position before buffer?");
-  // We only look at the non-blocking interval
-  StreamTime frameBufferTime = aStream->GraphTimeToStreamTime(mProcessedTime);
-  StreamTime bufferEndTime = aStream->GraphTimeToStreamTime(aStream->mStartBlocking);
-  StreamTime start;
-  const VideoChunk* chunk;
-  for ( ;
-       frameBufferTime < bufferEndTime;
-       frameBufferTime = start + chunk->GetDuration()) {
-    // Pick the last track that has a video chunk for the time, and
-    // schedule its frame.
-    chunk = nullptr;
-    for (StreamTracks::TrackIter tracks(aStream->GetStreamTracks(),
-                                        MediaSegment::VIDEO);
-         !tracks.IsEnded();
-         tracks.Next()) {
-      VideoSegment* segment = tracks->Get<VideoSegment>();
-      StreamTime thisStart;
-      const VideoChunk* thisChunk =
-        segment->FindChunkContaining(frameBufferTime, &thisStart);
-      if (thisChunk && thisChunk->mFrame.GetImage()) {
-        start = thisStart;
-        chunk = thisChunk;
-      }
-    }
-    if (!chunk)
-      break;
-
-    const VideoFrame* frame = &chunk->mFrame;
-    if (*frame == aStream->mLastPlayedVideoFrame) {
-      continue;
-    }
-
-    Image* image = frame->GetImage();
-    STREAM_LOG(LogLevel::Verbose,
-               ("MediaStream %p writing video frame %p (%dx%d)",
-                aStream, image, frame->GetIntrinsicSize().width,
-                frame->GetIntrinsicSize().height));
-    // Schedule this frame after the previous frame finishes, instead of at
-    // its start time.  These times only differ in the case of multiple
-    // tracks.
-    // frameBufferTime is in the non-blocking interval.
-    GraphTime frameTime = aStream->StreamTimeToGraphTime(frameBufferTime);
-    TimeStamp targetTime = currentTimeStamp +
-      TimeDuration::FromSeconds(MediaTimeToSeconds(frameTime - IterationEnd()));
-
-    if (frame->GetForceBlack()) {
-      if (!blackImage) {
-        blackImage = aStream->mVideoOutputs[0]->GetImageContainer()->CreatePlanarYCbCrImage();
-        if (blackImage) {
-          // Sets the image to a single black pixel, which will be scaled to
-          // fill the rendered size.
-          SetImageToBlackPixel(blackImage->AsPlanarYCbCrImage());
-        }
-      }
-      if (blackImage) {
-        image = blackImage;
-      }
-    }
-    newImages.AppendElement(ImageContainer::NonOwningImage(image, targetTime));
-
-    lastPrincipalHandle = chunk->GetPrincipalHandle();
-
-    aStream->mLastPlayedVideoFrame = *frame;
-  }
-
-  if (!aStream->mLastPlayedVideoFrame.GetImage())
-    return;
-
-  AutoTArray<ImageContainer::NonOwningImage,4> images;
-  bool haveMultipleImages = false;
-
-  for (uint32_t i = 0; i < aStream->mVideoOutputs.Length(); ++i) {
-    VideoFrameContainer* output = aStream->mVideoOutputs[i];
-
-    bool principalHandleChanged =
-      lastPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
-      lastPrincipalHandle != output->GetLastPrincipalHandle();
-
-    // Find previous frames that may still be valid.
-    AutoTArray<ImageContainer::OwningImage,4> previousImages;
-    output->GetImageContainer()->GetCurrentImages(&previousImages);
-    uint32_t j = previousImages.Length();
-    if (j) {
-      // Re-use the most recent frame before currentTimeStamp and subsequent,
-      // always keeping at least one frame.
-      do {
-        --j;
-      } while (j > 0 && previousImages[j].mTimeStamp > currentTimeStamp);
-    }
-    if (previousImages.Length() - j + newImages.Length() > 1) {
-      haveMultipleImages = true;
-    }
-
-    // Don't update if there are no changes.
-    if (j == 0 && newImages.IsEmpty())
-      continue;
-
-    for ( ; j < previousImages.Length(); ++j) {
-      const auto& image = previousImages[j];
-      // Cope with potential clock skew with AudioCallbackDriver.
-      if (newImages.Length() && image.mTimeStamp > newImages[0].mTimeStamp) {
-        STREAM_LOG(LogLevel::Warning,
-                   ("Dropping %u video frames due to clock skew",
-                    unsigned(previousImages.Length() - j)));
-        break;
-      }
-
-      images.AppendElement(ImageContainer::
-                           NonOwningImage(image.mImage,
-                                          image.mTimeStamp, image.mFrameID));
-    }
-
-    // Add the frames from this iteration.
-    for (auto& image : newImages) {
-      image.mFrameID = output->NewFrameID();
-      images.AppendElement(image);
-    }
-
-    if (principalHandleChanged) {
-      output->UpdatePrincipalHandleForFrameID(lastPrincipalHandle,
-                                              newImages.LastElement().mFrameID);
-    }
-
-    output->SetCurrentFrames(aStream->mLastPlayedVideoFrame.GetIntrinsicSize(),
-                             images);
-
-    nsCOMPtr<nsIRunnable> event =
-      new VideoFrameContainerInvalidateRunnable(output);
-    DispatchToMainThreadAfterStreamStateUpdate(event.forget());
-
-    images.ClearAndRetainStorage();
-  }
-
-  // If the stream has finished and the timestamps of all frames have expired
-  // then no more updates are required.
-  if (aStream->mFinished && !haveMultipleImages) {
-    aStream->mLastPlayedVideoFrame.SetNull();
-  }
-}
-
 void
 MediaStreamGraphImpl::OpenAudioInputImpl(int aID,
                                          AudioDataListener *aListener)
 {
   // Bug 1238038 Need support for multiple mics at once
   if (mInputDeviceUsers.Count() > 0 &&
       !mInputDeviceUsers.Get(aListener, nullptr)) {
     NS_ASSERTION(false, "Input from multiple mics not yet supported; bug 1238038");
@@ -1521,17 +1335,16 @@ MediaStreamGraphImpl::Process()
         StreamTime ticksPlayedForThisStream = PlayAudio(stream);
         if (!ticksPlayed) {
           ticksPlayed = ticksPlayedForThisStream;
         } else {
           MOZ_ASSERT(!ticksPlayedForThisStream || ticksPlayedForThisStream == ticksPlayed,
               "Each stream should have the same number of frame.");
         }
       }
-      PlayVideo(stream);
     }
     if (stream->mStartBlocking > mProcessedTime) {
       allBlockedForever = false;
     }
   }
 
   if (CurrentDriver()->AsAudioCallbackDriver() && ticksPlayed) {
     mMixer.FinishMixing();
@@ -2254,65 +2067,90 @@ MediaStream::RemoveAudioOutput(void* aKe
       mStream->RemoveAudioOutputImpl(mKey);
     }
     void* mKey;
   };
   GraphImpl()->AppendMessage(MakeUnique<Message>(this, aKey));
 }
 
 void
-MediaStream::AddVideoOutputImpl(already_AddRefed<VideoFrameContainer> aContainer)
+MediaStream::AddVideoOutputImpl(already_AddRefed<MediaStreamVideoSink> aSink,
+                                TrackID aID)
 {
-  RefPtr<VideoFrameContainer> container = aContainer;
-  STREAM_LOG(LogLevel::Info, ("MediaStream %p Adding VideoFrameContainer %p as output",
-                              this, container.get()));
-  *mVideoOutputs.AppendElement() = container.forget();
+  RefPtr<MediaStreamVideoSink> sink = aSink;
+  STREAM_LOG(LogLevel::Info, ("MediaStream %p Adding MediaStreamVideoSink %p as output",
+                              this, sink.get()));
+  MOZ_ASSERT(aID != TRACK_NONE);
+   for (auto entry : mVideoOutputs) {
+     if (entry.mListener == sink &&
+         (entry.mTrackID == TRACK_ANY || entry.mTrackID == aID)) {
+       return;
+     }
+   }
+   TrackBound<MediaStreamVideoSink>* l = mVideoOutputs.AppendElement();
+   l->mListener = sink;
+   l->mTrackID = aID;
+
+   AddDirectTrackListenerImpl(sink.forget(), aID);
 }
 
 void
-MediaStream::RemoveVideoOutputImpl(VideoFrameContainer* aContainer)
+MediaStream::RemoveVideoOutputImpl(MediaStreamVideoSink* aSink,
+                                   TrackID aID)
 {
-  STREAM_LOG(LogLevel::Info, ("MediaStream %p Removing VideoFrameContainer %p as output",
-                              this, aContainer));
+  STREAM_LOG(LogLevel::Info, ("MediaStream %p Removing MediaStreamVideoSink %p as output",
+                              this, aSink));
+  MOZ_ASSERT(aID != TRACK_NONE);
+
   // Ensure that any frames currently queued for playback by the compositor
   // are removed.
-  aContainer->ClearFutureFrames();
-  mVideoOutputs.RemoveElement(aContainer);
+  aSink->ClearFrames();
+  for (size_t i = 0; i < mVideoOutputs.Length(); ++i) {
+    if (mVideoOutputs[i].mListener == aSink &&
+        (mVideoOutputs[i].mTrackID == TRACK_ANY ||
+         mVideoOutputs[i].mTrackID == aID)) {
+      mVideoOutputs.RemoveElementAt(i);
+    }
+  }
+
+  RemoveDirectTrackListenerImpl(aSink, aID);
 }
 
 void
-MediaStream::AddVideoOutput(VideoFrameContainer* aContainer)
+MediaStream::AddVideoOutput(MediaStreamVideoSink* aSink, TrackID aID)
 {
   class Message : public ControlMessage {
   public:
-    Message(MediaStream* aStream, VideoFrameContainer* aContainer) :
-      ControlMessage(aStream), mContainer(aContainer) {}
+    Message(MediaStream* aStream, MediaStreamVideoSink* aSink, TrackID aID) :
+      ControlMessage(aStream), mSink(aSink), mID(aID) {}
     void Run() override
     {
-      mStream->AddVideoOutputImpl(mContainer.forget());
+      mStream->AddVideoOutputImpl(mSink.forget(), mID);
     }
-    RefPtr<VideoFrameContainer> mContainer;
+    RefPtr<MediaStreamVideoSink> mSink;
+    TrackID mID;
   };
-  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aContainer));
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aSink, aID));
 }
 
 void
-MediaStream::RemoveVideoOutput(VideoFrameContainer* aContainer)
+MediaStream::RemoveVideoOutput(MediaStreamVideoSink* aSink, TrackID aID)
 {
   class Message : public ControlMessage {
   public:
-    Message(MediaStream* aStream, VideoFrameContainer* aContainer) :
-      ControlMessage(aStream), mContainer(aContainer) {}
+    Message(MediaStream* aStream, MediaStreamVideoSink* aSink, TrackID aID) :
+      ControlMessage(aStream), mSink(aSink), mID(aID) {}
     void Run() override
     {
-      mStream->RemoveVideoOutputImpl(mContainer);
+      mStream->RemoveVideoOutputImpl(mSink, mID);
     }
-    RefPtr<VideoFrameContainer> mContainer;
+    RefPtr<MediaStreamVideoSink> mSink;
+    TrackID mID;
   };
-  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aContainer));
+  GraphImpl()->AppendMessage(MakeUnique<Message>(this, aSink, aID));
 }
 
 void
 MediaStream::Suspend()
 {
   class Message : public ControlMessage {
   public:
     explicit Message(MediaStream* aStream) :
@@ -2793,16 +2631,26 @@ SourceMediaStream::ResampleAudioToGraphS
       return;
     }
     aTrackData->mResampler.own(state);
     aTrackData->mResamplerChannelCount = channels;
   }
   segment->ResampleChunks(aTrackData->mResampler, aTrackData->mInputRate, GraphImpl()->GraphRate());
 }
 
+void
+SourceMediaStream::AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime,
+                                                         GraphTime aBlockedTime)
+{
+  MutexAutoLock lock(mMutex);
+  mTracksStartTime += aBlockedTime;
+  mStreamTracksStartTimeStamp += TimeDuration::FromSeconds(GraphImpl()->MediaTimeToSeconds(aBlockedTime));
+  mTracks.ForgetUpTo(aCurrentTime - mTracksStartTime);
+}
+
 bool
 SourceMediaStream::AppendToTrack(TrackID aID, MediaSegment* aSegment, MediaSegment *aRawSegment)
 {
   MutexAutoLock lock(mMutex);
   // ::EndAllTrackAndFinished() can end these before the sources notice
   bool appended = false;
   auto graph = GraphImpl();
   if (!mFinished && graph) {
@@ -2915,32 +2763,54 @@ SourceMediaStream::RemoveDirectListener(
 }
 
 void
 SourceMediaStream::AddDirectTrackListenerImpl(already_AddRefed<DirectMediaStreamTrackListener> aListener,
                                               TrackID aTrackID)
 {
   MOZ_ASSERT(IsTrackIDExplicit(aTrackID));
   TrackData* data;
-  bool found;
-  bool isAudio;
-  bool isVideo;
+  bool found = false;
+  bool isAudio = false;
+  bool isVideo = false;
   RefPtr<DirectMediaStreamTrackListener> listener = aListener;
   STREAM_LOG(LogLevel::Debug, ("Adding direct track listener %p bound to track %d to source stream %p",
              listener.get(), aTrackID, this));
 
   {
     MutexAutoLock lock(mMutex);
     data = FindDataForTrack(aTrackID);
     found = !!data;
     if (found) {
       isAudio = data->mData->GetType() == MediaSegment::AUDIO;
       isVideo = data->mData->GetType() == MediaSegment::VIDEO;
     }
+
+    // The track might be removed from mUpdateTrack but still exist in
+    // mTracks.
+    auto streamTrack = FindTrack(aTrackID);
+    bool foundTrack = !!streamTrack;
+    if (foundTrack) {
+      MediaStreamVideoSink* videoSink = listener->AsMediaStreamVideoSink();
+      // Re-send missed VideoSegment to new added MediaStreamVideoSink.
+      if (streamTrack->GetType() == MediaSegment::VIDEO && videoSink) {
+        videoSink->SetCurrentFrames(*(static_cast<VideoSegment*>(streamTrack->GetSegment())));
+      }
+    }
+
     if (found && (isAudio || isVideo)) {
+      for (auto entry : mDirectTrackListeners) {
+        if (entry.mListener == listener &&
+            (entry.mTrackID == TRACK_ANY || entry.mTrackID == aTrackID)) {
+          listener->NotifyDirectListenerInstalled(
+            DirectMediaStreamTrackListener::InstallationResult::ALREADY_EXISTS);
+          return;
+        }
+      }
+
       TrackBound<DirectMediaStreamTrackListener>* sourceListener =
         mDirectTrackListeners.AppendElement();
       sourceListener->mListener = listener;
       sourceListener->mTrackID = aTrackID;
     }
   }
   if (!found) {
     STREAM_LOG(LogLevel::Warning, ("Couldn't find source track for direct track listener %p",
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -10,18 +10,18 @@
 #include "mozilla/Mutex.h"
 #include "mozilla/TaskQueue.h"
 
 #include "mozilla/dom/AudioChannelBinding.h"
 
 #include "AudioStream.h"
 #include "nsTArray.h"
 #include "nsIRunnable.h"
-#include "VideoFrameContainer.h"
 #include "VideoSegment.h"
+#include "StreamTracks.h"
 #include "MainThreadUtils.h"
 #include "StreamTracks.h"
 #include "nsAutoPtr.h"
 #include "nsAutoRef.h"
 #include <speex/speex_resampler.h>
 
 class nsIRunnable;
 
@@ -161,18 +161,20 @@ class AudioNodeStream;
 class AudioSegment;
 class CameraPreviewMediaStream;
 class DirectMediaStreamListener;
 class DirectMediaStreamTrackListener;
 class MediaInputPort;
 class MediaStreamGraphImpl;
 class MediaStreamListener;
 class MediaStreamTrackListener;
+class MediaStreamVideoSink;
 class ProcessedMediaStream;
 class SourceMediaStream;
+class TrackUnionStream;
 
 enum MediaStreamGraphEvent : uint32_t;
 enum TrackEventCommand : uint32_t;
 
 /**
  * Helper struct for binding a track listener to a specific TrackID.
  */
 template<typename Listener>
@@ -204,17 +206,17 @@ struct TrackBound
  * Transitions into and out of the "blocked" and "finished" states are managed
  * by the MediaStreamGraph on the media graph thread.
  *
  * We buffer media data ahead of the consumers' reading offsets. It is possible
  * to have buffered data but still be blocked.
  *
  * Any stream can have its audio and video playing when requested. The media
  * stream graph plays audio by constructing audio output streams as necessary.
- * Video is played by setting video frames into an VideoFrameContainer at the right
+ * Video is played by setting video frames into an MediaStreamVideoSink at the right
  * time. To ensure video plays in sync with audio, make sure that the same
  * stream is playing both the audio and video.
  *
  * The data in a stream is managed by StreamTracks. It consists of a set of
  * tracks of various types that can start and end over time.
  *
  * Streams are explicitly managed. The client creates them via
  * MediaStreamGraph::CreateInput/ProcessedMediaStream, and releases them by calling
@@ -286,20 +288,22 @@ public:
   // separate. Since the stream is always playing the same contents, only
   // a single audio output stream is used; the volumes are combined.
   // Currently only the first enabled audio track is played.
   // XXX change this so all enabled audio tracks are mixed and played.
   virtual void AddAudioOutput(void* aKey);
   virtual void SetAudioOutputVolume(void* aKey, float aVolume);
   virtual void RemoveAudioOutput(void* aKey);
   // Since a stream can be played multiple ways, we need to be able to
-  // play to multiple VideoFrameContainers.
+  // play to multiple MediaStreamVideoSinks.
   // Only the first enabled video track is played.
-  virtual void AddVideoOutput(VideoFrameContainer* aContainer);
-  virtual void RemoveVideoOutput(VideoFrameContainer* aContainer);
+  virtual void AddVideoOutput(MediaStreamVideoSink* aSink,
+                              TrackID aID = TRACK_ANY);
+  virtual void RemoveVideoOutput(MediaStreamVideoSink* aSink,
+                                 TrackID aID = TRACK_ANY);
   // Explicitly suspend. Useful for example if a media element is pausing
   // and we need to stop its stream emitting its buffered data. As soon as the
   // Suspend message reaches the graph, the stream stops processing. It
   // ignores its inputs and produces silence/no video until Resumed. Its
   // current time does not advance.
   virtual void Suspend();
   virtual void Resume();
   // Events will be dispatched by calling methods of aListener.
@@ -398,16 +402,17 @@ public:
 
   friend class MediaStreamGraphImpl;
   friend class MediaInputPort;
   friend class AudioNodeExternalInputStream;
 
   virtual SourceMediaStream* AsSourceStream() { return nullptr; }
   virtual ProcessedMediaStream* AsProcessedStream() { return nullptr; }
   virtual AudioNodeStream* AsAudioNodeStream() { return nullptr; }
+  virtual TrackUnionStream* AsTrackUnionStream() { return nullptr; }
 
   // These Impl methods perform the core functionality of the control methods
   // above, on the media graph thread.
   /**
    * Stop all stream activity and disconnect it from all inputs and outputs.
    * This must be idempotent.
    */
   virtual void DestroyImpl();
@@ -418,18 +423,19 @@ public:
   void SetAudioOutputVolumeImpl(void* aKey, float aVolume);
   void AddAudioOutputImpl(void* aKey);
   // Returns true if this stream has an audio output.
   bool HasAudioOutput()
   {
     return !mAudioOutputs.IsEmpty();
   }
   void RemoveAudioOutputImpl(void* aKey);
-  void AddVideoOutputImpl(already_AddRefed<VideoFrameContainer> aContainer);
-  void RemoveVideoOutputImpl(VideoFrameContainer* aContainer);
+  void AddVideoOutputImpl(already_AddRefed<MediaStreamVideoSink> aSink,
+                          TrackID aID);
+  void RemoveVideoOutputImpl(MediaStreamVideoSink* aSink, TrackID aID);
   void AddListenerImpl(already_AddRefed<MediaStreamListener> aListener);
   void RemoveListenerImpl(MediaStreamListener* aListener);
   void RemoveAllListenersImpl();
   virtual void AddTrackListenerImpl(already_AddRefed<MediaStreamTrackListener> aListener,
                                     TrackID aTrackID);
   virtual void RemoveTrackListenerImpl(MediaStreamTrackListener* aListener,
                                        TrackID aTrackID);
   virtual void AddDirectTrackListenerImpl(already_AddRefed<DirectMediaStreamTrackListener> aListener,
@@ -532,17 +538,19 @@ public:
   void IncrementSuspendCount() { ++mSuspendedCount; }
   void DecrementSuspendCount()
   {
     NS_ASSERTION(mSuspendedCount > 0, "Suspend count underrun");
     --mSuspendedCount;
   }
 
 protected:
-  void AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, GraphTime aBlockedTime)
+  // |AdvanceTimeVaryingValuesToCurrentTime| will be override in SourceMediaStream.
+  virtual void AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime,
+                                                     GraphTime aBlockedTime)
   {
     mTracksStartTime += aBlockedTime;
     mTracks.ForgetUpTo(aCurrentTime - mTracksStartTime);
   }
 
   void NotifyMainThreadListeners()
   {
     NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
@@ -578,17 +586,17 @@ protected:
 
   // Client-set volume of this stream
   struct AudioOutput {
     explicit AudioOutput(void* aKey) : mKey(aKey), mVolume(1.0f) {}
     void* mKey;
     float mVolume;
   };
   nsTArray<AudioOutput> mAudioOutputs;
-  nsTArray<RefPtr<VideoFrameContainer>> mVideoOutputs;
+  nsTArray<TrackBound<MediaStreamVideoSink>> mVideoOutputs;
   // We record the last played video frame to avoid playing the frame again
   // with a different frame id.
   VideoFrame mLastPlayedVideoFrame;
   nsTArray<RefPtr<MediaStreamListener> > mListeners;
   nsTArray<TrackBound<MediaStreamTrackListener>> mTrackListeners;
   nsTArray<MainThreadMediaStreamListener*> mMainThreadListeners;
   nsTArray<TrackID> mDisabledTrackIDs;
 
@@ -794,16 +802,21 @@ public:
 
   /**
    * Returns true if this SourceMediaStream contains at least one audio track
    * that is in pending state.
    * This is thread safe, and takes the SourceMediaStream mutex.
    */
   bool HasPendingAudioTrack();
 
+  TimeStamp GetStreamTracksStrartTimeStamp() {
+    MutexAutoLock lock(mMutex);
+    return mStreamTracksStartTimeStamp;
+  }
+
   // XXX need a Reset API
 
   friend class MediaStreamGraphImpl;
 
 protected:
   enum TrackCommands : uint32_t;
 
   virtual ~SourceMediaStream();
@@ -858,27 +871,40 @@ protected:
    * Notify direct consumers of new data to one of the stream tracks.
    * The data doesn't have to be resampled (though it may be).  This is called
    * from AppendToTrack on the thread providing the data, and will call
    * the Listeners on this thread.
    */
   void NotifyDirectConsumers(TrackData *aTrack,
                              MediaSegment *aSegment);
 
+  virtual void
+  AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime,
+                                        GraphTime aBlockedTime) override;
+  void SetStreamTracksStartTimeStamp(const TimeStamp& aTimeStamp)
+  {
+    MutexAutoLock lock(mMutex);
+    mStreamTracksStartTimeStamp = aTimeStamp;
+  }
+
   // Only accessed on the MSG thread.  Used so to ask the MSGImpl to usecount
   // users of a specific input.
   // XXX Should really be a CubebUtils::AudioDeviceID, but they aren't
   // copyable (opaque pointers)
   RefPtr<AudioDataListener> mInputListener;
 
   // This must be acquired *before* MediaStreamGraphImpl's lock, if they are
   // held together.
   Mutex mMutex;
   // protected by mMutex
   StreamTime mUpdateKnownTracksTime;
+  // This time stamp will be updated in adding and blocked SourceMediaStream,
+  // |AddStreamGraphThread| and |AdvanceTimeVaryingValuesToCurrentTime| in
+  // particularly.
+  TimeStamp mStreamTracksStartTimeStamp;
   nsTArray<TrackData> mUpdateTracks;
   nsTArray<TrackData> mPendingTracks;
   nsTArray<RefPtr<DirectMediaStreamListener>> mDirectListeners;
   nsTArray<TrackBound<DirectMediaStreamTrackListener>> mDirectTrackListeners;
   bool mPullEnabled;
   bool mUpdateFinished;
   bool mNeedsMixing;
 };
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -407,20 +407,16 @@ public:
    */
   void CreateOrDestroyAudioStreams(MediaStream* aStream);
   /**
    * Queue audio (mix of stream audio and silence for blocked intervals)
    * to the audio output stream. Returns the number of frames played.
    */
   StreamTime PlayAudio(MediaStream* aStream);
   /**
-   * Set the correct current video frame for stream aStream.
-   */
-  void PlayVideo(MediaStream* aStream);
-  /**
    * No more data will be forthcoming for aStream. The stream will end
    * at the current buffer end point. The StreamTracks's tracks must be
    * explicitly set to finished by the caller.
    */
   void OpenAudioInputImpl(int aID,
                           AudioDataListener *aListener);
   virtual nsresult OpenAudioInput(int aID,
                                   AudioDataListener *aListener) override;
--- a/dom/media/MediaStreamListener.h
+++ b/dom/media/MediaStreamListener.h
@@ -2,20 +2,25 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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_MEDIASTREAMLISTENER_h_
 #define MOZILLA_MEDIASTREAMLISTENER_h_
 
+#include "StreamTracks.h"
+
 namespace mozilla {
 
+class AudioSegment;
 class MediaStream;
 class MediaStreamGraph;
+class MediaStreamVideoSink;
+class VideoSegment;
 
 enum MediaStreamGraphEvent : uint32_t {
   EVENT_FINISHED,
   EVENT_REMOVED,
   EVENT_HAS_DIRECT_LISTENERS, // transition from no direct listeners
   EVENT_HAS_NO_DIRECT_LISTENERS,  // transition to no direct listeners
 };
 
@@ -236,29 +241,35 @@ public:
    * successful or not. The results of this installation are the following:
    * TRACK_NOT_FOUND_AT_SOURCE
    *    We found the source stream of media data for this track, but the track
    *    didn't exist. This should only happen if you try to install the listener
    *    directly to a SourceMediaStream that doesn't contain the given TrackID.
    * STREAM_NOT_SUPPORTED
    *    While looking for the data source of this track, we found a MediaStream
    *    that is not a SourceMediaStream or a TrackUnionStream.
+   * ALREADY_EXIST
+   *    This DirectMediaStreamTrackListener already exists in the
+   *    SourceMediaStream.
    * SUCCESS
    *    Installation was successful and this listener will start receiving
    *    NotifyRealtimeData on the next AppendToTrack().
    */
   enum class InstallationResult {
     TRACK_NOT_FOUND_AT_SOURCE,
     TRACK_TYPE_NOT_SUPPORTED,
     STREAM_NOT_SUPPORTED,
+    ALREADY_EXISTS,
     SUCCESS
   };
   virtual void NotifyDirectListenerInstalled(InstallationResult aResult) {}
   virtual void NotifyDirectListenerUninstalled() {}
 
+  virtual MediaStreamVideoSink* AsMediaStreamVideoSink() { return nullptr; }
+
 protected:
   virtual ~DirectMediaStreamTrackListener() {}
 
   void MirrorAndDisableSegment(AudioSegment& aFrom, AudioSegment& aTo);
   void MirrorAndDisableSegment(VideoSegment& aFrom, VideoSegment& aTo);
   void NotifyRealtimeTrackDataAndApplyTrackDisabling(MediaStreamGraph* aGraph,
                                                      StreamTime aTrackOffset,
                                                      MediaSegment& aMedia);
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaStreamVideoSink.cpp
@@ -0,0 +1,21 @@
+/* -*- 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 "MediaStreamVideoSink.h"
+
+#include "VideoSegment.h"
+
+namespace mozilla {
+void
+MediaStreamVideoSink::NotifyRealtimeTrackData(MediaStreamGraph* aGraph,
+                                              StreamTime aTrackOffset,
+                                              const MediaSegment& aMedia)
+{
+  if (aMedia.GetType() == MediaSegment::VIDEO) {
+    SetCurrentFrames(static_cast<const VideoSegment&>(aMedia));
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaStreamVideoSink.h
@@ -0,0 +1,44 @@
+/* -*- 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/. */
+
+#ifndef MEDIASTREAMVIDEOSINK_H_
+#define MEDIASTREAMVIDEOSINK_H_
+
+#include "mozilla/Pair.h"
+
+#include "gfxPoint.h"
+#include "MediaStreamListener.h"
+
+namespace mozilla {
+
+class VideoFrameContainer;
+
+/**
+ * Base class of MediaStreamVideoSink family. This is the output of MediaStream.
+ */
+class MediaStreamVideoSink : public DirectMediaStreamTrackListener {
+public:
+  // Method of DirectMediaStreamTrackListener.
+  void NotifyRealtimeTrackData(MediaStreamGraph* aGraph,
+                               StreamTime aTrackOffset,
+                               const MediaSegment& aMedia) override;
+
+  // Call on any thread
+  virtual void SetCurrentFrames(const VideoSegment& aSegment) = 0;
+  virtual void ClearFrames() = 0;
+
+  virtual VideoFrameContainer* AsVideoFrameContainer() { return nullptr; }
+  virtual void Invalidate() {}
+
+  virtual MediaStreamVideoSink* AsMediaStreamVideoSink() override { return this; }
+
+protected:
+  virtual ~MediaStreamVideoSink() {};
+};
+
+} // namespace mozilla
+
+#endif /* MEDIASTREAMVIDEOSINK_H_ */
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -327,22 +327,16 @@ TrackUnionStream::TrackUnionStream() :
         MediaStreamListener* l = mListeners[j];
         // Separate Audio and Video.
         if (segment->GetType() == MediaSegment::AUDIO) {
           l->NotifyQueuedAudioData(Graph(), outputTrack->GetID(),
                                    outputStart,
                                    *static_cast<AudioSegment*>(segment),
                                    map->mInputPort->GetSource(),
                                    map->mInputTrackID);
-        } else {
-          // This part will be removed in bug 1201363.
-          l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(),
-                                      outputStart, TrackEventCommand::TRACK_EVENT_NONE, *segment,
-                                      map->mInputPort->GetSource(),
-                                      map->mInputTrackID);
         }
       }
       for (TrackBound<MediaStreamTrackListener>& b : mTrackListeners) {
         if (b.mTrackID != outputTrack->GetID()) {
           continue;
         }
         b.mListener->NotifyQueuedChanges(Graph(), outputStart, *segment);
       }
--- a/dom/media/TrackUnionStream.h
+++ b/dom/media/TrackUnionStream.h
@@ -14,24 +14,29 @@ namespace mozilla {
 
 /**
  * See MediaStreamGraph::CreateTrackUnionStream.
  */
 class TrackUnionStream : public ProcessedMediaStream {
 public:
   explicit TrackUnionStream();
 
+  virtual TrackUnionStream* AsTrackUnionStream() override { return this; }
+  friend class DOMMediaStream;
+
   void RemoveInput(MediaInputPort* aPort) override;
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
   void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) override;
 
   MediaStream* GetInputStreamFor(TrackID aTrackID) override;
   TrackID GetInputTrackIDFor(TrackID aTrackID) override;
 
+  friend class MediaStreamGraphImpl;
+
 protected:
   // Only non-ended tracks are allowed to persist in this map.
   struct TrackMapEntry {
     // mEndOfConsumedInputTicks is the end of the input ticks that we've consumed.
     // 0 if we haven't consumed any yet.
     StreamTime mEndOfConsumedInputTicks;
     // mEndOfLastInputIntervalInInputStream is the timestamp for the end of the
     // previous interval which was unblocked for both the input and output
--- a/dom/media/VideoFrameContainer.cpp
+++ b/dom/media/VideoFrameContainer.cpp
@@ -9,49 +9,186 @@
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "nsIFrame.h"
 #include "nsDisplayList.h"
 #include "nsSVGEffects.h"
 
 using namespace mozilla::layers;
 
 namespace mozilla {
+PRLogModuleInfo* gVideoFrameContainerLog;
+#define CONTAINER_LOG(type, msg) MOZ_LOG(gVideoFrameContainerLog, type, msg)
 
 VideoFrameContainer::VideoFrameContainer(dom::HTMLMediaElement* aElement,
                                          already_AddRefed<ImageContainer> aContainer)
   : mElement(aElement),
     mImageContainer(aContainer), mMutex("nsVideoFrameContainer"),
+    mBlackImage(nullptr),
     mFrameID(0),
     mIntrinsicSizeChanged(false), mImageSizeChanged(false),
     mPendingPrincipalHandle(PRINCIPAL_HANDLE_NONE), mFrameIDForPendingPrincipalHandle(0)
 {
   NS_ASSERTION(aElement, "aElement must not be null");
   NS_ASSERTION(mImageContainer, "aContainer must not be null");
+  if (!gVideoFrameContainerLog) {
+    gVideoFrameContainerLog = PR_NewLogModule("VideoFrameContainer");
+  }
 }
 
 VideoFrameContainer::~VideoFrameContainer()
 {}
 
 PrincipalHandle VideoFrameContainer::GetLastPrincipalHandle()
 {
   MutexAutoLock lock(mMutex);
+  return GetLastPrincipalHandleLocked();
+}
+
+PrincipalHandle VideoFrameContainer::GetLastPrincipalHandleLocked()
+{
   return mLastPrincipalHandle;
 }
 
 void VideoFrameContainer::UpdatePrincipalHandleForFrameID(const PrincipalHandle& aPrincipalHandle,
                                                           const ImageContainer::FrameID& aFrameID)
 {
   MutexAutoLock lock(mMutex);
+  UpdatePrincipalHandleForFrameIDLocked(aPrincipalHandle, aFrameID);
+}
+
+void VideoFrameContainer::UpdatePrincipalHandleForFrameIDLocked(const PrincipalHandle& aPrincipalHandle,
+                                                                const ImageContainer::FrameID& aFrameID)
+{
   if (mPendingPrincipalHandle == aPrincipalHandle) {
     return;
   }
   mPendingPrincipalHandle = aPrincipalHandle;
   mFrameIDForPendingPrincipalHandle = aFrameID;
 }
 
+static void
+SetImageToBlackPixel(PlanarYCbCrImage* aImage)
+{
+  uint8_t blackPixel[] = { 0x10, 0x80, 0x80 };
+
+  PlanarYCbCrData data;
+  data.mYChannel = blackPixel;
+  data.mCbChannel = blackPixel + 1;
+  data.mCrChannel = blackPixel + 2;
+  data.mYStride = data.mCbCrStride = 1;
+  data.mPicSize = data.mYSize = data.mCbCrSize = gfx::IntSize(1, 1);
+  aImage->CopyData(data);
+}
+
+class VideoFrameContainerInvalidateRunnable : public Runnable {
+public:
+  explicit VideoFrameContainerInvalidateRunnable(VideoFrameContainer* aVideoFrameContainer)
+    : mVideoFrameContainer(aVideoFrameContainer)
+  {}
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mVideoFrameContainer->Invalidate();
+
+    return NS_OK;
+  }
+private:
+  RefPtr<VideoFrameContainer> mVideoFrameContainer;
+};
+
+void VideoFrameContainer::SetCurrentFrames(const VideoSegment& aSegment)
+{
+  if (aSegment.IsEmpty()) {
+    return;
+  }
+
+  MutexAutoLock lock(mMutex);
+
+  // Collect any new frames produced in this iteration.
+  AutoTArray<ImageContainer::NonOwningImage,4> newImages;
+  PrincipalHandle lastPrincipalHandle = PRINCIPAL_HANDLE_NONE;
+
+  VideoSegment::ConstChunkIterator iter(aSegment);
+  while (!iter.IsEnded()) {
+    VideoChunk chunk = *iter;
+
+    const VideoFrame* frame = &chunk.mFrame;
+    if (*frame == mLastPlayedVideoFrame) {
+      iter.Next();
+      continue;
+    }
+
+    Image* image = frame->GetImage();
+    CONTAINER_LOG(LogLevel::Verbose,
+                  ("VideoFrameContainer %p writing video frame %p (%d x %d)",
+                  this, image, frame->GetIntrinsicSize().width,
+                  frame->GetIntrinsicSize().height));
+
+    if (frame->GetForceBlack()) {
+      if (!mBlackImage) {
+        mBlackImage = GetImageContainer()->CreatePlanarYCbCrImage();
+        if (mBlackImage) {
+          // Sets the image to a single black pixel, which will be scaled to
+          // fill the rendered size.
+          SetImageToBlackPixel(mBlackImage->AsPlanarYCbCrImage());
+        }
+      }
+      if (mBlackImage) {
+        image = mBlackImage;
+      }
+    }
+    // Don't append null image to the newImages.
+    if (!image) {
+      iter.Next();
+      continue;
+    }
+    newImages.AppendElement(ImageContainer::NonOwningImage(image, chunk.mTimeStamp));
+
+    lastPrincipalHandle = chunk.GetPrincipalHandle();
+
+    mLastPlayedVideoFrame = *frame;
+    iter.Next();
+  }
+
+  // Don't update if there are no changes.
+  if (newImages.IsEmpty()) {
+    return;
+  }
+
+  AutoTArray<ImageContainer::NonOwningImage,4> images;
+
+  bool principalHandleChanged =
+     lastPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
+     lastPrincipalHandle != GetLastPrincipalHandleLocked();
+
+  // Add the frames from this iteration.
+  for (auto& image : newImages) {
+    image.mFrameID = NewFrameID();
+    images.AppendElement(image);
+  }
+
+  if (principalHandleChanged) {
+    UpdatePrincipalHandleForFrameIDLocked(lastPrincipalHandle,
+                                          newImages.LastElement().mFrameID);
+  }
+
+  SetCurrentFramesLocked(mLastPlayedVideoFrame.GetIntrinsicSize(), images);
+  nsCOMPtr<nsIRunnable> event =
+    new VideoFrameContainerInvalidateRunnable(this);
+  NS_DispatchToMainThread(event.forget());
+
+  images.ClearAndRetainStorage();
+}
+
+void VideoFrameContainer::ClearFrames()
+{
+  ClearFutureFrames();
+}
+
 void VideoFrameContainer::SetCurrentFrame(const gfx::IntSize& aIntrinsicSize,
                                           Image* aImage,
                                           const TimeStamp& aTargetTime)
 {
   if (aImage) {
     MutexAutoLock lock(mMutex);
     AutoTArray<ImageContainer::NonOwningImage,1> imageList;
     imageList.AppendElement(
--- a/dom/media/VideoFrameContainer.h
+++ b/dom/media/VideoFrameContainer.h
@@ -8,60 +8,66 @@
 #define VIDEOFRAMECONTAINER_H_
 
 #include "mozilla/Mutex.h"
 #include "mozilla/TimeStamp.h"
 #include "gfxPoint.h"
 #include "nsCOMPtr.h"
 #include "ImageContainer.h"
 #include "MediaSegment.h"
+#include "MediaStreamVideoSink.h"
+#include "VideoSegment.h"
 
 namespace mozilla {
 
 namespace dom {
 class HTMLMediaElement;
 } // namespace dom
 
 /**
  * This object is used in the decoder backend threads and the main thread
  * to manage the "current video frame" state. This state includes timing data
  * and an intrinsic size (see below).
  * This has to be a thread-safe object since it's accessed by resource decoders
  * and other off-main-thread components. So we can't put this state in the media
  * element itself ... well, maybe we could, but it could be risky and/or
  * confusing.
  */
-class VideoFrameContainer {
-  ~VideoFrameContainer();
+class VideoFrameContainer : public MediaStreamVideoSink {
+  virtual ~VideoFrameContainer();
 
 public:
   typedef layers::ImageContainer ImageContainer;
   typedef layers::Image Image;
 
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoFrameContainer)
-
   VideoFrameContainer(dom::HTMLMediaElement* aElement,
                       already_AddRefed<ImageContainer> aContainer);
 
   // Call on any thread
+  virtual void SetCurrentFrames(const VideoSegment& aSegment) override;
+  virtual void ClearFrames() override;
+  void SetCurrentFrame(const gfx::IntSize& aIntrinsicSize, Image* aImage,
+                       const TimeStamp& aTargetTime);
   // Returns the last principalHandle we notified mElement about.
   PrincipalHandle GetLastPrincipalHandle();
+  PrincipalHandle GetLastPrincipalHandleLocked();
   // We will notify mElement that aPrincipalHandle has been applied when all
   // FrameIDs prior to aFrameID have been flushed out.
   // aFrameID is ignored if aPrincipalHandle already is our pending principalHandle.
   void UpdatePrincipalHandleForFrameID(const PrincipalHandle& aPrincipalHandle,
                                        const ImageContainer::FrameID& aFrameID);
-  void SetCurrentFrame(const gfx::IntSize& aIntrinsicSize, Image* aImage,
-                       const TimeStamp& aTargetTime);
+  void UpdatePrincipalHandleForFrameIDLocked(const PrincipalHandle& aPrincipalHandle,
+                                             const ImageContainer::FrameID& aFrameID);
   void SetCurrentFrames(const gfx::IntSize& aIntrinsicSize,
                         const nsTArray<ImageContainer::NonOwningImage>& aImages);
   void ClearCurrentFrame(const gfx::IntSize& aIntrinsicSize)
   {
     SetCurrentFrames(aIntrinsicSize, nsTArray<ImageContainer::NonOwningImage>());
   }
+  VideoFrameContainer* AsVideoFrameContainer() override { return this; }
 
   void ClearCurrentFrame();
   // Make the current frame the only frame in the container, i.e. discard
   // all future frames.
   void ClearFutureFrames();
   // Time in seconds by which the last painted video frame was late by.
   // E.g. if the last painted frame should have been painted at time t,
   // but was actually painted at t+n, this returns n in seconds. Threadsafe.
@@ -75,17 +81,17 @@ public:
     return ++mFrameID;
   }
 
   // Call on main thread
   enum {
     INVALIDATE_DEFAULT,
     INVALIDATE_FORCE
   };
-  void Invalidate() { InvalidateWithFlags(INVALIDATE_DEFAULT); }
+  void Invalidate() override { InvalidateWithFlags(INVALIDATE_DEFAULT); }
   void InvalidateWithFlags(uint32_t aFlags);
   ImageContainer* GetImageContainer();
   void ForgetElement() { mElement = nullptr; }
 
   uint32_t GetDroppedImageCount() { return mImageContainer->GetDroppedImageCount(); }
 
 protected:
   void SetCurrentFramesLocked(const gfx::IntSize& aIntrinsicSize,
@@ -93,25 +99,31 @@ protected:
 
   // Non-addreffed pointer to the element. The element calls ForgetElement
   // to clear this reference when the element is destroyed.
   dom::HTMLMediaElement* mElement;
   RefPtr<ImageContainer> mImageContainer;
 
   // mMutex protects all the fields below.
   Mutex mMutex;
+  // Once the frame is forced to black, we initialize mBlackImage for following
+  // frames.
+  RefPtr<Image> mBlackImage;
   // The intrinsic size is the ideal size which we should render the
   // ImageContainer's current Image at.
   // This can differ from the Image's actual size when the media resource
   // specifies that the Image should be stretched to have the correct aspect
   // ratio.
   gfx::IntSize mIntrinsicSize;
   // We maintain our own mFrameID which is auto-incremented at every
   // SetCurrentFrame() or NewFrameID() call.
   ImageContainer::FrameID mFrameID;
+  // We record the last played video frame to avoid playing the frame again
+  // with a different frame id.
+  VideoFrame mLastPlayedVideoFrame;
   // True when the intrinsic size has been changed by SetCurrentFrame() since
   // the last call to Invalidate().
   // The next call to Invalidate() will recalculate
   // and update the intrinsic size on the element, request a frame reflow and
   // then reset this flag.
   bool mIntrinsicSizeChanged;
   // True when the Image size has changed since the last time Invalidate() was
   // called. When set, the next call to Invalidate() will ensure that the
--- a/dom/media/VideoSegment.cpp
+++ b/dom/media/VideoSegment.cpp
@@ -95,19 +95,21 @@ VideoChunk::VideoChunk()
 VideoChunk::~VideoChunk()
 {}
 
 void
 VideoSegment::AppendFrame(already_AddRefed<Image>&& aImage,
                           StreamTime aDuration,
                           const IntSize& aIntrinsicSize,
                           const PrincipalHandle& aPrincipalHandle,
-                          bool aForceBlack)
+                          bool aForceBlack,
+                          TimeStamp aTimeStamp)
 {
   VideoChunk* chunk = AppendChunk(aDuration);
+  chunk->mTimeStamp = aTimeStamp;
   VideoFrame frame(aImage, aIntrinsicSize);
   frame.SetForceBlack(aForceBlack);
   frame.SetPrincipalHandle(aPrincipalHandle);
   chunk->mFrame.TakeFrom(&frame);
 }
 
 VideoSegment::VideoSegment()
   : MediaSegmentBase<VideoSegment, VideoChunk>(VIDEO)
--- a/dom/media/VideoSegment.h
+++ b/dom/media/VideoSegment.h
@@ -104,17 +104,18 @@ public:
 
   VideoSegment();
   ~VideoSegment();
 
   void AppendFrame(already_AddRefed<Image>&& aImage,
                    StreamTime aDuration,
                    const IntSize& aIntrinsicSize,
                    const PrincipalHandle& aPrincipalHandle,
-                   bool aForceBlack = false);
+                   bool aForceBlack = false,
+                   TimeStamp aTimeStamp = TimeStamp::Now());
   const VideoFrame* GetLastFrame(StreamTime* aStart = nullptr)
   {
     VideoChunk* c = GetLastChunk();
     if (!c) {
       return nullptr;
     }
     if (aStart) {
       *aStart = mDuration - c->mDuration;
@@ -132,13 +133,19 @@ public:
 
   // Segment-generic methods not in MediaSegmentBase
   static Type StaticType() { return VIDEO; }
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
+
+  bool IsEmpty() const
+  {
+    return mChunks.IsEmpty();
+  }
+
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_VIDEOSEGMENT_H_ */
--- a/dom/media/VideoStreamTrack.cpp
+++ b/dom/media/VideoStreamTrack.cpp
@@ -1,20 +1,35 @@
 /* -*- 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 "VideoStreamTrack.h"
 
+#include "MediaStreamVideoSink.h"
+#include "MediaStreamGraph.h"
+
 #include "mozilla/dom/VideoStreamTrackBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 JSObject*
 VideoStreamTrack::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return VideoStreamTrackBinding::Wrap(aCx, this, aGivenProto);
 }
 
+void
+VideoStreamTrack::AddVideoOutput(MediaStreamVideoSink* aSink)
+{
+  GetOwnedStream()->AddVideoOutput(aSink, mTrackID);
+}
+
+void
+VideoStreamTrack::RemoveVideoOutput(MediaStreamVideoSink* aSink)
+{
+  GetOwnedStream()->RemoveVideoOutput(aSink, mTrackID);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/VideoStreamTrack.h
+++ b/dom/media/VideoStreamTrack.h
@@ -5,32 +5,38 @@
 
 #ifndef VIDEOSTREAMTRACK_H_
 #define VIDEOSTREAMTRACK_H_
 
 #include "MediaStreamTrack.h"
 #include "DOMMediaStream.h"
 
 namespace mozilla {
+
+class MediaStreamVideoSink;
+
 namespace dom {
 
 class VideoStreamTrack : public MediaStreamTrack {
 public:
   VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                    TrackID aInputTrackID,
                    MediaStreamTrackSource* aSource,
                    const MediaTrackConstraints& aConstraints = MediaTrackConstraints())
     : MediaStreamTrack(aStream, aTrackID, aInputTrackID, aSource, aConstraints) {}
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   VideoStreamTrack* AsVideoStreamTrack() override { return this; }
 
   const VideoStreamTrack* AsVideoStreamTrack() const override { return this; }
 
+  void AddVideoOutput(MediaStreamVideoSink* aSink);
+  void RemoveVideoOutput(MediaStreamVideoSink* aSink);
+
   // WebIDL
   void GetKind(nsAString& aKind) override { aKind.AssignLiteral("video"); }
 
 protected:
   already_AddRefed<MediaStreamTrack> CloneInternal(DOMMediaStream* aOwningStream,
                                                    TrackID aTrackID) override
   {
     return do_AddRef(new VideoStreamTrack(aOwningStream,
--- a/dom/media/VideoTrackList.cpp
+++ b/dom/media/VideoTrackList.cpp
@@ -60,16 +60,25 @@ VideoTrackList::RemoveTrack(const RefPtr
 
 void
 VideoTrackList::EmptyTracks()
 {
   mSelectedIndex = -1;
   MediaTrackList::EmptyTracks();
 }
 
+VideoTrack* VideoTrackList::GetSelectedTrack()
+{
+  if (mSelectedIndex < 0 || static_cast<size_t>(mSelectedIndex) >= mTracks.Length()) {
+    return nullptr;
+  }
+
+  return operator[](mSelectedIndex);
+}
+
 VideoTrack*
 VideoTrackList::IndexedGetter(uint32_t aIndex, bool& aFound)
 {
   MediaTrack* track = MediaTrackList::IndexedGetter(aIndex, aFound);
   return track ? track->AsVideoTrack() : nullptr;
 }
 
 VideoTrack*
--- a/dom/media/VideoTrackList.h
+++ b/dom/media/VideoTrackList.h
@@ -27,16 +27,18 @@ public:
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   VideoTrack* operator[](uint32_t aIndex);
 
   void RemoveTrack(const RefPtr<MediaTrack>& aTrack) override;
 
   void EmptyTracks() override;
 
+  VideoTrack* GetSelectedTrack();
+
   // WebIDL
   int32_t SelectedIndex() const
   {
     return mSelectedIndex;
   }
 
   VideoTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
 
--- a/dom/media/android/AndroidMediaReader.cpp
+++ b/dom/media/android/AndroidMediaReader.cpp
@@ -9,16 +9,17 @@
 #include "MediaResource.h"
 #include "VideoUtils.h"
 #include "AndroidMediaDecoder.h"
 #include "AndroidMediaPluginHost.h"
 #include "MediaDecoderStateMachine.h"
 #include "ImageContainer.h"
 #include "AbstractMediaDecoder.h"
 #include "gfx2DGlue.h"
+#include "VideoFrameContainer.h"
 
 namespace mozilla {
 
 using namespace mozilla::gfx;
 using namespace mozilla::media;
 
 typedef mozilla::layers::Image Image;
 typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
@@ -180,17 +181,16 @@ bool AndroidMediaReader::DecodeVideoFram
         // was reported relative to the picture size reported by the container.
         picture.x = (mPicture.x * frameSize.width) / mInitialFrame.width;
         picture.y = (mPicture.y * frameSize.height) / mInitialFrame.height;
         picture.width = (frameSize.width * mPicture.width) / mInitialFrame.width;
         picture.height = (frameSize.height * mPicture.height) / mInitialFrame.height;
       }
 
       v = VideoData::CreateFromImage(mInfo.mVideo,
-                                     mDecoder->GetImageContainer(),
                                      pos,
                                      frame.mTimeUs,
                                      1, // We don't know the duration yet.
                                      currentImage,
                                      frame.mKeyFrame,
                                      -1,
                                      picture);
     } else {
@@ -225,25 +225,25 @@ bool AndroidMediaReader::DecodeVideoFram
         // was reported relative to the picture size reported by the container.
         picture.x = (mPicture.x * frame.Y.mWidth) / mInitialFrame.width;
         picture.y = (mPicture.y * frame.Y.mHeight) / mInitialFrame.height;
         picture.width = (frame.Y.mWidth * mPicture.width) / mInitialFrame.width;
         picture.height = (frame.Y.mHeight * mPicture.height) / mInitialFrame.height;
       }
 
       // This is the approximate byte position in the stream.
-      v = VideoData::Create(mInfo.mVideo,
-                            mDecoder->GetImageContainer(),
-                            pos,
-                            frame.mTimeUs,
-                            1, // We don't know the duration yet.
-                            b,
-                            frame.mKeyFrame,
-                            -1,
-                            picture);
+      v = VideoData::CreateAndCopyData(mInfo.mVideo,
+                                       mDecoder->GetImageContainer(),
+                                       pos,
+                                       frame.mTimeUs,
+                                       1, // We don't know the duration yet.
+                                       b,
+                                       frame.mKeyFrame,
+                                       -1,
+                                       picture);
     }
 
     if (!v) {
       return false;
     }
     a.mStats.mParsedFrames++;
     a.mStats.mDecodedFrames++;
     NS_ASSERTION(a.mStats.mDecodedFrames <= a.mStats.mParsedFrames, "Expect to decode fewer frames than parsed in AndroidMedia...");
--- a/dom/media/encoder/MediaEncoder.cpp
+++ b/dom/media/encoder/MediaEncoder.cpp
@@ -29,16 +29,23 @@
 #endif
 
 mozilla::LazyLogModule gMediaEncoderLog("MediaEncoder");
 #define LOG(type, msg) MOZ_LOG(gMediaEncoderLog, type, msg)
 
 namespace mozilla {
 
 void
+MediaStreamVideoRecorderSink::SetCurrentFrames(const VideoSegment& aSegment)
+{
+  MOZ_ASSERT(mVideoEncoder);
+  mVideoEncoder->SetCurrentFrames(aSegment);
+}
+
+void
 MediaEncoder::SetDirectConnect(bool aConnected)
 {
   mDirectConnected = aConnected;
 }
 
 void
 MediaEncoder::NotifyRealtimeData(MediaStreamGraph* aGraph,
                                  TrackID aID,
@@ -48,18 +55,19 @@ MediaEncoder::NotifyRealtimeData(MediaSt
 {
   if (mSuspended == RECORD_NOT_SUSPENDED) {
     // Process the incoming raw track data from MediaStreamGraph, called on the
     // thread of MediaStreamGraph.
     if (mAudioEncoder && aRealtimeMedia.GetType() == MediaSegment::AUDIO) {
       mAudioEncoder->NotifyQueuedTrackChanges(aGraph, aID,
                                               aTrackOffset, aTrackEvents,
                                               aRealtimeMedia);
-
-    } else if (mVideoEncoder && aRealtimeMedia.GetType() == MediaSegment::VIDEO) {
+    } else if (mVideoEncoder &&
+               aRealtimeMedia.GetType() == MediaSegment::VIDEO &&
+               aTrackEvents != TrackEventCommand::TRACK_EVENT_NONE) {
       mVideoEncoder->NotifyQueuedTrackChanges(aGraph, aID,
                                               aTrackOffset, aTrackEvents,
                                               aRealtimeMedia);
     }
   }
 }
 
 void
@@ -136,17 +144,18 @@ MediaEncoder::NotifyEvent(MediaStreamGra
     mVideoEncoder->NotifyEvent(aGraph, event);
   }
 }
 
 /* static */
 already_AddRefed<MediaEncoder>
 MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint32_t aAudioBitrate,
                             uint32_t aVideoBitrate, uint32_t aBitrate,
-                            uint8_t aTrackTypes)
+                            uint8_t aTrackTypes,
+                            TrackRate aTrackRate)
 {
   PROFILER_LABEL("MediaEncoder", "CreateEncoder",
     js::ProfileEntry::Category::OTHER);
 
   nsAutoPtr<ContainerWriter> writer;
   nsAutoPtr<AudioTrackEncoder> audioEncoder;
   nsAutoPtr<VideoTrackEncoder> videoEncoder;
   RefPtr<MediaEncoder> encoder;
@@ -159,32 +168,32 @@ MediaEncoder::CreateEncoder(const nsAStr
   else if (MediaEncoder::IsWebMEncoderEnabled() &&
           (aMIMEType.EqualsLiteral(VIDEO_WEBM) ||
           (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK))) {
     if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK
         && MediaDecoder::IsOpusEnabled()) {
       audioEncoder = new OpusTrackEncoder();
       NS_ENSURE_TRUE(audioEncoder, nullptr);
     }
-    videoEncoder = new VP8TrackEncoder();
+    videoEncoder = new VP8TrackEncoder(aTrackRate);
     writer = new WebMWriter(aTrackTypes);
     NS_ENSURE_TRUE(writer, nullptr);
     NS_ENSURE_TRUE(videoEncoder, nullptr);
     mimeType = NS_LITERAL_STRING(VIDEO_WEBM);
   }
 #endif //MOZ_WEBM_ENCODER
 #ifdef MOZ_OMX_ENCODER
   else if (MediaEncoder::IsOMXEncoderEnabled() &&
           (aMIMEType.EqualsLiteral(VIDEO_MP4) ||
           (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK))) {
     if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) {
       audioEncoder = new OmxAACAudioTrackEncoder();
       NS_ENSURE_TRUE(audioEncoder, nullptr);
     }
-    videoEncoder = new OmxVideoTrackEncoder();
+    videoEncoder = new OmxVideoTrackEncoder(aTrackRate);
     writer = new ISOMediaWriter(aTrackTypes);
     NS_ENSURE_TRUE(writer, nullptr);
     NS_ENSURE_TRUE(videoEncoder, nullptr);
     mimeType = NS_LITERAL_STRING(VIDEO_MP4);
   } else if (MediaEncoder::IsOMXEncoderEnabled() &&
             (aMIMEType.EqualsLiteral(AUDIO_3GPP))) {
     audioEncoder = new OmxAMRAudioTrackEncoder();
     NS_ENSURE_TRUE(audioEncoder, nullptr);
--- a/dom/media/encoder/MediaEncoder.h
+++ b/dom/media/encoder/MediaEncoder.h
@@ -4,25 +4,42 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MediaEncoder_h_
 #define MediaEncoder_h_
 
 #include "mozilla/DebugOnly.h"
 #include "TrackEncoder.h"
 #include "ContainerWriter.h"
+#include "CubebUtils.h"
 #include "MediaStreamGraph.h"
 #include "MediaStreamListener.h"
 #include "nsAutoPtr.h"
+#include "MediaStreamVideoSink.h"
 #include "nsIMemoryReporter.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Atomics.h"
 
 namespace mozilla {
 
+class MediaStreamVideoRecorderSink : public MediaStreamVideoSink
+{
+public:
+  explicit MediaStreamVideoRecorderSink(VideoTrackEncoder* aEncoder)
+    : mVideoEncoder(aEncoder) {}
+
+  // MediaStreamVideoSink methods
+  virtual void SetCurrentFrames(const VideoSegment& aSegment) override;
+  virtual void ClearFrames() override {}
+
+private:
+  virtual ~MediaStreamVideoRecorderSink() {}
+  VideoTrackEncoder* mVideoEncoder;
+};
+
 /**
  * MediaEncoder is the framework of encoding module, it controls and manages
  * procedures between ContainerWriter and TrackEncoder. ContainerWriter packs
  * the encoded track data with a specific container (e.g. ogg, mp4).
  * AudioTrackEncoder and VideoTrackEncoder are subclasses of TrackEncoder, and
  * are responsible for encoding raw data coming from MediaStreamGraph.
  *
  * Also, MediaEncoder is a type of MediaStreamListener, it starts to receive raw
@@ -49,16 +66,17 @@ namespace mozilla {
  * 3) To start encoding, add this component to its source stream.
  *    => sourceStream->AddListener(encoder);
  *
  * 4) To stop encoding, remove this component from its source stream.
  *    => sourceStream->RemoveListener(encoder);
  */
 class MediaEncoder : public DirectMediaStreamListener
 {
+  friend class MediaStreamVideoRecorderSink;
 public :
   enum {
     ENCODE_METADDATA,
     ENCODE_TRACK,
     ENCODE_DONE,
     ENCODE_ERROR,
   };
 
@@ -67,16 +85,17 @@ public :
                VideoTrackEncoder* aVideoEncoder,
                const nsAString& aMIMEType,
                uint32_t aAudioBitrate,
                uint32_t aVideoBitrate,
                uint32_t aBitrate)
     : mWriter(aWriter)
     , mAudioEncoder(aAudioEncoder)
     , mVideoEncoder(aVideoEncoder)
+    , mVideoSink(new MediaStreamVideoRecorderSink(mVideoEncoder))
     , mStartTime(TimeStamp::Now())
     , mMIMEType(aMIMEType)
     , mSizeOfBuffer(0)
     , mState(MediaEncoder::ENCODE_METADDATA)
     , mShutdown(false)
     , mDirectConnected(false)
     , mSuspended(false)
 {}
@@ -150,17 +169,18 @@ public :
   /**
    * Creates an encoder with a given MIME type. Returns null if we are unable
    * to create the encoder. For now, default aMIMEType to "audio/ogg" and use
    * Ogg+Opus if it is empty.
    */
   static already_AddRefed<MediaEncoder> CreateEncoder(const nsAString& aMIMEType,
                                                       uint32_t aAudioBitrate, uint32_t aVideoBitrate,
                                                       uint32_t aBitrate,
-                                                      uint8_t aTrackTypes = ContainerWriter::CREATE_AUDIO_TRACK);
+                                                      uint8_t aTrackTypes = ContainerWriter::CREATE_AUDIO_TRACK,
+                                                      TrackRate aTrackRate = CubebUtils::PreferredSampleRate());
   /**
    * Encodes the raw track data and returns the final container data. Assuming
    * it is called on a single worker thread. The buffer of container data is
    * allocated in ContainerWriter::GetContainerData(), and is appended to
    * aOutputBufs. aMIMEType is the valid mime-type of this returned container
    * data.
    */
   void GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
@@ -203,24 +223,29 @@ public :
 
   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
   /*
    * Measure the size of the buffer, and memory occupied by mAudioEncoder
    * and mVideoEncoder
    */
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
+  MediaStreamVideoRecorderSink* GetVideoSink() {
+    return mVideoSink.get();
+  }
+
 private:
   // Get encoded data from trackEncoder and write to muxer
   nsresult WriteEncodedDataToMuxer(TrackEncoder *aTrackEncoder);
   // Get metadata from trackEncoder and copy to muxer
   nsresult CopyMetadataToMuxer(TrackEncoder* aTrackEncoder);
   nsAutoPtr<ContainerWriter> mWriter;
   nsAutoPtr<AudioTrackEncoder> mAudioEncoder;
   nsAutoPtr<VideoTrackEncoder> mVideoEncoder;
+  RefPtr<MediaStreamVideoRecorderSink> mVideoSink;
   TimeStamp mStartTime;
   nsString mMIMEType;
   int64_t mSizeOfBuffer;
   int mState;
   bool mShutdown;
   bool mDirectConnected;
   Atomic<int> mSuspended;
   // Get duration from create encoder, for logging purpose
--- a/dom/media/encoder/OmxTrackEncoder.cpp
+++ b/dom/media/encoder/OmxTrackEncoder.cpp
@@ -21,30 +21,29 @@
 
 using namespace android;
 
 namespace mozilla {
 
 #define ENCODER_CONFIG_FRAME_RATE 30 // fps
 #define GET_ENCODED_VIDEO_FRAME_TIMEOUT 100000 // microseconds
 
-OmxVideoTrackEncoder::OmxVideoTrackEncoder()
-  : VideoTrackEncoder()
+OmxVideoTrackEncoder::OmxVideoTrackEncoder(TrackRate aTrackRate)
+  : VideoTrackEncoder(aTrackRate)
 {}
 
 OmxVideoTrackEncoder::~OmxVideoTrackEncoder()
 {}
 
 nsresult
 OmxVideoTrackEncoder::Init(int aWidth, int aHeight, int aDisplayWidth,
-                           int aDisplayHeight, TrackRate aTrackRate)
+                           int aDisplayHeight)
 {
   mFrameWidth = aWidth;
   mFrameHeight = aHeight;
-  mTrackRate = aTrackRate;
   mDisplayWidth = aDisplayWidth;
   mDisplayHeight = aDisplayHeight;
 
   mEncoder = OMXCodecWrapper::CreateAVCEncoder();
   NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE);
 
   nsresult rv = mEncoder->Configure(mFrameWidth, mFrameHeight,
                                     ENCODER_CONFIG_FRAME_RATE);
--- a/dom/media/encoder/OmxTrackEncoder.h
+++ b/dom/media/encoder/OmxTrackEncoder.h
@@ -22,27 +22,26 @@ class OMXAudioEncoder;
  * Bean platform.
  */
 
 namespace mozilla {
 
 class OmxVideoTrackEncoder: public VideoTrackEncoder
 {
 public:
-  OmxVideoTrackEncoder();
+  explicit OmxVideoTrackEncoder(TrackRate aTrackRate);
   ~OmxVideoTrackEncoder();
 
   already_AddRefed<TrackMetadataBase> GetMetadata() override;
 
   nsresult GetEncodedTrack(EncodedFrameContainer& aData) override;
 
 protected:
   nsresult Init(int aWidth, int aHeight,
-                int aDisplayWidth, int aDisplayHeight,
-                TrackRate aTrackRate) override;
+                int aDisplayWidth, int aDisplayHeight) override;
 
 private:
   nsAutoPtr<android::OMXVideoEncoder> mEncoder;
 };
 
 class OmxAudioTrackEncoder : public AudioTrackEncoder
 {
 public:
--- a/dom/media/encoder/TrackEncoder.cpp
+++ b/dom/media/encoder/TrackEncoder.cpp
@@ -188,67 +188,83 @@ AudioTrackEncoder::DeInterleaveTrackData
 
 size_t
 AudioTrackEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   return mRawSegment.SizeOfExcludingThis(aMallocSizeOf);
 }
 
 void
+VideoTrackEncoder::Init(const VideoSegment& aSegment)
+{
+  if (mInitialized) {
+    return;
+  }
+
+  mInitCounter++;
+  TRACK_LOG(LogLevel::Debug, ("Init the video encoder %d times", mInitCounter));
+  VideoSegment::ConstChunkIterator iter(aSegment);
+  while (!iter.IsEnded()) {
+   VideoChunk chunk = *iter;
+   if (!chunk.IsNull()) {
+     gfx::IntSize imgsize = chunk.mFrame.GetImage()->GetSize();
+     gfx::IntSize intrinsicSize = chunk.mFrame.GetIntrinsicSize();
+     nsresult rv = Init(imgsize.width, imgsize.height,
+                        intrinsicSize.width, intrinsicSize.height);
+
+     if (NS_FAILED(rv)) {
+       LOG("[VideoTrackEncoder]: Fail to initialize the encoder!");
+       NotifyCancel();
+     }
+     break;
+   }
+
+   iter.Next();
+  }
+}
+
+void
+VideoTrackEncoder::SetCurrentFrames(const VideoSegment& aSegment)
+{
+  if (mCanceled) {
+    return;
+  }
+
+  Init(aSegment);
+  AppendVideoSegment(aSegment);
+}
+
+void
 VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
                                             TrackID aID,
                                             StreamTime aTrackOffset,
                                             uint32_t aTrackEvents,
                                             const MediaSegment& aQueuedMedia)
 {
   if (mCanceled) {
     return;
   }
 
+  if (!(aTrackEvents == TRACK_EVENT_CREATED ||
+       aTrackEvents == TRACK_EVENT_ENDED)) {
+    return;
+  }
+
   const VideoSegment& video = static_cast<const VideoSegment&>(aQueuedMedia);
 
    // Check and initialize parameters for codec encoder.
-  if (!mInitialized) {
-    mInitCounter++;
-    TRACK_LOG(LogLevel::Debug, ("Init the video encoder %d times", mInitCounter));
-    VideoSegment::ChunkIterator iter(const_cast<VideoSegment&>(video));
-    while (!iter.IsEnded()) {
-      VideoChunk chunk = *iter;
-      if (!chunk.IsNull()) {
-        gfx::IntSize imgsize = chunk.mFrame.GetImage()->GetSize();
-        gfx::IntSize intrinsicSize = chunk.mFrame.GetIntrinsicSize();
-        nsresult rv = Init(imgsize.width, imgsize.height,
-                           intrinsicSize.width, intrinsicSize.height,
-                           aGraph->GraphRate());
-        if (NS_FAILED(rv)) {
-          LOG("[VideoTrackEncoder]: Fail to initialize the encoder!");
-          NotifyCancel();
-        }
-        break;
-      }
-
-      iter.Next();
-    }
-
-    mNotInitDuration += aQueuedMedia.GetDuration();
-    if (!mInitialized &&
-        (mNotInitDuration / aGraph->GraphRate() > INIT_FAILED_DURATION) &&
-        mInitCounter > 1) {
-      LOG("[VideoTrackEncoder]: Initialize failed for 30s.");
-      NotifyEndOfStream();
-      return;
-    }
-  }
+  Init(video);
 
   AppendVideoSegment(video);
 
   // The stream has stopped and reached the end of track.
   if (aTrackEvents == TrackEventCommand::TRACK_EVENT_ENDED) {
     LOG("[VideoTrackEncoder]: Receive TRACK_EVENT_ENDED .");
     NotifyEndOfStream();
+    mFirstFrame = true;
   }
 
 }
 
 nsresult
 VideoTrackEncoder::AppendVideoSegment(const VideoSegment& aSegment)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
@@ -261,30 +277,45 @@ VideoTrackEncoder::AppendVideoSegment(co
     mTotalFrameDuration += chunk.GetDuration();
     mLastFrameDuration += chunk.GetDuration();
     // Send only the unique video frames for encoding.
     // Or if we got the same video chunks more than 1 seconds,
     // force to send into encoder.
     if ((mLastFrame != chunk.mFrame) ||
         (mLastFrameDuration >= mTrackRate)) {
       RefPtr<layers::Image> image = chunk.mFrame.GetImage();
+
+      // Fixme: see bug 1290777. We should remove the useage of duration here and
+      // in |GetEncodedTrack|.
+      StreamTime duration;
+      if (mFirstFrame)
+      {
+        duration = chunk.GetDuration();
+        mFirstFrame = false;
+      } else {
+        MOZ_ASSERT(chunk.mTimeStamp >= mLastFrameTimeStamp);
+        TimeDuration timeDuration = chunk.mTimeStamp - mLastFrameTimeStamp;
+        duration = SecondsToMediaTime(timeDuration.ToSeconds());
+      }
+
       // Because we may get chunks with a null image (due to input blocking),
       // accumulate duration and give it to the next frame that arrives.
       // Canonically incorrect - the duration should go to the previous frame
       // - but that would require delaying until the next frame arrives.
       // Best would be to do like OMXEncoder and pass an effective timestamp
       // in with each frame.
       if (image) {
         mRawSegment.AppendFrame(image.forget(),
-                                mLastFrameDuration,
+                                duration,
                                 chunk.mFrame.GetIntrinsicSize(),
                                 PRINCIPAL_HANDLE_NONE,
                                 chunk.mFrame.GetForceBlack());
         mLastFrameDuration = 0;
       }
+      mLastFrameTimeStamp = chunk.mTimeStamp;
     }
     mLastFrame.TakeFrom(&chunk.mFrame);
     iter.Next();
   }
 
   if (mRawSegment.GetDuration() > 0) {
     mReentrantMonitor.NotifyAll();
   }
@@ -294,17 +325,17 @@ VideoTrackEncoder::AppendVideoSegment(co
 
 void
 VideoTrackEncoder::NotifyEndOfStream()
 {
   // If source video track is muted till the end of encoding, initialize the
   // encoder with default frame width, frame height, and track rate.
   if (!mCanceled && !mInitialized) {
     Init(DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT,
-         DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, DEFAULT_TRACK_RATE);
+         DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT);
   }
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   mEndOfStream = true;
   mReentrantMonitor.NotifyAll();
 }
 
 size_t
--- a/dom/media/encoder/TrackEncoder.h
+++ b/dom/media/encoder/TrackEncoder.h
@@ -243,26 +243,28 @@ protected:
   AudioSegment mRawSegment;
 
   uint32_t mAudioBitrate;
 };
 
 class VideoTrackEncoder : public TrackEncoder
 {
 public:
-  VideoTrackEncoder()
+  explicit VideoTrackEncoder(TrackRate aTrackRate)
     : TrackEncoder()
     , mFrameWidth(0)
     , mFrameHeight(0)
     , mDisplayWidth(0)
     , mDisplayHeight(0)
-    , mTrackRate(0)
+    , mTrackRate(aTrackRate)
     , mTotalFrameDuration(0)
     , mLastFrameDuration(0)
     , mVideoBitrate(0)
+    , mLastFrameTimeStamp(TimeStamp::Now())
+    , mFirstFrame(true)
   {}
 
   /**
    * Notified by the same callback of MediaEncoder when it has received a track
    * change from MediaStreamGraph. Called on the MediaStreamGraph thread.
    */
   void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                 StreamTime aTrackOffset,
@@ -272,26 +274,38 @@ public:
   * Measure size of mRawSegment
   */
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   void SetBitrate(const uint32_t aBitrate) override
   {
     mVideoBitrate = aBitrate;
   }
+
+  void Init(const VideoSegment& aSegment);
+
+  void SetCurrentFrames(const VideoSegment& aSegment);
+
+  StreamTime SecondsToMediaTime(double aS) const
+  {
+    NS_ASSERTION(0 <= aS && aS <= TRACK_TICKS_MAX/TRACK_RATE_MAX,
+                 "Bad seconds");
+    return mTrackRate * aS;
+  }
+
 protected:
   /**
    * Initialized the video encoder. In order to collect the value of width and
    * height of source frames, this initialization is delayed until we have
    * received the first valid video frame from MediaStreamGraph;
    * mReentrantMonitor will be notified after it has successfully initialized,
    * and this method is called on the MediaStramGraph thread.
    */
   virtual nsresult Init(int aWidth, int aHeight, int aDisplayWidth,
-                        int aDisplayHeight, TrackRate aTrackRate) = 0;
+                        int aDisplayHeight) = 0;
 
   /**
    * Appends source video frames to mRawSegment. We only append the source chunk
    * if it is unique to mLastChunk. Called on the MediaStreamGraph thread.
    */
   nsresult AppendVideoSegment(const VideoSegment& aSegment);
 
   /**
@@ -340,13 +354,17 @@ protected:
   StreamTime mLastFrameDuration;
 
   /**
    * A segment queue of audio track data, protected by mReentrantMonitor.
    */
   VideoSegment mRawSegment;
 
   uint32_t mVideoBitrate;
+
+private:
+  TimeStamp mLastFrameTimeStamp;
+  bool mFirstFrame;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/encoder/VP8TrackEncoder.cpp
+++ b/dom/media/encoder/VP8TrackEncoder.cpp
@@ -23,18 +23,18 @@ LazyLogModule gVP8TrackEncoderLog("VP8Tr
 // Debug logging macro with object pointer and class name.
 
 #define DEFAULT_BITRATE_BPS 2500000
 #define DEFAULT_ENCODE_FRAMERATE 30
 
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
-VP8TrackEncoder::VP8TrackEncoder()
-  : VideoTrackEncoder()
+VP8TrackEncoder::VP8TrackEncoder(TrackRate aTrackRate)
+  : VideoTrackEncoder(aTrackRate)
   , mEncodedFrameDuration(0)
   , mEncodedTimestamp(0)
   , mRemainingTicks(0)
   , mVPXContext(new vpx_codec_ctx_t())
   , mVPXImageWrapper(new vpx_image_t())
 {
   MOZ_COUNT_CTOR(VP8TrackEncoder);
 }
@@ -48,26 +48,24 @@ VP8TrackEncoder::~VP8TrackEncoder()
   if (mVPXImageWrapper) {
     vpx_img_free(mVPXImageWrapper);
   }
   MOZ_COUNT_DTOR(VP8TrackEncoder);
 }
 
 nsresult
 VP8TrackEncoder::Init(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
-                      int32_t aDisplayHeight,TrackRate aTrackRate)
+                      int32_t aDisplayHeight)
 {
-  if (aWidth < 1 || aHeight < 1 || aDisplayWidth < 1 || aDisplayHeight < 1
-      || aTrackRate <= 0) {
+  if (aWidth < 1 || aHeight < 1 || aDisplayWidth < 1 || aDisplayHeight < 1) {
     return NS_ERROR_FAILURE;
   }
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
-  mTrackRate = aTrackRate;
   mEncodedFrameRate = DEFAULT_ENCODE_FRAMERATE;
   mEncodedFrameDuration = mTrackRate / mEncodedFrameRate;
   mFrameWidth = aWidth;
   mFrameHeight = aHeight;
   mDisplayWidth = aDisplayWidth;
   mDisplayHeight = aDisplayHeight;
 
   // Encoder configuration structure.
--- a/dom/media/encoder/VP8TrackEncoder.h
+++ b/dom/media/encoder/VP8TrackEncoder.h
@@ -24,27 +24,26 @@ typedef struct vpx_image vpx_image_t;
 class VP8TrackEncoder : public VideoTrackEncoder
 {
   enum EncodeOperation {
     ENCODE_NORMAL_FRAME, // VP8 track encoder works normally.
     ENCODE_I_FRAME, // The next frame will be encoded as I-Frame.
     SKIP_FRAME, // Skip the next frame.
   };
 public:
-  VP8TrackEncoder();
+  explicit VP8TrackEncoder(TrackRate aTrackRate);
   virtual ~VP8TrackEncoder();
 
   already_AddRefed<TrackMetadataBase> GetMetadata() final override;
 
   nsresult GetEncodedTrack(EncodedFrameContainer& aData) final override;
 
 protected:
   nsresult Init(int32_t aWidth, int32_t aHeight,
-                int32_t aDisplayWidth, int32_t aDisplayHeight,
-                TrackRate aTrackRate) final override;
+                int32_t aDisplayWidth, int32_t aDisplayHeight) final override;
 
 private:
   // Calculate the target frame's encoded duration.
   StreamTime CalculateEncodedDuration(StreamTime aDurationCopied);
 
   // Calculate the mRemainingTicks for next target frame.
   StreamTime CalculateRemainingTicks(StreamTime aDurationCopied,
                                      StreamTime aEncodedDuration);
--- a/dom/media/gtest/TestMediaFormatReader.cpp
+++ b/dom/media/gtest/TestMediaFormatReader.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/TaskQueue.h"
 #include "ImageContainer.h"
 #include "Layers.h"
 #include "MediaData.h"
 #include "MediaFormatReader.h"
 #include "MP4Decoder.h"
 #include "MockMediaDecoderOwner.h"
 #include "MockMediaResource.h"
+#include "VideoFrameContainer.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 class MockMP4Decoder : public MP4Decoder
 {
 public:
   MockMP4Decoder()
--- a/dom/media/gtest/TestVideoTrackEncoder.cpp
+++ b/dom/media/gtest/TestVideoTrackEncoder.cpp
@@ -170,74 +170,73 @@ private:
   mozilla::gfx::IntSize mImageSize;
   nsTArray<uint8_t> mSourceBuffer;
 };
 
 struct InitParam {
   bool mShouldSucceed;  // This parameter should cause success or fail result
   int  mWidth;          // frame width
   int  mHeight;         // frame height
-  mozilla::TrackRate mTrackRate; // track rate. 90K is the most commond track rate.
 };
 
 class TestVP8TrackEncoder: public VP8TrackEncoder
 {
 public:
+  explicit TestVP8TrackEncoder(TrackRate aTrackRate = 90000)
+    : VP8TrackEncoder(aTrackRate) {}
+
   ::testing::AssertionResult TestInit(const InitParam &aParam)
   {
-    nsresult result = Init(aParam.mWidth, aParam.mHeight, aParam.mWidth, aParam.mHeight, aParam.mTrackRate);
+    nsresult result = Init(aParam.mWidth, aParam.mHeight, aParam.mWidth, aParam.mHeight);
 
     if (((NS_FAILED(result) && aParam.mShouldSucceed)) || (NS_SUCCEEDED(result) && !aParam.mShouldSucceed))
     {
       return ::testing::AssertionFailure()
                 << " width = " << aParam.mWidth
-                << " height = " << aParam.mHeight
-                << " TrackRate = " << aParam.mTrackRate << ".";
+                << " height = " << aParam.mHeight;
     }
     else
     {
       return ::testing::AssertionSuccess();
     }
   }
 };
 
 // Init test
 TEST(VP8VideoTrackEncoder, Initialization)
 {
   InitParam params[] = {
     // Failure cases.
-    { false, 640, 480, 0 },      // Trackrate should be larger than 1.
-    { false, 640, 480, -1 },     // Trackrate should be larger than 1.
-    { false, 0, 0, 90000 },      // Height/ width should be larger than 1.
-    { false, 0, 1, 90000 },      // Height/ width should be larger than 1.
-    { false, 1, 0, 90000},       // Height/ width should be larger than 1.
+    { false, 0, 0},      // Height/ width should be larger than 1.
+    { false, 0, 1},      // Height/ width should be larger than 1.
+    { false, 1, 0},       // Height/ width should be larger than 1.
 
     // Success cases
-    { true, 640, 480, 90000},    // Standard VGA
-    { true, 800, 480, 90000},    // Standard WVGA
-    { true, 960, 540, 90000},    // Standard qHD
-    { true, 1280, 720, 90000}    // Standard HD
+    { true, 640, 480},    // Standard VGA
+    { true, 800, 480},    // Standard WVGA
+    { true, 960, 540},    // Standard qHD
+    { true, 1280, 720}    // Standard HD
   };
 
   for (size_t i = 0; i < ArrayLength(params); i++)
   {
     TestVP8TrackEncoder encoder;
     EXPECT_TRUE(encoder.TestInit(params[i]));
   }
 }
 
 // Get MetaData test
 TEST(VP8VideoTrackEncoder, FetchMetaData)
 {
   InitParam params[] = {
     // Success cases
-    { true, 640, 480, 90000},    // Standard VGA
-    { true, 800, 480, 90000},    // Standard WVGA
-    { true, 960, 540, 90000},    // Standard qHD
-    { true, 1280, 720, 90000}    // Standard HD
+    { true, 640, 480},    // Standard VGA
+    { true, 800, 480},    // Standard WVGA
+    { true, 960, 540},    // Standard qHD
+    { true, 1280, 720}    // Standard HD
   };
 
   for (size_t i = 0; i < ArrayLength(params); i++)
   {
     TestVP8TrackEncoder encoder;
     EXPECT_TRUE(encoder.TestInit(params[i]));
 
     RefPtr<TrackMetadataBase> meta = encoder.GetMetadata();
@@ -249,17 +248,17 @@ TEST(VP8VideoTrackEncoder, FetchMetaData
   }
 }
 
 // Encode test
 TEST(VP8VideoTrackEncoder, FrameEncode)
 {
   // Initiate VP8 encoder
   TestVP8TrackEncoder encoder;
-  InitParam param = {true, 640, 480, 90000};
+  InitParam param = {true, 640, 480};
   encoder.TestInit(param);
 
   // Create YUV images as source.
   nsTArray<RefPtr<Image>> images;
   YUVBufferGenerator generator;
   generator.Init(mozilla::gfx::IntSize(640, 480));
   generator.Generate(images);
 
@@ -271,29 +270,29 @@ TEST(VP8VideoTrackEncoder, FrameEncode)
     RefPtr<Image> image = images[i];
     segment.AppendFrame(image.forget(),
                         mozilla::StreamTime(90000),
                         generator.GetSize(),
                         PRINCIPAL_HANDLE_NONE);
   }
 
   // track change notification.
-  encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, 0, segment);
+  encoder.SetCurrentFrames(segment);
 
   // Pull Encoded Data back from encoder.
   EncodedFrameContainer container;
   EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
 }
 
 // EOS test
 TEST(VP8VideoTrackEncoder, EncodeComplete)
 {
   // Initiate VP8 encoder
   TestVP8TrackEncoder encoder;
-  InitParam param = {true, 640, 480, 90000};
+  InitParam param = {true, 640, 480};
   encoder.TestInit(param);
 
   // track end notification.
   VideoSegment segment;
   encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, TrackEventCommand::TRACK_EVENT_ENDED, segment);
 
   // Pull Encoded Data back from encoder. Since we have sent
   // EOS to encoder, encoder.GetEncodedTrack should return
--- a/dom/media/gtest/TestWebMWriter.cpp
+++ b/dom/media/gtest/TestWebMWriter.cpp
@@ -23,21 +23,23 @@ public:
     }
     return false;
   }
 };
 
 class WebMVP8TrackEncoder: public VP8TrackEncoder
 {
 public:
+  explicit WebMVP8TrackEncoder(TrackRate aTrackRate = 90000)
+    : VP8TrackEncoder(aTrackRate) {}
+
   bool TestVP8Creation(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
-                       int32_t aDisplayHeight, TrackRate aTrackRate)
+                       int32_t aDisplayHeight)
   {
-    if (NS_SUCCEEDED(Init(aWidth, aHeight, aDisplayWidth, aDisplayHeight,
-                          aTrackRate))) {
+    if (NS_SUCCEEDED(Init(aWidth, aHeight, aDisplayWidth, aDisplayHeight))) {
       return true;
     }
     return false;
   }
 };
 
 const uint64_t FIXED_DURATION = 1000000;
 const uint32_t FIXED_FRAMESIZE = 500;
@@ -55,17 +57,17 @@ public:
     EXPECT_TRUE(opusEncoder.TestOpusCreation(aChannels, aSampleRate));
     RefPtr<TrackMetadataBase> opusMeta = opusEncoder.GetMetadata();
     SetMetadata(opusMeta);
   }
   void SetVP8Metadata(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
                       int32_t aDisplayHeight,TrackRate aTrackRate) {
     WebMVP8TrackEncoder vp8Encoder;
     EXPECT_TRUE(vp8Encoder.TestVP8Creation(aWidth, aHeight, aDisplayWidth,
-                                           aDisplayHeight, aTrackRate));
+                                           aDisplayHeight));
     RefPtr<TrackMetadataBase> vp8Meta = vp8Encoder.GetMetadata();
     SetMetadata(vp8Meta);
   }
 
   // When we append an I-Frame into WebM muxer, the muxer will treat previous
   // data as "a cluster".
   // In these test cases, we will call the function many times to enclose the
   // previous cluster so that we can retrieve data by |GetContainerData|.
--- a/dom/media/imagecapture/CaptureTask.cpp
+++ b/dom/media/imagecapture/CaptureTask.cpp
@@ -10,16 +10,42 @@
 #include "mozilla/dom/ImageEncoder.h"
 #include "mozilla/dom/MediaStreamTrack.h"
 #include "mozilla/dom/VideoStreamTrack.h"
 #include "gfxUtils.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 
+class CaptureTask::MediaStreamEventListener : public MediaStreamTrackListener
+{
+public:
+  explicit MediaStreamEventListener(CaptureTask* aCaptureTask)
+    : mCaptureTask(aCaptureTask) {};
+
+  // MediaStreamListener methods.
+  void NotifyEnded() override
+  {
+    if(!mCaptureTask->mImageGrabbedOrTrackEnd) {
+      mCaptureTask->PostTrackEndEvent();
+    }
+  }
+
+private:
+  CaptureTask* mCaptureTask;
+};
+
+CaptureTask::CaptureTask(dom::ImageCapture* aImageCapture)
+  : mImageCapture(aImageCapture)
+  , mEventListener(new MediaStreamEventListener(this))
+  , mImageGrabbedOrTrackEnd(false)
+  , mPrincipalChanged(false)
+{
+}
+
 nsresult
 CaptureTask::TaskComplete(already_AddRefed<dom::Blob> aBlob, nsresult aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   DetachTrack();
 
   nsresult rv;
@@ -50,47 +76,45 @@ CaptureTask::TaskComplete(already_AddRef
 
 void
 CaptureTask::AttachTrack()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   dom::VideoStreamTrack* track = mImageCapture->GetVideoStreamTrack();
   track->AddPrincipalChangeObserver(this);
-  track->AddListener(this);
+  track->AddListener(mEventListener.get());
+  track->AddDirectListener(this);
 }
 
 void
 CaptureTask::DetachTrack()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   dom::VideoStreamTrack* track = mImageCapture->GetVideoStreamTrack();
   track->RemovePrincipalChangeObserver(this);
-  track->RemoveListener(this);
+  track->RemoveListener(mEventListener.get());
+  track->RemoveDirectListener(this);
 }
 
 void
 CaptureTask::PrincipalChanged(dom::MediaStreamTrack* aMediaStreamTrack)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mPrincipalChanged = true;
 }
 
 void
-CaptureTask::NotifyQueuedChanges(MediaStreamGraph* aGraph,
-                                 StreamTime aTrackOffset,
-                                 const MediaSegment& aQueuedMedia)
+CaptureTask::SetCurrentFrames(const VideoSegment& aSegment)
 {
   if (mImageGrabbedOrTrackEnd) {
     return;
   }
 
-  MOZ_ASSERT(aQueuedMedia.GetType() == MediaSegment::VIDEO);
-
   // Callback for encoding complete, it calls on main thread.
   class EncodeComplete : public dom::EncodeCompleteCallback
   {
   public:
     explicit EncodeComplete(CaptureTask* aTask) : mTask(aTask) {}
 
     nsresult ReceiveBlob(already_AddRefed<dom::Blob> aBlob) override
     {
@@ -99,21 +123,23 @@ CaptureTask::NotifyQueuedChanges(MediaSt
       mTask = nullptr;
       return NS_OK;
     }
 
   protected:
     RefPtr<CaptureTask> mTask;
   };
 
-  VideoSegment* video =
-    const_cast<VideoSegment*> (static_cast<const VideoSegment*>(&aQueuedMedia));
-  VideoSegment::ChunkIterator iter(*video);
+  VideoSegment::ConstChunkIterator iter(aSegment);
+
+
+
   while (!iter.IsEnded()) {
     VideoChunk chunk = *iter;
+
     // Extract the first valid video frame.
     VideoFrame frame;
     if (!chunk.IsNull()) {
       RefPtr<layers::Image> image;
       if (chunk.mFrame.GetForceBlack()) {
         // Create a black image.
         image = VideoFrame::CreateBlackImage(chunk.mFrame.GetIntrinsicSize());
       } else {
@@ -137,24 +163,16 @@ CaptureTask::NotifyQueuedChanges(MediaSt
       }
       return;
     }
     iter.Next();
   }
 }
 
 void
-CaptureTask::NotifyEnded()
-{
-  if(!mImageGrabbedOrTrackEnd) {
-    PostTrackEndEvent();
-  }
-}
-
-void
 CaptureTask::PostTrackEndEvent()
 {
   mImageGrabbedOrTrackEnd = true;
 
   // Got track end or finish event, stop the task.
   class TrackEndRunnable : public Runnable
   {
   public:
--- a/dom/media/imagecapture/CaptureTask.h
+++ b/dom/media/imagecapture/CaptureTask.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CAPTURETASK_H
 #define CAPTURETASK_H
 
 #include "MediaStreamGraph.h"
 #include "MediaStreamListener.h"
 #include "PrincipalChangeObserver.h"
+#include "MediaStreamVideoSink.h"
 
 namespace mozilla {
 
 namespace dom {
 class Blob;
 class ImageCapture;
 class MediaStreamTrack;
 } // namespace dom
@@ -24,26 +25,25 @@ class MediaStreamTrack;
  * ImageEncoder. The whole procedures start at AttachTrack(), it will add this
  * class into MediaStream and retrieves an image in MediaStreamGraph thread.
  * Once the image is retrieved, it will be sent to ImageEncoder and the encoded
  * blob will be sent out via encoder callback in main thread.
  *
  * CaptureTask holds a reference of ImageCapture to ensure ImageCapture won't be
  * released during the period of the capturing process described above.
  */
-class CaptureTask : public MediaStreamTrackListener,
+class CaptureTask : public MediaStreamVideoSink,
                     public dom::PrincipalChangeObserver<dom::MediaStreamTrack>
 {
 public:
-  // MediaStreamTrackListener methods.
-  void NotifyQueuedChanges(MediaStreamGraph* aGraph,
-                           StreamTime aTrackOffset,
-                           const MediaSegment& aQueuedMedia) override;
+  class MediaStreamEventListener;
 
-  void NotifyEnded() override;
+  // MediaStreamVideoSink methods.
+  void SetCurrentFrames(const VideoSegment& aSegment) override;
+  void ClearFrames() override {}
 
   // PrincipalChangeObserver<MediaStreamTrack> method.
   void PrincipalChanged(dom::MediaStreamTrack* aMediaStreamTrack) override;
 
   // CaptureTask methods.
 
   // It is called when aBlob is ready to post back to script in company with
   // aRv == NS_OK. If aRv is not NS_OK, it will post an error event to script.
@@ -56,33 +56,32 @@ public:
   // It should be on main thread only.
   void AttachTrack();
 
   // Remove listeners from MediaStreamTrack and PrincipalChangeObserver.
   // It should be on main thread only.
   void DetachTrack();
 
   // CaptureTask should be created on main thread.
-  explicit CaptureTask(dom::ImageCapture* aImageCapture)
-    : mImageCapture(aImageCapture)
-    , mImageGrabbedOrTrackEnd(false)
-    , mPrincipalChanged(false) {}
+  explicit CaptureTask(dom::ImageCapture* aImageCapture);
 
 protected:
   virtual ~CaptureTask() {}
 
   // Post a runnable on main thread to end this task and call TaskComplete to post
   // error event to script. It is called off-main-thread.
   void PostTrackEndEvent();
 
   // The ImageCapture associates with this task. This reference count should not
   // change in this class unless it clears this reference after a blob or error
   // event to script.
   RefPtr<dom::ImageCapture> mImageCapture;
 
+  RefPtr<MediaStreamEventListener> mEventListener;
+
   // True when an image is retrieved from MediaStreamGraph or MediaStreamGraph
   // sends a track finish, end, or removed event.
   bool mImageGrabbedOrTrackEnd;
 
   // True after MediaStreamTrack principal changes while waiting for a photo
   // to finish and we should raise a security error.
   bool mPrincipalChanged;
 };
--- a/dom/media/imagecapture/ImageCapture.cpp
+++ b/dom/media/imagecapture/ImageCapture.cpp
@@ -142,18 +142,17 @@ ImageCapture::TakePhoto(ErrorResult& aRe
 
   // Try if MediaEngine supports taking photo.
   nsresult rv = TakePhotoByMediaEngine();
 
   // It falls back to MediaStreamGraph image capture if MediaEngine doesn't
   // support TakePhoto().
   if (rv == NS_ERROR_NOT_IMPLEMENTED) {
     IC_LOG("MediaEngine doesn't support TakePhoto(), it falls back to MediaStreamGraph.");
-    RefPtr<CaptureTask> task =
-      new CaptureTask(this);
+    RefPtr<CaptureTask> task = new CaptureTask(this);
 
     // It adds itself into MediaStreamGraph, so ImageCapture doesn't need to hold
     // the reference.
     task->AttachTrack();
   }
 }
 
 nsresult
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -493,24 +493,26 @@ DecodedStream::SendAudio(double aVolume,
 }
 
 static void
 WriteVideoToMediaStream(MediaStream* aStream,
                         layers::Image* aImage,
                         int64_t aEndMicroseconds,
                         int64_t aStartMicroseconds,
                         const mozilla::gfx::IntSize& aIntrinsicSize,
+                        const TimeStamp& aTimeStamp,
                         VideoSegment* aOutput,
                         const PrincipalHandle& aPrincipalHandle)
 {
   RefPtr<layers::Image> image = aImage;
   StreamTime duration =
       aStream->MicrosecondsToStreamTimeRoundDown(aEndMicroseconds) -
       aStream->MicrosecondsToStreamTimeRoundDown(aStartMicroseconds);
-  aOutput->AppendFrame(image.forget(), duration, aIntrinsicSize, aPrincipalHandle);
+  aOutput->AppendFrame(image.forget(), duration, aIntrinsicSize,
+                       aPrincipalHandle, false, aTimeStamp);
 }
 
 static bool
 ZeroDurationAtLastChunk(VideoSegment& aInput)
 {
   // Get the last video frame's start time in VideoSegment aInput.
   // If the start time is equal to the duration of aInput, means the last video
   // frame's duration is zero.
@@ -532,38 +534,48 @@ DecodedStream::SendVideo(bool aIsSameOri
   TrackID videoTrackId = mInfo.mVideo.mTrackId;
   AutoTArray<RefPtr<MediaData>, 10> video;
   SourceMediaStream* sourceStream = mData->mStream;
 
   // It's OK to hold references to the VideoData because VideoData
   // is ref-counted.
   mVideoQueue.GetElementsAfter(mData->mNextVideoTime, &video);
 
+  // tracksStartTimeStamp might be null when the SourceMediaStream not yet
+  // be added to MediaStreamGraph.
+  TimeStamp tracksStartTimeStamp = sourceStream->GetStreamTracksStrartTimeStamp();
+  if (tracksStartTimeStamp.IsNull()) {
+    tracksStartTimeStamp = TimeStamp::Now();
+  }
+
   for (uint32_t i = 0; i < video.Length(); ++i) {
     VideoData* v = video[i]->As<VideoData>();
 
     if (mData->mNextVideoTime < v->mTime) {
       // Write last video frame to catch up. mLastVideoImage can be null here
       // which is fine, it just means there's no video.
 
       // TODO: |mLastVideoImage| should come from the last image rendered
       // by the state machine. This will avoid the black frame when capture
       // happens in the middle of playback (especially in th middle of a
       // video frame). E.g. if we have a video frame that is 30 sec long
       // and capture happens at 15 sec, we'll have to append a black frame
       // that is 15 sec long.
       WriteVideoToMediaStream(sourceStream, mData->mLastVideoImage, v->mTime,
-          mData->mNextVideoTime, mData->mLastVideoImageDisplaySize, &output,
-          aPrincipalHandle);
+          mData->mNextVideoTime, mData->mLastVideoImageDisplaySize,
+          tracksStartTimeStamp + TimeDuration::FromMicroseconds(v->mTime),
+          &output, aPrincipalHandle);
       mData->mNextVideoTime = v->mTime;
     }
 
     if (mData->mNextVideoTime < v->GetEndTime()) {
       WriteVideoToMediaStream(sourceStream, v->mImage, v->GetEndTime(),
-          mData->mNextVideoTime, v->mDisplay, &output, aPrincipalHandle);
+          mData->mNextVideoTime, v->mDisplay,
+          tracksStartTimeStamp + TimeDuration::FromMicroseconds(v->GetEndTime()),
+          &output, aPrincipalHandle);
       mData->mNextVideoTime = v->GetEndTime();
       mData->mLastVideoImage = v->mImage;
       mData->mLastVideoImageDisplaySize = v->mDisplay;
     }
   }
 
   // Check the output is not empty.
   if (output.GetLastFrame()) {
@@ -580,17 +592,19 @@ DecodedStream::SendVideo(bool aIsSameOri
 
   if (mVideoQueue.IsFinished() && !mData->mHaveSentFinishVideo) {
     if (mData->mEOSVideoCompensation) {
       VideoSegment endSegment;
       // Calculate the deviation clock time from DecodedStream.
       int64_t deviation_usec = sourceStream->StreamTimeToMicroseconds(1);
       WriteVideoToMediaStream(sourceStream, mData->mLastVideoImage,
           mData->mNextVideoTime + deviation_usec, mData->mNextVideoTime,
-          mData->mLastVideoImageDisplaySize, &endSegment, aPrincipalHandle);
+          mData->mLastVideoImageDisplaySize,
+          tracksStartTimeStamp + TimeDuration::FromMicroseconds(mData->mNextVideoTime + deviation_usec),
+          &endSegment, aPrincipalHandle);
       mData->mNextVideoTime += deviation_usec;
       MOZ_ASSERT(endSegment.GetDuration() > 0);
       if (!aIsSameOrigin) {
         endSegment.ReplaceWithDisabled();
       }
       sourceStream->AppendToTrack(videoTrackId, &endSegment);
     }
     sourceStream->EndTrack(videoTrackId);
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -122,16 +122,17 @@ EXPORTS += [
     'MediaQueue.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaResourceCallback.h',
     'MediaSegment.h',
     'MediaStatistics.h',
     'MediaStreamGraph.h',
     'MediaStreamListener.h',
+    'MediaStreamVideoSink.h',
     'MediaTimer.h',
     'MediaTrack.h',
     'MediaTrackList.h',
     'MP3Decoder.h',
     'MP3Demuxer.h',
     'MP3FrameParser.h',
     'NextFrameSeekTask.h',
     'nsIDocumentActivity.h',
@@ -234,16 +235,17 @@ UNIFIED_SOURCES += [
     'MediaPrefs.cpp',
     'MediaRecorder.cpp',
     'MediaResource.cpp',
     'MediaShutdownManager.cpp',
     'MediaStreamError.cpp',
     'MediaStreamGraph.cpp',
     'MediaStreamListener.cpp',
     'MediaStreamTrack.cpp',
+    'MediaStreamVideoSink.cpp',
     'MediaTimer.cpp',
     'MediaTrack.cpp',
     'MediaTrackList.cpp',
     'MP3Decoder.cpp',
     'MP3Demuxer.cpp',
     'MP3FrameParser.cpp',
     'NextFrameSeekTask.cpp',
     'QueueObject.cpp',
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -20,16 +20,17 @@ extern "C" {
 #include "mozilla/TimeStamp.h"
 #include "VorbisUtils.h"
 #include "MediaMetadataManager.h"
 #include "nsAutoPtr.h"
 #include "nsISeekableStream.h"
 #include "gfx2DGlue.h"
 #include "mozilla/Telemetry.h"
 #include "nsPrintfCString.h"
+#include "VideoFrameContainer.h"
 
 using namespace mozilla::gfx;
 using namespace mozilla::media;
 
 namespace mozilla {
 
 // On B2G estimate the buffered ranges rather than calculating them explicitly.
 // This prevents us doing I/O on the main thread, which is prohibited in B2G.
@@ -875,25 +876,26 @@ nsresult OggReader::DecodeTheora(ogg_pac
   for (uint32_t i=0; i < 3; ++i) {
     b.mPlanes[i].mData = buffer[i].data;
     b.mPlanes[i].mHeight = buffer[i].height;
     b.mPlanes[i].mWidth = buffer[i].width;
     b.mPlanes[i].mStride = buffer[i].stride;
     b.mPlanes[i].mOffset = b.mPlanes[i].mSkip = 0;
   }
 
-  RefPtr<VideoData> v = VideoData::Create(mInfo.mVideo,
-                                            mDecoder->GetImageContainer(),
-                                            mResource.Tell(),
-                                            time,
-                                            endTime - time,
-                                            b,
-                                            isKeyframe,
-                                            aPacket->granulepos,
-                                            mPicture);
+  RefPtr<VideoData> v =
+    VideoData::CreateAndCopyData(mInfo.mVideo,
+                                 mDecoder->GetImageContainer(),
+                                 mResource.Tell(),
+                                 time,
+                                 endTime - time,
+                                 b,
+                                 isKeyframe,
+                                 aPacket->granulepos,
+                                 mPicture);
   if (!v) {
     // There may be other reasons for this error, but for
     // simplicity just assume the worst case: out of memory.
     NS_WARNING("Failed to allocate memory for video frame");
     return NS_ERROR_OUT_OF_MEMORY;
   }
   mVideoQueue.Push(v);
   return NS_OK;
--- a/dom/media/omx/MediaOmxReader.cpp
+++ b/dom/media/omx/MediaOmxReader.cpp
@@ -12,16 +12,17 @@
 #include "VideoUtils.h"
 #include "MediaOmxDecoder.h"
 #include "AbstractMediaDecoder.h"
 #include "AudioChannelService.h"
 #include "OmxDecoder.h"
 #include "MPAPI.h"
 #include "gfx2DGlue.h"
 #include "MediaStreamSource.h"
+#include "VideoFrameContainer.h"
 
 #define MAX_DROPPED_FRAMES 25
 // Try not to spend more than this much time in a single call to DecodeVideoFrame.
 #define MAX_VIDEO_DECODE_SECONDS 0.1
 
 using namespace mozilla::gfx;
 using namespace mozilla::media;
 using namespace android;
@@ -403,35 +404,35 @@ bool MediaOmxReader::DecodeVideoFrame(bo
 
       b.mPlanes[2].mData = static_cast<uint8_t *>(frame.Cr.mData);
       b.mPlanes[2].mStride = frame.Cr.mStride;
       b.mPlanes[2].mHeight = frame.Cr.mHeight;
       b.mPlanes[2].mWidth = frame.Cr.mWidth;
       b.mPlanes[2].mOffset = frame.Cr.mOffset;
       b.mPlanes[2].mSkip = frame.Cr.mSkip;
 
-      v = VideoData::Create(mInfo.mVideo,
-                            mDecoder->GetImageContainer(),
-                            pos,
-                            frame.mTimeUs,
-                            1, // We don't know the duration.
-                            b,
-                            frame.mKeyFrame,
-                            -1,
-                            picture);
+      v = VideoData::CreateAndCopyData(mInfo.mVideo,
+                                       mDecoder->GetImageContainer(),
+                                       pos,
+                                       frame.mTimeUs,
+                                       1, // We don't know the duration.
+                                       b,
+                                       frame.mKeyFrame,
+                                       -1,
+                                       picture);
     } else {
-      v = VideoData::Create(mInfo.mVideo,
-                            mDecoder->GetImageContainer(),
-                            pos,
-                            frame.mTimeUs,
-                            1, // We don't know the duration.
-                            frame.mGraphicBuffer,
-                            frame.mKeyFrame,
-                            -1,
-                            picture);
+      v = VideoData::CreateAndCopyData(mInfo.mVideo,
+                                       mDecoder->GetImageContainer(),
+                                       pos,
+                                       frame.mTimeUs,
+                                       1, // We don't know the duration.
+                                       frame.mGraphicBuffer,
+                                       frame.mKeyFrame,
+                                       -1,
+                                       picture);
     }
 
     if (!v) {
       NS_WARNING("Unable to create VideoData");
       return false;
     }
 
     a.mStats.mDecodedFrames++;
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -158,26 +158,25 @@ public:
     buffer.mPlanes[2].mWidth = mFrameWidth / 2;
     buffer.mPlanes[2].mOffset = 0;
     buffer.mPlanes[2].mSkip = 0;
 
     // Set to color white.
     memset(buffer.mPlanes[0].mData, 255, sizeY);
     memset(buffer.mPlanes[1].mData, 128, sizeCbCr);
 
-    return VideoData::Create(mInfo,
-                             mImageContainer,
-                             nullptr,
-                             aOffsetInStream,
-                             aDTS.ToMicroseconds(),
-                             aDuration.ToMicroseconds(),
-                             buffer,
-                             true,
-                             aDTS.ToMicroseconds(),
-                             mPicture);
+    return VideoData::CreateAndCopyData(mInfo,
+                                        mImageContainer,
+                                        aOffsetInStream,
+                                        aDTS.ToMicroseconds(),
+                                        aDuration.ToMicroseconds(),
+                                        buffer,
+                                        true,
+                                        aDTS.ToMicroseconds(),
+                                        mPicture);
   }
 
 private:
   VideoInfo mInfo;
   gfx::IntRect mPicture;
   uint32_t mFrameWidth;
   uint32_t mFrameHeight;
   RefPtr<layers::ImageContainer> mImageContainer;
--- a/dom/media/platforms/agnostic/TheoraDecoder.cpp
+++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp
@@ -163,26 +163,27 @@ TheoraDecoder::DoDecode(MediaRawData* aS
     b.mPlanes[2].mWidth = mTheoraInfo.frame_width >> hdec;
     b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
 
     IntRect pictureArea(mTheoraInfo.pic_x, mTheoraInfo.pic_y,
                         mTheoraInfo.pic_width, mTheoraInfo.pic_height);
 
     VideoInfo info;
     info.mDisplay = mInfo.mDisplay;
-    RefPtr<VideoData> v = VideoData::Create(info,
-                                            mImageContainer,
-                                            aSample->mOffset,
-                                            aSample->mTime,
-                                            aSample->mDuration,
-                                            b,
-                                            aSample->mKeyframe,
-                                            aSample->mTimecode,
-                                            mInfo.ScaledImageRect(mTheoraInfo.frame_width,
-                                                                  mTheoraInfo.frame_height));
+    RefPtr<VideoData> v =
+      VideoData::CreateAndCopyData(info,
+                                   mImageContainer,
+                                   aSample->mOffset,
+                                   aSample->mTime,
+                                   aSample->mDuration,
+                                   b,
+                                   aSample->mKeyframe,
+                                   aSample->mTimecode,
+                                   mInfo.ScaledImageRect(mTheoraInfo.frame_width,
+                                                         mTheoraInfo.frame_height));
     if (!v) {
       LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld",
           mTheoraInfo.frame_width, mTheoraInfo.frame_height, mInfo.mDisplay.width, mInfo.mDisplay.height,
           mInfo.mImage.width, mInfo.mImage.height);
       return -1;
     }
     mCallback->Output(v);
     return 0;
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -155,26 +155,27 @@ VPXDecoder::DoDecode(MediaRawData* aSamp
 
       b.mPlanes[2].mHeight = img->d_h;
       b.mPlanes[2].mWidth = img->d_w;
     } else {
       LOG("VPX Unknown image format");
       return -1;
     }
 
-    RefPtr<VideoData> v = VideoData::Create(mInfo,
-                                            mImageContainer,
-                                            aSample->mOffset,
-                                            aSample->mTime,
-                                            aSample->mDuration,
-                                            b,
-                                            aSample->mKeyframe,
-                                            aSample->mTimecode,
-                                            mInfo.ScaledImageRect(img->d_w,
-                                                                  img->d_h));
+    RefPtr<VideoData> v =
+      VideoData::CreateAndCopyData(mInfo,
+                                   mImageContainer,
+                                   aSample->mOffset,
+                                   aSample->mTime,
+                                   aSample->mDuration,
+                                   b,
+                                   aSample->mKeyframe,
+                                   aSample->mTimecode,
+                                   mInfo.ScaledImageRect(img->d_w,
+                                                         img->d_h));
 
     if (!v) {
       LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld",
           img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
           mInfo.mImage.width, mInfo.mImage.height);
       return -1;
     }
     mCallback->Output(v);
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -36,25 +36,26 @@ VideoCallbackAdapter::Decoded(GMPVideoi4
       b.mPlanes[i].mWidth = (decodedFrame->Width() + 1) / 2;
       b.mPlanes[i].mHeight = (decodedFrame->Height() + 1) / 2;
     }
     b.mPlanes[i].mOffset = 0;
     b.mPlanes[i].mSkip = 0;
   }
 
   gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(), decodedFrame->Height());
-  RefPtr<VideoData> v = VideoData::Create(mVideoInfo,
-                                            mImageContainer,
-                                            mLastStreamOffset,
-                                            decodedFrame->Timestamp(),
-                                            decodedFrame->Duration(),
-                                            b,
-                                            false,
-                                            -1,
-                                            pictureRegion);
+  RefPtr<VideoData> v =
+    VideoData::CreateAndCopyData(mVideoInfo,
+                                 mImageContainer,
+                                 mLastStreamOffset,
+                                 decodedFrame->Timestamp(),
+                                 decodedFrame->Duration(),
+                                 b,
+                                 false,
+                                 -1,
+                                 pictureRegion);
   if (v) {
     mCallback->Output(v);
   } else {
     mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
   }
 }
 
 void
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -139,17 +139,16 @@ public:
     int32_t offset;
     NS_ENSURE_SUCCESS(rv = aInfo->Offset(&offset), rv);
 
     int64_t presentationTimeUs;
     NS_ENSURE_SUCCESS(rv = aInfo->PresentationTimeUs(&presentationTimeUs), rv);
 
     RefPtr<VideoData> v =
       VideoData::CreateFromImage(mConfig,
-                                 mImageContainer,
                                  offset,
                                  presentationTimeUs,
                                  aDuration.ToMicroseconds(),
                                  img,
                                  isSync,
                                  presentationTimeUs,
                                  gfx::IntRect(0, 0,
                                               mConfig.mDisplay.width,
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -361,40 +361,38 @@ AppleVTDecoder::OutputFrame(CVPixelBuffe
     buffer.mPlanes[2].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 1);
     buffer.mPlanes[2].mWidth = (width+1) / 2;
     buffer.mPlanes[2].mHeight = (height+1) / 2;
     buffer.mPlanes[2].mOffset = 1;
     buffer.mPlanes[2].mSkip = 1;
 
     // Copy the image data into our own format.
     data =
-      VideoData::Create(info,
-                        mImageContainer,
-                        nullptr,
-                        aFrameRef.byte_offset,
-                        aFrameRef.composition_timestamp.ToMicroseconds(),
-                        aFrameRef.duration.ToMicroseconds(),
-                        buffer,
-                        aFrameRef.is_sync_point,
-                        aFrameRef.decode_timestamp.ToMicroseconds(),
-                        visible);
+      VideoData::CreateAndCopyData(info,
+                                   mImageContainer,
+                                   aFrameRef.byte_offset,
+                                   aFrameRef.composition_timestamp.ToMicroseconds(),
+                                   aFrameRef.duration.ToMicroseconds(),
+                                   buffer,
+                                   aFrameRef.is_sync_point,
+                                   aFrameRef.decode_timestamp.ToMicroseconds(),
+                                   visible);
     // Unlock the returned image data.
     CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
   } else {
 #ifndef MOZ_WIDGET_UIKIT
     IOSurfacePtr surface = MacIOSurfaceLib::CVPixelBufferGetIOSurface(aImage);
     MOZ_ASSERT(surface, "Decoder didn't return an IOSurface backed buffer");
 
     RefPtr<MacIOSurface> macSurface = new MacIOSurface(surface);
 
     RefPtr<layers::Image> image = new MacIOSurfaceImage(macSurface);
 
     data =
       VideoData::CreateFromImage(info,
-                                 mImageContainer,
                                  aFrameRef.byte_offset,
                                  aFrameRef.composition_timestamp.ToMicroseconds(),
                                  aFrameRef.duration.ToMicroseconds(),
                                  image.forget(),
                                  aFrameRef.is_sync_point,
                                  aFrameRef.decode_timestamp.ToMicroseconds(),
                                  visible);
 #else
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
@@ -284,26 +284,27 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecode(
     if (mCodecContext->pix_fmt == AV_PIX_FMT_YUV444P) {
       b.mPlanes[1].mWidth = b.mPlanes[2].mWidth = mFrame->width;
       b.mPlanes[1].mHeight = b.mPlanes[2].mHeight = mFrame->height;
     } else {
       b.mPlanes[1].mWidth = b.mPlanes[2].mWidth = (mFrame->width + 1) >> 1;
       b.mPlanes[1].mHeight = b.mPlanes[2].mHeight = (mFrame->height + 1) >> 1;
     }
 
-    RefPtr<VideoData> v = VideoData::Create(mInfo,
-                                            mImageContainer,
-                                            aSample->mOffset,
-                                            pts,
-                                            duration,
-                                            b,
-                                            !!mFrame->key_frame,
-                                            -1,
-                                            mInfo.ScaledImageRect(mFrame->width,
-                                                                  mFrame->height));
+    RefPtr<VideoData> v =
+      VideoData::CreateAndCopyData(mInfo,
+                                   mImageContainer,
+                                   aSample->mOffset,
+                                   pts,
+                                   duration,
+                                   b,
+                                   !!mFrame->key_frame,
+                                   -1,
+                                   mInfo.ScaledImageRect(mFrame->width,
+                                                         mFrame->height));
 
     if (!v) {
       NS_WARNING("image allocation error.");
       return DecodeResult::FATAL_ERROR;
     }
     mCallback->Output(v);
     return DecodeResult::DECODE_FRAME;
   }
--- a/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp
+++ b/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp
@@ -420,26 +420,26 @@ GonkVideoDecoderManager::CreateVideoData
 
     CopyGraphicBuffer(srcBuffer, destBuffer);
   } else {
     textureClient = mNativeWindow->getTextureClientFromBuffer(srcBuffer.get());
     textureClient->SetRecycleCallback(GonkVideoDecoderManager::RecycleCallback, this);
     static_cast<GrallocTextureData*>(textureClient->GetInternalData())->SetMediaBuffer(aSource);
   }
 
-  RefPtr<VideoData> data = VideoData::Create(mConfig,
-                                             mImageContainer,
-                                             0, // Filled later by caller.
-                                             0, // Filled later by caller.
-                                             1, // No way to pass sample duration from muxer to
-                                                // OMX codec, so we hardcode the duration here.
-                                             textureClient,
-                                             false, // Filled later by caller.
-                                             -1,
-                                             aPicture);
+  RefPtr<VideoData> data =
+    VideoData::CreateAndCopyIntoTextureClient(mConfig,
+                                              0, // Filled later by caller.
+                                              0, // Filled later by caller.
+                                              1, // No way to pass sample duration from muxer to
+                                                 // OMX codec, so we hardcode the duration here.
+                                              textureClient,
+                                              false, // Filled later by caller.
+                                              -1,
+                                              aPicture);
   return data.forget();
 }
 
 already_AddRefed<VideoData>
 GonkVideoDecoderManager::CreateVideoDataFromDataBuffer(MediaBuffer* aSource, gfx::IntRect& aPicture)
 {
   if (!aSource->data()) {
     GVDM_LOG("No data in Video Buffer!");
@@ -489,25 +489,26 @@ GonkVideoDecoderManager::CreateVideoData
 
   b.mPlanes[2].mData = yuv420p_v;
   b.mPlanes[2].mWidth =(mFrameInfo.mWidth + 1) / 2;
   b.mPlanes[2].mHeight = (mFrameInfo.mHeight + 1) / 2;
   b.mPlanes[2].mStride = (stride + 1) / 2;
   b.mPlanes[2].mOffset = 0;
   b.mPlanes[2].mSkip = 0;
 
-  RefPtr<VideoData> data = VideoData::Create(mConfig,
-                                             mImageContainer,
-                                             0, // Filled later by caller.
-                                             0, // Filled later by caller.
-                                             1, // We don't know the duration.
-                                             b,
-                                             0, // Filled later by caller.
-                                             -1,
-                                             aPicture);
+  RefPtr<VideoData> data =
+    VideoData::CreateAndCopyData(mConfig,
+                                 mImageContainer,
+                                 0, // Filled later by caller.
+                                 0, // Filled later by caller.
+                                 1, // We don't know the duration.
+                                 b,
+                                 0, // Filled later by caller.
+                                 -1,
+                                 aPicture);
 
   return data.forget();
 }
 
 bool
 GonkVideoDecoderManager::SetVideoFormat()
 {
   // read video metadata from MediaCodec
--- a/dom/media/platforms/omx/GonkOmxPlatformLayer.cpp
+++ b/dom/media/platforms/omx/GonkOmxPlatformLayer.cpp
@@ -304,25 +304,25 @@ GonkBufferData::GetPlatformMediaData()
   }
 
   if (!mTextureClientRecycleHandler) {
     // There is no GraphicBuffer, it should fallback to normal YUV420 VideoData.
     return nullptr;
   }
 
   VideoInfo info(*mGonkPlatformLayer->GetTrackInfo()->GetAsVideoInfo());
-  RefPtr<VideoData> data = VideoData::Create(info,
-                                             mGonkPlatformLayer->GetImageContainer(),
-                                             0,
-                                             mBuffer->nTimeStamp,
-                                             1,
-                                             mTextureClientRecycleHandler->GetTextureClient(),
-                                             false,
-                                             0,
-                                             info.ImageRect());
+  RefPtr<VideoData> data =
+    VideoData::CreateAndCopyIntoTextureClient(info,
+                                              0,
+                                              mBuffer->nTimeStamp,
+                                              1,
+                                              mTextureClientRecycleHandler->GetTextureClient(),
+                                              false,
+                                              0,
+                                              info.ImageRect());
   LOG("%p, disp width %d, height %d, pic width %d, height %d, time %ld",
       this, info.mDisplay.width, info.mDisplay.height,
       info.mImage.width, info.mImage.height, mBuffer->nTimeStamp);
 
   // Get TextureClient Promise here to wait for resolved.
   RefPtr<GonkBufferData> self(this);
   mTextureClientRecycleHandler->WaitforRecycle()
     ->Then(mGonkPlatformLayer->GetTaskQueue(), __func__,
--- a/dom/media/platforms/omx/OmxDataDecoder.cpp
+++ b/dom/media/platforms/omx/OmxDataDecoder.cpp
@@ -1019,25 +1019,26 @@ MediaDataHelper::CreateYUV420VideoData(B
   b.mPlanes[2].mData = yuv420p_v;
   b.mPlanes[2].mWidth =(width + 1) / 2;
   b.mPlanes[2].mHeight = (height + 1) / 2;
   b.mPlanes[2].mStride = (stride + 1) / 2;
   b.mPlanes[2].mOffset = 0;
   b.mPlanes[2].mSkip = 0;
 
   VideoInfo info(*mTrackInfo->GetAsVideoInfo());
-  RefPtr<VideoData> data = VideoData::Create(info,
-                                             mImageContainer,
-                                             0, // Filled later by caller.
-                                             0, // Filled later by caller.
-                                             1, // We don't know the duration.
-                                             b,
-                                             0, // Filled later by caller.
-                                             -1,
-                                             info.ImageRect());
+  RefPtr<VideoData> data =
+    VideoData::CreateAndCopyData(info,
+                                 mImageContainer,
+                                 0, // Filled later by caller.
+                                 0, // Filled later by caller.
+                                 1, // We don't know the duration.
+                                 b,
+                                 0, // Filled later by caller.
+                                 -1,
+                                 info.ImageRect());
 
   LOG("YUV420 VideoData: disp width %d, height %d, pic width %d, height %d, time %ld",
       info.mDisplay.width, info.mDisplay.height, info.mImage.width,
       info.mImage.height, aBufferData->mBuffer->nTimeStamp);
 
   return data.forget();
 }
 
--- a/dom/media/platforms/wmf/DXVA2Manager.cpp
+++ b/dom/media/platforms/wmf/DXVA2Manager.cpp
@@ -93,17 +93,16 @@ public:
   HRESULT Init(nsACString& aFailureReason);
 
   IUnknown* GetDXVADeviceManager() override;
 
   // Copies a region (aRegion) of the video frame stored in aVideoSample
   // into an image which is returned by aOutImage.
   HRESULT CopyToImage(IMFSample* aVideoSample,
                       const nsIntRect& aRegion,
-                      ImageContainer* aContainer,
                       Image** aOutImage) override;
 
   bool SupportsConfig(IMFMediaType* aType, float aFramerate) override;
 
 private:
   RefPtr<IDirect3D9Ex> mD3D9;
   RefPtr<IDirect3DDevice9Ex> mDevice;
   RefPtr<IDirect3DDeviceManager9> mDeviceManager;
@@ -434,17 +433,16 @@ D3D9DXVA2Manager::Init(nsACString& aFail
   reporter.SetSuccessful();
 
   return S_OK;
 }
 
 HRESULT
 D3D9DXVA2Manager::CopyToImage(IMFSample* aSample,
                               const nsIntRect& aRegion,
-                              ImageContainer* aImageContainer,
                               Image** aOutImage)
 {
   RefPtr<IMFMediaBuffer> buffer;
   HRESULT hr = aSample->GetBufferByIndex(0, getter_AddRefs(buffer));
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   RefPtr<IDirect3DSurface9> surface;
   hr = wmf::MFGetService(buffer,
@@ -514,17 +512,16 @@ public:
   HRESULT Init(nsACString& aFailureReason);
 
   IUnknown* GetDXVADeviceManager() override;
 
   // Copies a region (aRegion) of the video frame stored in aVideoSample
   // into an image which is returned by aOutImage.
   HRESULT CopyToImage(IMFSample* aVideoSample,
                       const nsIntRect& aRegion,
-                      ImageContainer* aContainer,
                       Image** aOutImage) override;
 
   HRESULT ConfigureForSize(uint32_t aWidth, uint32_t aHeight) override;
 
   bool IsD3D11() override { return true; }
 
   bool SupportsConfig(IMFMediaType* aType, float aFramerate) override;
 
@@ -777,21 +774,19 @@ D3D11DXVA2Manager::CreateOutputSample(Re
 
   aSample = sample;
   return S_OK;
 }
 
 HRESULT
 D3D11DXVA2Manager::CopyToImage(IMFSample* aVideoSample,
                                const nsIntRect& aRegion,
-                               ImageContainer* aContainer,
                                Image** aOutImage)
 {
   NS_ENSURE_TRUE(aVideoSample, E_POINTER);
-  NS_ENSURE_TRUE(aContainer, E_POINTER);
   NS_ENSURE_TRUE(aOutImage, E_POINTER);
 
   // Our video frame is stored in a non-sharable ID3D11Texture2D. We need
   // to create a copy of that frame as a sharable resource, save its share
   // handle, and put that handle into the rendering pipeline.
 
   RefPtr<D3D11ShareHandleImage> image =
     new D3D11ShareHandleImage(gfx::IntSize(mWidth, mHeight), aRegion);
--- a/dom/media/platforms/wmf/DXVA2Manager.h
+++ b/dom/media/platforms/wmf/DXVA2Manager.h
@@ -30,17 +30,16 @@ public:
   // device we're using for hardware accelerated video decoding. If we're using
   // D3D9Ex, this is an IDirect3DDeviceManager9. For D3D11 this is an
   // IMFDXGIDeviceManager. It is safe to call this on any thread.
   virtual IUnknown* GetDXVADeviceManager() = 0;
 
   // Creates an Image for the video frame stored in aVideoSample.
   virtual HRESULT CopyToImage(IMFSample* aVideoSample,
                               const nsIntRect& aRegion,
-                              layers::ImageContainer* aContainer,
                               layers::Image** aOutImage) = 0;
 
   virtual HRESULT ConfigureForSize(uint32_t aWidth, uint32_t aHeight) { return S_OK; }
 
   virtual bool IsD3D11() { return false; }
 
   virtual ~DXVA2Manager();
 
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -706,25 +706,26 @@ WMFVideoMFTManager::CreateBasicVideoFram
   media::TimeUnit pts = GetSampleTime(aSample);
   NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
   media::TimeUnit duration = GetSampleDuration(aSample);
   NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
   nsIntRect pictureRegion = mVideoInfo.ScaledImageRect(videoWidth, videoHeight);
 
   if (mLayersBackend != LayersBackend::LAYERS_D3D9 &&
       mLayersBackend != LayersBackend::LAYERS_D3D11) {
-    RefPtr<VideoData> v = VideoData::Create(mVideoInfo,
-                                            mImageContainer,
-                                            aStreamOffset,
-                                            pts.ToMicroseconds(),
-                                            duration.ToMicroseconds(),
-                                            b,
-                                            false,
-                                            -1,
-                                            pictureRegion);
+    RefPtr<VideoData> v =
+      VideoData::CreateAndCopyData(mVideoInfo,
+                                   mImageContainer,
+                                   aStreamOffset,
+                                   pts.ToMicroseconds(),
+                                   duration.ToMicroseconds(),
+                                   b,
+                                   false,
+                                   -1,
+                                   pictureRegion);
     if (twoDBuffer) {
       twoDBuffer->Unlock2D();
     } else {
       buffer->Unlock();
     }
     v.forget(aOutVideoData);
     return S_OK;
   }
@@ -735,17 +736,16 @@ WMFVideoMFTManager::CreateBasicVideoFram
   VideoData::SetVideoDataToImage(image,
                                  mVideoInfo,
                                  b,
                                  pictureRegion,
                                  false);
 
   RefPtr<VideoData> v =
     VideoData::CreateFromImage(mVideoInfo,
-                               mImageContainer,
                                aStreamOffset,
                                pts.ToMicroseconds(),
                                duration.ToMicroseconds(),
                                image.forget(),
                                false,
                                -1,
                                pictureRegion);
 
@@ -766,27 +766,25 @@ WMFVideoMFTManager::CreateD3DVideoFrame(
   *aOutVideoData = nullptr;
   HRESULT hr;
 
   nsIntRect pictureRegion =
     mVideoInfo.ScaledImageRect(mImageSize.width, mImageSize.height);
   RefPtr<Image> image;
   hr = mDXVA2Manager->CopyToImage(aSample,
                                   pictureRegion,
-                                  mImageContainer,
                                   getter_AddRefs(image));
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
   NS_ENSURE_TRUE(image, E_FAIL);
 
   media::TimeUnit pts = GetSampleTime(aSample);
   NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
   media::TimeUnit duration = GetSampleDuration(aSample);
   NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
   RefPtr<VideoData> v = VideoData::CreateFromImage(mVideoInfo,
-                                                   mImageContainer,
                                                    aStreamOffset,
                                                    pts.ToMicroseconds(),
                                                    duration.ToMicroseconds(),
                                                    image.forget(),
                                                    false,
                                                    -1,
                                                    pictureRegion);
 
--- a/dom/media/raw/RawReader.cpp
+++ b/dom/media/raw/RawReader.cpp
@@ -181,25 +181,26 @@ bool RawReader::DecodeVideoFrame(bool &a
   b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0;
 
   b.mPlanes[2].mData = b.mPlanes[1].mData + mMetadata.frameHeight * cbcrStride / 2;
   b.mPlanes[2].mStride = cbcrStride;
   b.mPlanes[2].mHeight = mMetadata.frameHeight / 2;
   b.mPlanes[2].mWidth = mMetadata.frameWidth / 2;
   b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
 
-  RefPtr<VideoData> v = VideoData::Create(mInfo.mVideo,
-                                            mDecoder->GetImageContainer(),
-                                            -1,
-                                            currentFrameTime,
-                                            (USECS_PER_S / mFrameRate),
-                                            b,
-                                            1, // In raw video every frame is a keyframe
-                                            -1,
-                                            mPicture);
+  RefPtr<VideoData> v =
+    VideoData::CreateAndCopyData(mInfo.mVideo,
+                                 mDecoder->GetImageContainer(),
+                                 -1,
+                                 currentFrameTime,
+                                 (USECS_PER_S / mFrameRate),
+                                 b,
+                                 1, // In raw video every frame is a keyframe
+                                 -1,
+                                 mPicture);
   if (!v)
     return false;
 
   mVideoQueue.Push(v);
   mCurrentFrame++;
   a.mStats.mDecodedFrames++;
 
   return true;
--- a/dom/media/webaudio/test/browser_bug1181073.js
+++ b/dom/media/webaudio/test/browser_bug1181073.js
@@ -1,76 +1,40 @@
 add_task(function*() {
   // Make the min_background_timeout_value very high to avoid problems on slow machines
   yield new Promise(resolve => SpecialPowers.pushPrefEnv({
     'set': [['dom.min_background_timeout_value', 3000]]
   }, resolve));
 
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com");
-  let browser = gBrowser.selectedBrowser;
+  // Make a new tab, and put it in the background
+  yield BrowserTestUtils.withNewTab("about:blank", function*(browser) {
+    yield BrowserTestUtils.withNewTab("about:blank", function*() {
+      let time = yield ContentTask.spawn(browser, null, function () {
+        return new Promise(resolve => {
+          let start = content.performance.now();
+          let id = content.window.setInterval(function() {
+            let end = content.performance.now();
+            content.window.clearInterval(id);
+            resolve(end - start);
+          }, 0);
+        });
+      });
 
-  // Make the tab a background tab, so that setInterval will be throttled.
-  yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+      ok(time > 2000, "Interval is throttled with no webaudio (" + time + " ms)");
 
-  let time = yield ContentTask.spawn(browser, null, function () {
-    return new Promise(resolve => {
-      let start = content.performance.now();
-      let id = content.window.setInterval(function() {
-        let end = content.performance.now();
-        content.window.clearInterval(id);
-        resolve(end - start);
-      }, 0);
-    });
-  });
+      time = yield ContentTask.spawn(browser, null, function () {
+        return new Promise(resolve => {
+          // Create an audio context, and save it on the window so it doesn't get GCed
+          content.window._audioCtx = new content.window.AudioContext();
 
-  ok(time > 2000, "Interval is throttled with no webaudio (" + time + " ms)");
+          let start = content.performance.now();
+          let id = content.window.setInterval(function() {
+            let end = content.performance.now();
+            content.window.clearInterval(id);
+            resolve(end - start);
+          }, 0);
+        });
+      });
 
-  // Set up a listener for the oscillator's demise
-  let oscillatorDemisePromise = ContentTask.spawn(browser, null, function() {
-    return new Promise(resolve => {
-      let observer = () => resolve();
-      // Record the observer on the content object so we can throw it out later
-      content.__bug1181073_observer = observer;
-      Services.obs.addObserver(observer, "webaudio-node-demise", false);
+      ok(time < 1000, "Interval is not throttled with an audio context present (" + time + " ms)");
     });
   });
-
-  time = yield ContentTask.spawn(browser, null, function () {
-    return new Promise(resolve => {
-      // Start playing audio, save it on the window so it doesn't get GCed
-      let audioCtx = content.window.audioCtx = new content.window.AudioContext();
-      let oscillator = audioCtx.createOscillator();
-      oscillator.type = 'square';
-      oscillator.frequency.value = 3000;
-      oscillator.start();
-
-      let start = content.performance.now();
-      let id = content.window.setInterval(function() {
-        let end = content.performance.now();
-        content.window.clearInterval(id);
-        oscillator.stop();
-        resolve(end - start);
-      }, 0);
-    });
-  });
-
-  ok(time < 1000, "Interval is not throttled with audio playing (" + time + " ms)");
-
-  // Destroy the oscillator, but not the audio context
-  yield new Promise(resolve => SpecialPowers.exactGC(resolve));
-  yield oscillatorDemisePromise;
-
-  time = yield ContentTask.spawn(browser, null, function () {
-    return new Promise(resolve => {
-      let start = content.performance.now();
-      let id = content.window.setInterval(function() {
-        let end = content.performance.now();
-        content.window.clearInterval(id);
-        resolve(end - start);
-      }, 0);
-    });
-  });
-
-  ok(time > 2000, "Interval is throttled with audio stopped (" + time + " ms)");
-
-  while (gBrowser.tabs.length > 1)
-    gBrowser.removeCurrentTab();
 });
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -103,17 +103,16 @@ void AudioInputCubeb::UpdateDeviceList()
   }
   mDevices = devices;
 }
 
 MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
   : mMutex("mozilla::MediaEngineWebRTC"),
     mVoiceEngine(nullptr),
     mAudioInput(nullptr),
-    mAudioEngineInit(false),
     mFullDuplex(aPrefs.mFullDuplex),
     mExtendedFilter(aPrefs.mExtendedFilter),
     mDelayAgnostic(aPrefs.mDelayAgnostic)
 {
 #ifndef MOZ_B2G_CAMERA
   nsCOMPtr<nsIComponentRegistrar> compMgr;
   NS_GetComponentRegistrar(getter_AddRefs(compMgr));
   if (compMgr) {
@@ -344,21 +343,22 @@ MediaEngineWebRTC::EnumerateAudioDevices
     }
   }
 
   ptrVoEBase = webrtc::VoEBase::GetInterface(mVoiceEngine);
   if (!ptrVoEBase) {
     return;
   }
 
-  if (!mAudioEngineInit) {
-    if (ptrVoEBase->Init() < 0) {
-      return;
-    }
-    mAudioEngineInit = true;
+  // Always re-init the voice engine, since if we close the last use we
+  // DeInitEngine() and Terminate(), which shuts down Process() - but means
+  // we have to Init() again before using it.  Init() when already inited is
+  // just a no-op, so call always.
+  if (ptrVoEBase->Init() < 0) {
+    return;
   }
 
   if (!mAudioInput) {
     if (SupportsDuplex()) {
       // The platform_supports_full_duplex.
       mAudioInput = new mozilla::AudioInputCubeb(mVoiceEngine);
     } else {
       mAudioInput = new mozilla::AudioInputWebRTC(mVoiceEngine);
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -594,17 +594,16 @@ private:
 
   nsCOMPtr<nsIThread> mThread;
 
   // gUM runnables can e.g. Enumerate from multiple threads
   Mutex mMutex;
   webrtc::VoiceEngine* mVoiceEngine;
   webrtc::Config mConfig;
   RefPtr<mozilla::AudioInput> mAudioInput;
-  bool mAudioEngineInit;
   bool mFullDuplex;
   bool mExtendedFilter;
   bool mDelayAgnostic;
   bool mHasTabVideoSource;
 
   // Store devices we've already seen in a hashtable for quick return.
   // Maps UUID to MediaEngineSource (one set for audio, one for video).
   nsRefPtrHashtable<nsStringHashKey, MediaEngineVideoSource> mVideoSources;
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -908,17 +908,17 @@ void* nsNPAPIPluginInstance::AcquireCont
 {
   if (!mContentSurface) {
     mContentSurface = CreateSurfaceTexture();
 
     if (!mContentSurface)
       return nullptr;
   }
 
-  return mContentSurface->NativeWindow()->Handle();
+  return mContentSurface->NativeWindow();
 }
 
 AndroidSurfaceTexture*
 nsNPAPIPluginInstance::AsSurfaceTexture()
 {
   if (!mContentSurface)
     return nullptr;
 
@@ -929,17 +929,17 @@ void* nsNPAPIPluginInstance::AcquireVide
 {
   RefPtr<AndroidSurfaceTexture> surface = CreateSurfaceTexture();
   if (!surface) {
     return nullptr;
   }
 
   VideoInfo* info = new VideoInfo(surface);
 
-  void* window = info->mSurfaceTexture->NativeWindow()->Handle();
+  void* window = info->mSurfaceTexture->NativeWindow();
   mVideos.insert(std::pair<void*, VideoInfo*>(window, info));
 
   return window;
 }
 
 void nsNPAPIPluginInstance::ReleaseVideoWindow(void* window)
 {
   std::map<void*, VideoInfo*>::iterator it = mVideos.find(window);
--- a/dom/presentation/PresentationCallbacks.cpp
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -172,54 +172,56 @@ PresentationResponderLoadingCallback::In
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if ((busyFlags == nsIDocShell::BUSY_FLAGS_NONE) ||
       (busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING)) {
     // The docshell has finished loading or is receiving data (|STATE_TRANSFERRING|
     // has already been fired), so the page is ready for presentation use.
-    return NotifyReceiverReady();
+    return NotifyReceiverReady(/* isLoading = */ true);
   }
 
   // Start to listen to document state change event |STATE_TRANSFERRING|.
   return mProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
 }
 
 nsresult
-PresentationResponderLoadingCallback::NotifyReceiverReady()
+PresentationResponderLoadingCallback::NotifyReceiverReady(bool aIsLoading)
 {
   nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(mProgress);
   if (NS_WARN_IF(!window || !window->GetCurrentInnerWindow())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   uint64_t windowId = window->GetCurrentInnerWindow()->WindowID();
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  return service->NotifyReceiverReady(mSessionId, windowId);
+  return service->NotifyReceiverReady(mSessionId, windowId, aIsLoading);
 }
 
 // nsIWebProgressListener
 NS_IMETHODIMP
 PresentationResponderLoadingCallback::OnStateChange(nsIWebProgress* aWebProgress,
                                                     nsIRequest* aRequest,
                                                     uint32_t aStateFlags,
                                                     nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) {
+  if (aStateFlags & (nsIWebProgressListener::STATE_TRANSFERRING |
+                     nsIWebProgressListener::STATE_STOP)) {
     mProgress->RemoveProgressListener(this);
 
-    return NotifyReceiverReady();
+    bool isLoading = aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING;
+    return NotifyReceiverReady(isLoading);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationResponderLoadingCallback::OnProgressChange(nsIWebProgress* aWebProgress,
                                                        nsIRequest* aRequest,
--- a/dom/presentation/PresentationCallbacks.h
+++ b/dom/presentation/PresentationCallbacks.h
@@ -71,17 +71,17 @@ public:
 
   explicit PresentationResponderLoadingCallback(const nsAString& aSessionId);
 
   nsresult Init(nsIDocShell* aDocShell);
 
 private:
   ~PresentationResponderLoadingCallback();
 
-  nsresult NotifyReceiverReady();
+  nsresult NotifyReceiverReady(bool aIsLoading);
 
   nsString mSessionId;
   nsCOMPtr<nsIWebProgress> mProgress;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -884,26 +884,31 @@ NS_IMETHODIMP
 PresentationService::GetExistentSessionIdAtLaunch(uint64_t aWindowId,
                                                   nsAString& aSessionId)
 {
   return GetExistentSessionIdAtLaunchInternal(aWindowId, aSessionId);
 }
 
 NS_IMETHODIMP
 PresentationService::NotifyReceiverReady(const nsAString& aSessionId,
-                                         uint64_t aWindowId)
+                                         uint64_t aWindowId,
+                                         bool aIsLoading)
 {
   RefPtr<PresentationSessionInfo> info =
     GetSessionInfo(aSessionId, nsIPresentationService::ROLE_RECEIVER);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   AddRespondingSessionId(aWindowId, aSessionId);
 
+  if (!aIsLoading) {
+    return static_cast<PresentationPresentingInfo*>(info.get())->NotifyResponderFailure();
+  }
+
   nsCOMPtr<nsIPresentationRespondingListener> listener;
   if (mRespondingListeners.Get(aWindowId, getter_AddRefs(listener))) {
     nsresult rv = listener->NotifySessionConnect(aWindowId, aSessionId);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -853,18 +853,20 @@ PresentationControllingInfo::NotifyDisco
   }
 
   // Unset control channel here so it won't try to re-close it in potential
   // subsequent |Shutdown| calls.
   SetControlChannel(nullptr);
 
   if (NS_WARN_IF(NS_FAILED(aReason) || !mIsResponderReady)) {
     // The presentation session instance may already exist.
-    // Change the state to TERMINATED since it never succeeds.
-    SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, aReason);
+    // Change the state to CLOSED if it is not terminated.
+    if (nsIPresentationSessionListener::STATE_TERMINATED != mState) {
+      SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aReason);
+    }
 
     // Reply error for an abnormal close.
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   return NS_OK;
 }
 
@@ -1208,16 +1210,27 @@ PresentationPresentingInfo::NotifyRespon
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
     }
   }
 
   return NS_OK;
 }
 
+nsresult
+PresentationPresentingInfo::NotifyResponderFailure()
+{
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+}
+
 // nsIPresentationControlChannelListener
 NS_IMETHODIMP
 PresentationPresentingInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
 {
   if (NS_WARN_IF(mHasFlushPendingEvents)) {
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -225,16 +225,17 @@ public:
   {
     MOZ_ASSERT(aDevice);
     SetDevice(aDevice);
   }
 
   nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
 
   nsresult NotifyResponderReady();
+  nsresult NotifyResponderFailure();
 
   NS_IMETHODIMP OnSessionTransport(nsIPresentationSessionTransport* transport) override;
 
   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void SetPromise(Promise* aPromise)
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -169,24 +169,24 @@ interface nsIPresentationService : nsISu
    *
    * @param windowId: The inner window ID used to look up the session ID.
    */
   DOMString getExistentSessionIdAtLaunch(in unsigned long long windowId);
 
   /*
    * Notify the receiver page is ready for presentation use.
    *
-   * @param sessionId: An ID to identify presentation session.
-   * @param windowId: The inner window ID associated with the presentation
-   *                  session. (0 implies no window ID since no actual window
-   *                  uses 0 as its ID. Generally it's the case the window is
-   *                  located in different process from this service)
+   * @param sessionId An ID to identify presentation session.
+   * @param windowId  The inner window ID associated with the presentation
+   *                  session.
+   * @param isLoading true if receiver page is loading successfully.
    */
   void notifyReceiverReady(in DOMString sessionId,
-                           [optional] in unsigned long long windowId);
+                           in unsigned long long windowId,
+                           in boolean isLoading);
 
   /*
    * Notify the transport is closed
    *
    * @param sessionId: An ID to identify presentation session.
    * @param role: Identify the function called by controller or receiver.
    * @param reason: the error message. NS_OK indicates it is closed normally.
    */
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -90,14 +90,14 @@ parent:
   async RegisterSessionHandler(nsString aSessionId, uint8_t aRole);
   async UnregisterSessionHandler(nsString aSessionId, uint8_t aRole);
 
   async RegisterRespondingHandler(uint64_t aWindowId);
   async UnregisterRespondingHandler(uint64_t aWindowId);
 
   async PPresentationRequest(PresentationIPCRequest aRequest);
 
-  async NotifyReceiverReady(nsString aSessionId, uint64_t aWindowId);
+  async NotifyReceiverReady(nsString aSessionId, uint64_t aWindowId, bool aIsLoading);
   async NotifyTransportClosed(nsString aSessionId, uint8_t aRole, nsresult aReason);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -359,30 +359,32 @@ NS_IMETHODIMP
 PresentationIPCService::GetExistentSessionIdAtLaunch(uint64_t aWindowId,
                                                      nsAString& aSessionId)
 {
   return GetExistentSessionIdAtLaunchInternal(aWindowId, aSessionId);;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::NotifyReceiverReady(const nsAString& aSessionId,
-                                            uint64_t aWindowId)
+                                            uint64_t aWindowId,
+                                            bool aIsLoading)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // No actual window uses 0 as its ID.
   if (NS_WARN_IF(aWindowId == 0)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Track the responding info for an OOP receiver page.
   AddRespondingSessionId(aWindowId, aSessionId);
 
   NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsString(aSessionId),
-                                                          aWindowId));
+                                                          aWindowId,
+                                                          aIsLoading));
 
   // Release mCallback after using aSessionId
   // because aSessionId is held by mCallback.
   mCallback = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -275,22 +275,25 @@ PresentationParent::NotifySessionConnect
                  !SendNotifySessionConnect(aWindowId, nsString(aSessionId)))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 bool
 PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId,
-                                            const uint64_t& aWindowId)
+                                            const uint64_t& aWindowId,
+                                            const bool& aIsLoading)
 {
   MOZ_ASSERT(mService);
 
   RegisterTransportBuilder(aSessionId, nsIPresentationService::ROLE_RECEIVER);
-  NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId, aWindowId)));
+  NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId,
+                                                     aWindowId,
+                                                     aIsLoading)));
   return true;
 }
 
 bool
 PresentationParent::RecvNotifyTransportClosed(const nsString& aSessionId,
                                               const uint8_t& aRole,
                                               const nsresult& aReason)
 {
--- a/dom/presentation/ipc/PresentationParent.h
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -66,17 +66,18 @@ public:
   virtual bool RecvUnregisterSessionHandler(const nsString& aSessionId,
                                             const uint8_t& aRole) override;
 
   virtual bool RecvRegisterRespondingHandler(const uint64_t& aWindowId) override;
 
   virtual bool RecvUnregisterRespondingHandler(const uint64_t& aWindowId) override;
 
   virtual bool RecvNotifyReceiverReady(const nsString& aSessionId,
-                                       const uint64_t& aWindowId) override;
+                                       const uint64_t& aWindowId,
+                                       const bool& aIsLoading) override;
 
   virtual bool RecvNotifyTransportClosed(const nsString& aSessionId,
                                          const uint8_t& aRole,
                                          const nsresult& aReason) override;
 
 private:
   virtual ~PresentationParent();
 
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/unknown
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -15,16 +15,19 @@ support-files =
   file_presentation_receiver_auxiliary_navigation.html
   test_presentation_receiver_auxiliary_navigation.js
   file_presentation_sandboxed_presentation.html
   file_presentation_terminate.html
   test_presentation_terminate.js
   file_presentation_terminate_establish_connection_error.html
   test_presentation_terminate_establish_connection_error.js
   file_presentation_reconnect.html
+  file_presentation_unknown_content_type.test
+  file_presentation_unknown_content_type.test^headers^
+  test_presentation_tcp_receiver_establish_connection_unknown_content_type.js
 
 [test_presentation_dc_sender.html]
 [test_presentation_dc_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_dc_receiver_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_1ua_sender_and_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
@@ -38,16 +41,20 @@ skip-if = (e10s || toolkit == 'gonk' || 
 [test_presentation_tcp_sender_disconnect.html]
 skip-if = toolkit == 'android' # Bug 1129785
 [test_presentation_tcp_sender_establish_connection_error.html]
 skip-if = toolkit == 'android' # Bug 1129785
 [test_presentation_tcp_receiver_establish_connection_error.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android' || os == 'mac' || os == 'win' || buildapp == 'mulet') # Bug 1129785, Bug 1204709
 [test_presentation_tcp_receiver_establish_connection_timeout.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
+[test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android')
+[test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android')
 [test_presentation_tcp_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_tcp_receiver_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_receiver_auxiliary_navigation_inproc.html]
 skip-if = (e10s || toolkit == 'gonk')
 [test_presentation_receiver_auxiliary_navigation_oop.html]
 skip-if = (e10s || toolkit == 'gonk')
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type.js
@@ -0,0 +1,86 @@
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_unknown_content_type.test');
+
+var obs = SpecialPowers.Cc['@mozilla.org/observer-service;1']
+          .getService(SpecialPowers.Ci.nsIObserverService);
+
+var receiverIframe;
+
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.sendAsyncMessage('trigger-device-add');
+
+    receiverIframe = document.createElement('iframe');
+    receiverIframe.setAttribute('mozbrowser', 'true');
+    receiverIframe.setAttribute('mozpresentation', receiverUrl);
+    receiverIframe.setAttribute('src', receiverUrl);
+    var oop = location.pathname.indexOf('_inproc') == -1;
+    receiverIframe.setAttribute("remote", oop);
+
+    var promise = new Promise(function(aResolve, aReject) {
+      document.body.appendChild(receiverIframe);
+
+      aResolve(receiverIframe);
+    });
+    obs.notifyObservers(promise, 'setup-request-promise', null);
+
+    aResolve();
+  });
+}
+
+function testIncomingSessionRequestReceiverLaunchUnknownContentType() {
+  let promise = Promise.all([
+    new Promise(function(aResolve, aReject) {
+      gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+        gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+        info('Trying to launch receiver page.');
+
+        receiverIframe.addEventListener('mozbrowserclose', function() {
+          ok(true, 'observe receiver window closed');
+          aResolve();
+        });
+      });
+    }),
+    new Promise(function(aResolve, aReject) {
+      gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+        gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+        is(aReason, 0x80530020 /* NS_ERROR_DOM_OPERATION_ERR */, 'The control channel is closed due to load failure.');
+        aResolve();
+      });
+    })
+  ]);
+
+  gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+  return promise;
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  setup().
+  then(testIncomingSessionRequestReceiverLaunchUnknownContentType).
+  then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+  {type: 'presentation-device-manage', allow: false, context: document},
+  {type: 'presentation', allow: true, context: document},
+  {type: 'browser', allow: true, context: document},
+], function() {
+  SpecialPowers.pushPrefEnv({ 'set': [['dom.presentation.enabled', true],
+                                      ['dom.presentation.session_transport.data_channel.enable', false],
+                                      ['dom.mozBrowserFramesEnabled', true],
+                                      ['dom.ipc.tabs.disabled', false]]},
+                            runTests);
+});
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+  <meta charset="utf-8">
+  <title>Test for unknown content type of B2G Presentation API at receiver side</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1287717">Test for unknown content type of B2G Presentation API at receiver side</a>
+    <script type="application/javascript;version=1.8" src="test_presentation_tcp_receiver_establish_connection_unknown_content_type.js">
+    </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+  <meta charset="utf-8">
+  <title>Test for unknown content type of B2G Presentation API at receiver side (OOP)</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1287717">Test for unknown content type of B2G Presentation API at receiver side (OOP)</a>
+    <script type="application/javascript;version=1.8" src="test_presentation_tcp_receiver_establish_connection_unknown_content_type.js">
+    </script>
+</body>
+</html>
--- a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
@@ -100,19 +100,19 @@ function testStartConnectionUnexpectedCo
       gScript.removeMessageListener('offer-sent', offerSentHandler);
       ok(aIsValid, "A valid offer is sent out.");
       gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
     });
 
     request.start().then(
       function(aConnection) {
         is(aConnection.state, "connecting", "The initial state should be connecting.");
-        aConnection.onterminate = function() {
-          aConnection.onterminate = null;
-          is(aConnection.state, "terminated", "Connection should be terminated.");
+        aConnection.onclose = function() {
+          aConnection.onclose = null;
+          is(aConnection.state, "closed", "Connection should be closed.");
           aResolve();
         };
       },
       function(aError) {
         ok(false, "Error occurred when establishing a connection: " + aError);
         teardown();
         aReject();
       }
@@ -148,19 +148,19 @@ function testStartConnectionUnexpectedCo
       gScript.removeMessageListener('offer-sent', offerSentHandler);
       ok(aIsValid, "A valid offer is sent out.");
       gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
     });
 
     request.start().then(
       function(aConnection) {
         is(aConnection.state, "connecting", "The initial state should be connecting.");
-        aConnection.onterminate = function() {
-          aConnection.onterminate = null;
-          is(aConnection.state, "terminated", "Connection should be terminated.");
+        aConnection.onclose = function() {
+          aConnection.onclose = null;
+          is(aConnection.state, "closed", "Connection should be closed.");
           aResolve();
         };
       },
       function(aError) {
         ok(false, "Error occurred when establishing a connection: " + aError);
         teardown();
         aReject();
       }
@@ -207,19 +207,19 @@ function testStartConnectionUnexpectedCo
     gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
       gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     request.start().then(
       function(aConnection) {
         is(aConnection.state, "connecting", "The initial state should be connecting.");
-        aConnection.onterminate = function() {
-          aConnection.onterminate = null;
-          is(aConnection.state, "terminated", "Connection should be terminated.");
+        aConnection.onclose = function() {
+          aConnection.onclose = null;
+          is(aConnection.state, "closed", "Connection should be closed.");
           aResolve();
         };
       },
       function(aError) {
         ok(false, "Error occurred when establishing a connection: " + aError);
         teardown();
         aReject();
       }
@@ -266,19 +266,19 @@ function testStartConnectionUnexpectedCo
     gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
       gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     request.start().then(
       function(aConnection) {
         is(aConnection.state, "connecting", "The initial state should be connecting.");
-        aConnection.onterminate = function() {
-          aConnection.onterminate = null;
-          is(aConnection.state, "terminated", "Connection should be terminated.");
+        aConnection.onclose = function() {
+          aConnection.onclose = null;
+          is(aConnection.state, "closed", "Connection should be closed.");
           aResolve();
         };
       },
       function(aError) {
         ok(false, "Error occurred when establishing a connection: " + aError);
         teardown();
         aReject();
       }
@@ -325,19 +325,19 @@ function testStartConnectionUnexpectedDa
     gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
       gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     request.start().then(
       function(aConnection) {
         is(aConnection.state, "connecting", "The initial state should be connecting.");
-        aConnection.onterminate = function() {
-          aConnection.onterminate = null;
-          is(aConnection.state, "terminated", "Connection should be terminated.");
+        aConnection.onclose = function() {
+          aConnection.onclose = null;
+          is(aConnection.state, "closed", "Connection should be closed.");
           aResolve();
         };
       },
       function(aError) {
         ok(false, "Error occurred when establishing a connection: " + aError);
         teardown();
         aReject();
       }
--- a/dom/tests/mochitest/pointerlock/file_childIframe.html
+++ b/dom/tests/mochitest/pointerlock/file_childIframe.html
@@ -32,17 +32,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
     Mozilla Bug 633602
   </a>
 
   <div id="parent">
     <table id="childTable">
       <tr>
         <td>
-          <iframe id="iframe" src="iframe_differentDOM.html" onload="start();">
+          <iframe id="iframe" src="iframe_differentDOM.html">
           </iframe>
         </td>
         <td>
           <div id="childDiv">
           </div>
         </td>
       </tr>
     </table>
--- a/dom/tests/mochitest/pointerlock/mochitest.ini
+++ b/dom/tests/mochitest/pointerlock/mochitest.ini
@@ -18,9 +18,9 @@ support-files =
   file_suppressSomeMouseEvents.html
   file_locksvgelement.html
   file_allowPointerLockSandboxFlag.html
   file_changeLockElement.html
   iframe_differentDOM.html
 
 [test_pointerlock-api.html]
 tags = fullscreen
-skip-if = buildapp == 'b2g' || toolkit == 'android' || os == 'win' # B2G - window.open focus issues using fullscreen. Win: Bug 931445
+skip-if = buildapp == 'b2g' || toolkit == 'android' # B2G - window.open focus issues using fullscreen.
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -456,16 +456,17 @@ bool
 Factory::DoesBackendSupportDataDrawtarget(BackendType aType)
 {
   switch (aType) {
   case BackendType::DIRECT2D:
   case BackendType::DIRECT2D1_1:
   case BackendType::RECORDING:
   case BackendType::NONE:
   case BackendType::COREGRAPHICS_ACCELERATED:
+  case BackendType::BACKEND_LAST:
     return false;
   case BackendType::CAIRO:
   case BackendType::COREGRAPHICS:
   case BackendType::SKIA:
     return true;
   }
 
   return false;
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -132,17 +132,20 @@ enum class DrawTargetType : int8_t {
 enum class BackendType : int8_t {
   NONE = 0,
   DIRECT2D, // Used for version independent D2D objects.
   COREGRAPHICS,
   COREGRAPHICS_ACCELERATED,
   CAIRO,
   SKIA,
   RECORDING,
-  DIRECT2D1_1
+  DIRECT2D1_1,
+
+  // Add new entries above this line.
+  BACKEND_LAST
 };
 
 enum class FontType : int8_t {
   DWRITE,
   GDI,
   MAC,
   SKIA,
   CAIRO,
new file mode 100644
--- /dev/null
+++ b/gfx/config/gfxVarReceiver.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sts=2 ts=8 sw=2 tw=99 et: */
+/* 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_gfx_config_gfxVarReceiver_h
+#define mozilla_gfx_config_gfxVarReceiver_h
+
+namespace mozilla {
+namespace gfx {
+
+class GfxVarUpdate;
+
+// This allows downstream processes (such as PContent, PGPU) to listen for
+// updates on gfxVarReceiver.
+class gfxVarReceiver
+{
+public:
+  virtual void OnVarChanged(const GfxVarUpdate& aVar) = 0;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_config_gfxVarReceiver_h
new file mode 100644
--- /dev/null
+++ b/gfx/config/gfxVars.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sts=2 ts=8 sw=2 tw=99 et: */
+/* 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 "gfxVars.h"
+#include "gfxVarReceiver.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+namespace gfx {
+
+StaticAutoPtr<gfxVars> gfxVars::sInstance;
+StaticAutoPtr<nsTArray<gfxVars::VarBase*>> gfxVars::sVarList;
+
+void
+gfxVars::Initialize()
+{
+  if (sInstance) {
+    return;
+  }
+
+  // sVarList must be initialized first since it's used in the constructor for
+  // sInstance.
+  sVarList = new nsTArray<gfxVars::VarBase*>();
+  sInstance = new gfxVars;
+
+  // Like Preferences, we want content to synchronously get initial data on
+  // init. Note the GPU process is not handled here - it cannot send sync
+  // messages, so instead the initial data is pushed down.
+  if (XRE_IsContentProcess()) {
+    InfallibleTArray<GfxVarUpdate> vars;
+    dom::ContentChild::GetSingleton()->SendGetGfxVars(&vars);
+    for (const auto& var : vars) {
+      ApplyUpdate(var);
+    }
+  }
+}
+
+gfxVars::gfxVars()
+{
+}
+
+void
+gfxVars::Shutdown()
+{
+  sInstance = nullptr;
+  sVarList = nullptr;
+}
+
+/* static */ void
+gfxVars::ApplyUpdate(const GfxVarUpdate& aUpdate)
+{
+  // Only subprocesses receive updates and apply them locally.
+  MOZ_ASSERT(!XRE_IsParentProcess());
+  sVarList->ElementAt(aUpdate.index())->SetValue(aUpdate.value());
+}
+
+/* static */ void
+gfxVars::AddReceiver(gfxVarReceiver* aReceiver)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Don't double-add receivers, in case a broken content process sends two
+  // init messages.
+  if (!sInstance->mReceivers.Contains(aReceiver)) {
+    sInstance->mReceivers.AppendElement(aReceiver);
+  }
+}
+
+/* static */ void
+gfxVars::RemoveReceiver(gfxVarReceiver* aReceiver)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (sInstance) {
+    sInstance->mReceivers.RemoveElement(aReceiver);
+  }
+}
+
+/* static */ nsTArray<GfxVarUpdate>
+gfxVars::FetchNonDefaultVars()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(sVarList);
+
+  nsTArray<GfxVarUpdate> updates;
+  for (size_t i = 0; i < sVarList->Length(); i++) {
+    VarBase* var = sVarList->ElementAt(i);
+    if (var->HasDefaultValue()) {
+      continue;
+    }
+
+    GfxVarValue value;
+    var->GetValue(&value);
+
+    updates.AppendElement(GfxVarUpdate(i, value));
+  }
+
+  return updates;
+}
+
+gfxVars::VarBase::VarBase()
+{
+  mIndex = gfxVars::sVarList->Length();
+  gfxVars::sVarList->AppendElement(this);
+}
+
+void
+gfxVars::NotifyReceivers(VarBase* aVar)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  GfxVarValue value;
+  aVar->GetValue(&value);
+
+  GfxVarUpdate update(aVar->Index(), value);
+  for (auto& receiver : mReceivers) {
+    receiver->OnVarChanged(update);
+  }
+}
+
+} // namespace gfx
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/config/gfxVars.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sts=2 ts=8 sw=2 tw=99 et: */
+/* 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_gfx_config_gfxVars_h
+#define mozilla_gfx_config_gfxVars_h
+
+#include <stdint.h>
+#include "mozilla/Assertions.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/gfx/GraphicsMessages.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Types.h"
+#include "nsTArray.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace gfx {
+
+class gfxVarReceiver;
+
+// Generator for graphics vars.
+#define GFX_VARS_LIST(_)                                                \
+  /* C++ Name,                  Data Type,        Default Value */      \
+  _(BrowserTabsRemoteAutostart, bool,             false)                \
+  _(ContentBackend,             BackendType,      BackendType::NONE)    \
+  _(TileSize,                   IntSize,          IntSize(-1, -1))      \
+  _(UseXRender,                 bool,             false)                \
+  /* Add new entries above this line. */
+
+// Some graphics settings are computed on the UI process and must be
+// communicated to content and GPU processes. gfxVars helps facilitate
+// this. Its function is similar to gfxPrefs, except rather than hold
+// user preferences, it holds dynamically computed values.
+//
+// Each variable in GFX_VARS_LIST exposes the following static methods:
+//
+//    const DataType& CxxName();
+//    void SetCxxName(const DataType& aValue);
+//
+// Note that the setter may only be called in the UI process; a gfxVar must be
+// a variable that is determined in the UI process and pushed to child
+// processes.
+class gfxVars final
+{
+public:
+  static void Initialize();
+  static void Shutdown();
+
+  static void ApplyUpdate(const GfxVarUpdate& aUpdate);
+  static void AddReceiver(gfxVarReceiver* aReceiver);
+  static void RemoveReceiver(gfxVarReceiver* aReceiver);
+
+  // Return a list of updates for all variables with non-default values.
+  static nsTArray<GfxVarUpdate> FetchNonDefaultVars();
+
+public:
+  // Each variable must expose Set and Get methods for IPDL.
+  class VarBase
+  {
+  public:
+    VarBase();
+    virtual void SetValue(const GfxVarValue& aValue) = 0;
+    virtual void GetValue(GfxVarValue* aOutValue) = 0;
+    virtual bool HasDefaultValue() const = 0;
+    size_t Index() const {
+      return mIndex;
+    }
+  private:
+    size_t mIndex;
+  };
+
+private:
+  static StaticAutoPtr<gfxVars> sInstance;
+  static StaticAutoPtr<nsTArray<VarBase*>> sVarList;
+
+  template <typename T, T Default()>
+  class VarImpl final : public VarBase
+  {
+  public:
+    VarImpl()
+     : mValue(Default())
+    {}
+    void SetValue(const GfxVarValue& aValue) override {
+      aValue.get(&mValue);
+    }
+    void GetValue(GfxVarValue* aOutValue) override {
+      *aOutValue = GfxVarValue(mValue);
+    }
+    bool HasDefaultValue() const override {
+      return mValue == Default();
+    }
+    const T& Get() const {
+      return mValue;
+    }
+    // Return true if the value changed, false otherwise.
+    bool Set(const T& aValue) {
+      MOZ_ASSERT(XRE_IsParentProcess());
+      if (mValue == aValue) {
+        return false;
+      }
+      mValue = aValue;
+      return true;
+    }
+  private:
+    T mValue;
+  };
+
+#define GFX_VAR_DECL(CxxName, DataType, DefaultValue)           \
+private:                                                        \
+  static DataType Get##CxxName##Default() {                     \
+    return DefaultValue;                                        \
+  }                                                             \
+  VarImpl<DataType, Get##CxxName##Default> mVar##CxxName;       \
+public:                                                         \
+  static const DataType& CxxName() {                            \
+    return sInstance->mVar##CxxName.Get();                      \
+  }                                                             \
+  static void Set##CxxName(const DataType& aValue) {            \
+    if (sInstance->mVar##CxxName.Set(aValue)) {                 \
+      sInstance->NotifyReceivers(&sInstance->mVar##CxxName);    \
+    }                                                           \
+  }
+
+  GFX_VARS_LIST(GFX_VAR_DECL)
+#undef GFX_VAR_DECL
+
+private:
+  gfxVars();
+
+  void NotifyReceivers(VarBase* aVar);
+
+private:
+  nsTArray<gfxVarReceiver*> mReceivers;
+};
+
+#undef GFX_VARS_LIST
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // mozilla_gfx_config_gfxVars_h
--- a/gfx/config/moz.build
+++ b/gfx/config/moz.build
@@ -5,14 +5,22 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
   'gfxConfig.h',
   'gfxFallback.h',
   'gfxFeature.h',
 ]
 
+EXPORTS.mozilla.gfx += [
+  'gfxVarReceiver.h',
+  'gfxVars.h',
+]
+
 UNIFIED_SOURCES += [
   'gfxConfig.cpp',
   'gfxFeature.cpp',
+  'gfxVars.cpp',
 ]
 
+include('/ipc/chromium/chromium-config.mozbuild')
+
 FINAL_LIBRARY = 'xul'
deleted file mode 100644
--- a/gfx/gl/AndroidNativeWindow.cpp
+++ /dev/null
@@ -1,281 +0,0 @@
-#ifdef MOZ_WIDGET_ANDROID
-
-#include "AndroidNativeWindow.h"
-#include "prlink.h"
-
-// #define ANDROID_NATIVE_WINDOW_DEBUG
-
-#if defined(ANDROID_NATIVE_WINDOW_DEBUG) || defined(DEBUG)
-#define ALOG(args...)  __android_log_print(ANDROID_LOG_INFO, "AndroidNativeWindow" , ## args)
-#else
-#define ALOG(args...) ((void)0)
-#endif
-
-using namespace mozilla::gfx;
-using namespace mozilla::gl;
-using namespace mozilla;
-
-class NativeWindowLibrary
-{
-public:
-
-  NativeWindowLibrary()
-    : fANativeWindow_fromSurface(nullptr)
-    , fANativeWindow_release(nullptr)
-    , fANativeWindow_setBuffersGeometry(nullptr)
-    , fANativeWindow_lock(nullptr)
-    , fANativeWindow_unlockAndPost(nullptr)
-    , fANativeWindow_getFormat(nullptr)
-    , fANativeWindow_getWidth(nullptr)
-    , fANativeWindow_getHeight(nullptr)
-  {
-    PRLibrary* lib = PR_LoadLibrary("libandroid.so");
-
-    fANativeWindow_fromSurface = (pfnANativeWindow_fromSurface)PR_FindSymbol(lib, "ANativeWindow_fromSurface");
-    fANativeWindow_release = (pfnANativeWindow_release)PR_FindSymbol(lib, "ANativeWindow_release");
-    fANativeWindow_setBuffersGeometry = (pfnANativeWindow_setBuffersGeometry)PR_FindSymbol(lib, "ANativeWindow_setBuffersGeometry");
-    fANativeWindow_lock = (pfnANativeWindow_lock)PR_FindSymbol(lib, "ANativeWindow_lock");
-    fANativeWindow_unlockAndPost = (pfnANativeWindow_unlockAndPost)PR_FindSymbol(lib, "ANativeWindow_unlockAndPost");
-    fANativeWindow_getFormat = (pfnANativeWindow_getFormat)PR_FindSymbol(lib, "ANativeWindow_getFormat");
-    fANativeWindow_getWidth = (pfnANativeWindow_getWidth)PR_FindSymbol(lib, "ANativeWindow_getWidth");
-    fANativeWindow_getHeight = (pfnANativeWindow_getHeight)PR_FindSymbol(lib, "ANativeWindow_getHeight");
-  }
-
-  void* ANativeWindow_fromSurface(JNIEnv* aEnv, jobject aSurface) {
-    ALOG("%s: env=%p, surface=%p\n", __PRETTY_FUNCTION__, aEnv, aSurface);
-    if (!Initialized()) {
-      return nullptr;
-    }
-
-    return fANativeWindow_fromSurface(aEnv, aSurface);
-  }
-
-  void ANativeWindow_release(void* aWindow) {
-    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
-    if (!Initialized()) {
-      return;
-    }
-
-    fANativeWindow_release(aWindow);
-  }
-
-  bool ANativeWindow_setBuffersGeometry(void* aWindow, int32_t aWidth, int32_t aHeight, int32_t aFormat) {
-    ALOG("%s: window=%p, width=%d, height=%d, format=%d\n", __PRETTY_FUNCTION__, aWindow, aWidth, aHeight, aFormat);
-    if (!Initialized()) {
-      return false;
-    }
-
-    return fANativeWindow_setBuffersGeometry(aWindow, aWidth, aHeight, (int32_t)aFormat) == 0;
-  }
-
-  bool ANativeWindow_lock(void* aWindow, void* out_buffer, void*in_out_dirtyBounds) {
-    ALOG("%s: window=%p, out_buffer=%p, in_out_dirtyBounds=%p\n", __PRETTY_FUNCTION__,
-         aWindow, out_buffer, in_out_dirtyBounds);
-    if (!Initialized()) {
-      return false;
-    }
-
-    return fANativeWindow_lock(aWindow, out_buffer, in_out_dirtyBounds) == 0;
-  }
-
-  bool ANativeWindow_unlockAndPost(void* aWindow) {
-    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
-    if (!Initialized()) {
-      return false;
-    }
-
-    return fANativeWindow_unlockAndPost(aWindow) == 0;
-  }
-
-  AndroidWindowFormat ANativeWindow_getFormat(void* aWindow) {
-    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
-    if (!Initialized()) {
-      return AndroidWindowFormat::Unknown;
-    }
-
-    return (AndroidWindowFormat)fANativeWindow_getFormat(aWindow);
-  }
-
-  int32_t ANativeWindow_getWidth(void* aWindow) {
-    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
-    if (!Initialized()) {
-      return -1;
-    }
-
-    return fANativeWindow_getWidth(aWindow);
-  }
-
-  int32_t ANativeWindow_getHeight(void* aWindow) {
-    ALOG("%s: window=%p\n", __PRETTY_FUNCTION__, aWindow);
-    if (!Initialized()) {
-      return -1;
-    }
-
-    return fANativeWindow_getHeight(aWindow);
-  }
-
-  bool Initialized() {
-    return fANativeWindow_fromSurface && fANativeWindow_release && fANativeWindow_setBuffersGeometry
-      && fANativeWindow_lock && fANativeWindow_unlockAndPost && fANativeWindow_getFormat && fANativeWindow_getWidth
-      && fANativeWindow_getHeight;
-  }
-
-private:
-
-  typedef void* (*pfnANativeWindow_fromSurface)(JNIEnv* env, jobject surface);
-  pfnANativeWindow_fromSurface fANativeWindow_fromSurface;
-
-  typedef void (*pfnANativeWindow_release)(void* window);
-  pfnANativeWindow_release fANativeWindow_release;
-
-  typedef int32_t (*pfnANativeWindow_setBuffersGeometry)(void* window, int32_t width, int32_t height, int32_t format);
-  pfnANativeWindow_setBuffersGeometry fANativeWindow_setBuffersGeometry;
-
-  typedef int32_t (*pfnANativeWindow_lock)(void* window, void* out_buffer, void* in_out_dirtyBounds);
-  pfnANativeWindow_lock fANativeWindow_lock;
-
-  typedef int32_t (*pfnANativeWindow_unlockAndPost)(void* window);
-  pfnANativeWindow_unlockAndPost fANativeWindow_unlockAndPost;
-
-  typedef AndroidWindowFormat (*pfnANativeWindow_getFormat)(void* window);
-  pfnANativeWindow_getFormat fANativeWindow_getFormat;
-
-  typedef int32_t (*pfnANativeWindow_getWidth)(void* window);
-  pfnANativeWindow_getWidth fANativeWindow_getWidth;
-
-  typedef int32_t (*pfnANativeWindow_getHeight)(void* window);
-  pfnANativeWindow_getHeight fANativeWindow_getHeight;
-};
-
-static NativeWindowLibrary* sLibrary = nullptr;
-
-static bool
-EnsureInit()
-{
-  static bool initialized = false;
-  if (!initialized) {
-    if (!sLibrary) {
-      sLibrary = new NativeWindowLibrary();
-    }
-    initialized = sLibrary->Initialized();
-  }
-
-  return initialized;
-}
-
-
-namespace mozilla {
-
-/* static */ AndroidNativeWindow*
-AndroidNativeWindow::CreateFromSurface(JNIEnv* aEnv, jobject aSurface)
-{
-  if (!EnsureInit()) {
-    ALOG("Not initialized");
-    return nullptr;
-  }
-
-  void* window = sLibrary->ANativeWindow_fromSurface(aEnv, aSurface);
-  if (!window) {
-    ALOG("Failed to create window from surface");
-    return nullptr;
-  }
-
-  return new AndroidNativeWindow(window);
-}
-
-AndroidNativeWindow::~AndroidNativeWindow()
-{
-  if (EnsureInit() && mWindow) {
-    sLibrary->ANativeWindow_release(mWindow);
-    mWindow = nullptr;
-  }
-}
-
-IntSize
-AndroidNativeWindow::Size()
-{
-  MOZ_ASSERT(mWindow);
-  if (!EnsureInit()) {
-    return IntSize(0, 0);
-  }
-
-  return IntSize(sLibrary->ANativeWindow_getWidth(mWindow), sLibrary->ANativeWindow_getHeight(mWindow));
-}
-
-AndroidWindowFormat
-AndroidNativeWindow::Format()
-{
-  MOZ_ASSERT(mWindow);
-  if (!EnsureInit()) {
-    return AndroidWindowFormat::Unknown;
-  }
-
-  return sLibrary->ANativeWindow_getFormat(mWindow);
-}
-
-bool
-AndroidNativeWindow::SetBuffersGeometry(int32_t aWidth, int32_t aHeight, AndroidWindowFormat aFormat)
-{
-  MOZ_ASSERT(mWindow);
-  if (!EnsureInit())
-    return false;
-
-  return sLibrary->ANativeWindow_setBuffersGeometry(mWindow, aWidth, aHeight, (int32_t)aFormat);
-}
-
-bool
-AndroidNativeWindow::Lock(void** out_bits,int32_t* out_width, int32_t* out_height,
-                          int32_t* out_stride, AndroidWindowFormat* out_format)
-{
-  /* Copied from native_window.h in Android NDK (platform-9) */
-  typedef struct ANativeWindow_Buffer {
-      // The number of pixels that are show horizontally.
-      int32_t width;
-
-      // The number of pixels that are shown vertically.
-      int32_t height;
-
-      // The number of *pixels* that a line in the buffer takes in
-      // memory.  This may be >= width.
-      int32_t stride;
-
-      // The format of the buffer.  One of WINDOW_FORMAT_*
-      int32_t format;
-
-      // The actual bits.
-      void* bits;
-
-      // Do not touch.
-      uint32_t reserved[6];
-  } ANativeWindow_Buffer;
-
-
-  ANativeWindow_Buffer buffer;
-
-  if (!sLibrary->ANativeWindow_lock(mWindow, &buffer, nullptr)) {
-    ALOG("Failed to lock");
-    return false;
-  }
-
-  *out_bits = buffer.bits;
-  *out_width = buffer.width;
-  *out_height = buffer.height;
-  *out_stride = buffer.stride;
-  *out_format = (AndroidWindowFormat)buffer.format;
-  return true;
-}
-
-bool
-AndroidNativeWindow::UnlockAndPost()
-{
-  if (!EnsureInit()) {
-    ALOG("Not initialized");
-    return false;
-  }
-
-  return sLibrary->ANativeWindow_unlockAndPost(mWindow);
-}
-
-}
-
-#endif // MOZ_WIDGET_ANDROID
deleted file mode 100644
--- a/gfx/gl/AndroidNativeWindow.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-// vim:set ts=2 sts=2 sw=2 et cin:
-/* 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 AndroidNativeWindow_h__
-#define AndroidNativeWindow_h__
-#ifdef MOZ_WIDGET_ANDROID
-
-#include <jni.h>
-#include "GLDefs.h"
-
-#include "nsISupports.h"
-#include "mozilla/gfx/2D.h"
-
-
-namespace mozilla {
-namespace gl {
-
-enum class AndroidWindowFormat {
-  Unknown = -1,
-  RGBA_8888 = 1,
-  RGBX_8888 = 1 << 1,
-  RGB_565 = 1 << 2
-};
-
-/**
- * This class is a wrapper around Android's SurfaceTexture class.
- * Usage is pretty much exactly like the Java class, so see
- * the Android documentation for details.
- */
-class AndroidNativeWindow {
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AndroidNativeWindow)
-
-public:
-
-  static AndroidNativeWindow* CreateFromSurface(JNIEnv* aEnv, jobject aSurface);
-
-  gfx::IntSize Size();
-  AndroidWindowFormat Format();
-
-  bool SetBuffersGeometry(int32_t aWidth, int32_t aHeight, AndroidWindowFormat aFormat);
-
-  bool Lock(void** out_bits, int32_t* out_width, int32_t* out_height, int32_t* out_stride, AndroidWindowFormat* out_format);
-  bool UnlockAndPost();
-
-  void* Handle() { return mWindow; }
-
-protected:
-  AndroidNativeWindow(void* aWindow)
-    : mWindow(aWindow)
-  {
-
-  }
-
-  virtual ~AndroidNativeWindow();
-
-  void* mWindow;
-};
-
-}
-}
-
-
-#endif
-#endif
--- a/gfx/gl/AndroidSurfaceTexture.cpp
+++ b/gfx/gl/AndroidSurfaceTexture.cpp
@@ -2,16 +2,17 @@
 // vim:set ts=2 sts=2 sw=2 et cin:
 /* 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/. */
 
 #ifdef MOZ_WIDGET_ANDROID
 
 #include <map>
+#include <android/native_window_jni.h>
 #include <android/log.h>
 #include "AndroidSurfaceTexture.h"
 #include "gfxImageSurface.h"
 #include "gfxPrefs.h"
 #include "AndroidBridge.h"
 #include "nsThreadUtils.h"
 #include "mozilla/gfx/Matrix.h"
 #include "GeneratedJNIWrappers.h"
@@ -194,18 +195,18 @@ AndroidSurfaceTexture::Init(GLContext* a
 
   mAttachedContext = aContext;
 
   if (NS_WARN_IF(NS_FAILED(
       Surface::New(mSurfaceTexture, ReturnTo(&mSurface))))) {
     return false;
   }
 
-  mNativeWindow = AndroidNativeWindow::CreateFromSurface(jni::GetEnvForThread(),
-                                                         mSurface.Get());
+  mNativeWindow = ANativeWindow_fromSurface(jni::GetEnvForThread(),
+                                            mSurface.Get());
   MOZ_ASSERT(mNativeWindow, "Failed to create native window from surface");
 
   mID = sInstances.Add(this);
 
   return true;
 }
 
 AndroidSurfaceTexture::AndroidSurfaceTexture()
@@ -222,16 +223,21 @@ AndroidSurfaceTexture::~AndroidSurfaceTe
   sInstances.Remove(mID);
 
   mFrameAvailableCallback = nullptr;
 
   if (mSurfaceTexture) {
     GeckoAppShell::UnregisterSurfaceTextureFrameListener(mSurfaceTexture);
     mSurfaceTexture = nullptr;
   }
+
+  if (mNativeWindow) {
+    ANativeWindow_release(mNativeWindow);
+    mNativeWindow = nullptr;
+  }
 }
 
 void
 AndroidSurfaceTexture::UpdateTexImage()
 {
   mSurfaceTexture->UpdateTexImage();
 }
 
--- a/gfx/gl/AndroidSurfaceTexture.h
+++ b/gfx/gl/AndroidSurfaceTexture.h
@@ -4,25 +4,25 @@
  * 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 AndroidSurfaceTexture_h__
 #define AndroidSurfaceTexture_h__
 #ifdef MOZ_WIDGET_ANDROID
 
 #include <jni.h>
+#include <android/native_window.h>
 #include "nsIRunnable.h"
 #include "gfxPlatform.h"
 #include "GLDefs.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/MatrixFwd.h"
 #include "mozilla/Monitor.h"
 
 #include "SurfaceTexture.h"
-#include "AndroidNativeWindow.h"
 
 namespace mozilla {
 namespace gl {
 
 class GLContext;
 
 /**
  * This class is a wrapper around Android's SurfaceTexture class.
@@ -57,17 +57,17 @@ public:
   nsresult Detach();
 
   // Ability to detach is based on API version (16+), and we also block PowerVR
   // since it has some type of fencing problem. Bug 1100126.
   bool CanDetach() const;
 
   GLContext* AttachedContext() const { return mAttachedContext; }
 
-  AndroidNativeWindow* NativeWindow() const {
+  ANativeWindow* NativeWindow() const {
     return mNativeWindow;
   }
 
   // This attaches the updated data to the TEXTURE_EXTERNAL target
   void UpdateTexImage();
 
   void GetTransformMatrix(mozilla::gfx::Matrix4x4& aMatrix) const;
   int ID() const { return mID; }
@@ -92,17 +92,17 @@ private:
   bool Init(GLContext* aContext, GLuint aTexture);
 
   GLuint mTexture;
   java::sdk::SurfaceTexture::GlobalRef mSurfaceTexture;
   java::sdk::Surface::GlobalRef mSurface;
 
   GLContext* mAttachedContext;
 
-  RefPtr<AndroidNativeWindow> mNativeWindow;
+  ANativeWindow* mNativeWindow;
   int mID;
   nsCOMPtr<nsIRunnable> mFrameAvailableCallback;
 
   mutable Monitor mMonitor;
 };
 
 }
 }
--- a/gfx/gl/moz.build
+++ b/gfx/gl/moz.build
@@ -21,17 +21,16 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'an
     gl_provider = 'EGL'
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     gl_provider = 'EGL'
 
 if CONFIG['MOZ_GL_PROVIDER']:
     gl_provider = CONFIG['MOZ_GL_PROVIDER']
 
 EXPORTS += [
-    'AndroidNativeWindow.h',
     'AndroidSurfaceTexture.h',
     'DecomposeIntoNoRepeatTriangles.h',
     'EGLUtils.h',
     'ForceDiscreteGPUHelperCGL.h',
     'GfxTexturesReporter.h',
     'GLBlitHelper.h',
     'GLConsts.h',
     'GLContext.h',
@@ -124,17 +123,16 @@ elif gl_provider == 'GLX':
         'GLContextProviderGLX.cpp',
         'SharedSurfaceGLX.cpp'
     ]
     EXPORTS += [
         'SharedSurfaceGLX.h'
     ]
 
 UNIFIED_SOURCES += [
-    'AndroidNativeWindow.cpp',
     'AndroidSurfaceTexture.cpp',
     'DecomposeIntoNoRepeatTriangles.cpp',
     'EGLUtils.cpp',
     'GfxTexturesReporter.cpp',
     'GLBlitHelper.cpp',
     'GLContext.cpp',
     'GLContextFeatures.cpp',
     'GLContextProviderEGL.cpp',
--- a/gfx/ipc/GPUChild.cpp
+++ b/gfx/ipc/GPUChild.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=99: */
 /* 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 "GPUChild.h"
 #include "gfxPrefs.h"
 #include "GPUProcessHost.h"
+#include "mozilla/gfx/gfxVars.h"
 
 namespace mozilla {
 namespace gfx {
 
 GPUChild::GPUChild(GPUProcessHost* aHost)
  : mHost(aHost)
 {
   MOZ_COUNT_CTOR(GPUChild);
@@ -35,22 +36,32 @@ GPUChild::Init()
       continue;
     }
 
     GfxPrefValue value;
     pref->GetCachedValue(&value);
     prefs.AppendElement(GfxPrefSetting(pref->Index(), value));
   }
 
-  SendInit(prefs);
+  nsTArray<GfxVarUpdate> updates = gfxVars::FetchNonDefaultVars();
+  SendInit(prefs, updates);
+
+  gfxVars::AddReceiver(this);
+}
+
+void
+GPUChild::OnVarChanged(const GfxVarUpdate& aVar)
+{
+  SendUpdateVar(aVar);
 }
 
 void
 GPUChild::ActorDestroy(ActorDestroyReason aWhy)
 {
+  gfxVars::RemoveReceiver(this);
   mHost->OnChannelClosed();
 }
 
 class DeferredDeleteGPUChild : public Runnable
 {
 public:
   explicit DeferredDeleteGPUChild(UniquePtr<GPUChild>&& aChild)
     : mChild(Move(aChild))
--- a/gfx/ipc/GPUChild.h
+++ b/gfx/ipc/GPUChild.h
@@ -4,34 +4,41 @@
  * 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 _include_mozilla_gfx_ipc_GPUChild_h_
 #define _include_mozilla_gfx_ipc_GPUChild_h_
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/gfx/PGPUChild.h"
+#include "mozilla/gfx/gfxVarReceiver.h"
 
 namespace mozilla {
 namespace gfx {
 
 class GPUProcessHost;
 
-class GPUChild final : public PGPUChild
+class GPUChild final
+  : public PGPUChild,
+    public gfxVarReceiver
 {
 public:
   explicit GPUChild(GPUProcessHost* aHost);
   ~GPUChild();
 
   void Init();
 
-  static void Destroy(UniquePtr<GPUChild>&& aChild);
+  // gfxVarReceiver overrides.
+  void OnVarChanged(const GfxVarUpdate& aVar) override;
 
+  // PGPUChild overrides.
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
+  static void Destroy(UniquePtr<GPUChild>&& aChild);
+
 private:
   GPUProcessHost* mHost;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif // _include_mozilla_gfx_ipc_GPUChild_h_
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -4,16 +4,17 @@
  * 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 "GPUParent.h"
 #include "gfxConfig.h"
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "GPUProcessHost.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "VRManager.h"
 #include "VRManagerParent.h"
 #include "VsyncBridgeParent.h"
 
@@ -37,30 +38,35 @@ GPUParent::Init(base::ProcessId aParentP
                 IPC::Channel* aChannel)
 {
   if (NS_WARN_IF(!Open(aChannel, aParentPid, aIOLoop))) {
     return false;
   }
 
   // Ensure gfxPrefs are initialized.
   gfxPrefs::GetSingleton();
+  gfxVars::Initialize();
   CompositorThreadHolder::Start();
   VRManager::ManagerInit();
   gfxPlatform::InitNullMetadata();
   return true;
 }
 
 bool
-GPUParent::RecvInit(nsTArray<GfxPrefSetting>&& prefs)
+GPUParent::RecvInit(nsTArray<GfxPrefSetting>&& prefs,
+                    nsTArray<GfxVarUpdate>&& vars)
 {
   const nsTArray<gfxPrefs::Pref*>& globalPrefs = gfxPrefs::all();
   for (auto& setting : prefs) {
     gfxPrefs::Pref* pref = globalPrefs[setting.index()];
     pref->SetCachedValue(setting.value());
   }
+  for (const auto& var : vars) {
+    gfxVars::ApplyUpdate(var);
+  }
   return true;
 }
 
 bool
 GPUParent::RecvInitVsyncBridge(Endpoint<PVsyncBridgeParent>&& aVsyncEndpoint)
 {
   VsyncBridgeParent::Start(Move(aVsyncEndpoint));
   return true;
@@ -83,16 +89,23 @@ GPUParent::RecvInitVRManager(Endpoint<PV
 bool
 GPUParent::RecvUpdatePref(const GfxPrefSetting& setting)
 {
   gfxPrefs::Pref* pref = gfxPrefs::all()[setting.index()];
   pref->SetCachedValue(setting.value());
   return true;
 }
 
+bool
+GPUParent::RecvUpdateVar(const GfxVarUpdate& aUpdate)
+{
+  gfxVars::ApplyUpdate(aUpdate);
+  return true;
+}
+
 static void
 OpenParent(RefPtr<CompositorBridgeParent> aParent,
            Endpoint<PCompositorBridgeParent>&& aEndpoint)
 {
   if (!aParent->Bind(Move(aEndpoint))) {
     MOZ_CRASH("Failed to bind compositor");
   }
 }
--- a/gfx/ipc/GPUParent.h
+++ b/gfx/ipc/GPUParent.h
@@ -19,21 +19,23 @@ class GPUParent final : public PGPUParen
 public:
   GPUParent();
   ~GPUParent();
 
   bool Init(base::ProcessId aParentPid,
             MessageLoop* aIOLoop,
             IPC::Channel* aChannel);
 
-  bool RecvInit(nsTArray<GfxPrefSetting>&& prefs) override;
+  bool RecvInit(nsTArray<GfxPrefSetting>&& prefs,
+                nsTArray<GfxVarUpdate>&& vars) override;
   bool RecvInitVsyncBridge(Endpoint<PVsyncBridgeParent>&& aVsyncEndpoint) override;
   bool RecvInitImageBridge(Endpoint<PImageBridgeParent>&& aEndpoint) override;
   bool RecvInitVRManager(Endpoint<PVRManagerParent>&& aEndpoint) override;
   bool RecvUpdatePref(const GfxPrefSetting& pref) override;
+  bool RecvUpdateVar(const GfxVarUpdate& pref) override;
   bool RecvNewWidgetCompositor(
     Endpoint<PCompositorBridgeParent>&& aEndpoint,
     const CSSToLayoutDeviceScale& aScale,
     const TimeDuration& aVsyncRate,
     const bool& aUseExternalSurface,
     const IntSize& aSurfaceSize) override;
   bool RecvNewContentCompositorBridge(Endpoint<PCompositorBridgeParent>&& aEndpoint) override;
   bool RecvNewContentImageBridge(Endpoint<PImageBridgeParent>&& aEndpoint) override;
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -217,16 +217,24 @@ template <>
 struct ParamTraits<mozilla::layers::LayersBackend>
   : public ContiguousEnumSerializer<
              mozilla::layers::LayersBackend,
              mozilla::layers::LayersBackend::LAYERS_NONE,
              mozilla::layers::LayersBackend::LAYERS_LAST>
 {};
 
 template <>
+struct ParamTraits<mozilla::gfx::BackendType>
+  : public ContiguousEnumSerializer<
+             mozilla::gfx::BackendType,
+             mozilla::gfx::BackendType::NONE,
+             mozilla::gfx::BackendType::BACKEND_LAST>
+{};
+
+template <>
 struct ParamTraits<mozilla::layers::ScaleMode>
   : public ContiguousEnumSerializer<
              mozilla::layers::ScaleMode,
              mozilla::layers::ScaleMode::SCALE_NONE,
              mozilla::layers::ScaleMode::SENTINEL>
 {};
 
 template <>
--- a/gfx/ipc/GraphicsMessages.ipdlh
+++ b/gfx/ipc/GraphicsMessages.ipdlh
@@ -1,26 +1,41 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: sw=2 ts=8 et :
  */
 /* 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/. */
 
 using struct DxgiAdapterDesc from "mozilla/D3DMessageUtils.h";
-
+using mozilla::gfx::BackendType from "mozilla/gfx/Types.h";
+using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
+
 namespace mozilla {
 namespace gfx {
 
 struct DeviceInitData
 {
   bool useHwCompositing;
 
   // Windows only.
   bool useD3D11;
   bool useD3D11WARP;
   bool d3d11TextureSharingWorks;
   bool useD2D1;
   DxgiAdapterDesc adapter;
 };
 
+union GfxVarValue
+{
+  BackendType;
+  bool;
+  IntSize;
+};
+
+struct GfxVarUpdate
+{
+  size_t index;
+  GfxVarValue value;
+};
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -1,13 +1,14 @@
 /* -*- Mode: C++; tab-width: 8; 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 GraphicsMessages;
 include protocol PCompositorBridge;
 include protocol PImageBridge;
 include protocol PVRManager;
 include protocol PVsyncBridge;
 
 using mozilla::TimeDuration from "mozilla/TimeStamp.h";
 using mozilla::CSSToLayoutDeviceScale from "Units.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/2D.h";
@@ -26,24 +27,25 @@ struct GfxPrefSetting {
   int32_t index;
   GfxPrefValue value;
 };
 
 sync protocol PGPU
 {
 parent:
   // Sent by the UI process to initiate core settings.
-  async Init(GfxPrefSetting[] prefs);
+  async Init(GfxPrefSetting[] prefs, GfxVarUpdate[] vars);
 
   async InitVsyncBridge(Endpoint<PVsyncBridgeParent> endpoint);
   async InitImageBridge(Endpoint<PImageBridgeParent> endpoint);
   async InitVRManager(Endpoint<PVRManagerParent> endpoint);
 
-  // Called to update a gfx preference.
+  // Called to update a gfx preference or variable.
   async UpdatePref(GfxPrefSetting pref);
+  async UpdateVar(GfxVarUpdate var);
 
   // Create a new top-level compositor.
   async NewWidgetCompositor(Endpoint<PCompositorBridgeParent> endpoint,
                             CSSToLayoutDeviceScale scale,
                             TimeDuration vsyncRate,
                             bool useExternalSurface,
                             IntSize surfaceSize);
 
--- a/gfx/layers/BufferTexture.cpp
+++ b/gfx/layers/BufferTexture.cpp
@@ -87,17 +87,17 @@ public:
 
 protected:
   mozilla::ipc::Shmem mShmem;
 };
 
 static bool UsingX11Compositor()
 {
 #ifdef MOZ_WIDGET_GTK
-  return gfxPlatformGtk::GetPlatform()->UseXRender();
+  return gfx::gfxVars::UseXRender();
 #endif
   return false;
 }
 
 bool ComputeHasIntermediateBuffer(gfx::SurfaceFormat aFormat,
                                   LayersBackend aLayersBackend)
 {
   return aLayersBackend != LayersBackend::LAYERS_BASIC
--- a/gfx/layers/TiledLayerBuffer.h
+++ b/gfx/layers/TiledLayerBuffer.h
@@ -7,18 +7,18 @@
 
 // Debug defines
 //#define GFX_TILEDLAYER_DEBUG_OVERLAY
 //#define GFX_TILEDLAYER_PREF_WARNINGS
 //#define GFX_TILEDLAYER_RETAINING_LOG
 
 #include <stdint.h>                     // for uint16_t, uint32_t
 #include <sys/types.h>                  // for int32_t
-#include "gfxPlatform.h"                // for GetTileWidth/GetTileHeight
 #include "LayersLogging.h"              // for print_stderr
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/Logging.h"        // for gfxCriticalError
 #include "mozilla/layers/LayersTypes.h" // for TextureDumpMode
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTArray.h"                   // for nsTArray
 
@@ -137,18 +137,17 @@ inline int RoundDownToTileEdge(int aX, i
 
 template<typename Derived, typename Tile>
 class TiledLayerBuffer
 {
 public:
   TiledLayerBuffer()
     : mTiles(0, 0, 0, 0)
     , mResolution(1)
-    , mTileSize(gfxPlatform::GetPlatform()->GetTileWidth(),
-                gfxPlatform::GetPlatform()->GetTileHeight())
+    , mTileSize(gfxVars::TileSize())
   {}
 
   ~TiledLayerBuffer() {}
 
   gfx::IntPoint GetTileOffset(TileIntPoint aPosition) const {
     gfx::IntSize scaledTileSize = GetScaledTileSize();
     return gfx::IntPoint(aPosition.x * scaledTileSize.width,
                          aPosition.y * scaledTileSize.height) + mTileOrigin;
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1285070.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width; initial-scale=1.0">
+  <title>Test pointer events are dispatched once for touch tap</title>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript">
+    function test() {
+      let pointerEventsList = ["pointerover", "pointerenter", "pointerdown",
+                               "pointerup", "pointerleave", "pointerout"];
+      let pointerEventsCount = {};
+
+      pointerEventsList.forEach((eventName) => {
+        pointerEventsCount[eventName] = 0;
+        document.getElementById('div1').addEventListener(eventName, (event) => {
+          dump("Received event " + event.type + "\n");
+          ++pointerEventsCount[event.type];
+        }, false);
+      });
+
+      document.addEventListener("click", (event) => {
+        is(event.target, document.getElementById('div1'), "Clicked on div (at " + event.clientX + "," + event.clientY + ")");
+        for (var key in pointerEventsCount) {
+          is(pointerEventsCount[key], 1, "Event " + key + " should be generated once");
+        }
+        subtestDone();
+      }, false);
+
+      synthesizeNativeTap(document.getElementById('div1'), 100, 100, () => {
+        dump("Finished synthesizing tap, waiting for div to be clicked...\n");
+      });
+    }
+
+    waitUntilApzStable().then(test);
+
+  </script>
+</head>
+<body>
+  <div id="div1" style="width: 200px; height: 200px; background: black"></div>
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   apz_test_utils.js
   apz_test_native_event_utils.js
   helper_bug982141.html
   helper_bug1151663.html
+  helper_bug1285070.html
   helper_iframe1.html
   helper_iframe2.html
   helper_subframe_style.css
   helper_basic_pan.html
   helper_div_pan.html
   helper_iframe_pan.html
   helper_scrollto_tap.html
   helper_tap.html
@@ -54,8 +55,12 @@ skip-if = (toolkit == 'windows')
 skip-if = (toolkit == 'android') # wheel events not supported on mobile
 [test_group_mouseevents.html]
 [test_touch_listeners_impacting_wheel.html]
 skip-if = (toolkit == 'android') || (toolkit == 'cocoa') # wheel events not supported on mobile, and synthesized wheel smooth-scrolling not supported on OS X
 [test_bug1253683.html]
 skip-if = (os == 'android') || (os == 'b2g') # uses wheel events which are not supported on mobile
 [test_group_zoom.html]
 skip-if = (toolkit != 'android') # only android supports zoom
+[test_bug1285070.html]
+# Windows touch injection doesn't work in automation, but this test can be run locally on a windows touch device.
+# On OS X we don't support touch events at all.
+skip-if = (toolkit == 'windows') || (toolkit == 'cocoa')
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_bug1285070.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1285070
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1285070</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  var subtests = [
+    {'file': 'helper_bug1285070.html', 'prefs': [["dom.w3c_pointer_events.enabled", true]]}
+  ];
+
+  if (isApzEnabled()) {
+    SimpleTest.waitForExplicitFinish();
+    window.onload = function() {
+      runSubtestsSeriallyInFreshWindows(subtests)
+      .then(SimpleTest.finish);
+    };
+  }
+
+  </script>
+</head>
+<body>
+</body>
+</html>
--- a/gfx/layers/apz/test/reftest/reftest.list
+++ b/gfx/layers/apz/test/reftest/reftest.list
@@ -1,16 +1,16 @@
 # The following tests test the async positioning of the scrollbars.
 # Basic root-frame scrollbar with async scrolling
 skip-if(!asyncPan) fuzzy-if(Android,6,8) == async-scrollbar-1-v.html async-scrollbar-1-v-ref.html
 skip-if(!asyncPan) fuzzy-if(Android,6,8) == async-scrollbar-1-h.html async-scrollbar-1-h-ref.html
 skip-if(!asyncPan) fuzzy-if(Android,6,8) == async-scrollbar-1-vh.html async-scrollbar-1-vh-ref.html
 skip-if(!asyncPan) fuzzy-if(Android,6,8) == async-scrollbar-1-v-rtl.html async-scrollbar-1-v-rtl-ref.html
 skip-if(!asyncPan) fuzzy-if(Android,13,8) == async-scrollbar-1-h-rtl.html async-scrollbar-1-h-rtl-ref.html
-skip-if(!asyncPan) fuzzy-if(Android,8,10) == async-scrollbar-1-vh-rtl.html async-scrollbar-1-vh-rtl-ref.html
+skip-if(!asyncPan) fuzzy-if(Android,8,11) == async-scrollbar-1-vh-rtl.html async-scrollbar-1-vh-rtl-ref.html
 
 # Different async zoom levels. Since the scrollthumb gets async-scaled in the
 # compositor, the border-radius ends of the scrollthumb are going to be a little
 # off, hence the fuzzy-if clauses.
 skip-if(!asyncZoom) fuzzy-if(B2G,98,82) == async-scrollbar-zoom-1.html async-scrollbar-zoom-1-ref.html
 skip-if(!asyncZoom) fuzzy-if(B2G,94,146) == async-scrollbar-zoom-2.html async-scrollbar-zoom-2-ref.html
 
 # Meta-viewport tag support
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -475,17 +475,20 @@ APZCCallbackHelper::DispatchSynthesizedM
   event.mTime = aTime;
   event.button = WidgetMouseEvent::eLeftButton;
   event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
   event.mIgnoreRootScrollFrame = true;
   if (aMsg != eMouseMove) {
     event.mClickCount = 1;
   }
   event.mModifiers = aModifiers;
-
+  // Real touch events will generate corresponding pointer events. We set
+  // convertToPointer to false to prevent the synthesized mouse events generate
+  // pointer events again.
+  event.convertToPointer = false;
   return DispatchWidgetEvent(event);
 }
 
 bool
 APZCCallbackHelper::DispatchMouseEvent(const nsCOMPtr<nsIPresShell>& aPresShell,
                                        const nsString& aType,
                                        const CSSPoint& aPoint,
                                        int32_t aButton,
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -5,16 +5,17 @@
 
 #include "BasicCompositor.h"
 #include "BasicLayersImpl.h"            // for FillRectWithMask
 #include "TextureHostBasic.h"
 #include "mozilla/layers/Effects.h"
 #include "nsIWidget.h"
 #include "gfx2DGlue.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/Helpers.h"
 #include "mozilla/gfx/Tools.h"
 #include "mozilla/gfx/ssse3-scaler.h"
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/SSE.h"
 #include "gfxUtils.h"
 #include "YCbCrUtils.h"
 #include <algorithm>
@@ -179,18 +180,17 @@ public:
 };
 
 BasicCompositor::BasicCompositor(CompositorBridgeParent* aParent, widget::CompositorWidget* aWidget)
   : Compositor(aWidget, aParent)
   , mDidExternalComposition(false)
 {
   MOZ_COUNT_CTOR(BasicCompositor);
 
-  mMaxTextureSize =
-    Factory::GetMaxSurfaceSize(gfxPlatform::GetPlatform()->GetContentBackendFor(LayersBackend::LAYERS_BASIC));
+  mMaxTextureSize = Factory::GetMaxSurfaceSize(gfxVars::ContentBackend());
 }
 
 BasicCompositor::~BasicCompositor()
 {
   MOZ_COUNT_DTOR(BasicCompositor);
 }
 
 bool
--- a/gfx/layers/client/ClientTiledPaintedLayer.cpp
+++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp
@@ -7,16 +7,17 @@
 #include "Units.h"                      // for ScreenIntRect, CSSPoint, etc
 #include "UnitTransforms.h"             // for TransformTo
 #include "ClientLayerManager.h"         // for ClientLayerManager, etc
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "gfxRect.h"                    // for gfxRect
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/Rect.h"           // for Rect, RectTyped
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
 #include "mozilla/layers/LayersMessages.h"
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "LayersLogging.h"
 #include "mozilla/layers/SingleTiledContentClient.h"
@@ -418,18 +419,17 @@ ClientTiledPaintedLayer::EndPaint()
 void
 ClientTiledPaintedLayer::RenderLayer()
 {
   LayerManager::DrawPaintedLayerCallback callback =
     ClientManager()->GetPaintedLayerCallback();
   void *data = ClientManager()->GetPaintedLayerCallbackData();
 
   IntSize layerSize = mVisibleRegion.ToUnknownRegion().GetBounds().Size();
-  IntSize tileSize(gfxPlatform::GetPlatform()->GetTileWidth(),
-                   gfxPlatform::GetPlatform()->GetTileHeight());
+  IntSize tileSize = gfx::gfxVars::TileSize();
   bool isHalfTileWidthOrHeight = layerSize.width <= tileSize.width / 2 ||
     layerSize.height <= tileSize.height / 2;
 
   // Use single tile when layer is not scrollable, is smaller than one
   // tile, or when more than half of the tiles' pixels in either
   // dimension would be wasted.
   bool wantSingleTiledContentClient =
       (mCreationHint == LayerManager::NONE ||
@@ -471,19 +471,17 @@ ClientTiledPaintedLayer::RenderLayer()
   // This is handled by PadDrawTargetOutFromRegion in TiledContentClient for mobile
   if (MayResample()) {
     // If we're resampling then bilinear filtering can read up to 1 pixel
     // outside of our texture coords. Make the visible region a single rect,
     // and pad it out by 1 pixel (restricted to tile boundaries) so that
     // we always have valid content or transparent pixels to sample from.
     IntRect bounds = neededRegion.GetBounds();
     IntRect wholeTiles = bounds;
-    wholeTiles.InflateToMultiple(IntSize(
-      gfxPlatform::GetPlatform()->GetTileWidth(),
-      gfxPlatform::GetPlatform()->GetTileHeight()));
+    wholeTiles.InflateToMultiple(gfx::gfxVars::TileSize());
     IntRect padded = bounds;
     padded.Inflate(1);
     padded.IntersectRect(padded, wholeTiles);
     neededRegion = padded;
   }
 #endif
 
   nsIntRegion invalidRegion;
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -74,17 +74,17 @@ ContentClient::CreateContentClient(Compo
     useDoubleBuffering = gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend();
   } else
 #endif
 #ifdef MOZ_WIDGET_GTK
   // We can't use double buffering when using image content with
   // Xrender support on Linux, as ContentHostDoubleBuffered is not
   // suited for direct uploads to the server.
   if (!gfxPlatformGtk::GetPlatform()->UseImageOffscreenSurfaces() ||
-      !gfxPlatformGtk::GetPlatform()->UseXRender())
+      !gfxVars::UseXRender())
 #endif
   {
     useDoubleBuffering = (LayerManagerComposite::SupportsDirectTexturing() &&
                          backend != LayersBackend::LAYERS_D3D9) ||
                          backend == LayersBackend::LAYERS_BASIC;
   }
 
   if (useDoubleBuffering || gfxEnv::ForceDoubleBuffering()) {
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -11,16 +11,17 @@
 #include "ClientLayerManager.h"         // for ClientLayerManager
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/task.h"                  // for NewRunnableMethod, etc
 #include "gfxPrefs.h"
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/PLayerTransactionChild.h"
 #include "mozilla/layers/TextureClient.h"// for TextureClient
 #include "mozilla/layers/TextureClientPool.h"// for TextureClientPool
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "nsAutoPtr.h"
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
 #include "nsIObserver.h"                // for nsIObserver
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl
 #include "nsXULAppAPI.h"                // for XRE_GetIOMessageLoop, etc
@@ -177,21 +178,16 @@ CompositorBridgeChild::InitForContent(En
     NS_RUNTIMEABORT("Couldn't Open() Compositor channel.");
     return false;
   }
 
   child->mCanSend = true;
 
   // We release this ref in DeferredDestroyCompositor.
   sCompositorBridge = child;
-
-  int32_t width;
-  int32_t height;
-  sCompositorBridge->SendGetTileSize(&width, &height);
-  gfxPlatform::GetPlatform()->SetTileSize(width, height);
   return true;
 }
 
 CompositorBridgeParent*
 CompositorBridgeChild::InitSameProcess(widget::CompositorWidget* aWidget,
                                        const uint64_t& aLayerTreeId,
                                        CSSToLayoutDeviceScale aScale,
                                        bool aUseAPZ,
@@ -752,26 +748,16 @@ CompositorBridgeChild::SendFlushRenderin
   MOZ_ASSERT(mCanSend);
   if (!mCanSend) {
     return true;
   }
   return PCompositorBridgeChild::SendFlushRendering();
 }
 
 bool
-CompositorBridgeChild::SendGetTileSize(int32_t* tileWidth, int32_t* tileHeight)
-{
-  MOZ_ASSERT(mCanSend);
-  if (!mCanSend) {
-    return true;
-  }
-  return PCompositorBridgeChild::SendGetTileSize(tileWidth, tileHeight);
-}
-
-bool
 CompositorBridgeChild::SendStartFrameTimeRecording(const int32_t& bufferSize, uint32_t* startIndex)
 {
   MOZ_ASSERT(mCanSend);
   if (!mCanSend) {
     return true;
   }
   return PCompositorBridgeChild::SendStartFrameTimeRecording(bufferSize, startIndex);
 }
@@ -939,18 +925,17 @@ CompositorBridgeChild::GetTexturePool(La
         mTexturePools[i]->GetFormat() == aFormat &&
         mTexturePools[i]->GetFlags() == aFlags) {
       return mTexturePools[i];
     }
   }
 
   mTexturePools.AppendElement(
       new TextureClientPool(aBackend, aFormat,
-                            IntSize(gfxPlatform::GetPlatform()->GetTileWidth(),
-                                    gfxPlatform::GetPlatform()->GetTileHeight()),
+                            gfx::gfxVars::TileSize(),
                             aFlags,
                             gfxPrefs::LayersTileInitialPoolSize(),
                             gfxPrefs::LayersTilePoolUnusedSize(),
                             this));
 
   return mTexturePools.LastElement();
 }
 
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -55,17 +55,16 @@
 #endif
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_ASSERTION, etc
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsIWidget.h"                  // for nsIWidget
 #include "nsTArray.h"                   // for nsTArray
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "nsXULAppAPI.h"                // for XRE_GetIOMessageLoop
-#include "nsIXULRuntime.h"              // for BrowserTabsRemoteAutostart
 #ifdef XP_WIN
 #include "mozilla/layers/CompositorD3D11.h"
 #include "mozilla/layers/CompositorD3D9.h"
 #endif
 #include "GeckoProfiler.h"
 #include "mozilla/ipc/ProtocolTypes.h"
 #include "mozilla/unused.h"
 #include "mozilla/Hal.h"
@@ -816,24 +815,16 @@ CompositorBridgeParent::RecvForcePresent
   // During the shutdown sequence mLayerManager may be null
   if (mLayerManager) {
     mLayerManager->ForcePresent();
   }
   return true;
 }
 
 bool
-CompositorBridgeParent::RecvGetTileSize(int32_t* aWidth, int32_t* aHeight)
-{
-  *aWidth = gfxPlatform::GetPlatform()->GetTileWidth();
-  *aHeight = gfxPlatform::GetPlatform()->GetTileHeight();
-  return true;
-}
-
-bool
 CompositorBridgeParent::RecvNotifyRegionInvalidated(const nsIntRegion& aRegion)
 {
   if (mLayerManager) {
     mLayerManager->AddInvalidRegion(aRegion);
   }
   return true;
 }
 
@@ -1212,17 +1203,17 @@ CompositorBridgeParent::CompositeToTarge
   bool updatePluginsFlag = true;
   AutoResolveRefLayers resolve(mCompositionManager, this,
                                &hasRemoteContent,
                                &updatePluginsFlag);
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   // We do not support plugins in local content. When switching tabs
   // to local pages, hide every plugin associated with the window.
-  if (!hasRemoteContent && BrowserTabsRemoteAutostart() &&
+  if (!hasRemoteContent && gfxVars::BrowserTabsRemoteAutostart() &&
       mCachedPluginData.Length()) {
     Unused << SendHideAllPlugins(GetWidget()->GetWidgetKey());
     mCachedPluginData.Clear();
   }
 #endif
 
   if (aTarget) {
     mLayerManager->BeginTransactionWithDrawTarget(aTarget, *aRect);
@@ -1580,17 +1571,17 @@ CompositorBridgeParent::NewCompositor(co
     if (aBackendHints[i] == LayersBackend::LAYERS_OPENGL) {
       compositor = new CompositorOGL(this,
                                      mWidget,
                                      mEGLSurfaceSize.width,
                                      mEGLSurfaceSize.height,
                                      mUseExternalSurfaceSize);
     } else if (aBackendHints[i] == LayersBackend::LAYERS_BASIC) {
 #ifdef MOZ_WIDGET_GTK
-      if (gfxPlatformGtk::GetPlatform()->UseXRender()) {
+      if (gfxVars::UseXRender()) {
         compositor = new X11BasicCompositor(this, mWidget);
       } else
 #endif
       {
         compositor = new BasicCompositor(this, mWidget);
       }
 #ifdef XP_WIN
     } else if (aBackendHints[i] == LayersBackend::LAYERS_D3D11) {
@@ -2038,23 +2029,16 @@ public:
     }
 
     parent->UpdateVisibleRegion(aCounter, aGuid, aRegion);
     return true;
   }
 
   virtual bool RecvAllPluginsCaptured() override { return true; }
 
-  virtual bool RecvGetTileSize(int32_t* aWidth, int32_t* aHeight) override
-  {
-    *aWidth = gfxPlatform::GetPlatform()->GetTileWidth();
-    *aHeight = gfxPlatform::GetPlatform()->GetTileHeight();
-    return true;
-  }
-
   virtual bool RecvGetFrameUniformity(FrameUniformityData* aOutData) override
   {
     // Don't support calculating frame uniformity on the child process and
     // this is just a stub for now.
     MOZ_ASSERT(false);
     return true;
   }
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -242,18 +242,16 @@ public:
   virtual bool RecvFlushRendering() override;
   virtual bool RecvForcePresent() override;
 
   virtual bool RecvAcknowledgeCompositorUpdate(const uint64_t& aLayersId) override {
     MOZ_ASSERT_UNREACHABLE("This message is only sent cross-process");
     return true;
   }
 
-  virtual bool RecvGetTileSize(int32_t* aWidth, int32_t* aHeight) override;
-
   virtual bool RecvNotifyRegionInvalidated(const nsIntRegion& aRegion) override;
   virtual bool RecvStartFrameTimeRecording(const int32_t& aBufferSize, uint32_t* aOutStartIndex) override;
   virtual bool RecvStopFrameTimeRecording(const uint32_t& aStartIndex, InfallibleTArray<float>* intervals) override;
 
   // Unused for chrome <-> compositor communication (which this class does).
   // @see CrossProcessCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint
   virtual bool RecvRequestNotifyAfterRemotePaint() override { return true; };
 
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -157,20 +157,16 @@ parent:
   // Make sure any pending composites are started immediately and
   // block until they are completed.
   sync FlushRendering();
 
   // Force an additional frame presentation to be executed. This is used to
   // work around a windows presentation bug (See Bug 1232042)
   async ForcePresent();
 
-  // Get the size of the tiles. This number should not change at runtime.
-  sync GetTileSize()
-    returns (int32_t tileWidth, int32_t tileHeight);
-
   sync StartFrameTimeRecording(int32_t bufferSize)
     returns (uint32_t startIndex);
 
   sync StopFrameTimeRecording(uint32_t startIndex)
     returns (float[] intervals);
 
   // layersBackendHints is an ordered list of preffered backends where
   // layersBackendHints[0] is the best backend. If any hints are LayersBackend::LAYERS_NONE
--- a/gfx/qcms/transform_util.h
+++ b/gfx/qcms/transform_util.h
@@ -20,16 +20,17 @@
 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 #ifndef _QCMS_TRANSFORM_UTIL_H
 #define _QCMS_TRANSFORM_UTIL_H
 
 #include <stdlib.h>
+#include <math.h>
 
 #define CLU(table,x,y,z) table[(x*len + y*x_len + z*xy_len)*3]
 
 //XXX: could use a bettername
 typedef uint16_t uint16_fract_t;
 
 float lut_interp_linear(double value, uint16_t *table, int length);
 float lut_interp_linear_float(float value, float *table, int length);
--- a/gfx/skia/skia/src/utils/SkDashPath.cpp
+++ b/gfx/skia/skia/src/utils/SkDashPath.cpp
@@ -181,17 +181,17 @@ public:
         dst->incReserve(n);
 
         // we will take care of the stroking
         rec->setFillStyle();
         return true;
     }
 
     void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const {
-        SkASSERT(d0 < fPathLength);
+        SkASSERT(d0 <= fPathLength);
         // clamp the segment to our length
         if (d1 > fPathLength) {
             d1 = fPathLength;
         }
 
         SkScalar x0 = fPts[0].fX + SkScalarMul(fTangent.fX, d0);
         SkScalar x1 = fPts[0].fX + SkScalarMul(fTangent.fX, d1);
         SkScalar y0 = fPts[0].fY + SkScalarMul(fTangent.fY, d0);
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/SharedBufferManagerChild.h"
 #include "mozilla/layers/ISurfaceAllocator.h"     // for GfxMemoryImageReporter
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 
 #include "mozilla/Logging.h"
 #include "mozilla/Services.h"
 #include "prprf.h"
@@ -478,19 +479,17 @@ MemoryPressureObserver::Observe(nsISuppo
     gfxGradientCache::PurgeAllCaches();
 
     gfxPlatform::PurgeSkiaFontCache();
     gfxPlatform::GetPlatform()->PurgeSkiaGPUCache();
     return NS_OK;
 }
 
 gfxPlatform::gfxPlatform()
-  : mTileWidth(-1)
-  , mTileHeight(-1)
-  , mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
+  : mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
   , mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo)
   , mTilesInfoCollector(this, &gfxPlatform::GetTilesSupportInfo)
   , mCompositorBackend(layers::LayersBackend::LAYERS_NONE)
   , mScreenDepth(0)
   , mDeviceCounter(0)
 {
     mAllowDownloadableFonts = UNINITIALIZED_VALUE;
     mFallbackUsesCmaps = UNINITIALIZED_VALUE;
@@ -588,16 +587,17 @@ gfxPlatform::Init()
     if (gEverInitialized) {
         NS_RUNTIMEABORT("Already started???");
     }
     gEverInitialized = true;
 
     // Initialize the preferences by creating the singleton.
     gfxPrefs::GetSingleton();
     MediaPrefs::GetSingleton();
+    gfxVars::Initialize();
 
     gfxConfig::Init();
 
     if (XRE_IsParentProcess()) {
       GPUProcessManager::Initialize();
     }
 
     auto fwd = new CrashStatsLogForwarder("GraphicsCriticalError");
@@ -867,16 +867,17 @@ gfxPlatform::Shutdown()
     // delete it.
     delete mozilla::gfx::Factory::GetLogForwarder();
     mozilla::gfx::Factory::SetLogForwarder(nullptr);
 
     gfx::Factory::ShutDown();
 
     delete gGfxPlatformPrefsLock;
 
+    gfxVars::Shutdown();
     gfxPrefs::DestroySingleton();
     gfxFont::DestroySingletons();
 
     gfxConfig::Shutdown();
 
     delete gPlatform;
     gPlatform = nullptr;
 }
@@ -1153,42 +1154,16 @@ gfxPlatform::GetScaledFontForFont(DrawTa
 {
   NativeFont nativeFont;
   nativeFont.mType = NativeFontType::CAIRO_FONT_FACE;
   nativeFont.mFont = aFont->GetCairoScaledFont();
   return Factory::CreateScaledFontForNativeFont(nativeFont,
                                                 aFont->GetAdjustedSize());
 }
 
-int
-gfxPlatform::GetTileWidth()
-{
-  MOZ_ASSERT(mTileWidth != -1);
-  return mTileWidth;
-}
-
-int
-gfxPlatform::GetTileHeight()
-{
-  MOZ_ASSERT(mTileHeight != -1);
-  return mTileHeight;
-}
-
-void
-gfxPlatform::SetTileSize(int aWidth, int aHeight)
-{
-  // Don't allow changing the tile size after we've set it.
-  // Right now the code assumes that the tile size doesn't change.
-  MOZ_ASSERT((mTileWidth == -1 && mTileHeight == -1) ||
-    (mTileWidth == aWidth && mTileHeight == aHeight));
-
-  mTileWidth = aWidth;
-  mTileHeight = aHeight;
-}
-
 void
 gfxPlatform::ComputeTileSize()
 {
   // The tile size should be picked in the parent processes
   // and sent to the child processes over IPDL GetTileSize.
   if (!XRE_IsParentProcess()) {
     return;
   }
@@ -1213,17 +1188,22 @@ gfxPlatform::ComputeTileSize()
                                      android::GraphicBuffer::USAGE_HW_TEXTURE);
 
     if (alloc.get()) {
       w = alloc->getStride(); // We want the tiles to be gralloc stride aligned.
     }
 #endif
   }
 
-  SetTileSize(w, h);
+  // Don't allow changing the tile size after we've set it.
+  // Right now the code assumes that the tile size doesn't change.
+  MOZ_ASSERT(gfxVars::TileSize().width == -1 &&
+             gfxVars::TileSize().height == -1);
+
+  gfxVars::SetTileSize(IntSize(w, h));
 }
 
 void
 gfxPlatform::PopulateScreenInfo()
 {
   nsCOMPtr<nsIScreenManager> manager = do_GetService("@mozilla.org/gfx/screenmanager;1");
   MOZ_ASSERT(manager, "failed to get nsIScreenManager");
 
@@ -1647,16 +1627,20 @@ gfxPlatform::InitBackendPrefs(uint32_t a
     mContentBackend = GetContentBackendPref(mContentBackendBitmask);
     if (mContentBackend == BackendType::NONE) {
         mContentBackend = aContentDefault;
         // mContentBackendBitmask is our canonical reference for supported
         // backends so we need to add the default if we are using it and
         // overriding the prefs.
         mContentBackendBitmask |= BackendTypeBit(aContentDefault);
     }
+
+    if (XRE_IsParentProcess()) {
+        gfxVars::SetContentBackend(mContentBackend);
+    }
 }
 
 /* static */ BackendType
 gfxPlatform::GetCanvasBackendPref(uint32_t aBackendBitmask)
 {
     return GetBackendPref("gfx.canvas.azure.backends", aBackendBitmask);
 }
 
@@ -2090,17 +2074,16 @@ gfxPlatform::OptimalFormatForContent(gfx
  * that should be consistent for the lifetime of the application (bug 840967).
  * As such, we will evaluate them all as soon as one of them is evaluated
  * and remember the values.  Changing these preferences during the run will
  * not have any effect until we restart.
  */
 static mozilla::Atomic<bool> sLayersSupportsHardwareVideoDecoding(false);
 static bool sLayersHardwareVideoDecodingFailed = false;
 static bool sBufferRotationCheckPref = true;
-static bool sPrefBrowserTabsRemoteAutostart = false;
 
 static mozilla::Atomic<bool> sLayersAccelerationPrefsInitialized(false);
 
 void
 gfxPlatform::InitAcceleration()
 {
   if (sLayersAccelerationPrefsInitialized) {
     return;
@@ -2110,17 +2093,20 @@ gfxPlatform::InitAcceleration()
 
   // If this is called for the first time on a non-main thread, we're screwed.
   // At the moment there's no explicit guarantee that the main thread calls
   // this before the compositor thread, but let's at least make the assumption
   // explicit.
   MOZ_ASSERT(NS_IsMainThread(), "can only initialize prefs on the main thread");
 
   gfxPrefs::GetSingleton();
-  sPrefBrowserTabsRemoteAutostart = BrowserTabsRemoteAutostart();
+
+  if (XRE_IsParentProcess()) {
+    gfxVars::SetBrowserTabsRemoteAutostart(BrowserTabsRemoteAutostart());
+  }
 
   nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
   nsCString discardFailureId;
   int32_t status;
 
   if (Preferences::GetBool("media.hardware-video-decoding.enabled", false) &&
 #ifdef XP_WIN
     Preferences::GetBool("media.windows-media-foundation.use-dxva", true) &&
@@ -2252,17 +2238,17 @@ gfxPlatform::GetParentDevicePrefs()
 gfxPlatform::UsesOffMainThreadCompositing()
 {
   static bool firstTime = true;
   static bool result = false;
 
   if (firstTime) {
     MOZ_ASSERT(sLayersAccelerationPrefsInitialized);
     result =
-      sPrefBrowserTabsRemoteAutostart ||
+      gfxVars::BrowserTabsRemoteAutostart() ||
       !gfxPrefs::LayersOffMainThreadCompositionForceDisabled();
 #if defined(MOZ_WIDGET_GTK)
     // Linux users who chose OpenGL are being grandfathered in to OMTC
     result |= gfxPrefs::LayersAccelerationForceEnabledDoNotUseDirectly();
 
 #endif
     firstTime = false;
   }
@@ -2339,18 +2325,19 @@ gfxPlatform::GetApzSupportInfo(mozilla::
 
 void
 gfxPlatform::GetTilesSupportInfo(mozilla::widget::InfoObject& aObj)
 {
   if (!gfxPrefs::LayersTilesEnabled()) {
     return;
   }
 
-  aObj.DefineProperty("TileHeight", mTileHeight);
-  aObj.DefineProperty("TileWidth", mTileWidth);
+  IntSize tileSize = gfxVars::TileSize();
+  aObj.DefineProperty("TileHeight", tileSize.height);
+  aObj.DefineProperty("TileWidth", tileSize.width);
 }
 
 /*static*/ bool
 gfxPlatform::AsyncPanZoomEnabled()
 {
 #if !defined(MOZ_B2G) && !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_UIKIT)
   // For XUL applications (everything but B2G on mobile and desktop, and
   // Firefox on Android) we only want to use APZ when E10S is enabled. If
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -111,16 +111,18 @@ GetBackendName(mozilla::gfx::BackendType
       case mozilla::gfx::BackendType::SKIA:
         return "skia";
       case mozilla::gfx::BackendType::RECORDING:
         return "recording";
       case mozilla::gfx::BackendType::DIRECT2D1_1:
         return "direct2d 1.1";
       case mozilla::gfx::BackendType::NONE:
         return "none";
+      case mozilla::gfx::BackendType::BACKEND_LAST:
+        return "invalid";
   }
   MOZ_CRASH("Incomplete switch");
 }
 
 enum class DeviceResetReason
 {
   OK = 0,
   HUNG,
@@ -309,20 +311,16 @@ public:
      * Fill aListOfFonts with the results of querying the list of font names
      * that correspond to the given language group or generic font family
      * (or both, or neither).
      */
     virtual nsresult GetFontList(nsIAtom *aLangGroup,
                                  const nsACString& aGenericFamily,
                                  nsTArray<nsString>& aListOfFonts);
 
-    int GetTileWidth();
-    int GetTileHeight();
-    void SetTileSize(int aWidth, int aHeight);
-
     /**
      * Rebuilds the any cached system font lists
      */
     virtual nsresult UpdateFontList();
 
     /**
      * Create the platform font-list object (gfxPlatformFontList concrete subclass).
      * This function is responsible to create the appropriate subclass of
@@ -801,19 +799,16 @@ private:
     mozilla::gfx::BackendType mPreferredCanvasBackend;
     // The fallback draw target backend to use for canvas, if the preferred backend fails
     mozilla::gfx::BackendType mFallbackCanvasBackend;
     // The backend to use for content
     mozilla::gfx::BackendType mContentBackend;
     // Bitmask of backend types we can use to render content
     uint32_t mContentBackendBitmask;
 
-    int mTileWidth;
-    int mTileHeight;
-
     mozilla::widget::GfxInfoCollector<gfxPlatform> mAzureCanvasBackendCollector;
     mozilla::widget::GfxInfoCollector<gfxPlatform> mApzSupportCollector;
     mozilla::widget::GfxInfoCollector<gfxPlatform> mTilesInfoCollector;
 
     RefPtr<mozilla::gfx::DrawEventRecorder> mRecorder;
     RefPtr<mozilla::gl::SkiaGLGlue> mSkiaGlue;
 
     // Backend that we are compositing with. NONE, if no compositor has been
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -68,36 +68,37 @@ using namespace mozilla::gfx;
 using namespace mozilla::unicode;
 
 gfxFontconfigUtils *gfxPlatformGtk::sFontconfigUtils = nullptr;
 
 #if (MOZ_WIDGET_GTK == 2)
 static cairo_user_data_key_t cairo_gdk_drawable_key;
 #endif
 
-#ifdef MOZ_X11
-    bool gfxPlatformGtk::sUseXRender = true;
-#endif
-
 bool gfxPlatformGtk::sUseFcFontList = false;
 
 gfxPlatformGtk::gfxPlatformGtk()
 {
     gtk_init(nullptr, nullptr);
 
     sUseFcFontList = mozilla::Preferences::GetBool("gfx.font_rendering.fontconfig.fontlist.enabled");
     if (!sUseFcFontList && !sFontconfigUtils) {
         sFontconfigUtils = gfxFontc