Merge m-c to m-i
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 04 Dec 2016 19:14:59 -0800
changeset 325279 91471056c4b91a6f7745e64644008d1cabac1dd4
parent 325278 e54021050e77f9672496192fb6b5a67fed641619 (current diff)
parent 325269 bd9e81439725f3d4135652cc3d65f2bfba527b7b (diff)
child 325280 7a6b69786d1e56d4e954fba49ee94c2b17653e37
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
milestone53.0a1
Merge m-c to m-i MozReview-Commit-ID: 6slXpLpZ9M2
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/ext-browsingData.js
@@ -0,0 +1,39 @@
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
+
+extensions.registerSchemaAPI("browsingData", "addon_parent", context => {
+  return {
+    browsingData: {
+      settings() {
+        const PREF_DOMAIN = "privacy.cpd.";
+        // The following prefs are the only ones in Firefox that match corresponding
+        // values used by Chrome when rerturning settings.
+        const PREF_LIST = ["cache", "cookies", "history", "formdata", "downloads"];
+
+        // since will be the start of what is returned by Sanitizer.getClearRange
+        // divided by 1000 to convert to ms.
+        let since = Sanitizer.getClearRange()[0] / 1000;
+        let options = {since};
+
+        let dataToRemove = {};
+        let dataRemovalPermitted = {};
+
+        for (let item of PREF_LIST) {
+          dataToRemove[item] = Preferences.get(`${PREF_DOMAIN}${item}`);
+          // Firefox doesn't have the same concept of dataRemovalPermitted
+          // as Chrome, so it will always be true.
+          dataRemovalPermitted[item] = true;
+        }
+        // formData has a different case than the pref formdata.
+        dataToRemove.formData = Preferences.get(`${PREF_DOMAIN}formdata`);
+        dataRemovalPermitted.formData = true;
+
+        return Promise.resolve({options, dataToRemove, dataRemovalPermitted});
+      },
+    },
+  };
+});
--- a/browser/components/extensions/extensions-browser.manifest
+++ b/browser/components/extensions/extensions-browser.manifest
@@ -1,11 +1,12 @@
 # scripts
 category webextension-scripts bookmarks chrome://browser/content/ext-bookmarks.js
 category webextension-scripts browserAction chrome://browser/content/ext-browserAction.js
+category webextension-scripts browsingData chrome://browser/content/ext-browsingData.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 omnibox chrome://browser/content/ext-omnibox.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
@@ -15,16 +16,17 @@ category webextension-scripts windows ch
 # scripts that must run in the same process as addon code.
 category webextension-scripts-addon contextMenus chrome://browser/content/ext-c-contextMenus.js
 category webextension-scripts-addon omnibox chrome://browser/content/ext-c-omnibox.js
 category webextension-scripts-addon tabs chrome://browser/content/ext-c-tabs.js
 
 # 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 browsing_data chrome://browser/content/schemas/browsing_data.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 omnibox chrome://browser/content/schemas/omnibox.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
--- a/browser/components/extensions/jar.mn
+++ b/browser/components/extensions/jar.mn
@@ -9,16 +9,17 @@ browser.jar:
     content/browser/extension-mac-panel.css
 #endif
 #ifdef XP_WIN
     content/browser/extension-win-panel.css
 #endif
     content/browser/extension.svg
     content/browser/ext-bookmarks.js
     content/browser/ext-browserAction.js
+    content/browser/ext-browsingData.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-omnibox.js
     content/browser/ext-pageAction.js
     content/browser/ext-sessions.js
     content/browser/ext-tabs.js
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/schemas/browsing_data.json
@@ -0,0 +1,421 @@
+// Copyright (c) 2012 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": [
+            "browsingData"
+          ]
+        }]
+      }
+    ]
+  },
+  {
+    "namespace": "browsingData",
+    "description": "Use the <code>chrome.browsingData</code> API to remove browsing data from a user's local profile.",
+    "permissions": ["browsingData"],
+    "types": [
+      {
+        "id": "RemovalOptions",
+        "type": "object",
+        "description": "Options that determine exactly what data will be removed.",
+        "properties": {
+          "since": {
+            "$ref": "extensionTypes.Date",
+            "optional": true,
+            "description": "Remove data accumulated on or after this date, represented in milliseconds since the epoch (accessible via the <code>getTime</code> method of the JavaScript <code>Date</code> object). If absent, defaults to 0 (which would remove all browsing data)."
+          },
+          "originTypes": {
+            "type": "object",
+            "optional": true,
+            "description": "An object whose properties specify which origin types ought to be cleared. If this object isn't specified, it defaults to clearing only \"unprotected\" origins. Please ensure that you <em>really</em> want to remove application data before adding 'protectedWeb' or 'extensions'.",
+            "properties": {
+              "unprotectedWeb": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Normal websites."
+              },
+              "protectedWeb": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Websites that have been installed as hosted applications (be careful!)."
+              },
+              "extension": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Extensions and packaged applications a user has installed (be _really_ careful!)."
+              }
+            }
+          }
+        }
+      },
+      {
+        "id": "DataTypeSet",
+        "type": "object",
+        "description": "A set of data types. Missing data types are interpreted as <code>false</code>.",
+        "properties": {
+          "cache": {
+            "type": "boolean",
+            "optional": true,
+            "description": "The browser's cache. Note: when removing data, this clears the <em>entire</em> cache: it is not limited to the range you specify."
+          },
+          "cookies": {
+            "type": "boolean",
+            "optional": true,
+            "description": "The browser's cookies."
+          },
+          "downloads": {
+            "type": "boolean",
+            "optional": true,
+            "description": "The browser's download list."
+          },
+          "formData": {
+            "type": "boolean",
+            "optional": true,
+            "description": "The browser's stored form data."
+          },
+          "history": {
+            "type": "boolean",
+            "optional": true,
+            "description": "The browser's history."
+          },
+          "indexedDB": {
+            "type": "boolean",
+            "optional": true,
+            "description": "Websites' IndexedDB data."
+          },
+          "localStorage": {
+            "type": "boolean",
+            "optional": true,
+            "description": "Websites' local storage data."
+          },
+          "serverBoundCertificates": {
+            "type": "boolean",
+            "optional": true,
+            "description": "Server-bound certificates."
+          },
+          "passwords": {
+            "type": "boolean",
+            "optional": true,
+            "description": "Stored passwords."
+          },
+          "pluginData": {
+            "type": "boolean",
+            "optional": true,
+            "description": "Plugins' data."
+          },
+          "serviceWorkers": {
+            "type": "boolean",
+            "optional": true,
+            "description": "Service Workers."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "settings",
+        "description": "Reports which types of data are currently selected in the 'Clear browsing data' settings UI.  Note: some of the data types included in this API are not available in the settings UI, and some UI settings control more than one data type listed here.",
+        "type": "function",
+        "async": "callback",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "object",
+                "properties": {
+                  "options": {
+                    "$ref": "RemovalOptions"
+                  },
+                  "dataToRemove": {
+                    "$ref": "DataTypeSet",
+                    "description": "All of the types will be present in the result, with values of <code>true</code> if they are both selected to be removed and permitted to be removed, otherwise <code>false</code>."
+                  },
+                  "dataRemovalPermitted": {
+                    "$ref": "DataTypeSet",
+                    "description": "All of the types will be present in the result, with values of <code>true</code> if they are permitted to be removed (e.g., by enterprise policy) and <code>false</code> if not."
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "remove",
+        "description": "Clears various types of browsing data stored in a user's profile.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "dataToRemove",
+            "$ref": "DataTypeSet",
+            "description": "The set of data types to remove."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when deletion has completed.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeAppcache",
+        "description": "Clears websites' appcache data.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' appcache data has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeCache",
+        "description": "Clears the browser's cache.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's cache has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeCookies",
+        "description": "Clears the browser's cookies and server-bound certificates modified within a particular timeframe.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's cookies and server-bound certificates have been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeDownloads",
+        "description": "Clears the browser's list of downloaded files (<em>not</em> the downloaded files themselves).",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's list of downloaded files has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeFileSystems",
+        "description": "Clears websites' file system data.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' file systems have been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeFormData",
+        "description": "Clears the browser's stored form data (autofill).",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's form data has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeHistory",
+        "description": "Clears the browser's history.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's history has cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeIndexedDB",
+        "description": "Clears websites' IndexedDB data.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' IndexedDB data has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeLocalStorage",
+        "description": "Clears websites' local storage data.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' local storage has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removePluginData",
+        "description": "Clears plugins' data.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when plugins' data has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removePasswords",
+        "description": "Clears the browser's stored passwords.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's passwords have been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeWebSQL",
+        "description": "Clears websites' WebSQL data.",
+        "type": "function",
+        "async": "callback",
+        "unsupported": true,
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' WebSQL databases have been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      }
+    ]
+  }
+]
--- a/browser/components/extensions/schemas/jar.mn
+++ b/browser/components/extensions/schemas/jar.mn
@@ -1,15 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
     content/browser/schemas/bookmarks.json
     content/browser/schemas/browser_action.json
+    content/browser/schemas/browsing_data.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/omnibox.json
     content/browser/schemas/page_action.json
     content/browser/schemas/sessions.json
     content/browser/schemas/tabs.json
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_browsingData_settings.js
@@ -0,0 +1,76 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
+
+const PREF_DOMAIN = "privacy.cpd.";
+
+add_task(function* testSettings() {
+  function background() {
+    browser.test.onMessage.addListener(msg => {
+      browser.browsingData.settings().then(settings => {
+        browser.test.sendMessage("settings", settings);
+      });
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      permissions: ["browsingData"],
+    },
+  });
+
+  yield extension.startup();
+
+  let branch = Services.prefs.getBranch(PREF_DOMAIN);
+
+  extension.sendMessage("settings");
+  let settings = yield extension.awaitMessage("settings");
+
+  let since = Sanitizer.getClearRange()[0] / 1000;
+
+  // Because it is based on the current timestamp, we cannot know the exact
+  // value to expect for since, so allow a 10s variance.
+  ok(Math.abs(settings.options.since - since) < 10000,
+     "settings.options contains the expected since value.");
+
+  let dataTypeSet = settings.dataToRemove;
+  for (let key of Object.keys(dataTypeSet)) {
+    equal(branch.getBoolPref(key.toLowerCase()), dataTypeSet[key], `${key} property of dataToRemove matches the expected pref.`);
+  }
+
+  dataTypeSet = settings.dataRemovalPermitted;
+  for (let key of Object.keys(dataTypeSet)) {
+    equal(true, dataTypeSet[key], `${key} property of dataRemovalPermitted is true.`);
+  }
+
+  // Explicitly set a pref to both true and false and then check.
+  const SINGLE_PREF = "cache";
+
+  do_register_cleanup(() => {
+    branch.clearUserPref(SINGLE_PREF);
+  });
+
+  branch.setBoolPref(SINGLE_PREF, true);
+
+  extension.sendMessage("settings");
+  settings = yield extension.awaitMessage("settings");
+
+  equal(settings.dataToRemove[SINGLE_PREF], true, "Preference that was set to true returns true.");
+
+  branch.setBoolPref(SINGLE_PREF, false);
+
+  extension.sendMessage("settings");
+  settings = yield extension.awaitMessage("settings");
+
+  equal(settings.dataToRemove[SINGLE_PREF], false, "Preference that was set to false returns false.");
+
+  do_register_cleanup(() => {
+    branch.clearUserPref(SINGLE_PREF);
+  });
+
+  yield extension.unload();
+});
--- a/browser/components/extensions/test/xpcshell/xpcshell.ini
+++ b/browser/components/extensions/test/xpcshell/xpcshell.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 head = head.js
 tail =
 firefox-appdir = browser
 tags = webextensions
 
 [test_ext_bookmarks.js]
+[test_ext_browsingData_settings.js]
 [test_ext_history.js]
 [test_ext_manifest_commands.js]
 [test_ext_manifest_omnibox.js]
 [test_ext_manifest_permissions.js]
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -637,16 +637,20 @@ public:
 
   const CryptoSample& mCrypto;
   RefPtr<MediaByteBuffer> mExtraData;
 
   // Used by the Vorbis decoder and Ogg demuxer.
   // Indicates that this is the last packet of the stream.
   bool mEOS = false;
 
+  // Indicate to the audio decoder that mDiscardPadding frames should be
+  // trimmed.
+  uint32_t mDiscardPadding = 0;
+
   RefPtr<SharedTrackInfo> mTrackInfo;
 
   // Return a deep copy or nullptr if out of memory.
   virtual already_AddRefed<MediaRawData> Clone() const;
   // Create a MediaRawDataWriter for this MediaRawData. The caller must
   // delete the writer once done. The writer is not thread-safe.
   virtual MediaRawDataWriter* CreateWriter();
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
--- a/dom/media/ogg/OggCodecState.cpp
+++ b/dom/media/ogg/OggCodecState.cpp
@@ -223,17 +223,17 @@ OggCodecState::PacketPeek()
 void
 OggCodecState::PushFront(OggPacketQueue &&aOther)
 {
   while (!aOther.IsEmpty()) {
     mPackets.PushFront(aOther.Pop());
   }
 }
 
-RefPtr<MediaRawData>
+already_AddRefed<MediaRawData>
 OggCodecState::PacketOutAsMediaRawData()
 {
   ogg_packet* packet = PacketOut();
   if (!packet) {
     return nullptr;
   }
 
   NS_ASSERTION(!IsHeader(packet), "PacketOutAsMediaRawData can only be called on non-header packets");
@@ -248,17 +248,17 @@ OggCodecState::PacketOutAsMediaRawData()
   sample->mTimecode = packet->granulepos;
   sample->mTime = end_tstamp - duration;
   sample->mDuration = duration;
   sample->mKeyframe = IsKeyframe(packet);
   sample->mEOS = packet->e_o_s;
 
   ReleasePacket(packet);
 
-  return sample;
+  return sample.forget();
 }
 
 nsresult
 OggCodecState::PageIn(ogg_page* aPage)
 {
   if (!mActive) {
     return NS_OK;
   }
@@ -1227,16 +1227,47 @@ OpusState::ReconstructOpusGranulepos(voi
   // We MUST reject such streams.
   if (!mDoneReadingHeaders && GetOpusDeltaGP(mUnstamped[0]) > gp) {
     return false;
   }
   mPrevPageGranulepos = last->granulepos;
   return true;
 }
 
+already_AddRefed<MediaRawData>
+OpusState::PacketOutAsMediaRawData()
+{
+  ogg_packet* packet = PacketPeek();
+  uint32_t frames = 0;
+  const int64_t endFrame = packet->granulepos;
+
+  if (!packet) {
+    return nullptr;
+  }
+  if (packet->e_o_s) {
+    frames = GetOpusDeltaGP(packet);
+  }
+
+  RefPtr<MediaRawData> data = OggCodecState::PacketOutAsMediaRawData();
+
+  if (data->mEOS && mPrevPacketGranulepos != -1) {
+    // If this is the last packet, perform end trimming.
+    int64_t startFrame = mPrevPacketGranulepos;
+    frames -= std::max<int64_t>(
+      0, std::min(endFrame - startFrame, static_cast<int64_t>(frames)));
+    data->mDiscardPadding = frames;
+  }
+
+  // Save this packet's granule position in case we need to perform end
+  // trimming on the next packet.
+  mPrevPacketGranulepos = endFrame;
+
+  return data.forget();
+}
+
 FlacState::FlacState(ogg_page* aBosPage)
   : OggCodecState(aBosPage, true)
 {
 }
 
 bool
 FlacState::DecodeHeader(ogg_packet* aPacket)
 {
--- a/dom/media/ogg/OggCodecState.h
+++ b/dom/media/ogg/OggCodecState.h
@@ -190,17 +190,17 @@ public:
   // Releases the memory used by a cloned packet. Every packet returned by
   // PacketOut() must be free'd using this function.
   static void ReleasePacket(ogg_packet* aPacket);
 
   // Returns the next packet in the stream as a MediaRawData, or nullptr
   // if there are no more packets buffered in the packet queue. More packets
   // can be buffered by inserting one or more pages into the stream by calling
   // PageIn(). The packet will have a valid granulepos.
-  virtual RefPtr<MediaRawData> PacketOutAsMediaRawData();
+  virtual already_AddRefed<MediaRawData> PacketOutAsMediaRawData();
 
   // Extracts all packets from the page, and inserts them into the packet
   // queue. They can be extracted by calling PacketOut(). Packets from an
   // inactive stream are not buffered, i.e. this call has no effect for
   // inactive streams. Multiple pages may need to be inserted before
   // PacketOut() starts to return packets, as granulepos may need to be
   // captured.
   virtual nsresult PageIn(ogg_page* aPage);
@@ -386,17 +386,17 @@ public:
   bool DecodeHeader(ogg_packet* aPacket) override;
   int64_t Time(int64_t aGranulepos) override;
   int64_t PacketDuration(ogg_packet* aPacket) override;
   bool Init() override;
   nsresult Reset() override;
   nsresult Reset(bool aStart);
   bool IsHeader(ogg_packet* aPacket) override;
   nsresult PageIn(ogg_page* aPage) override;
-
+  already_AddRefed<MediaRawData> PacketOutAsMediaRawData() override;
   // Returns the end time that a granulepos represents.
   static int64_t Time(int aPreSkip, int64_t aGranulepos);
 
   // Various fields from the Ogg Opus header.
   int mRate;        // Sample rate the decoder uses (always 48 kHz).
   int mChannels;    // Number of channels the stream encodes.
   uint16_t mPreSkip; // Number of samples to strip after decoder reset.
 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
--- a/dom/media/ogg/OggDemuxer.cpp
+++ b/dom/media/ogg/OggDemuxer.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 "nsError.h"
 #include "MediaDecoderStateMachine.h"
 #include "AbstractMediaDecoder.h"
 #include "OggDemuxer.h"
 #include "OggCodecState.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "MediaDataDemuxer.h"
 #include "nsAutoRef.h"
 #include "XiphExtradata.h"
 #include "MediaPrefs.h"
@@ -45,16 +46,18 @@ using media::TimeIntervals;
 // from an exisiting connection than to do another bisection inside that
 // small range, which would open a new HTTP connetion.
 static const uint32_t OGG_SEEK_FUZZ_USECS = 500000;
 
 // The number of microseconds of "pre-roll" we use for Opus streams.
 // The specification recommends 80 ms.
 static const int64_t OGG_SEEK_OPUS_PREROLL = 80 * USECS_PER_MS;
 
+static Atomic<uint32_t> sStreamSourceID(0u);
+
 class OggHeaders
 {
 public:
   OggHeaders() {}
   ~OggHeaders()
   {
     for (size_t i = 0; i < mHeaders.Length(); i++) {
       delete[] mHeaders[i];
@@ -388,17 +391,17 @@ OggDemuxer::SetupTargetTheora(TheoraStat
   // Apply the aspect ratio to produce the intrinsic display size we report
   // to the element.
   ScaleDisplayByAspectRatio(displaySize, aTheoraState->mPixelAspectRatio);
 
   nsIntSize frameSize(aTheoraState->mInfo.frame_width,
                       aTheoraState->mInfo.frame_height);
   if (IsValidVideoRegion(frameSize, picture, displaySize)) {
     // Video track's frame sizes will not overflow. Activate the video track.
-    mInfo.mVideo.mMimeType = "video/ogg; codecs=theora";
+    mInfo.mVideo.mMimeType = "video/theora";
     mInfo.mVideo.mDisplay = displaySize;
     mInfo.mVideo.mImage = frameSize;
     mInfo.mVideo.SetImageRect(picture);
 
     // Copy Theora info data for time computations on other threads.
     memcpy(&mTheoraInfo, &aTheoraState->mInfo, sizeof(mTheoraInfo));
 
     // Save header packets for the decoder
@@ -418,17 +421,17 @@ OggDemuxer::SetupTargetVorbis(VorbisStat
   if (mVorbisState) {
     mVorbisState->Reset();
   }
 
   // Copy Vorbis info data for time computations on other threads.
   memcpy(&mVorbisInfo, &aVorbisState->mInfo, sizeof(mVorbisInfo));
   mVorbisInfo.codec_setup = nullptr;
 
-  mInfo.mAudio.mMimeType = "audio/ogg; codecs=vorbis";
+  mInfo.mAudio.mMimeType = "audio/vorbis";
   mInfo.mAudio.mRate = aVorbisState->mInfo.rate;
   mInfo.mAudio.mChannels = aVorbisState->mInfo.channels;
 
   // Save header packets for the decoder
   if (!XiphHeadersToExtradata(mInfo.mAudio.mCodecSpecificConfig,
                               aHeaders.mHeaders, aHeaders.mHeaderLens)) {
     return;
   }
@@ -439,17 +442,17 @@ OggDemuxer::SetupTargetVorbis(VorbisStat
 
 void
 OggDemuxer::SetupTargetOpus(OpusState* aOpusState, OggHeaders& aHeaders)
 {
   if (mOpusState) {
     mOpusState->Reset();
   }
 
-  mInfo.mAudio.mMimeType = "audio/ogg; codecs=opus";
+  mInfo.mAudio.mMimeType = "audio/opus";
   mInfo.mAudio.mRate = aOpusState->mRate;
   mInfo.mAudio.mChannels = aOpusState->mChannels;
 
   // Save preskip & the first header packet for the Opus decoder
   uint64_t preSkip = aOpusState->Time(0, aOpusState->mPreSkip);
   uint8_t c[sizeof(preSkip)];
   BigEndian::writeUint64(&c[0], preSkip);
   mInfo.mAudio.mCodecSpecificConfig->AppendElements(&c[0], sizeof(preSkip));
@@ -825,17 +828,17 @@ OggDemuxer::ReadOggChain(const media::Ti
       (mVorbisState->mInfo.channels == newVorbisState->mInfo.channels)) {
 
     SetupTargetVorbis(newVorbisState, vorbisHeaders);
     LOG(LogLevel::Debug, ("New vorbis ogg link, serial=%d\n", mVorbisSerial));
 
     if (msgInfo) {
       InitTrack(msgInfo, &mInfo.mAudio, true);
     }
-    mInfo.mAudio.mMimeType = NS_LITERAL_CSTRING("audio/ogg; codec=vorbis");
+    mInfo.mAudio.mMimeType = NS_LITERAL_CSTRING("audio/vorbis");
     mInfo.mAudio.mRate = newVorbisState->mInfo.rate;
     mInfo.mAudio.mChannels = newVorbisState->mInfo.channels;
 
     chained = true;
     tags = newVorbisState->GetTags();
   }
 
   OggHeaders opusHeaders;
@@ -844,17 +847,17 @@ OggDemuxer::ReadOggChain(const media::Ti
       (mOpusState->mRate == newOpusState->mRate) &&
       (mOpusState->mChannels == newOpusState->mChannels)) {
 
     SetupTargetOpus(newOpusState, opusHeaders);
 
     if (msgInfo) {
       InitTrack(msgInfo, &mInfo.mAudio, true);
     }
-    mInfo.mAudio.mMimeType = NS_LITERAL_CSTRING("audio/ogg; codec=opus");
+    mInfo.mAudio.mMimeType = NS_LITERAL_CSTRING("audio/opus");
     mInfo.mAudio.mRate = newOpusState->mRate;
     mInfo.mAudio.mChannels = newOpusState->mChannels;
 
     chained = true;
     tags = newOpusState->GetTags();
   }
 
   OggHeaders flacHeaders;
@@ -880,16 +883,19 @@ OggDemuxer::ReadOggChain(const media::Ti
     mInfo.mMediaSeekable = false;
     mDecodedAudioDuration += aLastEndTime;
     if (mTimedMetadataEvent) {
       mTimedMetadataEvent->Notify(
         TimedMetadata(mDecodedAudioDuration,
                       Move(tags),
                       nsAutoPtr<MediaInfo>(new MediaInfo(mInfo))));
     }
+    // Setup a new TrackInfo so that the MediaFormatReader will flush the
+    // current decoder.
+    mSharedAudioTrackInfo = new SharedTrackInfo(mInfo.mAudio, ++sStreamSourceID);
     return true;
   }
 
   return false;
 }
 
 OggDemuxer::OggStateContext&
 OggDemuxer::OggState(TrackInfo::TrackType aType)
@@ -1483,29 +1489,36 @@ OggTrackDemuxer::Seek(const TimeUnit& aT
 }
 
 RefPtr<MediaRawData>
 OggTrackDemuxer::NextSample()
 {
   if (mQueuedSample) {
     RefPtr<MediaRawData> nextSample = mQueuedSample;
     mQueuedSample = nullptr;
+    if (mType == TrackInfo::kAudioTrack) {
+      nextSample->mTrackInfo = mParent->mSharedAudioTrackInfo;
+    }
     return nextSample;
   }
   ogg_packet* packet = mParent->GetNextPacket(mType);
   if (!packet) {
     return nullptr;
   }
   // Check the eos state in case we need to look for chained streams.
   bool eos = packet->e_o_s;
   OggCodecState* state = mParent->GetTrackCodecState(mType);
-  RefPtr<MediaRawData> data = state->PacketOutAsMediaRawData();;
+  RefPtr<MediaRawData> data = state->PacketOutAsMediaRawData();
+  if (mType == TrackInfo::kAudioTrack) {
+    data->mTrackInfo = mParent->mSharedAudioTrackInfo;
+  }
   if (eos) {
     // We've encountered an end of bitstream packet; check for a chained
     // bitstream following this one.
+    // This will also update mSharedAudioTrackInfo.
     mParent->ReadOggChain(TimeUnit::FromMicroseconds(data->GetEndTime()));
   }
   return data;
 }
 
 RefPtr<OggTrackDemuxer::SamplesPromise>
 OggTrackDemuxer::GetSamples(int32_t aNumSamples)
 {
--- a/dom/media/ogg/OggDemuxer.h
+++ b/dom/media/ogg/OggDemuxer.h
@@ -335,16 +335,23 @@ private:
 
   // Total audio duration played so far.
   media::TimeUnit mDecodedAudioDuration;
 
   // Events manager
   TimedMetadataEventProducer* mTimedMetadataEvent;
   MediaEventProducer<void>* mOnSeekableEvent;
 
+  // This will be populated only if a content change occurs, otherwise it
+  // will be left as null so the original metadata is used.
+  // It is updated once a chained ogg is encountered.
+  // As Ogg chaining is only supported for audio, we only need an audio track
+  // info.
+  RefPtr<SharedTrackInfo> mSharedAudioTrackInfo;
+
   friend class OggTrackDemuxer;
 };
 
 class OggTrackDemuxer : public MediaTrackDemuxer
 {
 public:
   OggTrackDemuxer(OggDemuxer* aParent,
                   TrackInfo::TrackType aType,
--- a/dom/media/platforms/agnostic/OpusDecoder.cpp
+++ b/dom/media/platforms/agnostic/OpusDecoder.cpp
@@ -162,20 +162,16 @@ OpusDataDecoder::ProcessDecode(MediaRawD
     return;
   }
   mCallback->InputExhausted();
 }
 
 MediaResult
 OpusDataDecoder::DoDecode(MediaRawData* aSample)
 {
-  int64_t aDiscardPadding = 0;
-  if (aSample->mExtraData) {
-    aDiscardPadding = BigEndian::readInt64(aSample->mExtraData->Elements());
-  }
   uint32_t channels = mOpusParser->mChannels;
 
   if (mPaddingDiscarded) {
     // Discard padding should be used only on the final packet, so
     // decoding after a padding discard is invalid.
     OPUS_DEBUG("Opus error, discard padding on interstitial packet");
     return MediaResult(
       NS_ERROR_DOM_MEDIA_FATAL_ERR,
@@ -184,36 +180,36 @@ OpusDataDecoder::DoDecode(MediaRawData* 
 
   if (!mLastFrameTime || mLastFrameTime.ref() != aSample->mTime) {
     // We are starting a new block.
     mFrames = 0;
     mLastFrameTime = Some(aSample->mTime);
   }
 
   // Maximum value is 63*2880, so there's no chance of overflow.
-  int32_t frames_number = opus_packet_get_nb_frames(aSample->Data(),
+  uint32_t frames_number = opus_packet_get_nb_frames(aSample->Data(),
                                                     aSample->Size());
   if (frames_number <= 0) {
     OPUS_DEBUG("Invalid packet header: r=%ld length=%ld",
                frames_number, aSample->Size());
     return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
                        RESULT_DETAIL("Invalid packet header: r=%d length=%u",
                                      frames_number, uint32_t(aSample->Size())));
   }
 
-  int32_t samples = opus_packet_get_samples_per_frame(aSample->Data(),
-                                           opus_int32(mOpusParser->mRate));
+  uint32_t samples = opus_packet_get_samples_per_frame(
+    aSample->Data(), opus_int32(mOpusParser->mRate));
 
 
   // A valid Opus packet must be between 2.5 and 120 ms long (48kHz).
-  int32_t frames = frames_number*samples;
+  uint32_t frames = frames_number*samples;
   if (frames < 120 || frames > 5760) {
-    OPUS_DEBUG("Invalid packet frames: %ld", frames);
+    OPUS_DEBUG("Invalid packet frames: %u", frames);
     return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
-                       RESULT_DETAIL("Invalid packet frames:%d", frames));
+                       RESULT_DETAIL("Invalid packet frames:%u", frames));
   }
 
   AlignedAudioBuffer buffer(frames * channels);
   if (!buffer) {
     return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
   }
 
   // Decode to the appropriate sample type.
@@ -225,78 +221,63 @@ OpusDataDecoder::DoDecode(MediaRawData* 
   int ret = opus_multistream_decode(mOpusDecoder,
                                     aSample->Data(), aSample->Size(),
                                     buffer.get(), frames, false);
 #endif
   if (ret < 0) {
     return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
                        RESULT_DETAIL("Opus decoding error:%d", ret));
   }
-  NS_ASSERTION(ret == frames, "Opus decoded too few audio samples");
+  NS_ASSERTION(uint32_t(ret) == frames, "Opus decoded too few audio samples");
   CheckedInt64 startTime = aSample->mTime;
 
   // Trim the initial frames while the decoder is settling.
   if (mSkip > 0) {
     int32_t skipFrames = std::min<int32_t>(mSkip, frames);
     int32_t keepFrames = frames - skipFrames;
     OPUS_DEBUG("Opus decoder skipping %d of %d frames", skipFrames, frames);
     PodMove(buffer.get(),
             buffer.get() + skipFrames * channels,
             keepFrames * channels);
     startTime = startTime + FramesToUsecs(skipFrames, mOpusParser->mRate);
     frames = keepFrames;
     mSkip -= skipFrames;
   }
 
-  if (aDiscardPadding < 0) {
-    // Negative discard padding is invalid.
-    OPUS_DEBUG("Opus error, negative discard padding");
-    return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
-                       RESULT_DETAIL("Negative discard padding"));
-  }
-  if (aDiscardPadding > 0) {
-    OPUS_DEBUG("OpusDecoder discardpadding %" PRId64 "", aDiscardPadding);
-    CheckedInt64 discardFrames =
-      TimeUnitToFrames(media::TimeUnit::FromNanoseconds(aDiscardPadding),
-                       mOpusParser->mRate);
-    if (!discardFrames.isValid()) {
-      return MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
-                         RESULT_DETAIL("Overflow in DiscardPadding"));
-    }
-    if (discardFrames.value() > frames) {
-      // Discarding more than the entire packet is invalid.
-      OPUS_DEBUG("Opus error, discard padding larger than packet");
-      return MediaResult(
-        NS_ERROR_DOM_MEDIA_FATAL_ERR,
-        RESULT_DETAIL("Discard padding larger than packet"));
-    }
-    OPUS_DEBUG("Opus decoder discarding %d of %d frames",
-        int32_t(discardFrames.value()), frames);
+  if (aSample->mDiscardPadding > 0) {
+    OPUS_DEBUG("Opus decoder discarding %u of %u frames",
+               aSample->mDiscardPadding, frames);
     // Padding discard is only supposed to happen on the final packet.
     // Record the discard so we can return an error if another packet is
     // decoded.
+    if (aSample->mDiscardPadding > frames) {
+    // Discarding more than the entire packet is invalid.
+      OPUS_DEBUG("Opus error, discard padding larger than packet");
+      return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                         RESULT_DETAIL("Discard padding larger than packet"));
+    }
+
     mPaddingDiscarded = true;
-    int32_t keepFrames = frames - discardFrames.value();
-    frames = keepFrames;
+    frames = frames - aSample->mDiscardPadding;
   }
 
   // Apply the header gain if one was specified.
 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
   if (mOpusParser->mGain != 1.0f) {
     float gain = mOpusParser->mGain;
-    int samples = frames * channels;
-    for (int i = 0; i < samples; i++) {
+    uint32_t samples = frames * channels;
+    for (uint32_t i = 0; i < samples; i++) {
       buffer[i] *= gain;
     }
   }
 #else
   if (mOpusParser->mGain_Q16 != 65536) {
     int64_t gain_Q16 = mOpusParser->mGain_Q16;
-    int samples = frames * channels;
-    for (int i = 0; i < samples; i++) {
+    uint32_t samples = frames * channels;
+    for (uint32_t i = 0; i < samples; i++) {
       int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16);
       buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val));
     }
   }
 #endif
 
   CheckedInt64 duration = FramesToUsecs(frames, mOpusParser->mRate);
   if (!duration.isValid()) {
@@ -354,16 +335,13 @@ OpusDataDecoder::Flush()
   SyncRunnable::DispatchToThread(mTaskQueue, runnable);
   mIsFlushing = false;
 }
 
 /* static */
 bool
 OpusDataDecoder::IsOpus(const nsACString& aMimeType)
 {
-  return aMimeType.EqualsLiteral("audio/webm; codecs=opus") ||
-         aMimeType.EqualsLiteral("audio/ogg; codecs=opus") ||
-         aMimeType.EqualsLiteral("audio/mp4; codecs=opus") ||
-         aMimeType.EqualsLiteral("audio/opus");
+  return aMimeType.EqualsLiteral("audio/opus");
 }
 
 } // namespace mozilla
 #undef OPUS_DEBUG
--- a/dom/media/platforms/agnostic/TheoraDecoder.cpp
+++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp
@@ -228,13 +228,13 @@ TheoraDecoder::Drain()
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
   mTaskQueue->Dispatch(NewRunnableMethod(this, &TheoraDecoder::ProcessDrain));
 }
 
 /* static */
 bool
 TheoraDecoder::IsTheora(const nsACString& aMimeType)
 {
-  return aMimeType.EqualsLiteral("video/ogg; codecs=theora");
+  return aMimeType.EqualsLiteral("video/theora");
 }
 
 } // namespace mozilla
 #undef LOG
--- a/dom/media/platforms/agnostic/VorbisDecoder.cpp
+++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp
@@ -283,18 +283,17 @@ VorbisDataDecoder::Flush()
   SyncRunnable::DispatchToThread(mTaskQueue, r);
   mIsFlushing = false;
 }
 
 /* static */
 bool
 VorbisDataDecoder::IsVorbis(const nsACString& aMimeType)
 {
-  return aMimeType.EqualsLiteral("audio/webm; codecs=vorbis") ||
-         aMimeType.EqualsLiteral("audio/ogg; codecs=vorbis");
+  return aMimeType.EqualsLiteral("audio/vorbis");
 }
 
 /* static */ const AudioConfig::Channel*
 VorbisDataDecoder::VorbisLayout(uint32_t aChannels)
 {
   // From https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
   // Section 4.3.9.
   typedef AudioConfig::Channel Channel;
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -7,16 +7,19 @@
 
 #include "MediaCodecDataDecoder.h"
 #include "RemoteDataDecoder.h"
 
 #include "MediaInfo.h"
 #include "VPXDecoder.h"
 
 #include "MediaPrefs.h"
+#include "OpusDecoder.h"
+#include "VorbisDecoder.h"
+
 #include "nsPromiseFlatString.h"
 #include "nsIGfxInfo.h"
 
 #include "prlog.h"
 
 #include <jni.h>
 
 #undef LOG
@@ -151,19 +154,20 @@ AndroidDecoderModule::SupportsMimeType(c
 
   if ((VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8) &&
        !GetFeatureStatus(nsIGfxInfo::FEATURE_VP8_HW_DECODE)) ||
       (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9) &&
        !GetFeatureStatus(nsIGfxInfo::FEATURE_VP9_HW_DECODE))) {
     return false;
   }
 
-  // Prefer the gecko decoder for opus; stagefright crashes
+  // Prefer the gecko decoder for opus and vorbis; stagefright crashes
   // on content demuxed from mp4.
-  if (aMimeType.EqualsLiteral("audio/opus")) {
+  if (OpusDataDecoder::IsOpus(aMimeType) ||
+      VorbisDataDecoder::IsVorbis(aMimeType)) {
     LOG("Rejecting audio of type %s", aMimeType.Data());
     return false;
   }
 
   return java::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType(
       nsCString(TranslateMimeType(aMimeType)));
 }
 
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -395,19 +395,19 @@ WebMDemuxer::ReadMetadata()
       if (r == -1) {
         return NS_ERROR_FAILURE;
       }
 
       mAudioTrack = track;
       mHasAudio = true;
       mAudioCodec = nestegg_track_codec_id(context, track);
       if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
-        mInfo.mAudio.mMimeType = "audio/webm; codecs=vorbis";
+        mInfo.mAudio.mMimeType = "audio/vorbis";
       } else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
-        mInfo.mAudio.mMimeType = "audio/webm; codecs=opus";
+        mInfo.mAudio.mMimeType = "audio/opus";
         OpusDataDecoder::AppendCodecDelay(mInfo.mAudio.mCodecSpecificConfig,
             media::TimeUnit::FromNanoseconds(params.codec_delay).ToMicroseconds());
       }
       mSeekPreroll = params.seek_preroll;
       mInfo.mAudio.mRate = params.rate;
       mInfo.mAudio.mChannels = params.channels;
 
       unsigned int nheaders = 0;
@@ -610,17 +610,19 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
     mLastVideoFrameTime = Some(tstamp);
   }
 
   if (mIsMediaSource && next_tstamp == INT64_MIN) {
     return false;
   }
 
   int64_t discardPadding = 0;
-  (void) nestegg_packet_discard_padding(holder->Packet(), &discardPadding);
+  if (aType == TrackInfo::kAudioTrack) {
+    (void) nestegg_packet_discard_padding(holder->Packet(), &discardPadding);
+  }
 
   int packetEncryption = nestegg_packet_encryption(holder->Packet());
 
   for (uint32_t i = 0; i < count; ++i) {
     unsigned char* data;
     size_t length;
     r = nestegg_packet_data(holder->Packet(), i, &data, &length);
     if (r == -1) {
@@ -686,20 +688,30 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
       sample = new MediaRawData(data, length);
     }
     sample->mTimecode = tstamp;
     sample->mTime = tstamp;
     sample->mDuration = next_tstamp - tstamp;
     sample->mOffset = holder->Offset();
     sample->mKeyframe = isKeyframe;
     if (discardPadding && i == count - 1) {
-      uint8_t c[8];
-      BigEndian::writeInt64(&c[0], discardPadding);
-      sample->mExtraData = new MediaByteBuffer;
-      sample->mExtraData->AppendElements(&c[0], 8);
+      CheckedInt64 discardFrames;
+      if (discardPadding < 0) {
+        // This is an invalid value as discard padding should never be negative.
+        // Set to maximum value so that the decoder will reject it as it's
+        // greater than the number of frames available.
+        discardFrames = INT32_MAX;
+        WEBM_DEBUG("Invalid negative discard padding");
+      } else {
+        discardFrames = TimeUnitToFrames(
+          media::TimeUnit::FromNanoseconds(discardPadding), mInfo.mAudio.mRate);
+      }
+      if (discardFrames.isValid()) {
+        sample->mDiscardPadding = discardFrames.value();
+      }
     }
 
     if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_UNENCRYPTED ||
         packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED) {
       nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
       unsigned char const* iv;
       size_t ivLength;
       nestegg_packet_iv(holder->Packet(), &iv, &ivLength);
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
@@ -1,28 +1,32 @@
 package org.mozilla.gecko.media;
 
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.media.AudioManager;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.support.annotation.CheckResult;
 import android.support.v4.app.NotificationManagerCompat;
 import android.util.Log;
 
-import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.util.ThreadUtils;
@@ -41,35 +45,38 @@ public class MediaControlService extends
 
     private static final int MEDIA_CONTROL_ID = 1;
     private static final String MEDIA_CONTROL_PREF = "dom.audiochannel.mediaControl";
 
     private String mActionState = ACTION_STOP;
 
     private MediaSession mSession;
     private MediaController mController;
+    private HeadSetStateReceiver mHeadSetStateReceiver;
 
     private PrefsHelper.PrefHandler mPrefsObserver;
     private final String[] mPrefs = { MEDIA_CONTROL_PREF };
 
     private boolean mInitialize = false;
     private boolean mIsMediaControlPrefOn = true;
 
     private static WeakReference<Tab> mTabReference = new WeakReference<>(null);
 
     private int minCoverSize;
     private int coverSize;
 
     @Override
     public void onCreate() {
         initialize();
+        mHeadSetStateReceiver = new HeadSetStateReceiver().registerReceiver(getApplicationContext());
     }
 
     @Override
     public void onDestroy() {
+        mHeadSetStateReceiver.unregisterReceiver(getApplicationContext());
         shutdown();
     }
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         handleIntent(intent);
         return START_NOT_STICKY;
     }
@@ -272,16 +279,17 @@ public class MediaControlService extends
             public void onStop() {
                 Log.d(LOGTAG, "Controller, onStop");
                 super.onStop();
                 notifyControlInterfaceChanged(ACTION_STOP);
                 notifyObservers("MediaControl", "mediaControlStopped");
                 mTabReference = new WeakReference<>(null);
             }
         });
+
     }
 
     private void notifyObservers(String topic, String data) {
         GeckoAppShell.notifyObservers(topic, data);
     }
 
     private boolean isNeedToRemoveControlInterface(String action) {
         return action.equals(ACTION_STOP);
@@ -423,9 +431,34 @@ public class MediaControlService extends
 
         canvas.drawBitmap(favicon,
                 new Rect(0, 0, favicon.getWidth(), favicon.getHeight()),
                 new Rect(left, top, right, bottom),
                 paint);
 
         return coverArt;
     }
+
+    private class HeadSetStateReceiver extends BroadcastReceiver {
+
+        @CheckResult(suggest = "new HeadSetStateReceiver().registerReceiver(Context)")
+        HeadSetStateReceiver registerReceiver(Context context) {
+            IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+            context.registerReceiver(this, intentFilter);
+            return this;
+        }
+
+        void unregisterReceiver(Context context) {
+            context.unregisterReceiver(HeadSetStateReceiver.this);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (isMediaPlaying()) {
+                Intent pauseIntent = new Intent(getApplicationContext(), MediaControlService.class);
+                pauseIntent.setAction(ACTION_PAUSE);
+                handleIntent(pauseIntent);
+            }
+        }
+
+    }
+
 }
\ No newline at end of file
--- a/taskcluster/docs/kinds.rst
+++ b/taskcluster/docs/kinds.rst
@@ -6,16 +6,20 @@ This section lists and documents the ava
 build
 ------
 
 Builds are tasks that produce an installer or other output that can be run by
 users or automated tests.  This is more restrictive than most definitions of
 "build" in a Mozilla context: it does not include tasks that run build-like
 actions for static analysis or to produce instrumented artifacts.
 
+build-signing
+--------------
+
+
 artifact-build
 --------------
 
 This kind performs an artifact build: one based on precompiled binaries
 discovered via the TaskCluster index.  This task verifies that such builds
 continue to work correctly.
 
 hazard
@@ -137,8 +141,11 @@ The tasks to generate each docker image 
 Docker images are built from subdirectories of ``testing/docker``, using
 ``docker build``.  There is currently no capability for one Docker image to
 depend on another in-tree docker image, without uploading the latter to a
 Docker repository
 
 The task definition used to create the image-building tasks is given in
 ``image.yml`` in the kind directory, and is interpreted as a :doc:`YAML
 Template <yaml-templates>`.
+
+android-stuff
+--------------
--- a/taskcluster/docs/transforms.rst
+++ b/taskcluster/docs/transforms.rst
@@ -146,16 +146,26 @@ implementations. Any other task-descript
 verbatim, although it is augmented by the run-using implementation.
 
 The run-using implementations are all located in
 ``taskcluster/taskgraph/transforms/job``, along with the schemas for their
 implementations.  Those well-commented source files are the canonical
 documentation for what constitutes a job description, and should be considered
 part of the documentation.
 
+following ``run-using`` are available
+
+  ``hazard``
+  ``mach``
+  ``mozharness``
+  ``run-task``
+  ``spidermonkey`` or ``spidermonkey-package`` or ``spidermonkey-mozjs-crate``
+  ``toolchain-script``
+
+
 Task Descriptions
 -----------------
 
 Every kind needs to create tasks, and all of those tasks have some things in
 common.  They all run on one of a small set of worker implementations, each
 with their own idiosyncracies.  And they all report to TreeHerder in a similar
 way.
 
--- a/taskcluster/taskgraph/decision.py
+++ b/taskcluster/taskgraph/decision.py
@@ -1,10 +1,9 @@
 # -*- coding: utf-8 -*-
-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import os
 import json
@@ -12,16 +11,17 @@ import logging
 
 import time
 import yaml
 
 from .generator import TaskGraphGenerator
 from .create import create_tasks
 from .parameters import Parameters
 from .taskgraph import TaskGraph
+from .util.verifydoc import verify_docs
 
 from taskgraph.util.templates import Templates
 from taskgraph.util.time import (
     json_time_from_now,
     current_json_time,
 )
 
 logger = logging.getLogger(__name__)
@@ -71,17 +71,17 @@ def taskgraph_decision(options):
      * processing decision task command-line options into parameters
      * running task-graph generation exactly the same way the other `mach
        taskgraph` commands do
      * generating a set of artifacts to memorialize the graph
      * calling TaskCluster APIs to create the graph
     """
 
     parameters = get_decision_parameters(options)
-
+    verify_parameters(parameters)
     # create a TaskGraphGenerator instance
     tgg = TaskGraphGenerator(
         root_dir=options['root'],
         parameters=parameters)
 
     # write out the parameters used to generate this graph
     write_artifact('parameters.yml', dict(**parameters))
 
@@ -182,8 +182,17 @@ def get_action_yml(parameters):
     action_parameters = parameters.copy()
     action_parameters.update({
         "decision_task_id": "{{decision_task_id}}",
         "task_labels": "{{task_labels}}",
         "from_now": json_time_from_now,
         "now": current_json_time()
     })
     return templates.load('action.yml', action_parameters)
+
+
+def verify_parameters(parameters):
+        parameters_dict = dict(**parameters)
+        verify_docs(
+            filename="parameters.rst",
+            identifiers=parameters_dict.keys(),
+            appearing_as="inline-literal"
+         )
--- a/taskcluster/taskgraph/generator.py
+++ b/taskcluster/taskgraph/generator.py
@@ -7,16 +7,17 @@ import logging
 import os
 import yaml
 
 from . import filter_tasks
 from .graph import Graph
 from .taskgraph import TaskGraph
 from .optimize import optimize_task_graph
 from .util.python_path import find_object
+from .util.verifydoc import verify_docs
 
 logger = logging.getLogger(__name__)
 
 
 class Kind(object):
 
     def __init__(self, name, path, config):
         self.name = name
@@ -152,16 +153,18 @@ class TaskGraphGenerator(object):
 
             yield Kind(kind_name, path, config)
 
     def _run(self):
         logger.info("Loading kinds")
         # put the kinds into a graph and sort topologically so that kinds are loaded
         # in post-order
         kinds = {kind.name: kind for kind in self._load_kinds()}
+        self.verify_kinds(kinds)
+
         edges = set()
         for kind in kinds.itervalues():
             for dep in kind.config.get('kind-dependencies', []):
                 edges.add((kind.name, dep, 'kind-dependency'))
         kind_graph = Graph(set(kinds), edges)
 
         logger.info("Generating full task set")
         all_tasks = {}
@@ -170,16 +173,18 @@ class TaskGraphGenerator(object):
             kind = kinds[kind_name]
             new_tasks = kind.load_tasks(self.parameters, list(all_tasks.values()))
             for task in new_tasks:
                 if task.label in all_tasks:
                     raise Exception("duplicate tasks with label " + task.label)
                 all_tasks[task.label] = task
             logger.info("Generated {} tasks for kind {}".format(len(new_tasks), kind_name))
         full_task_set = TaskGraph(all_tasks, Graph(set(all_tasks), set()))
+        self.verify_attributes(all_tasks)
+        self.verify_run_using()
         yield 'full_task_set', full_task_set
 
         logger.info("Generating full task graph")
         edges = set()
         for t in full_task_set:
             for dep, depname in t.get_dependencies(full_task_set):
                 edges.add((t.label, dep, depname))
 
@@ -209,24 +214,50 @@ class TaskGraphGenerator(object):
         target_graph = full_task_graph.graph.transitive_closure(target_tasks)
         target_task_graph = TaskGraph(
             {l: all_tasks[l] for l in target_graph.nodes},
             target_graph)
         yield 'target_task_graph', target_task_graph
 
         logger.info("Generating optimized task graph")
         do_not_optimize = set()
+
         if not self.parameters.get('optimize_target_tasks', True):
             do_not_optimize = target_task_set.graph.nodes
         optimized_task_graph, label_to_taskid = optimize_task_graph(target_task_graph,
                                                                     self.parameters,
                                                                     do_not_optimize)
         yield 'label_to_taskid', label_to_taskid
         yield 'optimized_task_graph', optimized_task_graph
 
     def _run_until(self, name):
         while name not in self._run_results:
             try:
                 k, v = self._run.next()
             except StopIteration:
                 raise AttributeError("No such run result {}".format(name))
             self._run_results[k] = v
         return self._run_results[name]
+
+    def verify_kinds(self, kinds):
+        verify_docs(
+            filename="kinds.rst",
+            identifiers=kinds.keys(),
+            appearing_as="heading"
+         )
+
+    def verify_attributes(self, all_tasks):
+        attribute_set = set()
+        for label, task in all_tasks.iteritems():
+            attribute_set.update(task.attributes.keys())
+        verify_docs(
+            filename="attributes.rst",
+            identifiers=list(attribute_set),
+            appearing_as="heading"
+         )
+
+    def verify_run_using(self):
+        from .transforms.job import registry
+        verify_docs(
+            filename="transforms.rst",
+            identifiers=registry.keys(),
+            appearing_as="inline-literal"
+         )
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/util/verifydoc.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# 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/.
+
+import re
+import os
+
+base_path = os.path.join(os.getcwd(), "taskcluster/docs/")
+
+
+def verify_docs(filename, identifiers, appearing_as):
+    with open(os.path.join(base_path, filename)) as fileObject:
+        doctext = "".join(fileObject.readlines())
+        if appearing_as == "inline-literal":
+            expression_list = ["``" + identifier + "``" for identifier in identifiers]
+        elif appearing_as == "heading":
+            expression_list = [identifier + "\n[-+\n*]+|[.+\n*]+" for identifier in identifiers]
+        else:
+            raise Exception("appearing_as = {} not defined".format(appearing_as))
+
+        for expression, identifier in zip(expression_list, identifiers):
+            match_group = re.search(expression, doctext)
+            if not match_group:
+                raise Exception(
+                    "{}: {} missing from doc file: {}".format(appearing_as, identifier, filename)
+                )