Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 07 Nov 2016 13:57:04 -0800
changeset 351402 000dc91517d648344729bc8764aee1cca8e91e77
parent 351381 060f80b690b8aaa5d927e03578673a3eff3b4c64 (current diff)
parent 351401 09c6816c1341232382eabfdeab220f79c6fb3ba6 (diff)
child 351438 86f702229e32c6119d092e86431afee576f033a1
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
Merge autoland to central, a=merge
testing/web-platform/meta/media-source/mediasource-errors.html.ini
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/ext-sessions.js
@@ -0,0 +1,44 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+                                  "resource:///modules/sessionstore/SessionStore.jsm");
+
+function getRecentlyClosed(maxResults, extension) {
+  let recentlyClosed = [];
+
+  // Get closed windows
+  let closedWindowData = SessionStore.getClosedWindowData(false);
+  for (let window of closedWindowData) {
+    recentlyClosed.push({
+      lastModified: window.closedAt,
+      window: WindowManager.convertFromSessionStoreClosedData(window, extension)});
+  }
+
+  // Get closed tabs
+  for (let window of WindowListManager.browserWindows()) {
+    let closedTabData = SessionStore.getClosedTabData(window, false);
+    for (let tab of closedTabData) {
+      recentlyClosed.push({
+        lastModified: tab.closedAt,
+        tab: TabManager.for(extension).convertFromSessionStoreClosedData(tab, window)});
+    }
+  }
+
+  // Sort windows and tabs
+  recentlyClosed.sort((a, b) => b.lastModified - a.lastModified);
+  return recentlyClosed.slice(0, maxResults);
+}
+
+extensions.registerSchemaAPI("sessions", "addon_parent", context => {
+  let {extension} = context;
+  return {
+    sessions: {
+      getRecentlyClosed: function(filter) {
+        let maxResults = filter.maxResults == undefined ? this.MAX_SESSION_RESULTS : filter.maxResults;
+        return Promise.resolve(getRecentlyClosed(maxResults, extension));
+      },
+    },
+  };
+});
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -660,16 +660,33 @@ ExtensionTabManager.prototype = {
       if (icon) {
         result.favIconUrl = icon;
       }
     }
 
     return result;
   },
 
+  // Converts tabs returned from SessionStore.getClosedTabData and
+  // SessionStore.getClosedWindowData into API tab objects
+  convertFromSessionStoreClosedData(tab, window) {
+    let result = {
+      sessionId: String(tab.closedId),
+      index: tab.pos ? tab.pos : 0,
+      windowId: WindowManager.getId(window),
+      selected: false,
+      highlighted: false,
+      active: false,
+      pinned: false,
+      incognito: Boolean(tab.state && tab.state.isPrivate),
+    };
+
+    return result;
+  },
+
   getTabs(window) {
     return Array.from(window.gBrowser.tabs)
                 .filter(tab => !tab.closing)
                 .map(tab => this.convert(tab));
   },
 };
 
 // Sends the tab and windowId upon request. This is primarily used to support
@@ -907,16 +924,29 @@ global.WindowManager = {
     for (let window of WindowListManager.browserWindows(true)) {
       if (this.getId(window) == id) {
         return window;
       }
     }
     return null;
   },
 
+  getState(window) {
+    const STATES = {
+      [window.STATE_MAXIMIZED]: "maximized",
+      [window.STATE_MINIMIZED]: "minimized",
+      [window.STATE_NORMAL]: "normal",
+    };
+    let state = STATES[window.windowState];
+    if (window.fullScreen) {
+      state = "fullscreen";
+    }
+    return state;
+  },
+
   setState(window, state) {
     if (state != "fullscreen" && window.fullScreen) {
       window.fullScreen = false;
     }
 
     switch (state) {
       case "maximized":
         window.maximize();
@@ -947,50 +977,62 @@ global.WindowManager = {
         break;
 
       default:
         throw new Error(`Unexpected window state: ${state}`);
     }
   },
 
   convert(extension, window, getInfo) {
-    const STATES = {
-      [window.STATE_MAXIMIZED]: "maximized",
-      [window.STATE_MINIMIZED]: "minimized",
-      [window.STATE_NORMAL]: "normal",
-    };
-    let state = STATES[window.windowState];
-    if (window.fullScreen) {
-      state = "fullscreen";
-    }
-
     let xulWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDocShell)
                           .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIXULWindow);
 
     let result = {
       id: this.getId(window),
       focused: window.document.hasFocus(),
       top: window.screenY,
       left: window.screenX,
       width: window.outerWidth,
       height: window.outerHeight,
       incognito: PrivateBrowsingUtils.isWindowPrivate(window),
       type: this.windowType(window),
-      state,
+      state: this.getState(window),
       alwaysOnTop: xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ,
     };
 
     if (getInfo && getInfo.populate) {
       result.tabs = TabManager.for(extension).getTabs(window);
     }
 
     return result;
   },
+
+  // Converts windows returned from SessionStore.getClosedWindowData
+  // into API window objects
+  convertFromSessionStoreClosedData(window, extension) {
+    let result = {
+      sessionId: String(window.closedId),
+      focused: false,
+      incognito: false,
+      type: "normal", // this is always "normal" for a closed window
+      state: this.getState(window),
+      alwaysOnTop: false,
+    };
+
+    if (window.tabs.length) {
+      result.tabs = [];
+      window.tabs.forEach((tab, index) => {
+        result.tabs.push(TabManager.for(extension).convertFromSessionStoreClosedData(tab, window, index));
+      });
+    }
+
+    return result;
+  },
 };
 
 // Manages listeners for window opening and closing. A window is
 // considered open when the "load" event fires on it. A window is
 // closed when a "domwindowclosed" notification fires for it.
 global.WindowListManager = {
   _openListeners: new Set(),
   _closeListeners: new Set(),
--- a/browser/components/extensions/extensions-browser.manifest
+++ b/browser/components/extensions/extensions-browser.manifest
@@ -1,16 +1,17 @@
 # scripts
 category webextension-scripts bookmarks chrome://browser/content/ext-bookmarks.js
 category webextension-scripts browserAction chrome://browser/content/ext-browserAction.js
 category webextension-scripts commands chrome://browser/content/ext-commands.js
 category webextension-scripts contextMenus chrome://browser/content/ext-contextMenus.js
 category webextension-scripts desktop-runtime chrome://browser/content/ext-desktop-runtime.js
 category webextension-scripts history chrome://browser/content/ext-history.js
 category webextension-scripts pageAction chrome://browser/content/ext-pageAction.js
+category webextension-scripts sessions chrome://browser/content/ext-sessions.js
 category webextension-scripts tabs chrome://browser/content/ext-tabs.js
 category webextension-scripts utils chrome://browser/content/ext-utils.js
 category webextension-scripts windows chrome://browser/content/ext-windows.js
 
 # scripts that must run in the same process as addon code.
 category webextension-scripts-addon browserAction chrome://browser/content/ext-c-browserAction.js
 category webextension-scripts-addon contextMenus chrome://browser/content/ext-c-contextMenus.js
 category webextension-scripts-addon pageAction chrome://browser/content/ext-c-pageAction.js
@@ -19,10 +20,11 @@ category webextension-scripts-addon tabs
 # schemas
 category webextension-schemas bookmarks chrome://browser/content/schemas/bookmarks.json
 category webextension-schemas browser_action chrome://browser/content/schemas/browser_action.json
 category webextension-schemas commands chrome://browser/content/schemas/commands.json
 category webextension-schemas context_menus chrome://browser/content/schemas/context_menus.json
 category webextension-schemas context_menus_internal chrome://browser/content/schemas/context_menus_internal.json
 category webextension-schemas history chrome://browser/content/schemas/history.json
 category webextension-schemas page_action chrome://browser/content/schemas/page_action.json
+category webextension-schemas sessions chrome://browser/content/schemas/sessions.json
 category webextension-schemas tabs chrome://browser/content/schemas/tabs.json
 category webextension-schemas windows chrome://browser/content/schemas/windows.json
--- a/browser/components/extensions/jar.mn
+++ b/browser/components/extensions/jar.mn
@@ -14,15 +14,16 @@ browser.jar:
     content/browser/extension.svg
     content/browser/ext-bookmarks.js
     content/browser/ext-browserAction.js
     content/browser/ext-commands.js
     content/browser/ext-contextMenus.js
     content/browser/ext-desktop-runtime.js
     content/browser/ext-history.js
     content/browser/ext-pageAction.js
+    content/browser/ext-sessions.js
     content/browser/ext-tabs.js
     content/browser/ext-utils.js
     content/browser/ext-windows.js
     content/browser/ext-c-browserAction.js
     content/browser/ext-c-contextMenus.js
     content/browser/ext-c-pageAction.js
     content/browser/ext-c-tabs.js
--- a/browser/components/extensions/schemas/jar.mn
+++ b/browser/components/extensions/schemas/jar.mn
@@ -5,10 +5,11 @@
 browser.jar:
     content/browser/schemas/bookmarks.json
     content/browser/schemas/browser_action.json
     content/browser/schemas/commands.json
     content/browser/schemas/context_menus.json
     content/browser/schemas/context_menus_internal.json
     content/browser/schemas/history.json
     content/browser/schemas/page_action.json
+    content/browser/schemas/sessions.json
     content/browser/schemas/tabs.json
     content/browser/schemas/windows.json
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/schemas/sessions.json
@@ -0,0 +1,147 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+  {
+    "namespace": "manifest",
+    "types": [
+      {
+        "$extend": "Permission",
+        "choices": [{
+          "type": "string",
+          "enum": [
+            "sessions"
+          ]
+        }]
+      }
+    ]
+  },
+  {
+    "namespace": "sessions",
+    "description": "Use the <code>chrome.sessions</code> API to query and restore tabs and windows from a browsing session.",
+    "permissions": ["sessions"],
+    "types": [
+      {
+        "id": "Filter",
+        "type": "object",
+        "properties": {
+          "maxResults": {
+            "type": "integer",
+            "minimum": 0,
+            "maximum": 25,
+            "optional": true,
+            "description": "The maximum number of entries to be fetched in the requested list. Omit this parameter to fetch the maximum number of entries ($(ref:sessions.MAX_SESSION_RESULTS))."
+          }
+        }
+      },
+      {
+        "id": "Session",
+        "type": "object",
+        "properties": {
+          "lastModified": {"type": "integer", "description": "The time when the window or tab was closed or modified, represented in milliseconds since the epoch."},
+          "tab": {"$ref": "tabs.Tab", "optional": true, "description": "The $(ref:tabs.Tab), if this entry describes a tab. Either this or $(ref:sessions.Session.window) will be set."},
+          "window": {"$ref": "windows.Window", "optional": true, "description": "The $(ref:windows.Window), if this entry describes a window. Either this or $(ref:sessions.Session.tab) will be set."}
+        }
+      },
+      {
+        "id": "Device",
+        "type": "object",
+        "properties": {
+          "info": {"type": "string"},
+          "deviceName": {"type": "string", "description": "The name of the foreign device."},
+          "sessions": {"type": "array", "items": {"$ref": "Session"}, "description": "A list of open window sessions for the foreign device, sorted from most recently to least recently modified session."}
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "getRecentlyClosed",
+        "type": "function",
+        "description": "Gets the list of recently closed tabs and/or windows.",
+        "async": "callback",
+        "parameters": [
+          {
+            "$ref": "Filter",
+            "name": "filter",
+            "optional": true,
+            "default": {}
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "sessions", "type": "array", "items": { "$ref": "Session" }, "description": "The list of closed entries in reverse order that they were closed (the most recently closed tab or window will be at index <code>0</code>). The entries may contain either tabs or windows."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getDevices",
+        "unsupported": true,
+        "type": "function",
+        "description": "Retrieves all devices with synced sessions.",
+        "async": "callback",
+        "parameters": [
+          {
+            "$ref": "Filter",
+            "name": "filter",
+            "optional": true
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "devices", "type": "array", "items": { "$ref": "Device" }, "description": "The list of $(ref:sessions.Device) objects for each synced session, sorted in order from device with most recently modified session to device with least recently modified session. $(ref:tabs.Tab) objects are sorted by recency in the $(ref:windows.Window) of the $(ref:sessions.Session) objects."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "restore",
+        "unsupported": true,
+        "type": "function",
+        "description": "Reopens a $(ref:windows.Window) or $(ref:tabs.Tab), with an optional callback to run when the entry has been restored.",
+        "async": "callback",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "sessionId",
+            "optional": true,
+            "description": "The $(ref:windows.Window.sessionId), or $(ref:tabs.Tab.sessionId) to restore. If this parameter is not specified, the most recently closed session is restored."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "$ref": "Session",
+                "name": "restoredSession",
+                "description": "A $(ref:sessions.Session) containing the restored $(ref:windows.Window) or $(ref:tabs.Tab) object."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onChanged",
+        "unsupported": true,
+        "description": "Fired when recently closed tabs and/or windows are changed. This event does not monitor synced sessions changes.",
+        "type": "function"
+      }
+    ],
+    "properties": {
+      "MAX_SESSION_RESULTS": {
+        "value": 25,
+        "description": "The maximum number of $(ref:sessions.Session) that will be included in a requested list."
+      }
+    }
+  }
+]
--- a/browser/components/extensions/schemas/windows.json
+++ b/browser/components/extensions/schemas/windows.json
@@ -73,17 +73,16 @@
             "optional": true,
             "description": "The state of this browser window."
           },
           "alwaysOnTop": {
             "type": "boolean",
             "description": "Whether the window is set to be always on top."
           },
           "sessionId": {
-            "unsupported": true,
             "type": "string",
             "optional": true,
             "description": "The session ID used to uniquely identify a Window obtained from the $(ref:sessions) API."
           }
         }
       },
       {
         "id": "CreateType",
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 support-files =
   head.js
   head_pageAction.js
+  head_sessions.js
   context.html
   ctxmenu-image.png
   context_tabs_onUpdated_page.html
   context_tabs_onUpdated_iframe.html
   file_popup_api_injection_a.html
   file_popup_api_injection_b.html
   file_iframe_document.html
   file_iframe_document.sjs
@@ -53,16 +54,18 @@ tags = webextensions
 [browser_ext_popup_api_injection.js]
 [browser_ext_popup_background.js]
 [browser_ext_popup_corners.js]
 [browser_ext_popup_sendMessage.js]
 [browser_ext_popup_shutdown.js]
 [browser_ext_runtime_openOptionsPage.js]
 [browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_runtime_setUninstallURL.js]
+[browser_ext_sessions_getRecentlyClosed.js]
+[browser_ext_sessions_getRecentlyClosed_private.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]
 [browser_ext_tabs_duplicate.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js
@@ -0,0 +1,97 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */
+
+SimpleTest.requestCompleteLog();
+
+Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href,
+                                    this);
+
+add_task(function* test_sessions_get_recently_closed() {
+  function* openAndCloseWindow(url = "http://example.com", tabUrls) {
+    let win = yield BrowserTestUtils.openNewBrowserWindow();
+    yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, url);
+    yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+    if (tabUrls) {
+      for (let url of tabUrls) {
+        yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+      }
+    }
+    yield BrowserTestUtils.closeWindow(win);
+  }
+
+  function background() {
+    Promise.all([
+      browser.sessions.getRecentlyClosed(),
+      browser.tabs.query({active: true, currentWindow: true}),
+    ]).then(([recentlyClosed, tabs]) => {
+      browser.test.sendMessage("initialData", {recentlyClosed, currentWindowId: tabs[0].windowId});
+    });
+
+    browser.test.onMessage.addListener((msg, filter) => {
+      if (msg == "check-sessions") {
+        browser.sessions.getRecentlyClosed(filter).then(recentlyClosed => {
+          browser.test.sendMessage("recentlyClosed", recentlyClosed);
+        });
+      }
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["sessions", "tabs"],
+    },
+    background,
+  });
+
+  // Open and close a window that will be ignored, to prove that we are removing previous entries
+  yield openAndCloseWindow();
+
+  yield extension.startup();
+
+  let {recentlyClosed, currentWindowId} = yield extension.awaitMessage("initialData");
+  recordInitialTimestamps(recentlyClosed.map(item => item.lastModified));
+
+  yield openAndCloseWindow();
+  extension.sendMessage("check-sessions");
+  recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 1, currentWindowId);
+
+  yield openAndCloseWindow("about:config", ["about:robots", "about:mozilla"]);
+  extension.sendMessage("check-sessions");
+  recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  // Check for multiple tabs in most recently closed window
+  is(recentlyClosed[0].window.tabs.length, 3, "most recently closed window has the expected number of tabs");
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+  yield BrowserTestUtils.removeTab(tab);
+
+  tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+  yield BrowserTestUtils.removeTab(tab);
+
+  yield openAndCloseWindow();
+  extension.sendMessage("check-sessions");
+  recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  let finalResult = recentlyClosed.filter(onlyNewItemsFilter);
+  checkRecentlyClosed(finalResult, 5, currentWindowId);
+
+  isnot(finalResult[0].window, undefined, "first item is a window");
+  is(finalResult[0].tab, undefined, "first item is not a tab");
+  isnot(finalResult[1].tab, undefined, "second item is a tab");
+  is(finalResult[1].window, undefined, "second item is not a window");
+  isnot(finalResult[2].tab, undefined, "third item is a tab");
+  is(finalResult[2].window, undefined, "third item is not a window");
+  isnot(finalResult[3].window, undefined, "fourth item is a window");
+  is(finalResult[3].tab, undefined, "fourth item is not a tab");
+  isnot(finalResult[4].window, undefined, "fifth item is a window");
+  is(finalResult[4].tab, undefined, "fifth item is not a tab");
+
+  // test with filter
+  extension.sendMessage("check-sessions", {maxResults: 2});
+  recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 2, currentWindowId);
+
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
@@ -0,0 +1,61 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */
+
+SimpleTest.requestCompleteLog();
+
+Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href,
+                                    this);
+
+add_task(function* test_sessions_get_recently_closed_private() {
+  function background() {
+    browser.test.onMessage.addListener((msg, filter) => {
+      if (msg == "check-sessions") {
+        browser.sessions.getRecentlyClosed(filter).then(recentlyClosed => {
+          browser.test.sendMessage("recentlyClosed", recentlyClosed);
+        });
+      }
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["sessions", "tabs"],
+    },
+    background,
+  });
+
+  // Open a private browsing window.
+  let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+  yield extension.startup();
+
+  let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  let privateWinId = WindowManager.getId(privateWin);
+
+  extension.sendMessage("check-sessions");
+  let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  recordInitialTimestamps(recentlyClosed.map(item => item.lastModified));
+
+  // Open and close two tabs in the private window
+  let tab = yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, "http://example.com");
+  yield BrowserTestUtils.removeTab(tab);
+
+  tab = yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, "http://example.com");
+  yield BrowserTestUtils.removeTab(tab);
+
+  extension.sendMessage("check-sessions");
+  recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 2, privateWinId, true);
+
+  // Close the private window.
+  yield BrowserTestUtils.closeWindow(privateWin);
+
+  extension.sendMessage("check-sessions");
+  recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  is(recentlyClosed.filter(onlyNewItemsFilter).length, 0, "the closed private window info was not found in recently closed data");
+
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/head_sessions.js
@@ -0,0 +1,47 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* exported recordInitialTimestamps onlyNewItemsFilter checkRecentlyClosed */
+
+let initialTimestamps = [];
+
+function recordInitialTimestamps(timestamps) {
+  initialTimestamps = timestamps;
+}
+
+function onlyNewItemsFilter(item) {
+  return !initialTimestamps.includes(item.lastModified);
+}
+
+function checkWindow(window) {
+  for (let prop of ["focused", "incognito", "alwaysOnTop"]) {
+    is(window[prop], false, `closed window has the expected value for ${prop}`);
+  }
+  for (let prop of ["state", "type"]) {
+    is(window[prop], "normal", `closed window has the expected value for ${prop}`);
+  }
+}
+
+function checkTab(tab, windowId, incognito) {
+  for (let prop of ["selected", "highlighted", "active", "pinned"]) {
+    is(tab[prop], false, `closed tab has the expected value for ${prop}`);
+  }
+  is(tab.windowId, windowId, "closed tab has the expected value for windowId");
+  is(tab.incognito, incognito, "closed tab has the expected value for incognito");
+}
+
+function checkRecentlyClosed(recentlyClosed, expectedCount, windowId, incognito = false) {
+  let sessionIds = new Set();
+  is(recentlyClosed.length, expectedCount, "the expected number of closed tabs/windows was found");
+  for (let item of recentlyClosed) {
+    if (item.window) {
+      sessionIds.add(item.window.sessionId);
+      checkWindow(item.window);
+    } else if (item.tab) {
+      sessionIds.add(item.tab.sessionId);
+      checkTab(item.tab, windowId, incognito);
+    }
+  }
+  is(sessionIds.size, expectedCount, "each item has a unique sessionId");
+}
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1017,16 +1017,19 @@ void HTMLMediaElement::AbortExistingLoad
   }
 
   mEventDeliveryPaused = false;
   mPendingEvents.Clear();
 }
 
 void HTMLMediaElement::NoSupportedMediaSourceError(const nsACString& aErrorDetails)
 {
+  if (mDecoder) {
+    ShutdownDecoder();
+  }
   mError = new MediaError(this, MEDIA_ERR_SRC_NOT_SUPPORTED, aErrorDetails);
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
   DispatchAsyncEvent(NS_LITERAL_STRING("error"));
   ChangeDelayLoadStatus(false);
   UpdateAudioChannelPlayingState();
   OpenUnsupportedMediaWithExtenalAppIfNeeded();
 }
 
@@ -4408,36 +4411,30 @@ void HTMLMediaElement::FirstFrameLoaded(
       mPreloadAction == HTMLMediaElement::PRELOAD_METADATA) {
     mSuspendedAfterFirstFrame = true;
     mDecoder->Suspend();
   }
 }
 
 void HTMLMediaElement::NetworkError()
 {
-  if (mDecoder) {
-    ShutdownDecoder();
-  }
   if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
     NoSupportedMediaSourceError();
   } else {
     Error(MEDIA_ERR_NETWORK);
   }
 }
 
 void HTMLMediaElement::DecodeError(const MediaResult& aError)
 {
   nsAutoString src;
   GetCurrentSrc(src);
   const char16_t* params[] = { src.get() };
   ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
 
-  if (mDecoder) {
-    ShutdownDecoder();
-  }
   AudioTracks()->EmptyTracks();
   VideoTracks()->EmptyTracks();
   if (mIsLoadingFromSourceChildren) {
     mError = nullptr;
     if (mSourceLoadCandidate) {
       DispatchAsyncSourceError(mSourceLoadCandidate);
       QueueLoadFromSourceTask();
     } else {
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -891,26 +891,24 @@ MediaDecoder::FirstFrameLoaded(nsAutoPtr
 }
 
 void
 MediaDecoder::NetworkError()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!IsShutdown());
   mOwner->NetworkError();
-  MOZ_ASSERT(IsShutdown());
 }
 
 void
 MediaDecoder::DecodeError(const MediaResult& aError)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!IsShutdown());
   mOwner->DecodeError(aError);
-  MOZ_ASSERT(IsShutdown());
 }
 
 void
 MediaDecoder::UpdateSameOriginStatus(bool aSameOrigin)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mSameOriginMedia = aSameOrigin;
 }
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -84,33 +84,32 @@ MP4Decoder::CanHandleMediaType(const Med
   if (!IsEnabled()) {
     return false;
   }
 
   // Whitelist MP4 types, so they explicitly match what we encounter on
   // the web, as opposed to what we use internally (i.e. what our demuxers
   // etc output).
   const bool isMP4Audio = aType.GetMIMEType().EqualsASCII("audio/mp4") ||
-                          aType.GetMIMEType().EqualsASCII("audio/x-m4a") ||
-                          aType.GetMIMEType().EqualsASCII("audio/opus");
+                          aType.GetMIMEType().EqualsASCII("audio/x-m4a");
   const bool isMP4Video =
   // On B2G, treat 3GPP as MP4 when Gonk PDM is available.
 #ifdef MOZ_GONK_MEDIACODEC
       aType.GetMIMEType().EqualsASCII(VIDEO_3GPP) ||
 #endif
       aType.GetMIMEType().EqualsASCII("video/mp4") ||
       aType.GetMIMEType().EqualsASCII("video/quicktime") ||
       aType.GetMIMEType().EqualsASCII("video/x-m4v");
   if (!isMP4Audio && !isMP4Video) {
     return false;
   }
 
   nsTArray<UniquePtr<TrackInfo>> trackInfos;
   if (aType.GetCodecs().IsEmpty()) {
-    // No codecs specified. Assume AAC/H.264
+    // No codecs specified. Assume H.264
     if (isMP4Audio) {
       trackInfos.AppendElement(
         CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
           NS_LITERAL_CSTRING("audio/mp4a-latm"), aType));
     } else {
       MOZ_ASSERT(isMP4Video);
       trackInfos.AppendElement(
         CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
@@ -131,16 +130,28 @@ MP4Decoder::CanHandleMediaType(const Med
         continue;
       }
       if (codec.EqualsLiteral("mp3")) {
         trackInfos.AppendElement(
           CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
             NS_LITERAL_CSTRING("audio/mpeg"), aType));
         continue;
       }
+      if (codec.EqualsLiteral("opus")) {
+        trackInfos.AppendElement(
+          CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+            NS_LITERAL_CSTRING("audio/opus"), aType));
+        continue;
+      }
+      if (codec.EqualsLiteral("flac")) {
+        trackInfos.AppendElement(
+          CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
+            NS_LITERAL_CSTRING("audio/flac"), aType));
+        continue;
+      }
       // Note: Only accept H.264 in a video content type, not in an audio
       // content type.
       if (IsWhitelistedH264Codec(codec) && isMP4Video) {
         trackInfos.AppendElement(
           CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
             NS_LITERAL_CSTRING("video/avc"), aType));
         continue;
       }
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -724,20 +724,21 @@ DecodedStream::GetPosition(TimeStamp* aT
   return mStartTime.ref() + mLastOutputTime;
 }
 
 void
 DecodedStream::NotifyOutput(int64_t aTime)
 {
   AssertOwnerThread();
   mLastOutputTime = aTime;
+  int64_t currentTime = GetPosition();
 
   // Remove audio samples that have been played by MSG from the queue.
   RefPtr<MediaData> a = mAudioQueue.PeekFront();
-  for (; a && a->mTime < aTime;) {
+  for (; a && a->mTime < currentTime;) {
     RefPtr<MediaData> releaseMe = mAudioQueue.PopFront();
     a = mAudioQueue.PeekFront();
   }
 }
 
 void
 DecodedStream::ConnectListener()
 {
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -33,45 +33,45 @@ namespace mozilla {
 ContainerParser::ContainerParser(const nsACString& aType)
   : mHasInitData(false)
   , mType(aType)
 {
 }
 
 ContainerParser::~ContainerParser() = default;
 
-bool
+MediaResult
 ContainerParser::IsInitSegmentPresent(MediaByteBuffer* aData)
 {
   MSE_DEBUG(ContainerParser, "aLength=%u [%x%x%x%x]",
             aData->Length(),
             aData->Length() > 0 ? (*aData)[0] : 0,
             aData->Length() > 1 ? (*aData)[1] : 0,
             aData->Length() > 2 ? (*aData)[2] : 0,
             aData->Length() > 3 ? (*aData)[3] : 0);
-  return false;
+  return NS_ERROR_NOT_AVAILABLE;
 }
 
-bool
+MediaResult
 ContainerParser::IsMediaSegmentPresent(MediaByteBuffer* aData)
 {
   MSE_DEBUG(ContainerParser, "aLength=%u [%x%x%x%x]",
             aData->Length(),
             aData->Length() > 0 ? (*aData)[0] : 0,
             aData->Length() > 1 ? (*aData)[1] : 0,
             aData->Length() > 2 ? (*aData)[2] : 0,
             aData->Length() > 3 ? (*aData)[3] : 0);
-  return false;
+  return NS_ERROR_NOT_AVAILABLE;
 }
 
-bool
+MediaResult
 ContainerParser::ParseStartAndEndTimestamps(MediaByteBuffer* aData,
                                             int64_t& aStart, int64_t& aEnd)
 {
-  return false;
+  return NS_ERROR_NOT_AVAILABLE;
 }
 
 bool
 ContainerParser::TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
 {
   return llabs(aLhs - aRhs) <= GetRoundingError();
 }
 
@@ -118,85 +118,90 @@ public:
     : ContainerParser(aType)
     , mParser(0)
     , mOffset(0)
   {}
 
   static const unsigned NS_PER_USEC = 1000;
   static const unsigned USEC_PER_SEC = 1000000;
 
-  bool IsInitSegmentPresent(MediaByteBuffer* aData) override
+  MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override
   {
     ContainerParser::IsInitSegmentPresent(aData);
     // XXX: This is overly primitive, needs to collect data as it's appended
     // to the SB and handle, rather than assuming everything is present in a
     // single aData segment.
     // 0x1a45dfa3 // EBML
     // ...
     // DocType == "webm"
     // ...
     // 0x18538067 // Segment (must be "unknown" size or contain a value large
                   // enough to include the Segment Information and Tracks
                   // elements that follow)
     // 0x1549a966 // -> Segment Info
     // 0x1654ae6b // -> One or more Tracks
 
     // 0x1a45dfa3 // EBML
-    if (aData->Length() >= 4 &&
-        (*aData)[0] == 0x1a && (*aData)[1] == 0x45 && (*aData)[2] == 0xdf &&
+    if (aData->Length() < 4) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+    if ((*aData)[0] == 0x1a && (*aData)[1] == 0x45 && (*aData)[2] == 0xdf &&
         (*aData)[3] == 0xa3) {
-      return true;
+      return NS_OK;
     }
-    return false;
+    return MediaResult(NS_ERROR_FAILURE, RESULT_DETAIL("Invalid webm content"));
   }
 
-  bool IsMediaSegmentPresent(MediaByteBuffer* aData) override
+  MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override
   {
     ContainerParser::IsMediaSegmentPresent(aData);
     // XXX: This is overly primitive, needs to collect data as it's appended
     // to the SB and handle, rather than assuming everything is present in a
     // single aData segment.
     // 0x1a45dfa3 // EBML
     // ...
     // DocType == "webm"
     // ...
     // 0x18538067 // Segment (must be "unknown" size)
     // 0x1549a966 // -> Segment Info
     // 0x1654ae6b // -> One or more Tracks
 
     // 0x1f43b675 // Cluster
-    if (aData->Length() >= 4 &&
-        (*aData)[0] == 0x1f && (*aData)[1] == 0x43 && (*aData)[2] == 0xb6 &&
+    if (aData->Length() < 4) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+    if ((*aData)[0] == 0x1f && (*aData)[1] == 0x43 && (*aData)[2] == 0xb6 &&
         (*aData)[3] == 0x75) {
-      return true;
+      return NS_OK;
     }
     // 0x1c53bb6b // Cues
-    if (aData->Length() >= 4 &&
-        (*aData)[0] == 0x1c && (*aData)[1] == 0x53 && (*aData)[2] == 0xbb &&
+    if ((*aData)[0] == 0x1c && (*aData)[1] == 0x53 && (*aData)[2] == 0xbb &&
         (*aData)[3] == 0x6b) {
-      return true;
+      return NS_OK;
     }
-    return false;
+    return MediaResult(NS_ERROR_FAILURE, RESULT_DETAIL("Invalid webm content"));
   }
 
-  bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
-                                  int64_t& aStart, int64_t& aEnd) override
+  MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+                                         int64_t& aStart,
+                                         int64_t& aEnd) override
   {
-    bool initSegment = IsInitSegmentPresent(aData);
+    bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
 
-    if (mLastMapping && (initSegment || IsMediaSegmentPresent(aData))) {
+    if (mLastMapping &&
+        (initSegment || NS_SUCCEEDED(IsMediaSegmentPresent(aData)))) {
       // The last data contained a complete cluster but we can only detect it
       // now that a new one is starting.
       // We use mOffset as end position to ensure that any blocks not reported
       // by WebMBufferParser are properly skipped.
       mCompleteMediaSegmentRange = MediaByteRange(mLastMapping.ref().mSyncOffset,
                                                   mOffset);
       mLastMapping.reset();
       MSE_DEBUG(WebMContainerParser, "New cluster found at start, ending previous one");
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
 
     if (initSegment) {
       mOffset = 0;
       mParser = WebMBufferedParser(0);
       mOverlappedMapping.Clear();
       mInitData = new MediaByteBuffer();
       mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/webm"));
@@ -218,33 +223,33 @@ public:
     // XXX This is a bit of a hack.  Assume if there are no timecodes
     // present and it's an init segment that it's _just_ an init segment.
     // We should be more precise.
     if (initSegment || !HasCompleteInitData()) {
       if (mParser.mInitEndOffset > 0) {
         MOZ_ASSERT(mParser.mInitEndOffset <= mResource->GetLength());
         if (!mInitData->SetLength(mParser.mInitEndOffset, fallible)) {
           // Super unlikely OOM
-          return false;
+          return NS_ERROR_OUT_OF_MEMORY;
         }
         mCompleteInitSegmentRange = MediaByteRange(0, mParser.mInitEndOffset);
         char* buffer = reinterpret_cast<char*>(mInitData->Elements());
         mResource->ReadFromCache(buffer, 0, mParser.mInitEndOffset);
         MSE_DEBUG(WebMContainerParser, "Stashed init of %u bytes.",
                   mParser.mInitEndOffset);
         mResource = nullptr;
       } else {
         MSE_DEBUG(WebMContainerParser, "Incomplete init found.");
       }
       mHasInitData = true;
     }
     mOffset += aData->Length();
 
     if (mapping.IsEmpty()) {
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
 
     // Calculate media range for first media segment.
 
     // Check if we have a cluster finishing in the current data.
     uint32_t endIdx = mapping.Length() - 1;
     bool foundNewCluster = false;
     while (mapping[0].mSyncOffset != mapping[endIdx].mSyncOffset) {
@@ -260,17 +265,17 @@ public:
     }
 
     // Save parsed blocks for which we do not have all data yet.
     mOverlappedMapping.AppendElements(mapping.Elements() + completeIdx + 1,
                                       mapping.Length() - completeIdx - 1);
 
     if (completeIdx < 0) {
       mLastMapping.reset();
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
 
     if (mCompleteMediaHeaderRange.IsEmpty()) {
       mCompleteMediaHeaderRange = MediaByteRange(mapping[0].mSyncOffset,
                                                  mapping[0].mEndOffset);
     }
 
     if (foundNewCluster && mOffset >= mapping[endIdx].mEndOffset) {
@@ -295,31 +300,31 @@ public:
       previousMapping = mLastMapping;
     }
 
     mLastMapping = Some(mapping[completeIdx]);
 
     if (!previousMapping && completeIdx + 1u >= mapping.Length()) {
       // We have no previous nor next block available,
       // so we can't estimate this block's duration.
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
 
     uint64_t frameDuration = (completeIdx + 1u < mapping.Length())
       ? mapping[completeIdx + 1].mTimecode - mapping[completeIdx].mTimecode
       : mapping[completeIdx].mTimecode - previousMapping.ref().mTimecode;
     aStart = mapping[0].mTimecode / NS_PER_USEC;
     aEnd = (mapping[completeIdx].mTimecode + frameDuration) / NS_PER_USEC;
 
     MSE_DEBUG(WebMContainerParser, "[%lld, %lld] [fso=%lld, leo=%lld, l=%u processedIdx=%u fs=%lld]",
               aStart, aEnd, mapping[0].mSyncOffset,
               mapping[completeIdx].mEndOffset, mapping.Length(), completeIdx,
               mCompleteMediaSegmentRange.mEnd);
 
-    return true;
+    return NS_OK;
   }
 
   int64_t GetRoundingError() override
   {
     int64_t error = mParser.GetTimecodeScale() / NS_PER_USEC;
     return error * 2;
   }
 
@@ -332,49 +337,84 @@ private:
 
 #ifdef MOZ_FMP4
 class MP4ContainerParser : public ContainerParser {
 public:
   explicit MP4ContainerParser(const nsACString& aType)
     : ContainerParser(aType)
   {}
 
-  bool IsInitSegmentPresent(MediaByteBuffer* aData) override
+  MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override
   {
     ContainerParser::IsInitSegmentPresent(aData);
     // Each MP4 atom has a chunk size and chunk type. The root chunk in an MP4
     // file is the 'ftyp' atom followed by a file type. We just check for a
     // vaguely valid 'ftyp' atom.
     AtomParser parser(mType, aData);
-    return parser.StartWithInitSegment();
+    if (!parser.IsValid()) {
+      return MediaResult(
+        NS_ERROR_FAILURE,
+        RESULT_DETAIL("Invalid Box:%s", parser.LastInvalidBox()));
+    }
+    return parser.StartWithInitSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
-  bool IsMediaSegmentPresent(MediaByteBuffer* aData) override
+  MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override
   {
     AtomParser parser(mType, aData);
-    return parser.StartWithMediaSegment();
+    if (!parser.IsValid()) {
+      return MediaResult(
+        NS_ERROR_FAILURE,
+        RESULT_DETAIL("Invalid Box:%s", parser.LastInvalidBox()));
+    }
+    return parser.StartWithMediaSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
   }
 
 private:
   class AtomParser {
   public:
     AtomParser(const nsACString& aType, const MediaByteBuffer* aData)
     {
       const nsCString mType(aType); // for logging macro.
       mp4_demuxer::ByteReader reader(aData);
       mp4_demuxer::AtomType initAtom("ftyp");
       mp4_demuxer::AtomType mediaAtom("moof");
 
+      // Valid top-level boxes defined in ISO/IEC 14496-12 (Table 1)
+      static const mp4_demuxer::AtomType validBoxes[] = {
+        "ftyp", "moov", // init segment
+        "pdin", "free", "sidx", // optional prior moov box
+        "styp", "moof", "mdat", // media segment
+        "mfra", "skip", "meta", "meco", "ssix", "prft" // others.
+        "pssh", // optional with encrypted EME, though ignored.
+      };
+
       while (reader.Remaining() >= 8) {
         uint64_t size = reader.ReadU32();
         const uint8_t* typec = reader.Peek(4);
-        uint32_t type = reader.ReadU32();
+        mp4_demuxer::AtomType type(reader.ReadU32());
         MSE_DEBUGV(AtomParser ,"Checking atom:'%c%c%c%c' @ %u",
                    typec[0], typec[1], typec[2], typec[3],
                    (uint32_t)reader.Offset() - 8);
+
+        for (const auto& boxType : validBoxes) {
+          if (type == boxType) {
+            mValid = true;
+            break;
+          }
+        }
+        if (!mValid) {
+          // No point continuing.
+          mLastInvalidBox[0] = typec[3];
+          mLastInvalidBox[1] = typec[2];
+          mLastInvalidBox[2] = typec[1];
+          mLastInvalidBox[3] = typec[0];
+          mLastInvalidBox[4] = '\0';
+          break;
+        }
         if (mInitOffset.isNothing() &&
             mp4_demuxer::AtomType(type) == initAtom) {
           mInitOffset = Some(reader.Offset());
         }
         if (mMediaOffset.isNothing() &&
             mp4_demuxer::AtomType(type) == mediaAtom) {
           mMediaOffset = Some(reader.Offset());
         }
@@ -396,62 +436,67 @@ private:
         if (reader.Remaining() < size - 8) {
           // Incomplete atom.
           break;
         }
         reader.Read(size - 8);
       }
     }
 
-    bool StartWithInitSegment()
+    bool StartWithInitSegment() const
     {
       return mInitOffset.isSome() &&
         (mMediaOffset.isNothing() || mInitOffset.ref() < mMediaOffset.ref());
     }
-    bool StartWithMediaSegment()
+    bool StartWithMediaSegment() const
     {
       return mMediaOffset.isSome() &&
         (mInitOffset.isNothing() || mMediaOffset.ref() < mInitOffset.ref());
     }
+    bool IsValid() const { return mValid; }
+    const char* LastInvalidBox() const { return mLastInvalidBox; }
   private:
     Maybe<size_t> mInitOffset;
     Maybe<size_t> mMediaOffset;
+    bool mValid = false;
+    char mLastInvalidBox[5];
   };
 
 public:
-  bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
-                                  int64_t& aStart, int64_t& aEnd) override
+  MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+                                         int64_t& aStart,
+                                         int64_t& aEnd) override
   {
-    bool initSegment = IsInitSegmentPresent(aData);
+    bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
     if (initSegment) {
       mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/mp4"));
       mStream = new MP4Stream(mResource);
       // We use a timestampOffset of 0 for ContainerParser, and require
       // consumers of ParseStartAndEndTimestamps to add their timestamp offset
       // manually. This allows the ContainerParser to be shared across different
       // timestampOffsets.
       mParser = new mp4_demuxer::MoofParser(mStream, 0, /* aIsAudio = */ false);
       mInitData = new MediaByteBuffer();
     } else if (!mStream || !mParser) {
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
 
     mResource->AppendData(aData);
     MediaByteRangeSet byteRanges;
     byteRanges +=
       MediaByteRange(int64_t(mParser->mOffset), mResource->GetLength());
     mParser->RebuildFragmentedIndex(byteRanges);
 
     if (initSegment || !HasCompleteInitData()) {
       MediaByteRange& range = mParser->mInitRange;
       if (range.Length()) {
         mCompleteInitSegmentRange = range;
         if (!mInitData->SetLength(range.Length(), fallible)) {
           // Super unlikely OOM
-          return false;
+          return NS_ERROR_OUT_OF_MEMORY;
         }
         char* buffer = reinterpret_cast<char*>(mInitData->Elements());
         mResource->ReadFromCache(buffer, range.mStart, range.Length());
         MSE_DEBUG(MP4ContainerParser ,"Stashed init of %u bytes.",
                   range.Length());
       } else {
         MSE_DEBUG(MP4ContainerParser, "Incomplete init found.");
       }
@@ -464,27 +509,27 @@ public:
     mCompleteMediaHeaderRange = mParser->FirstCompleteMediaHeader();
     mCompleteMediaSegmentRange = mParser->FirstCompleteMediaSegment();
     ErrorResult rv;
     if (HasCompleteInitData()) {
       mResource->EvictData(mParser->mOffset, mParser->mOffset, rv);
     }
     if (NS_WARN_IF(rv.Failed())) {
       rv.SuppressException();
-      return false;
+      return NS_ERROR_OUT_OF_MEMORY;
     }
 
     if (compositionRange.IsNull()) {
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
     aStart = compositionRange.start;
     aEnd = compositionRange.end;
     MSE_DEBUG(MP4ContainerParser, "[%lld, %lld]",
               aStart, aEnd);
-    return true;
+    return NS_OK;
   }
 
   // Gaps of up to 35ms (marginally longer than a single frame at 30fps) are considered
   // to be sequential frames.
   int64_t GetRoundingError() override
   {
     return 35000;
   }
@@ -549,92 +594,93 @@ public:
     // Return successfully parsed data.
     header.header_length = header_length;
     header.frame_length = header_length + data_length;
     header.aac_frames = frames;
     header.have_crc = have_crc;
     return true;
   }
 
-  bool IsInitSegmentPresent(MediaByteBuffer* aData) override
+  MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override
   {
     // Call superclass for logging.
     ContainerParser::IsInitSegmentPresent(aData);
 
     Header header;
     if (!Parse(aData, header)) {
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
 
     MSE_DEBUGV(ADTSContainerParser, "%llu byte frame %d aac frames%s",
         (unsigned long long)header.frame_length, (int)header.aac_frames,
         header.have_crc ? " crc" : "");
 
-    return true;
+    return NS_OK;
   }
 
-  bool IsMediaSegmentPresent(MediaByteBuffer* aData) override
+  MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override
   {
     // Call superclass for logging.
     ContainerParser::IsMediaSegmentPresent(aData);
 
     // Make sure we have a header so we know how long the frame is.
     // NB this assumes the media segment buffer starts with an
     // initialization segment. Since every frame has an ADTS header
     // this is a normal place to divide packets, but we can re-parse
     // mInitData if we need to handle separate media segments.
     Header header;
     if (!Parse(aData, header)) {
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
     // We're supposed to return true as long as aData contains the
     // start of a media segment, whether or not it's complete. So
     // return true if we have any data beyond the header.
     if (aData->Length() <= header.header_length) {
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
 
     // We should have at least a partial frame.
-    return true;
+    return NS_OK;
   }
 
-  bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
-                                  int64_t& aStart, int64_t& aEnd) override
+  MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+                                         int64_t& aStart,
+                                         int64_t& aEnd) override
   {
     // ADTS header.
     Header header;
     if (!Parse(aData, header)) {
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
     mHasInitData = true;
     mCompleteInitSegmentRange = MediaByteRange(0, int64_t(header.header_length));
 
     // Cache raw header in case the caller wants a copy.
     mInitData = new MediaByteBuffer(header.header_length);
     mInitData->AppendElements(aData->Elements(), header.header_length);
 
     // Check that we have enough data for the frame body.
     if (aData->Length() < header.frame_length) {
       MSE_DEBUGV(ADTSContainerParser, "Not enough data for %llu byte frame"
           " in %llu byte buffer.",
           (unsigned long long)header.frame_length,
           (unsigned long long)(aData->Length()));
-      return false;
+      return NS_ERROR_NOT_AVAILABLE;
     }
     mCompleteMediaSegmentRange = MediaByteRange(header.header_length,
                                                 header.frame_length);
     // The ADTS MediaSource Byte Stream Format document doesn't
     // define media header. Just treat it the same as the whole
     // media segment.
     mCompleteMediaHeaderRange = mCompleteMediaSegmentRange;
 
     MSE_DEBUG(ADTSContainerParser, "[%lld, %lld]",
               aStart, aEnd);
     // We don't update timestamps, regardless.
-    return false;
+    return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Audio shouldn't have gaps.
   // Especially when we generate the timestamps ourselves.
   int64_t GetRoundingError() override
   {
     return 0;
   }
--- a/dom/media/mediasource/ContainerParser.h
+++ b/dom/media/mediasource/ContainerParser.h
@@ -5,42 +5,50 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_CONTAINERPARSER_H_
 #define MOZILLA_CONTAINERPARSER_H_
 
 #include "mozilla/RefPtr.h"
 #include "nsString.h"
 #include "MediaResource.h"
+#include "MediaResult.h"
 
 namespace mozilla {
 
 class MediaByteBuffer;
 class SourceBufferResource;
 
 class ContainerParser {
 public:
   explicit ContainerParser(const nsACString& aType);
   virtual ~ContainerParser();
 
   // Return true if aData starts with an initialization segment.
   // The base implementation exists only for debug logging and is expected
   // to be called first from the overriding implementation.
-  virtual bool IsInitSegmentPresent(MediaByteBuffer* aData);
+  // Return NS_OK if segment is present, NS_ERROR_NOT_AVAILABLE if no sufficient
+  // data is currently available to make a determination. Any other value
+  // indicates an error.
+  virtual MediaResult IsInitSegmentPresent(MediaByteBuffer* aData);
 
   // Return true if aData starts with a media segment.
   // The base implementation exists only for debug logging and is expected
   // to be called first from the overriding implementation.
-  virtual bool IsMediaSegmentPresent(MediaByteBuffer* aData);
+  // Return NS_OK if segment is present, NS_ERROR_NOT_AVAILABLE if no sufficient
+  // data is currently available to make a determination. Any other value
+  // indicates an error.
+  virtual MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData);
 
   // Parse aData to extract the start and end frame times from the media
-  // segment.  aData may not start on a parser sync boundary.  Return true
-  // if aStart and aEnd have been updated.
-  virtual bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
-                                          int64_t& aStart, int64_t& aEnd);
+  // segment.  aData may not start on a parser sync boundary.  Return NS_OK
+  // if aStart and aEnd have been updated and NS_ERROR_NOT_AVAILABLE otherwise
+  // when no error were encountered.
+  virtual MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+                                                 int64_t& aStart, int64_t& aEnd);
 
   // Compare aLhs and rHs, considering any error that may exist in the
   // timestamps from the format's base representation.  Return true if aLhs
   // == aRhs within the error epsilon.
   bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs);
 
   virtual int64_t GetRoundingError();
 
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -631,38 +631,56 @@ TrackBuffersManager::SegmentParserLoop()
     // ignored from the start of the input buffer.
     // We do not remove bytes from our input buffer. Instead we enforce that
     // our ContainerParser is able to skip over all data that is supposed to be
     // ignored.
 
     // 4. If the append state equals WAITING_FOR_SEGMENT, then run the following
     // steps:
     if (mSourceBufferAttributes->GetAppendState() == AppendState::WAITING_FOR_SEGMENT) {
-      if (mParser->IsInitSegmentPresent(mInputBuffer)) {
+      MediaResult haveInitSegment = mParser->IsInitSegmentPresent(mInputBuffer);
+      if (NS_SUCCEEDED(haveInitSegment)) {
         SetAppendState(AppendState::PARSING_INIT_SEGMENT);
         if (mFirstInitializationSegmentReceived) {
           // This is a new initialization segment. Obsolete the old one.
           RecreateParser(false);
         }
         continue;
       }
-      if (mParser->IsMediaSegmentPresent(mInputBuffer)) {
+      MediaResult haveMediaSegment =
+        mParser->IsMediaSegmentPresent(mInputBuffer);
+      if (NS_SUCCEEDED(haveMediaSegment)) {
         SetAppendState(AppendState::PARSING_MEDIA_SEGMENT);
         mNewMediaSegmentStarted = true;
         continue;
       }
-      // We have neither an init segment nor a media segment, this is either
-      // invalid data or not enough data to detect a segment type.
-      MSE_DEBUG("Found invalid or incomplete data.");
+      // We have neither an init segment nor a media segment.
+      // Check if it was invalid data.
+      if (haveInitSegment != NS_ERROR_NOT_AVAILABLE) {
+        MSE_DEBUG("Found invalid data.");
+        RejectAppend(haveInitSegment, __func__);
+        return;
+      }
+      if (haveMediaSegment != NS_ERROR_NOT_AVAILABLE) {
+        MSE_DEBUG("Found invalid data.");
+        RejectAppend(haveMediaSegment, __func__);
+        return;
+      }
+      MSE_DEBUG("Found incomplete data.");
       NeedMoreData();
       return;
     }
 
     int64_t start, end;
-    bool newData = mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
+    MediaResult newData =
+      mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
+    if (!NS_SUCCEEDED(newData) && newData.Code() != NS_ERROR_NOT_AVAILABLE) {
+      RejectAppend(newData, __func__);
+      return;
+    }
     mProcessedInput += mInputBuffer->Length();
 
     // 5. If the append state equals PARSING_INIT_SEGMENT, then run the
     // following steps:
     if (mSourceBufferAttributes->GetAppendState() == AppendState::PARSING_INIT_SEGMENT) {
       if (mParser->InitSegmentRange().IsEmpty()) {
         mInputBuffer = nullptr;
         NeedMoreData();
@@ -679,23 +697,23 @@ TrackBuffersManager::SegmentParserLoop()
       }
 
       // We can't feed some demuxers (WebMDemuxer) with data that do not have
       // monotonizally increasing timestamps. So we check if we have a
       // discontinuity from the previous segment parsed.
       // If so, recreate a new demuxer to ensure that the demuxer is only fed
       // monotonically increasing data.
       if (mNewMediaSegmentStarted) {
-        if (newData && mLastParsedEndTime.isSome() &&
+        if (NS_SUCCEEDED(newData) && mLastParsedEndTime.isSome() &&
             start < mLastParsedEndTime.ref().ToMicroseconds()) {
           MSE_DEBUG("Re-creating demuxer");
           ResetDemuxingState();
           return;
         }
-        if (newData || !mParser->MediaSegmentRange().IsEmpty()) {
+        if (NS_SUCCEEDED(newData) || !mParser->MediaSegmentRange().IsEmpty()) {
           if (mPendingInputBuffer) {
             // We now have a complete media segment header. We can resume parsing
             // the data.
             AppendDataToCurrentInputBuffer(mPendingInputBuffer);
             mPendingInputBuffer = nullptr;
           }
           mNewMediaSegmentStarted = false;
         } else {
--- a/dom/media/mediasource/gtest/TestContainerParser.cpp
+++ b/dom/media/mediasource/gtest/TestContainerParser.cpp
@@ -42,47 +42,47 @@ TEST(ContainerParser, ADTSHeader) {
   parser = ContainerParser::CreateForMIMEType(NS_LITERAL_CSTRING("audio/aac"));
   ASSERT_NE(parser, nullptr);
 
   // Audio data should have no gaps.
   EXPECT_EQ(parser->GetRoundingError(), 0);
 
   // Test a valid header.
   RefPtr<MediaByteBuffer> header = make_adts_header();
-  EXPECT_TRUE(parser->IsInitSegmentPresent(header));
+  EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)));
 
   // Test variations.
   uint8_t save = header->ElementAt(1);
   for (uint8_t i = 1; i < 3; ++i) {
     // Set non-zero layer.
     header->ReplaceElementAt(1, (header->ElementAt(1) & 0xf9) | (i << 1));
-    EXPECT_FALSE(parser->IsInitSegmentPresent(header))
+    EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
       << "Accepted non-zero layer in header.";
   }
   header->ReplaceElementAt(1, save);
   save = header->ElementAt(2);
   header->ReplaceElementAt(2, (header->ElementAt(2) & 0x3b) | (15 << 2));
-  EXPECT_FALSE(parser->IsInitSegmentPresent(header))
+  EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
     << "Accepted explicit frequency in header.";
   header->ReplaceElementAt(2, save);
 
   // Test a short header.
   header->SetLength(6);
-  EXPECT_FALSE(parser->IsInitSegmentPresent(header))
+  EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header)))
     << "Accepted too-short header.";
-  EXPECT_FALSE(parser->IsMediaSegmentPresent(header))
+  EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header)))
     << "Found media segment when there was just a partial header.";
 
   // Test parse results.
   header = make_adts_header();
-  EXPECT_FALSE(parser->IsMediaSegmentPresent(header))
+  EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header)))
     << "Found media segment when there was just a header.";
   int64_t start = 0;
   int64_t end = 0;
-  EXPECT_FALSE(parser->ParseStartAndEndTimestamps(header, start, end));
+  EXPECT_TRUE(NS_FAILED(parser->ParseStartAndEndTimestamps(header, start, end)));
 
   EXPECT_TRUE(parser->HasInitData());
   EXPECT_TRUE(parser->HasCompleteInitData());
   MediaByteBuffer* init = parser->InitData();
   ASSERT_NE(init, nullptr);
   EXPECT_EQ(init->Length(), header->Length());
 
   EXPECT_EQ(parser->InitSegmentRange(), MediaByteRange(0, int64_t(header->Length())));
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -219,18 +219,16 @@ H264Converter::CreateDecoderAndInit(Medi
   UpdateConfigFromExtraData(extra_data);
 
   nsresult rv = CreateDecoder(/* DecoderDoctorDiagnostics* */ nullptr);
 
   if (NS_SUCCEEDED(rv)) {
     // Queue the incoming sample.
     mMediaRawSamples.AppendElement(aSample);
 
-    RefPtr<H264Converter> self = this;
-
     mInitPromiseRequest.Begin(mDecoder->Init()
       ->Then(AbstractThread::GetCurrent()->AsTaskQueue(), __func__, this,
              &H264Converter::OnDecoderInitDone,
              &H264Converter::OnDecoderInitFailed));
   }
   return rv;
 }
 
--- a/dom/media/test/test_can_play_type_mpeg.html
+++ b/dom/media/test/test_can_play_type_mpeg.html
@@ -73,16 +73,26 @@ function check_mp4(v, enabled) {
 
   // HE-AAC v1
   check("audio/mp4; codecs=\"mp4a.40.5\"", "probably");
   check("audio/mp4; codecs=mp4a.40.5", "probably");
   check("audio/x-m4a; codecs=\"mp4a.40.5\"", "probably");
   check("audio/x-m4a; codecs=mp4a.40.5", "probably");
   // HE-AAC v2
   check("audio/mp4; codecs=\"mp4a.40.29\"", "probably");
+
+  // Opus
+  check("audio/mp4; codecs=\"opus\"", "probably");
+  check("audio/mp4; codecs=opus", "probably");
+
+  // Flac.
+  // Not available on Android yet.
+  var expectedResult = IsSupportedAndroid() ? "" : "probably";
+  check("audio/mp4; codecs=\"flac\"", expectedResult);
+  check("audio/mp4; codecs=flac", expectedResult);
 }
 
 function check_mp3(v, enabled) {
   function check(type, expected) {
     var ex = enabled ? expected : "";
     is(v.canPlayType(type), ex, type + "='" + ex + "'");
   }
 
--- a/dom/media/webm/WebMBufferedParser.cpp
+++ b/dom/media/webm/WebMBufferedParser.cpp
@@ -28,17 +28,17 @@ VIntLength(unsigned char aFirstByte, uin
   }
   if (aMask) {
     *aMask = mask;
   }
   NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length.");
   return count;
 }
 
-void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
+bool WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
                                 nsTArray<WebMTimeDataOffset>& aMapping,
                                 ReentrantMonitor& aReentrantMonitor)
 {
   static const uint32_t EBML_ID = 0x1a45dfa3;
   static const uint32_t SEGMENT_ID = 0x18538067;
   static const uint32_t SEGINFO_ID = 0x1549a966;
   static const uint32_t TRACKS_ID = 0x1654AE6B;
   static const uint32_t CLUSTER_ID = 0x1f43b675;
@@ -158,17 +158,19 @@ void WebMBufferedParser::Append(const un
         mVInt.mValue <<= 8;
         mVInt.mValue |= *p++;
         mVIntLeft -= 1;
       } else {
         mState = mNextState;
       }
       break;
     case READ_TIMECODESCALE:
-      MOZ_ASSERT(mGotTimecodeScale);
+      if (!mGotTimecodeScale) {
+        return false;
+      }
       mTimecodeScale = mVInt.mValue;
       mState = READ_ELEMENT_ID;
       break;
     case READ_CLUSTER_TIMECODE:
       mClusterTimecode = mVInt.mValue;
       mState = READ_ELEMENT_ID;
       break;
     case READ_BLOCK_TIMECODE:
@@ -182,17 +184,19 @@ void WebMBufferedParser::Append(const un
         {
           ReentrantMonitorAutoEnter mon(aReentrantMonitor);
           int64_t endOffset = mBlockOffset + mBlockSize +
                               mElement.mID.mLength + mElement.mSize.mLength;
           uint32_t idx = aMapping.IndexOfFirstElementGt(endOffset);
           if (idx == 0 || aMapping[idx - 1] != endOffset) {
             // Don't insert invalid negative timecodes.
             if (mBlockTimecode >= 0 || mClusterTimecode >= uint16_t(abs(mBlockTimecode))) {
-              MOZ_ASSERT(mGotTimecodeScale);
+              if (!mGotTimecodeScale) {
+                return false;
+              }
               uint64_t absTimecode = mClusterTimecode + mBlockTimecode;
               absTimecode *= mTimecodeScale;
               // Avoid creating an entry if the timecode is out of order
               // (invalid according to the WebM specification) so that
               // ordering invariants of aMapping are not violated.
               if (idx == 0 ||
                   aMapping[idx - 1].mTimecode <= absTimecode ||
                   (idx + 1 < aMapping.Length() &&
@@ -243,16 +247,18 @@ void WebMBufferedParser::Append(const un
         mState = READ_ELEMENT_ID;
       }
       break;
     }
   }
 
   NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
   mCurrentOffset += aLength;
+
+  return true;
 }
 
 int64_t
 WebMBufferedParser::EndSegmentOffset(int64_t aOffset)
 {
   if (mLastInitStartOffset > aOffset || mClusterOffset > aOffset) {
     return std::min(mLastInitStartOffset >= 0 ? mLastInitStartOffset : INT64_MAX,
                     mClusterOffset >= 0 ? mClusterOffset : INT64_MAX);
--- a/dom/media/webm/WebMBufferedParser.h
+++ b/dom/media/webm/WebMBufferedParser.h
@@ -91,17 +91,18 @@ struct WebMBufferedParser
   void SetTimecodeScale(uint32_t aTimecodeScale) {
     mTimecodeScale = aTimecodeScale;
     mGotTimecodeScale = true;
   }
 
   // Steps the parser through aLength bytes of data.  Always consumes
   // aLength bytes.  Updates mCurrentOffset before returning.  Acquires
   // aReentrantMonitor before using aMapping.
-  void Append(const unsigned char* aBuffer, uint32_t aLength,
+  // Returns false if an error was encountered.
+  bool Append(const unsigned char* aBuffer, uint32_t aLength,
               nsTArray<WebMTimeDataOffset>& aMapping,
               ReentrantMonitor& aReentrantMonitor);
 
   bool operator==(int64_t aOffset) const {
     return mCurrentOffset == aOffset;
   }
 
   bool operator<(int64_t aOffset) const {
--- a/testing/web-platform/meta/encrypted-media/clearkey-mp4-playback-temporary-encrypted-clear.html.ini
+++ b/testing/web-platform/meta/encrypted-media/clearkey-mp4-playback-temporary-encrypted-clear.html.ini
@@ -1,15 +1,15 @@
 [clearkey-mp4-playback-temporary-encrypted-clear.html]
   type: testharness
   expected:
-    if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): ERROR
-    if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): ERROR
-    if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): ERROR
-    if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): ERROR
+    if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
+    if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
+    if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT
+    if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT
   [org.w3.clearkey, temporary, mp4, playback, single key, encrypted then clear content]
     expected:
       if not debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
       if debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
       if debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
       if not debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
       if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
       if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
--- a/testing/web-platform/meta/encrypted-media/clearkey-mp4-playback-temporary-multisession.html.ini
+++ b/testing/web-platform/meta/encrypted-media/clearkey-mp4-playback-temporary-multisession.html.ini
@@ -1,18 +1,8 @@
 [clearkey-mp4-playback-temporary-multisession.html]
   type: testharness
-  expected:
-    if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): ERROR
-    if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): ERROR
-    if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): ERROR
-    if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): ERROR
   [org.w3.clearkey, temporary, mp4, playback with multiple sessions, multikey video]
     expected:
       if not debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
       if debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
       if debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
       if not debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
-      if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
-      if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86") and (bits == 32): TIMEOUT
-      if debug and not e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT
-      if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/media-source/mediasource-errors.html.ini
+++ /dev/null
@@ -1,14 +0,0 @@
-[mediasource-errors.html]
-  type: testharness
-  expected:
-    if debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): CRASH
-    if debug and e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
-  [Signaling 'decode' error via endOfStream() after initialization segment has been appended and the HTMLMediaElement has reached HAVE_METADATA.]
-    expected: FAIL
-
-  [Signaling 'network' error via endOfStream() after initialization segment has been appended and the HTMLMediaElement has reached HAVE_METADATA.]
-    expected: FAIL
-
-  [Signaling 'decode' error via segment parser loop algorithm after initialization segment and partial media segment has been appended.]
-    expected: FAIL
-
--- a/testing/web-platform/tests/media-source/mediasource-errors.html
+++ b/testing/web-platform/tests/media-source/mediasource-errors.html
@@ -169,29 +169,66 @@
             test.step_timeout(test.step_func_done(), 0);
         });
     }, "Signaling 'network' error via endOfStream() after initialization segment has been appended and the HTMLMediaElement has reached HAVE_METADATA.");
 
     ErrorTest(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
     {
         assert_equals(mediaElement.readyState, HTMLMediaElement.HAVE_NOTHING);
 
+        var initSegment = MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.init);
+        test.expectEvent(sourceBuffer, "updateend", "mediaSegment append ended.");
+        test.expectEvent(mediaElement, "loadedmetadata", "mediaElement metadata.");
+        sourceBuffer.appendBuffer(initSegment);
+
+        test.waitForExpectedEvents(function()
+        {
+            assert_equals(mediaElement.readyState, HTMLMediaElement.HAVE_METADATA);
+            var mediaSegment = MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.media[0]);
+            var index = segmentInfo.init.size + (mediaSegment.length - 1) / 2;
+            // Corrupt the media data from index of mediaData, so it can signal 'decode' error.
+            // Here use mediaSegment to replace the original mediaData[index, index + mediaSegment.length]
+            mediaData.set(mediaSegment, index);
+
+            test.expectEvent(sourceBuffer, "error", "sourceBuffer error.");
+            test.expectEvent(sourceBuffer, "updateend", "mediaSegment append ended.");
+            test.expectEvent(mediaElement, "error", "mediaElement error.");
+            test.expectEvent(mediaSource, "sourceended", "mediaSource ended.");
+            sourceBuffer.appendBuffer(mediaData);
+        });
+
+        test.waitForExpectedEvents(function()
+        {
+            assert_true(mediaElement.error != null);
+            assert_equals(mediaElement.error.code, MediaError.MEDIA_ERR_DECODE);
+            test.done();
+        });
+    }, "Signaling 'decode' error via segment parser loop algorithm after initialization segment has been appended.");
+
+    ErrorTest(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
+    {
+        assert_equals(mediaElement.readyState, HTMLMediaElement.HAVE_NOTHING);
+
+        // Fail if the append error algorithm occurs, since the network
+        // error will be provided by us directly via endOfStream().
+        mediaElement.addEventListener("loadedmetadata", test.unreached_func("'loadedmetadata' should not be fired on mediaElement"));
+
         var mediaSegment = MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.media[0]);
         var index = segmentInfo.init.size + (mediaSegment.length - 1) / 2;
         // Corrupt the media data from index of mediaData, so it can signal 'decode' error.
         // Here use mediaSegment to replace the original mediaData[index, index + mediaSegment.length]
         mediaData.set(mediaSegment, index);
 
-        test.expectEvent(mediaElement, "loadedmetadata", "mediaElement metadata.");
         test.expectEvent(sourceBuffer, "error", "sourceBuffer error.");
         test.expectEvent(sourceBuffer, "updateend", "mediaSegment append ended.");
         test.expectEvent(mediaElement, "error", "mediaElement error.");
         test.expectEvent(mediaSource, "sourceended", "mediaSource ended.");
         sourceBuffer.appendBuffer(mediaData);
 
         test.waitForExpectedEvents(function()
         {
+            assert_equals(mediaElement.readyState, HTMLMediaElement.HAVE_NOTHING);
             assert_true(mediaElement.error != null);
-            assert_equals(mediaElement.error.code, MediaError.MEDIA_ERR_DECODE);
+            assert_equals(mediaElement.error.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
             test.done();
         });
-    }, "Signaling 'decode' error via segment parser loop algorithm after initialization segment and partial media segment has been appended.");
+    }, "Signaling 'decode' error via segment parser loop algorithm.");
 </script>