merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 08 Sep 2015 15:37:12 +0200
changeset 261308 b23b2fa33a9dcda59dbbca1d157eca3c32c5b862
parent 261307 bc2b0fbde72c40da54e0718d52eaf080549a7070 (current diff)
parent 261233 2bb231870f2d4ac66fea59f40e8d3e893816803c (diff)
child 261309 7fa38a96266144196e943333cacfec791deb6da8
child 261364 20cc75c647f1d3f8b4bc594b989f4cd0e73c9949
child 261381 c832cffd0ecc7e1f817a81655ba60244f266491d
push id64705
push usercbook@mozilla.com
push dateTue, 08 Sep 2015 14:02:43 +0000
treeherdermozilla-inbound@7fa38a962661 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/media/DecodedStream.cpp
dom/media/DecodedStream.h
js/src/jit/mips32/AtomicOperations-mips32.h
toolkit/system/dbus/moz.build
toolkit/system/dbus/nsDBusModule.cpp
toolkit/system/dbus/nsDBusService.cpp
toolkit/system/dbus/nsDBusService.h
toolkit/system/dbus/nsNetworkManagerListener.cpp
toolkit/system/dbus/nsNetworkManagerListener.h
--- a/.hgignore
+++ b/.hgignore
@@ -1,16 +1,17 @@
 # .hgignore - List of filenames hg should ignore
 
 # Filenames that should be ignored wherever they appear
 ~$
 \.py(c|o)$
 (?i)(^|/)TAGS$
 (^|/)ID$
 (^|/)\.DS_Store$
+.*\.egg-info
 
 # Vim swap files.
 ^\.sw.$
 .[^/]*\.sw.$
 
 # Emacs directory variable files.
 \.dir-locals\.el
 
--- a/addon-sdk/source/python-lib/cuddlefish/prefs.py
+++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py
@@ -53,22 +53,22 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
     'media.gmp-manager.cert.requireBuiltIn' : False,
     'media.gmp-manager.url' : 'http://localhost/media-dummy/gmpmanager',
     'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml',
     'browser.aboutHomeSnippets.updateUrl': 'https://localhost/snippet-dummy',
     'browser.newtab.url' : 'about:blank',
     'browser.search.update': False,
     'browser.search.suggest.enabled' : False,
     'browser.safebrowsing.enabled' : False,
-    'browser.safebrowsing.updateURL': 'http://localhost/safebrowsing-dummy/update',
-    'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
+    'browser.safebrowsing.provider.google.updateURL': 'http://localhost/safebrowsing-dummy/update',
+    'browser.safebrowsing.provider.google.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
     'browser.selfsupport.url': 'https://localhost/selfsupport-dummy',
-    'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
-    'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update',
+    'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
+    'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
 
     # Disable app update
     'app.update.enabled' : False,
     'app.update.staging.enabled': False,
 
     # Disable about:newtab content fetch and ping
     'browser.newtabpage.directory.source': 'data:application/json,{"jetpack":1}',
     'browser.newtabpage.directory.ping': '',
@@ -115,18 +115,20 @@ DEFAULT_FIREFOX_PREFS = {
     'devtools.errorconsole.enabled' : True,
     'devtools.chrome.enabled' : True,
 
     # From:
     # http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l388
     # Make url-classifier updates so rare that they won't affect tests.
     'urlclassifier.updateinterval' : 172800,
     # Point the url-classifier to a nonexistent local URL for fast failures.
-    'browser.safebrowsing.provider.0.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash',
-    'browser.safebrowsing.provider.0.updateURL' : 'http://localhost/safebrowsing-dummy/update',
+    'browser.safebrowsing.provider.google.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash',
+    'browser.safebrowsing.provider.google.updateURL' : 'http://localhost/safebrowsing-dummy/update',
+    'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
+    'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
 }
 
 # When launching a temporary new Thunderbird profile, use these preferences.
 # Note that these were taken from:
 # http://mxr.mozilla.org/comm-central/source/mail/test/mozmill/runtest.py
 DEFAULT_THUNDERBIRD_PREFS = {
     # say no to slow script warnings
     'dom.max_chrome_script_run_time': 200,
--- a/addon-sdk/source/test/preferences/firefox.json
+++ b/addon-sdk/source/test/preferences/firefox.json
@@ -1,10 +1,12 @@
 {
   "browser.startup.homepage": "about:blank",
   "startup.homepage_welcome_url": "about:blank",
   "devtools.browsertoolbox.panel": "jsdebugger",
   "devtools.errorconsole.enabled": true,
   "devtools.chrome.enabled": true,
   "urlclassifier.updateinterval": 172800,
-  "browser.safebrowsing.provider.0.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
-  "browser.safebrowsing.provider.0.updateURL": "http://localhost/safebrowsing-dummy/update"
+  "browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
+  "browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update",
+  "browser.safebrowsing.provider.mozilla.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
+  "browser.safebrowsing.provider.mozilla.updateURL": "http://localhost/safebrowsing-dummy/update"
 }
--- a/addon-sdk/source/test/preferences/no-connections.json
+++ b/addon-sdk/source/test/preferences/no-connections.json
@@ -10,22 +10,22 @@
   "media.gmp-manager.cert.requireBuiltIn": false,
   "media.gmp-manager.url": "http://localhost/media-dummy/gmpmanager",
   "media.gmp-manager.url.override": "http://localhost/dummy-gmp-manager.xml",
   "browser.aboutHomeSnippets.updateUrl": "https://localhost/snippet-dummy",
   "browser.newtab.url": "about:blank",
   "browser.search.update": false,
   "browser.search.suggest.enabled": false,
   "browser.safebrowsing.enabled": false,
-  "browser.safebrowsing.updateURL": "http://localhost/safebrowsing-dummy/update",
-  "browser.safebrowsing.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
-  "browser.safebrowsing.malware.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
+  "browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update",
+  "browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
+  "browser.safebrowsing.provider.google.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
   "browser.selfsupport.url": "https://localhost/selfsupport-dummy",
-  "browser.trackingprotection.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
-  "browser.trackingprotection.updateURL": "http://localhost/safebrowsing-dummy/update",
+  "browser.safebrowsing.provider.mozilla.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
+  "browser.safebrowsing.provider.mozilla.updateURL": "http://localhost/safebrowsing-dummy/update",
   "browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}",
   "browser.newtabpage.directory.ping": "",
   "extensions.update.url": "http://localhost/extensions-dummy/updateURL",
   "extensions.update.background.url": "http://localhost/extensions-dummy/updateBackgroundURL",
   "extensions.blocklist.url": "http://localhost/extensions-dummy/blocklistURL",
   "extensions.webservice.discoverURL": "http://localhost/extensions-dummy/discoveryURL",
   "extensions.getAddons.maxResults": 0,
   "geo.wifi.uri": "http://localhost/location-dummy/locationURL",
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -351,68 +351,50 @@ pref("image.onload.decode.limit", 24); /
 // XXX this isn't a good check for "are touch events supported", but
 // we don't really have a better one at the moment.
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
 pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
 pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
 
 #ifdef MOZ_SAFE_BROWSING
-// Safe browsing does nothing unless this pref is set
 pref("browser.safebrowsing.enabled", false);
-
 // Prevent loading of pages identified as malware
 pref("browser.safebrowsing.malware.enabled", false);
-
+pref("browser.safebrowsing.downloads.enabled", false);
+pref("browser.safebrowsing.downloads.remote.enabled", false);
+pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
 pref("browser.safebrowsing.debug", false);
-pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
-pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+
+pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
+pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
+pref("browser.safebrowsing.provider.google.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
+
 pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
-pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
 
 pref("browser.safebrowsing.id", "Firefox");
 
 // Tables for application reputation.
 pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");
 
-// Non-enhanced mode (local url lists) URL list to check for updates
-pref("browser.safebrowsing.provider.0.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client={moz:client}&appver={moz:version}&pver=2.2&key=%GOOGLE_API_KEY%");
-
-pref("browser.safebrowsing.dataProvider", 0);
-
-// Does the provider name need to be localizable?
-pref("browser.safebrowsing.provider.0.name", "Google");
-pref("browser.safebrowsing.provider.0.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
-pref("browser.safebrowsing.provider.0.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client={moz:client}&appver={moz:version}&pver=2.2");
-
-// HTML report pages
-pref("browser.safebrowsing.provider.0.reportGenericURL", "http://{moz:locale}.phish-generic.mozilla.com/?hl={moz:locale}");
-pref("browser.safebrowsing.provider.0.reportErrorURL", "http://{moz:locale}.phish-error.mozilla.com/?hl={moz:locale}");
-pref("browser.safebrowsing.provider.0.reportPhishURL", "http://{moz:locale}.phish-report.mozilla.com/?hl={moz:locale}");
-pref("browser.safebrowsing.provider.0.reportMalwareURL", "http://{moz:locale}.malware-report.mozilla.com/?hl={moz:locale}");
-pref("browser.safebrowsing.provider.0.reportMalwareErrorURL", "http://{moz:locale}.malware-error.mozilla.com/?hl={moz:locale}");
-
-// FAQ URLs
-
 // The number of random entries to send with a gethash request.
 pref("urlclassifier.gethashnoise", 4);
 
 // Gethash timeout for Safebrowsing.
 pref("urlclassifier.gethash.timeout_ms", 5000);
 
 // If an urlclassifier table has not been updated in this number of seconds,
 // a gethash request will be forced to check that the result is still in
 // the database.
 pref("urlclassifier.max-complete-age", 2700);
 
-// URL for checking the reason for a malware warning.
-pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
-
 // Tracking protection
 pref("privacy.trackingprotection.enabled", true);
 pref("privacy.trackingprotection.pbmode.enabled", false);
 
 #endif
 
 // True if this is the first time we are showing about:firstrun
 pref("browser.firstrun.show.uidiscovery", true);
--- a/b2g/components/FxAccountsMgmtService.jsm
+++ b/b2g/components/FxAccountsMgmtService.jsm
@@ -88,22 +88,33 @@ this.FxAccountsMgmtService = {
       return;
     }
     // Backwards compatibility: handle accountId coming from Gaia
     if (data.accountId && typeof(data.email === "undefined")) {
       data.email = data.accountId;
       delete data.accountId;
     }
 
-    // XXX dirty hack because Gaia is sending getAccounts.
+    // Bug 1202450 dirty hack because Gaia is sending getAccounts.
     if (data.method == "getAccounts") {
       data.method = "getAccount";
     }
 
     switch(data.method) {
+      case "getAssertion":
+        let principal = Services.scriptSecurityManager.getSystemPrincipal();
+        let audience = msg.audience || principal.originNoSuffix;
+        FxAccountsManager.getAssertion(audience, principal, {
+          silent: msg.silent || false
+        }).then(result => {
+          self._onFulfill(msg.id, result);
+        }, reason => {
+          self._onReject(msg.id, reason);
+        });
+        break;
       case "getAccount":
       case "getKeys":
         FxAccountsManager[data.method]().then(
           result => {
             // For the getAccounts case, we only expose the email and
             // verification status so far.
             self._onFulfill(msg.id, result);
           },
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -952,25 +952,25 @@ pref("gecko.handlerService.allowRegister
 #ifdef MOZ_SAFE_BROWSING
 pref("browser.safebrowsing.enabled", true);
 pref("browser.safebrowsing.malware.enabled", true);
 pref("browser.safebrowsing.downloads.enabled", true);
 pref("browser.safebrowsing.downloads.remote.enabled", true);
 pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
 pref("browser.safebrowsing.debug", false);
 
-pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
-pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
+pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
+pref("browser.safebrowsing.provider.google.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
+
 pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
-pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
-
-pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
-
 #ifdef MOZILLA_OFFICIAL
 // Normally the "client ID" sent in updates is appinfo.name, but for
 // official Firefox releases from Mozilla we use a special identifier.
 pref("browser.safebrowsing.id", "navclient-auto-ffox");
 #endif
 
 // Name of the about: page contributed by safebrowsing to handle display of error
 // pages on phishing/malware hits.  (bug 399233)
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -491,19 +491,16 @@
 @RESPATH@/browser/components/360seProfileMigrator.js
 @RESPATH@/browser/components/EdgeProfileMigrator.js
 @RESPATH@/browser/components/IEProfileMigrator.js
 @RESPATH@/browser/components/SafariProfileMigrator.js
 #endif
 #ifdef XP_MACOSX
 @RESPATH@/browser/components/SafariProfileMigrator.js
 #endif
-#ifdef MOZ_ENABLE_DBUS
-@RESPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
-#endif
 #ifdef MOZ_ENABLE_GNOME_COMPONENT
 @RESPATH@/components/@DLL_PREFIX@mozgnome@DLL_SUFFIX@
 #endif
 #if defined(MOZ_ENABLE_DBUS) || defined(MOZ_ENABLE_GNOME_COMPONENT)
 @RESPATH@/components/components.manifest
 #endif
 @RESPATH@/components/nsINIProcessor.manifest
 @RESPATH@/components/nsINIProcessor.js
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2400,17 +2400,17 @@ Element::SetAttrAndNotify(int32_t aNames
     // Don't pass aOldValue to AttributeChanged since it may not be reliable.
     // Callers only compute aOldValue under certain conditions which may not
     // be triggered by all nsIMutationObservers.
     nsNodeUtils::AttributeChanged(this, aNamespaceID, aName, aModType,
         oldValue == &aParsedValue ? &aParsedValue : nullptr);
   }
 
   if (aFireMutation) {
-    InternalMutationEvent mutation(true, NS_MUTATION_ATTRMODIFIED);
+    InternalMutationEvent mutation(true, eLegacyAttrModified);
 
     nsAutoString ns;
     nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
     Attr* attrNode =
       GetAttributeNodeNSInternal(ns, nsDependentAtomString(aName));
     mutation.mRelatedNode = attrNode;
 
     mutation.mAttrName = aName;
@@ -2642,17 +2642,17 @@ Element::UnsetAttr(int32_t aNameSpaceID,
   rv = AfterSetAttr(aNameSpaceID, aName, nullptr, aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
     OnSetDirAttr(this, nullptr, hadValidDir, hadDirAuto, aNotify);
   }
 
   if (hasMutationListeners) {
-    InternalMutationEvent mutation(true, NS_MUTATION_ATTRMODIFIED);
+    InternalMutationEvent mutation(true, eLegacyAttrModified);
 
     mutation.mRelatedNode = attrNode;
     mutation.mAttrName = aName;
 
     nsAutoString value;
     oldValue.ToString(value);
     if (!value.IsEmpty())
       mutation.mPrevAttrValue = do_GetAtom(value);
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -1212,17 +1212,17 @@ FragmentOrElement::FireNodeInserted(nsID
                                    nsTArray<nsCOMPtr<nsIContent> >& aNodes)
 {
   uint32_t count = aNodes.Length();
   for (uint32_t i = 0; i < count; ++i) {
     nsIContent* childContent = aNodes[i];
 
     if (nsContentUtils::HasMutationListeners(childContent,
           NS_EVENT_BITS_MUTATION_NODEINSERTED, aParent)) {
-      InternalMutationEvent mutation(true, NS_MUTATION_NODEINSERTED);
+      InternalMutationEvent mutation(true, eLegacyNodeInserted);
       mutation.mRelatedNode = do_QueryInterface(aParent);
 
       mozAutoSubtreeModified subtree(aDoc, aParent);
       (new AsyncEventDispatcher(childContent, mutation))->RunDOMEventWhenSafe();
     }
   }
 }
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -4080,17 +4080,17 @@ nsContentUtils::MaybeFireNodeRemoved(nsI
       NS_ERROR("Want to fire DOMNodeRemoved event, but it's not safe");
       WarnScriptWasIgnored(aOwnerDoc);
     }
     return;
   }
 
   if (HasMutationListeners(aChild,
         NS_EVENT_BITS_MUTATION_NODEREMOVED, aParent)) {
-    InternalMutationEvent mutation(true, NS_MUTATION_NODEREMOVED);
+    InternalMutationEvent mutation(true, eLegacyNodeRemoved);
     mutation.mRelatedNode = do_QueryInterface(aParent);
 
     mozAutoSubtreeModified subtree(aOwnerDoc, aParent);
     EventDispatcher::Dispatch(aChild, nullptr, &mutation);
   }
 }
 
 void
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9431,17 +9431,17 @@ nsDocument::MutationEventDispatched(nsIN
         realTargets.AppendObject(possibleTarget);
       }
     }
 
     mSubtreeModifiedTargets.Clear();
 
     int32_t realTargetCount = realTargets.Count();
     for (int32_t k = 0; k < realTargetCount; ++k) {
-      InternalMutationEvent mutation(true, NS_MUTATION_SUBTREEMODIFIED);
+      InternalMutationEvent mutation(true, eLegacySubtreeModified);
       (new AsyncEventDispatcher(realTargets[k], mutation))->
         RunDOMEventWhenSafe();
     }
   }
 }
 
 void
 nsDocument::AddStyleRelevantLink(Link* aLink)
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -382,17 +382,17 @@ nsGenericDOMDataNode::SetTextInternal(ui
       aOffset,
       endOffset,
       aLength,
       aDetails
     };
     nsNodeUtils::CharacterDataChanged(this, &info);
 
     if (haveMutationListeners) {
-      InternalMutationEvent mutation(true, NS_MUTATION_CHARACTERDATAMODIFIED);
+      InternalMutationEvent mutation(true, eLegacyCharacterDataModified);
 
       mutation.mPrevAttrValue = oldValue;
       if (aLength > 0) {
         nsAutoString val;
         mText.AppendTo(val);
         mutation.mNewAttrValue = do_GetAtom(val);
       }
 
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -1605,17 +1605,17 @@ nsINode::doInsertChildAt(nsIContent* aKi
     if (parent && isAppend) {
       nsNodeUtils::ContentAppended(parent, aKid, aIndex);
     } else {
       nsNodeUtils::ContentInserted(this, aKid, aIndex);
     }
 
     if (nsContentUtils::HasMutationListeners(aKid,
           NS_EVENT_BITS_MUTATION_NODEINSERTED, this)) {
-      InternalMutationEvent mutation(true, NS_MUTATION_NODEINSERTED);
+      InternalMutationEvent mutation(true, eLegacyNodeInserted);
       mutation.mRelatedNode = do_QueryInterface(this);
 
       mozAutoSubtreeModified subtree(OwnerDoc(), this);
       (new AsyncEventDispatcher(aKid, mutation))->RunDOMEventWhenSafe();
     }
   }
 
   return NS_OK;
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -787,16 +787,44 @@ ContentEventHandler::GetLineBreakType(Wi
 
 /* static */ LineBreakType
 ContentEventHandler::GetLineBreakType(bool aUseNativeLineBreak)
 {
   return aUseNativeLineBreak ?
     LINE_BREAK_TYPE_NATIVE : LINE_BREAK_TYPE_XP;
 }
 
+nsresult
+ContentEventHandler::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
+{
+  switch (aEvent->mMessage) {
+    case NS_QUERY_SELECTED_TEXT:
+      return OnQuerySelectedText(aEvent);
+    case NS_QUERY_TEXT_CONTENT:
+      return OnQueryTextContent(aEvent);
+    case NS_QUERY_CARET_RECT:
+      return OnQueryCaretRect(aEvent);
+    case NS_QUERY_TEXT_RECT:
+      return OnQueryTextRect(aEvent);
+    case NS_QUERY_EDITOR_RECT:
+      return OnQueryEditorRect(aEvent);
+    case NS_QUERY_CONTENT_STATE:
+      return OnQueryContentState(aEvent);
+    case NS_QUERY_SELECTION_AS_TRANSFERABLE:
+      return OnQuerySelectionAsTransferable(aEvent);
+    case NS_QUERY_CHARACTER_AT_POINT:
+      return OnQueryCharacterAtPoint(aEvent);
+    case NS_QUERY_DOM_WIDGET_HITTEST:
+      return OnQueryDOMWidgetHittest(aEvent);
+    default:
+      return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  return NS_OK;
+}
+
 // Similar to nsFrameSelection::GetFrameForNodeOffset,
 // but this is more flexible for OnQueryTextRect to use
 static nsresult GetFrameForTextRect(nsINode* aNode,
                                     int32_t aNodeOffset,
                                     bool aHint,
                                     nsIFrame** aReturnFrame)
 {
   NS_ENSURE_TRUE(aNode && aNode->IsNodeOfType(nsINode::eCONTENT),
--- a/dom/events/ContentEventHandler.h
+++ b/dom/events/ContentEventHandler.h
@@ -34,16 +34,19 @@ enum LineBreakType
 
 class MOZ_STACK_CLASS ContentEventHandler
 {
 public:
   typedef dom::Selection Selection;
 
   explicit ContentEventHandler(nsPresContext* aPresContext);
 
+  // Handle aEvent in the current process.
+  nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
+
   // NS_QUERY_SELECTED_TEXT event handler
   nsresult OnQuerySelectedText(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_TEXT_CONTENT event handler
   nsresult OnQueryTextContent(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_CARET_RECT event handler
   nsresult OnQueryCaretRect(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_TEXT_RECT event handler
   nsresult OnQueryTextRect(WidgetQueryContentEvent* aEvent);
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -68,29 +68,29 @@ static const uint32_t kAllMutationBits =
   NS_EVENT_BITS_MUTATION_NODEINSERTEDINTODOCUMENT |
   NS_EVENT_BITS_MUTATION_ATTRMODIFIED |
   NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED;
 
 static uint32_t
 MutationBitForEventType(EventMessage aEventType)
 {
   switch (aEventType) {
-    case NS_MUTATION_SUBTREEMODIFIED:
+    case eLegacySubtreeModified:
       return NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED;
-    case NS_MUTATION_NODEINSERTED:
+    case eLegacyNodeInserted:
       return NS_EVENT_BITS_MUTATION_NODEINSERTED;
-    case NS_MUTATION_NODEREMOVED:
+    case eLegacyNodeRemoved:
       return NS_EVENT_BITS_MUTATION_NODEREMOVED;
-    case NS_MUTATION_NODEREMOVEDFROMDOCUMENT:
+    case eLegacyNodeRemovedFromDocument:
       return NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT;
-    case NS_MUTATION_NODEINSERTEDINTODOCUMENT:
+    case eLegacyNodeInsertedIntoDocument:
       return NS_EVENT_BITS_MUTATION_NODEINSERTEDINTODOCUMENT;
-    case NS_MUTATION_ATTRMODIFIED:
+    case eLegacyAttrModified:
       return NS_EVENT_BITS_MUTATION_ATTRMODIFIED;
-    case NS_MUTATION_CHARACTERDATAMODIFIED:
+    case eLegacyCharacterDataModified:
       return NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED;
     default:
       break;
   }
   return 0;
 }
 
 uint32_t EventListenerManager::sMainThreadCreatedCount = 0;
@@ -294,32 +294,32 @@ EventListenerManager::AddEventListenerIn
   }
 
   if (aEventMessage == NS_AFTERPAINT) {
     mMayHavePaintEventListener = true;
     nsPIDOMWindow* window = GetInnerWindowForTarget();
     if (window) {
       window->SetHasPaintEventListeners();
     }
-  } else if (aEventMessage >= NS_MUTATION_START &&
-             aEventMessage <= NS_MUTATION_END) {
+  } else if (aEventMessage >= eLegacyMutationEventFirst &&
+             aEventMessage <= eLegacyMutationEventLast) {
     // For mutation listeners, we need to update the global bit on the DOM window.
     // Otherwise we won't actually fire the mutation event.
     mMayHaveMutationListeners = true;
     // Go from our target to the nearest enclosing DOM window.
     nsPIDOMWindow* window = GetInnerWindowForTarget();
     if (window) {
       nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
       if (doc) {
         doc->WarnOnceAbout(nsIDocument::eMutationEvent);
       }
-      // If aEventMessage is NS_MUTATION_SUBTREEMODIFIED, we need to listen all
+      // If aEventMessage is eLegacySubtreeModified, we need to listen all
       // mutations. nsContentUtils::HasMutationListeners relies on this.
       window->SetMutationListeners(
-        (aEventMessage == NS_MUTATION_SUBTREEMODIFIED) ?
+        (aEventMessage == eLegacySubtreeModified) ?
           kAllMutationBits : MutationBitForEventType(aEventMessage));
     }
   } else if (aTypeAtom == nsGkAtoms::ondeviceorientation) {
     EnableDevice(NS_DEVICE_ORIENTATION);
   } else if (aTypeAtom == nsGkAtoms::ondeviceproximity || aTypeAtom == nsGkAtoms::onuserproximity) {
     EnableDevice(NS_DEVICE_PROXIMITY);
   } else if (aTypeAtom == nsGkAtoms::ondevicelight) {
     EnableDevice(NS_DEVICE_LIGHT);
@@ -1221,37 +1221,37 @@ EventListenerManager::RemoveListenerForA
 
 bool
 EventListenerManager::HasMutationListeners()
 {
   if (mMayHaveMutationListeners) {
     uint32_t count = mListeners.Length();
     for (uint32_t i = 0; i < count; ++i) {
       Listener* listener = &mListeners.ElementAt(i);
-      if (listener->mEventMessage >= NS_MUTATION_START &&
-          listener->mEventMessage <= NS_MUTATION_END) {
+      if (listener->mEventMessage >= eLegacyMutationEventFirst &&
+          listener->mEventMessage <= eLegacyMutationEventLast) {
         return true;
       }
     }
   }
 
   return false;
 }
 
 uint32_t
 EventListenerManager::MutationListenerBits()
 {
   uint32_t bits = 0;
   if (mMayHaveMutationListeners) {
     uint32_t count = mListeners.Length();
     for (uint32_t i = 0; i < count; ++i) {
       Listener* listener = &mListeners.ElementAt(i);
-      if (listener->mEventMessage >= NS_MUTATION_START &&
-          listener->mEventMessage <= NS_MUTATION_END) {
-        if (listener->mEventMessage == NS_MUTATION_SUBTREEMODIFIED) {
+      if (listener->mEventMessage >= eLegacyMutationEventFirst &&
+          listener->mEventMessage <= eLegacyMutationEventLast) {
+        if (listener->mEventMessage == eLegacySubtreeModified) {
           return kAllMutationBits;
         }
         bits |= MutationBitForEventType(listener->mEventMessage);
       }
     }
   }
   return bits;
 }
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -499,21 +499,21 @@ WINDOW_EVENT(offline,
              eOffline,
              EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly,
              eBasicEventClass)
 WINDOW_EVENT(online,
              eOnline,
              EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly,
              eBasicEventClass)
 WINDOW_EVENT(pagehide,
-             NS_PAGE_HIDE,
+             ePageHide,
              EventNameType_HTMLBodyOrFramesetOnly,
              eBasicEventClass)
 WINDOW_EVENT(pageshow,
-             NS_PAGE_SHOW,
+             ePageShow,
              EventNameType_HTMLBodyOrFramesetOnly,
              eBasicEventClass)
 WINDOW_EVENT(popstate,
              ePopState,
              EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly,
              eBasicEventClass)
 // Not supported yet
 // WINDOW_EVENT(redo)
@@ -586,41 +586,41 @@ DOCUMENT_ONLY_EVENT(readystatechange,
                     eBasicEventClass)
 
 NON_IDL_EVENT(MozMouseHittest,
               eMouseHitTest,
               EventNameType_None,
               eMouseEventClass)
 
 NON_IDL_EVENT(DOMAttrModified,
-              NS_MUTATION_ATTRMODIFIED,
+              eLegacyAttrModified,
               EventNameType_HTMLXUL,
               eMutationEventClass)
 NON_IDL_EVENT(DOMCharacterDataModified,
-              NS_MUTATION_CHARACTERDATAMODIFIED,
+              eLegacyCharacterDataModified,
               EventNameType_HTMLXUL,
               eMutationEventClass)
 NON_IDL_EVENT(DOMNodeInserted,
-              NS_MUTATION_NODEINSERTED,
+              eLegacyNodeInserted,
               EventNameType_HTMLXUL,
               eMutationEventClass)
 NON_IDL_EVENT(DOMNodeRemoved,
-              NS_MUTATION_NODEREMOVED,
+              eLegacyNodeRemoved,
               EventNameType_HTMLXUL,
               eMutationEventClass)
 NON_IDL_EVENT(DOMNodeInsertedIntoDocument,
-              NS_MUTATION_NODEINSERTEDINTODOCUMENT,
+              eLegacyNodeInsertedIntoDocument,
               EventNameType_HTMLXUL,
               eMutationEventClass)
 NON_IDL_EVENT(DOMNodeRemovedFromDocument,
-              NS_MUTATION_NODEREMOVEDFROMDOCUMENT,
+              eLegacyNodeRemovedFromDocument,
               EventNameType_HTMLXUL,
               eMutationEventClass)
 NON_IDL_EVENT(DOMSubtreeModified,
-              NS_MUTATION_SUBTREEMODIFIED,
+              eLegacySubtreeModified,
               EventNameType_HTMLXUL,
               eMutationEventClass)
 
 NON_IDL_EVENT(DOMActivate,
               eLegacyDOMActivate,
               EventNameType_HTMLXUL,
               eUIEventClass)
 NON_IDL_EVENT(DOMFocusIn,
@@ -737,42 +737,42 @@ NON_IDL_EVENT(overflow,
               eBasicEventClass)
 NON_IDL_EVENT(underflow,
               NS_SCROLLPORT_UNDERFLOW,
               EventNameType_XUL,
               eBasicEventClass)
 
 // Various SVG events
 NON_IDL_EVENT(SVGLoad,
-              NS_SVG_LOAD,
+              eSVGLoad,
               EventNameType_None,
               eBasicEventClass)
 NON_IDL_EVENT(SVGUnload,
-              NS_SVG_UNLOAD,
+              eSVGUnload,
               EventNameType_None,
               eBasicEventClass)
 NON_IDL_EVENT(SVGResize,
-              NS_SVG_RESIZE,
+              eSVGResize,
               EventNameType_None,
               eBasicEventClass)
 NON_IDL_EVENT(SVGScroll,
-              NS_SVG_SCROLL,
+              eSVGScroll,
               EventNameType_None,
               eBasicEventClass)
 
 NON_IDL_EVENT(SVGZoom,
-              NS_SVG_ZOOM,
+              eSVGZoom,
               EventNameType_None,
               eSVGZoomEventClass)
 
 // Only map the ID to the real event name when MESSAGE_TO_EVENT is defined.
 #ifndef MESSAGE_TO_EVENT
 // This is a bit hackish, but SVG's event names are weird.
 NON_IDL_EVENT(zoom,
-              NS_SVG_ZOOM,
+              eSVGZoom,
               EventNameType_SVGSVG,
               eBasicEventClass)
 #endif
 // Only map the ID to the real event name when MESSAGE_TO_EVENT is defined.
 #ifndef MESSAGE_TO_EVENT
 NON_IDL_EVENT(begin,
               NS_SMIL_BEGIN,
               EventNameType_SMIL,
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -537,16 +537,21 @@ EventStateManager::PreHandleEvent(nsPres
     sLastScreenPoint =
       UIEvent::CalculateScreenPoint(aPresContext, aEvent);
     sLastClientPoint =
       UIEvent::CalculateClientPoint(aPresContext, aEvent, nullptr);
   }
 
   *aStatus = nsEventStatus_eIgnore;
 
+  if (aEvent->mClass == eQueryContentEventClass) {
+    HandleQueryContentEvent(aEvent->AsQueryContentEvent());
+    return NS_OK;
+  }
+
   switch (aEvent->mMessage) {
   case eContextMenu:
     if (sIsPointerLocked) {
       return NS_ERROR_DOM_INVALID_STATE_ERR;
     }
     break;
   case eMouseDown: {
     switch (mouseEvent->button) {
@@ -733,83 +738,16 @@ EventStateManager::PreHandleEvent(nsPres
       // Init lineOrPageDelta values for line scroll events for some devices
       // on some platforms which might dispatch wheel events which don't have
       // lineOrPageDelta values.  And also, if delta values are customized by
       // prefs, this recomputes them.
       DeltaAccumulator::GetInstance()->
         InitLineOrPageDelta(aTargetFrame, this, wheelEvent);
     }
     break;
-  case NS_QUERY_SELECTED_TEXT:
-    DoQuerySelectedText(aEvent->AsQueryContentEvent());
-    break;
-  case NS_QUERY_TEXT_CONTENT:
-    {
-      if (RemoteQueryContentEvent(aEvent)) {
-        break;
-      }
-      ContentEventHandler handler(mPresContext);
-      handler.OnQueryTextContent(aEvent->AsQueryContentEvent());
-    }
-    break;
-  case NS_QUERY_CARET_RECT:
-    {
-      if (RemoteQueryContentEvent(aEvent)) {
-        break;
-      }
-      ContentEventHandler handler(mPresContext);
-      handler.OnQueryCaretRect(aEvent->AsQueryContentEvent());
-    }
-    break;
-  case NS_QUERY_TEXT_RECT:
-    {
-      if (RemoteQueryContentEvent(aEvent)) {
-        break;
-      }
-      ContentEventHandler handler(mPresContext);
-      handler.OnQueryTextRect(aEvent->AsQueryContentEvent());
-    }
-    break;
-  case NS_QUERY_EDITOR_RECT:
-    {
-      if (RemoteQueryContentEvent(aEvent)) {
-        break;
-      }
-      ContentEventHandler handler(mPresContext);
-      handler.OnQueryEditorRect(aEvent->AsQueryContentEvent());
-    }
-    break;
-  case NS_QUERY_CONTENT_STATE:
-    {
-      // XXX remote event
-      ContentEventHandler handler(mPresContext);
-      handler.OnQueryContentState(aEvent->AsQueryContentEvent());
-    }
-    break;
-  case NS_QUERY_SELECTION_AS_TRANSFERABLE:
-    {
-      // XXX remote event
-      ContentEventHandler handler(mPresContext);
-      handler.OnQuerySelectionAsTransferable(aEvent->AsQueryContentEvent());
-    }
-    break;
-  case NS_QUERY_CHARACTER_AT_POINT:
-    {
-      // XXX remote event
-      ContentEventHandler handler(mPresContext);
-      handler.OnQueryCharacterAtPoint(aEvent->AsQueryContentEvent());
-    }
-    break;
-  case NS_QUERY_DOM_WIDGET_HITTEST:
-    {
-      // XXX remote event
-      ContentEventHandler handler(mPresContext);
-      handler.OnQueryDOMWidgetHittest(aEvent->AsQueryContentEvent());
-    }
-    break;
   case NS_SELECTION_SET:
     IMEStateManager::HandleSelectionEvent(aPresContext, GetFocusedContent(),
                                           aEvent->AsSelectionEvent());
     break;
   case NS_CONTENT_COMMAND_CUT:
   case NS_CONTENT_COMMAND_COPY:
   case NS_CONTENT_COMMAND_PASTE:
   case NS_CONTENT_COMMAND_DELETE:
@@ -827,27 +765,63 @@ EventStateManager::PreHandleEvent(nsPres
     break;
   case NS_COMPOSITION_START:
     if (aEvent->mFlags.mIsTrusted) {
       // If the event is trusted event, set the selected text to data of
       // composition event.
       WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
       WidgetQueryContentEvent selectedText(true, NS_QUERY_SELECTED_TEXT,
                                            compositionEvent->widget);
-      DoQuerySelectedText(&selectedText);
+      HandleQueryContentEvent(&selectedText);
       NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
       compositionEvent->mData = selectedText.mReply.mString;
     }
     break;
   default:
     break;
   }
   return NS_OK;
 }
 
+void
+EventStateManager::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
+{
+  switch (aEvent->mMessage) {
+    case NS_QUERY_SELECTED_TEXT:
+    case NS_QUERY_TEXT_CONTENT:
+    case NS_QUERY_CARET_RECT:
+    case NS_QUERY_TEXT_RECT:
+    case NS_QUERY_EDITOR_RECT:
+      if (!IsTargetCrossProcess(aEvent)) {
+        break;
+      }
+      // Will not be handled locally, remote the event
+      GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent);
+      return;
+    // Following events have not been supported in e10s mode yet.
+    case NS_QUERY_CONTENT_STATE:
+    case NS_QUERY_SELECTION_AS_TRANSFERABLE:
+    case NS_QUERY_CHARACTER_AT_POINT:
+    case NS_QUERY_DOM_WIDGET_HITTEST:
+      break;
+    default:
+      return;
+  }
+
+  // If there is an IMEContentObserver, we need to handle QueryContentEvent
+  // with it.
+  if (mIMEContentObserver) {
+    mIMEContentObserver->HandleQueryContentEvent(aEvent);
+    return;
+  }
+
+  ContentEventHandler handler(mPresContext);
+  handler.HandleQueryContentEvent(aEvent);
+}
+
 // static
 int32_t
 EventStateManager::GetAccessModifierMaskFor(nsISupports* aDocShell)
 {
   nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
   if (!treeItem)
     return -1; // invalid modifier
 
@@ -3363,28 +3337,16 @@ EventStateManager::PostHandleEvent(nsPre
 
   //Reset target frame to null to avoid mistargeting after reentrant event
   mCurrentTarget = nullptr;
   mCurrentTargetContent = nullptr;
 
   return ret;
 }
 
-bool
-EventStateManager::RemoteQueryContentEvent(WidgetEvent* aEvent)
-{
-  WidgetQueryContentEvent* queryEvent = aEvent->AsQueryContentEvent();
-  if (!IsTargetCrossProcess(queryEvent)) {
-    return false;
-  }
-  // Will not be handled locally, remote the event
-  GetCrossProcessTarget()->HandleQueryContentEvent(*queryEvent);
-  return true;
-}
-
 TabParent*
 EventStateManager::GetCrossProcessTarget()
 {
   return IMEStateManager::GetActiveTabParent();
 }
 
 bool
 EventStateManager::IsTargetCrossProcess(WidgetGUIEvent* aEvent)
@@ -5185,26 +5147,16 @@ EventStateManager::DoContentCommandScrol
   }
 
   // The caller may want synchronous scrolling.
   sf->ScrollBy(pt, scrollUnit, nsIScrollableFrame::INSTANT);
   return NS_OK;
 }
 
 void
-EventStateManager::DoQuerySelectedText(WidgetQueryContentEvent* aEvent)
-{
-  if (RemoteQueryContentEvent(aEvent)) {
-    return;
-  }
-  ContentEventHandler handler(mPresContext);
-  handler.OnQuerySelectedText(aEvent);
-}
-
-void
 EventStateManager::SetActiveManager(EventStateManager* aNewESM,
                                     nsIContent* aContent)
 {
   if (sActiveESM && aNewESM != sActiveESM) {
     sActiveESM->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
   }
   sActiveESM = aNewESM;
   if (sActiveESM && aContent) {
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -804,30 +804,29 @@ protected:
    * BeginTrackingDragGesture). aEvent->widget must be
    * mCurrentTarget->GetNearestWidget().
    */
   void FillInEventFromGestureDown(WidgetMouseEvent* aEvent);
 
   nsresult DoContentCommandEvent(WidgetContentCommandEvent* aEvent);
   nsresult DoContentCommandScrollEvent(WidgetContentCommandEvent* aEvent);
 
-  void DoQuerySelectedText(WidgetQueryContentEvent* aEvent);
-
-  bool RemoteQueryContentEvent(WidgetEvent* aEvent);
   dom::TabParent *GetCrossProcessTarget();
   bool IsTargetCrossProcess(WidgetGUIEvent* aEvent);
 
   bool DispatchCrossProcessEvent(WidgetEvent* aEvent,
                                  nsFrameLoader* aRemote,
                                  nsEventStatus *aStatus);
   bool HandleCrossProcessEvent(WidgetEvent* aEvent,
                                nsEventStatus* aStatus);
 
   void ReleaseCurrentIMEContentObserver();
 
+  void HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
+
 private:
   static inline void DoStateChange(dom::Element* aElement,
                                    EventStates aState, bool aAddState);
   static inline void DoStateChange(nsIContent* aContent, EventStates aState,
                                    bool aAddState);
   static void UpdateAncestorState(nsIContent* aStartNode,
                                   nsIContent* aStopBefore,
                                   EventStates aState,
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -1,14 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/Logging.h"
+
 #include "ContentEventHandler.h"
 #include "IMEContentObserver.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextComposition.h"
@@ -32,16 +34,115 @@
 #include "nsPresContext.h"
 #include "nsWeakReference.h"
 #include "WritingModes.h"
 
 namespace mozilla {
 
 using namespace widget;
 
+PRLogModuleInfo* sIMECOLog = nullptr;
+
+static const char*
+ToChar(bool aBool)
+{
+  return aBool ? "true" : "false";
+}
+
+static const char*
+ToChar(EventMessage aEventMessage)
+{
+  switch (aEventMessage) {
+    case NS_QUERY_SELECTED_TEXT:
+      return "NS_QUERY_SELECTED_TEXT";
+    case NS_QUERY_TEXT_CONTENT:
+      return "NS_QUERY_TEXT_CONTENT";
+    case NS_QUERY_CARET_RECT:
+      return "NS_QUERY_CARET_RECT";
+    case NS_QUERY_TEXT_RECT:
+      return "NS_QUERY_TEXT_RECT";
+    case NS_QUERY_EDITOR_RECT:
+      return "NS_QUERY_EDITOR_RECT";
+    case NS_QUERY_CONTENT_STATE:
+      return "NS_QUERY_CONTENT_STATE";
+    case NS_QUERY_SELECTION_AS_TRANSFERABLE:
+      return "NS_QUERY_SELECTION_AS_TRANSFERABLE";
+    case NS_QUERY_CHARACTER_AT_POINT:
+      return "NS_QUERY_CHARACTER_AT_POINT";
+    case NS_QUERY_DOM_WIDGET_HITTEST:
+      return "NS_QUERY_DOM_WIDGET_HITTEST";
+    default:
+      return "Unsupprted message";
+  }
+}
+
+class WritingModeToString final : public nsAutoCString
+{
+public:
+  explicit WritingModeToString(const WritingMode& aWritingMode)
+  {
+    if (!aWritingMode.IsVertical()) {
+      AssignLiteral("Horizontal");
+      return;
+    }
+    if (aWritingMode.IsVerticalLR()) {
+      AssignLiteral("Vertical (LR)");
+      return;
+    }
+    AssignLiteral("Vertical (RL)");
+  }
+  virtual ~WritingModeToString() {}
+};
+
+class SelectionChangeDataToString final : public nsAutoCString
+{
+public:
+  explicit SelectionChangeDataToString(
+             const IMENotification::SelectionChangeDataBase& aData)
+  {
+    if (!aData.IsValid()) {
+      AppendLiteral("{ IsValid()=false }");
+      return;
+    }
+    AppendPrintf("{ mOffset=%u, ", aData.mOffset);
+    if (aData.mString->Length() > 20) {
+      AppendPrintf("mString.Length()=%u, ", aData.mString->Length());
+    } else {
+      AppendPrintf("mString=\"%s\" (Length()=%u), ",
+                   NS_ConvertUTF16toUTF8(*aData.mString).get(),
+                   aData.mString->Length());
+    }
+    AppendPrintf("GetWritingMode()=%s, mReversed=%s, mCausedByComposition=%s, "
+                 "mCausedBySelectionEvent=%s }",
+                 WritingModeToString(aData.GetWritingMode()).get(),
+                 ToChar(aData.mReversed),
+                 ToChar(aData.mCausedByComposition),
+                 ToChar(aData.mCausedBySelectionEvent));
+  }
+  virtual ~SelectionChangeDataToString() {}
+};
+
+class TextChangeDataToString final : public nsAutoCString
+{
+public:
+  explicit TextChangeDataToString(
+             const IMENotification::TextChangeDataBase& aData)
+  {
+    if (!aData.IsValid()) {
+      AppendLiteral("{ IsValid()=false }");
+      return;
+    }
+    AppendPrintf("{ mStartOffset=%u, mRemovedEndOffset=%u, mAddedEndOffset=%u, "
+                 "mCausedByComposition=%s }", aData.mStartOffset,
+                 aData.mRemovedEndOffset, aData.mAddedEndOffset,
+                 ToChar(aData.mCausedByComposition));
+  }
+  virtual ~TextChangeDataToString() {}
+};
+
 /******************************************************************************
  * mozilla::IMEContentObserver
  ******************************************************************************/
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver)
   nsAutoScriptBlocker scriptBlocker;
@@ -58,16 +159,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IM
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartOfRemovingTextRangeCache.mContainerNode)
 
   tmp->mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING;
   tmp->mESM = nullptr;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWidget)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedWidget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootContent)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditableNode)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditor)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndOfAddedTextCache.mContainerNode)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
     mStartOfRemovingTextRangeCache.mContainerNode)
@@ -97,16 +199,19 @@ IMEContentObserver::IMEContentObserver()
   , mSelectionChangeCausedOnlyByComposition(false)
   , mSelectionChangeCausedOnlyBySelectionEvent(false)
   , mIsPositionChangeEventPending(false)
   , mIsFlushingPendingNotifications(false)
 {
 #ifdef DEBUG
   mTextChangeData.Test();
 #endif
+  if (!sIMECOLog) {
+    sIMECOLog = PR_NewLogModule("IMEContentObserver");
+  }
 }
 
 void
 IMEContentObserver::Init(nsIWidget* aWidget,
                          nsPresContext* aPresContext,
                          nsIContent* aContent,
                          nsIEditor* aEditor)
 {
@@ -251,26 +356,36 @@ IMEContentObserver::NotifyIMEOfBlur()
   // If we hasn't been set focus, we shouldn't send blur notification to IME.
   if (!mIMEHasFocus) {
     return;
   }
 
   // mWidget must have been non-nullptr if IME has focus.
   MOZ_RELEASE_ASSERT(widget);
 
+  nsRefPtr<IMEContentObserver> kungFuDeathGrip(this);
+
+  MOZ_LOG(sIMECOLog, LogLevel::Info,
+    ("IMECO: 0x%p IMEContentObserver::NotifyIMEOfBlur(), "
+     "sending NOTIFY_IME_OF_BLUR", this));
+
   // For now, we need to send blur notification in any condition because
   // we don't have any simple ways to send blur notification asynchronously.
   // After this call, Destroy() or Unlink() will stop observing the content
   // and forget everything.  Therefore, if it's not safe to send notification
   // when script blocker is unlocked, we cannot send blur notification after
   // that and before next focus notification.
   // Anyway, as far as we know, IME doesn't try to query content when it loses
   // focus.  So, this may not cause any problem.
   mIMEHasFocus = false;
   IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR), widget);
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::NotifyIMEOfBlur(), "
+     "sent NOTIFY_IME_OF_BLUR", this));
 }
 
 void
 IMEContentObserver::UnregisterObservers()
 {
   if (!mIsObserving) {
     return;
   }
@@ -280,16 +395,18 @@ IMEContentObserver::UnregisterObservers(
     mEditor->RemoveEditorObserver(this);
   }
 
   if (mUpdatePreference.WantSelectionChange() && mSelection) {
     nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection));
     if (selPrivate) {
       selPrivate->RemoveSelectionListener(this);
     }
+    mSelectionData.Clear();
+    mFocusedWidget = nullptr;
   }
 
   if (mUpdatePreference.WantTextChange() && mRootContent) {
     mRootContent->RemoveMutationObserver(this);
   }
 
   if (mUpdatePreference.WantPositionChanged() && mDocShell) {
     mDocShell->RemoveWeakScrollObserver(this);
@@ -438,16 +555,51 @@ IMEContentObserver::Reflow(DOMHighResTim
 NS_IMETHODIMP
 IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart,
                                         DOMHighResTimeStamp aEnd)
 {
   MaybeNotifyIMEOfPositionChange();
   return NS_OK;
 }
 
+nsresult
+IMEContentObserver::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::HandleQueryContentEvent(aEvent={ "
+     "mMessage=%s })", this, ToChar(aEvent->mMessage)));
+
+  // If the instance has cache, it should use the cached selection which was
+  // sent to the widget.
+  if (aEvent->mMessage == NS_QUERY_SELECTED_TEXT &&
+      aEvent->mUseNativeLineBreak &&
+      mSelectionData.IsValid()) {
+    aEvent->mReply.mContentsRoot = mRootContent;
+    aEvent->mReply.mHasSelection = !mSelectionData.IsCollapsed();
+    aEvent->mReply.mOffset = mSelectionData.mOffset;
+    aEvent->mReply.mString = mSelectionData.String();
+    aEvent->mReply.mWritingMode = mSelectionData.GetWritingMode();
+    aEvent->mReply.mReversed = mSelectionData.mReversed;
+    aEvent->mSucceeded = true;
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::HandleQueryContentEvent(aEvent={ "
+       "mMessage=%s })", this, ToChar(aEvent->mMessage)));
+    return NS_OK;
+  }
+
+  ContentEventHandler handler(GetPresContext());
+  nsresult rv = handler.HandleQueryContentEvent(aEvent);
+  if (aEvent->mSucceeded) {
+    // We need to guarantee that mRootContent should be always same value for
+    // the observing editor.
+    aEvent->mReply.mContentsRoot = mRootContent;
+  }
+  return rv;
+}
+
 bool
 IMEContentObserver::OnMouseButtonEvent(nsPresContext* aPresContext,
                                        WidgetMouseEvent* aMouseEvent)
 {
   if (!mUpdatePreference.WantMouseButtonEventOnChar()) {
     return false;
   }
   if (!aMouseEvent->mFlags.mIsTrusted ||
@@ -781,77 +933,174 @@ IMEContentObserver::AttributeChanged(nsI
                                                   LINE_BREAK_TYPE_NATIVE);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   TextChangeData data(start, start + mPreAttrChangeLength,
                       start + postAttrChangeLength, causedByComposition);
   MaybeNotifyIMEOfTextChange(data);
 }
 
+void
+IMEContentObserver::SuppressNotifyingIME()
+{
+  mSuppressNotifications++;
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::SuppressNotifyingIME(), "
+     "mSuppressNotifications=%u", this, mSuppressNotifications));
+}
+
+void
+IMEContentObserver::UnsuppressNotifyingIME()
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::UnsuppressNotifyingIME(), "
+     "mSuppressNotifications=%u", this, mSuppressNotifications));
+
+  if (!mSuppressNotifications || --mSuppressNotifications) {
+    return;
+  }
+  FlushMergeableNotifications();
+}
+
 NS_IMETHODIMP
 IMEContentObserver::EditAction()
 {
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::EditAction()", this));
+
   mEndOfAddedTextCache.Clear();
   mStartOfRemovingTextRangeCache.Clear();
   FlushMergeableNotifications();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IMEContentObserver::BeforeEditAction()
 {
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::BeforeEditAction()", this));
+
   mEndOfAddedTextCache.Clear();
   mStartOfRemovingTextRangeCache.Clear();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IMEContentObserver::CancelEditAction()
 {
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::CancelEditAction()", this));
+
   mEndOfAddedTextCache.Clear();
   mStartOfRemovingTextRangeCache.Clear();
   FlushMergeableNotifications();
   return NS_OK;
 }
 
 void
 IMEContentObserver::PostFocusSetNotification()
 {
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::PostFocusSetNotification()", this));
+
   mIsFocusEventPending = true;
 }
 
 void
 IMEContentObserver::PostTextChangeNotification(
                       const TextChangeDataBase& aTextChangeData)
 {
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::PostTextChangeNotification("
+     "aTextChangeData=%s)",
+     this, TextChangeDataToString(aTextChangeData).get()));
+
   mTextChangeData += aTextChangeData;
   MOZ_ASSERT(mTextChangeData.IsValid(),
              "mTextChangeData must have text change data");
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::PostTextChangeNotification(), "
+     "mTextChangeData=%s)",
+     this, TextChangeDataToString(mTextChangeData).get()));
 }
 
 void
 IMEContentObserver::PostSelectionChangeNotification(
                       bool aCausedByComposition,
                       bool aCausedBySelectionEvent)
 {
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::PostSelectionChangeNotification("
+     "aCausedByComposition=%s, aCausedBySelectionEvent=%s)",
+     this, ToChar(aCausedByComposition), ToChar(aCausedBySelectionEvent)));
+
   if (!mIsSelectionChangeEventPending) {
     mSelectionChangeCausedOnlyByComposition = aCausedByComposition;
   } else {
     mSelectionChangeCausedOnlyByComposition =
       mSelectionChangeCausedOnlyByComposition && aCausedByComposition;
   }
   if (!mSelectionChangeCausedOnlyBySelectionEvent) {
     mSelectionChangeCausedOnlyBySelectionEvent = aCausedBySelectionEvent;
   } else {
     mSelectionChangeCausedOnlyBySelectionEvent =
       mSelectionChangeCausedOnlyBySelectionEvent && aCausedBySelectionEvent;
   }
   mIsSelectionChangeEventPending = true;
 }
 
+void
+IMEContentObserver::MaybeNotifyIMEOfFocusSet()
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfFocusSet()", this));
+
+  PostFocusSetNotification();
+  FlushMergeableNotifications();
+}
+
+void
+IMEContentObserver::MaybeNotifyIMEOfTextChange(
+                      const TextChangeDataBase& aTextChangeData)
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfTextChange("
+     "aTextChangeData=%s)",
+     this, TextChangeDataToString(aTextChangeData).get()));
+
+  PostTextChangeNotification(aTextChangeData);
+  FlushMergeableNotifications();
+}
+
+void
+IMEContentObserver::MaybeNotifyIMEOfSelectionChange(
+                      bool aCausedByComposition,
+                      bool aCausedBySelectionEvent)
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfSelectionChange("
+     "aCausedByComposition=%s, aCausedBySelectionEvent=%s)",
+     this, ToChar(aCausedByComposition), ToChar(aCausedBySelectionEvent)));
+
+  PostSelectionChangeNotification(aCausedByComposition,
+                                  aCausedBySelectionEvent);
+  FlushMergeableNotifications();
+}
+
+void
+IMEContentObserver::MaybeNotifyIMEOfPositionChange()
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfPositionChange()", this));
+  PostPositionChangeNotification();
+  FlushMergeableNotifications();
+}
+
 bool
 IMEContentObserver::UpdateSelectionCache()
 {
   MOZ_ASSERT(IsSafeToNotifyIME());
 
   if (!mUpdatePreference.WantSelectionChange()) {
     return false;
   }
@@ -862,28 +1111,38 @@ IMEContentObserver::UpdateSelectionCache
   //     selection offset and writing mode?
   WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, mWidget);
   ContentEventHandler handler(GetPresContext());
   handler.OnQuerySelectedText(&selection);
   if (NS_WARN_IF(!selection.mSucceeded)) {
     return false;
   }
 
+  mFocusedWidget = selection.mReply.mFocusedWidget;
   mSelectionData.mOffset = selection.mReply.mOffset;
   *mSelectionData.mString = selection.mReply.mString;
   mSelectionData.SetWritingMode(selection.GetWritingMode());
   mSelectionData.mReversed = selection.mReply.mReversed;
   mSelectionData.mCausedByComposition = false;
   mSelectionData.mCausedBySelectionEvent = false;
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::UpdateSelectionCache(), "
+     "mSelectionData=%s",
+     this, SelectionChangeDataToString(mSelectionData).get()));
+
   return mSelectionData.IsValid();
 }
 
 void
 IMEContentObserver::PostPositionChangeNotification()
 {
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::PostPositionChangeNotification()", this));
+
   mIsPositionChangeEventPending = true;
 }
 
 bool
 IMEContentObserver::IsReflowLocked() const
 {
   nsPresContext* presContext = GetPresContext();
   if (NS_WARN_IF(!presContext)) {
@@ -933,71 +1192,96 @@ IMEContentObserver::IsSafeToNotifyIME() 
   return true;
 }
 
 void
 IMEContentObserver::FlushMergeableNotifications()
 {
   if (!IsSafeToNotifyIME()) {
     // So, if this is already called, this should do nothing.
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
+       "FAILED, due to unsafe to notify IME", this));
     return;
   }
 
   // Notifying something may cause nested call of this method.  For example,
   // when somebody notified one of the notifications may dispatch query content
   // event. Then, it causes flushing layout which may cause another layout
   // change notification.
 
   if (mIsFlushingPendingNotifications) {
     // So, if this is already called, this should do nothing.
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p   IMEContentObserver::FlushMergeableNotifications(), "
+       "FAILED, due to already flushing pending notifications", this));
     return;
   }
 
   AutoRestore<bool> flusing(mIsFlushingPendingNotifications);
   mIsFlushingPendingNotifications = true;
 
   // NOTE: Reset each pending flag because sending notification may cause
   //       another change.
 
   if (mIsFocusEventPending) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
+       "creating FocusSetEvent...", this));
     mIsFocusEventPending = false;
     nsContentUtils::AddScriptRunner(new FocusSetEvent(this));
     // This is the first notification to IME. So, we don't need to notify any
     // more since IME starts to query content after it gets focus.
     ClearPendingNotifications();
     return;
   }
 
   if (mTextChangeData.IsValid()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
+       "creating TextChangeEvent...", this));
     nsContentUtils::AddScriptRunner(new TextChangeEvent(this, mTextChangeData));
   }
 
   // Be aware, PuppetWidget depends on the order of this. A selection change
   // notification should not be sent before a text change notification because
   // PuppetWidget shouldn't query new text content every selection change.
   if (mIsSelectionChangeEventPending) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
+       "creating SelectionChangeEvent...", this));
     mIsSelectionChangeEventPending = false;
     nsContentUtils::AddScriptRunner(
       new SelectionChangeEvent(this, mSelectionChangeCausedOnlyByComposition,
                                mSelectionChangeCausedOnlyBySelectionEvent));
   }
 
   if (mIsPositionChangeEventPending) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
+       "creating PositionChangeEvent...", this));
     mIsPositionChangeEventPending = false;
     nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
   }
 
   // If notifications may cause new change, we should notify them now.
   if (mTextChangeData.IsValid() ||
       mIsSelectionChangeEventPending ||
       mIsPositionChangeEventPending) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
+       "posting AsyncMergeableNotificationsFlusher to current thread", this));
     nsRefPtr<AsyncMergeableNotificationsFlusher> asyncFlusher =
       new AsyncMergeableNotificationsFlusher(this);
     NS_DispatchToCurrentThread(asyncFlusher);
   }
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
+     "finished", this));
 }
 
 /******************************************************************************
  * mozilla::IMEContentObserver::AChangeEvent
  ******************************************************************************/
 
 bool
 IMEContentObserver::AChangeEvent::CanNotifyIME() const
@@ -1047,137 +1331,218 @@ IMEContentObserver::AChangeEvent::IsSafe
  ******************************************************************************/
 
 NS_IMETHODIMP
 IMEContentObserver::FocusSetEvent::Run()
 {
   if (!CanNotifyIME()) {
     // If IMEContentObserver has already gone, we don't need to notify IME of
     // focus.
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::FocusSetEvent::Run(), FAILED, due to "
+       "impossible to notify IME of focus", this));
     mIMEContentObserver->ClearPendingNotifications();
     return NS_OK;
   }
 
   if (!IsSafeToNotifyIME()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p   IMEContentObserver::FocusSetEvent::Run(), retrying to "
+       "send NOTIFY_IME_OF_FOCUS...", this));
     mIMEContentObserver->PostFocusSetNotification();
     return NS_OK;
   }
 
   mIMEContentObserver->mIMEHasFocus = true;
   // Initialize selection cache with the first selection data.
   mIMEContentObserver->UpdateSelectionCache();
+
+  MOZ_LOG(sIMECOLog, LogLevel::Info,
+    ("IMECO: 0x%p IMEContentObserver::FocusSetEvent::Run(), "
+     "sending NOTIFY_IME_OF_FOCUS...", this));
+
   IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS),
                              mIMEContentObserver->mWidget);
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::FocusSetEvent::Run(), "
+     "sent NOTIFY_IME_OF_FOCUS", this));
   return NS_OK;
 }
 
 /******************************************************************************
  * mozilla::IMEContentObserver::SelectionChangeEvent
  ******************************************************************************/
 
 NS_IMETHODIMP
 IMEContentObserver::SelectionChangeEvent::Run()
 {
   if (!CanNotifyIME()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), FAILED, "
+       "due to impossible to notify IME of selection change", this));
     return NS_OK;
   }
 
   if (!IsSafeToNotifyIME()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p   IMEContentObserver::SelectionChangeEvent::Run(), "
+       "retrying to send NOTIFY_IME_OF_SELECTION_CHANGE...", this));
     mIMEContentObserver->PostSelectionChangeNotification(
                            mCausedByComposition, mCausedBySelectionEvent);
     return NS_OK;
   }
 
   SelectionChangeData lastSelChangeData = mIMEContentObserver->mSelectionData;
   if (NS_WARN_IF(!mIMEContentObserver->UpdateSelectionCache())) {
+    MOZ_LOG(sIMECOLog, LogLevel::Error,
+      ("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), FAILED, "
+       "due to UpdateSelectionCache() failure", this));
     return NS_OK;
   }
 
   // If the IME doesn't want selection change notifications caused by
   // composition, we should do nothing anymore.
   if (mCausedByComposition &&
       !mIMEContentObserver->
         mUpdatePreference.WantChangesCausedByComposition()) {
     return NS_OK;
   }
 
   // The state may be changed since querying content causes flushing layout.
   if (!CanNotifyIME()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), FAILED, "
+       "due to flushing layout having changed something", this));
     return NS_OK;
   }
 
   // If the selection isn't changed actually, we shouldn't notify IME of
   // selection change.
   SelectionChangeData& newSelChangeData = mIMEContentObserver->mSelectionData;
   if (lastSelChangeData.IsValid() &&
       lastSelChangeData.mOffset == newSelChangeData.mOffset &&
       lastSelChangeData.String() == newSelChangeData.String() &&
       lastSelChangeData.GetWritingMode() == newSelChangeData.GetWritingMode() &&
       lastSelChangeData.mReversed == newSelChangeData.mReversed) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), not "
+       "notifying IME of NOTIFY_IME_OF_SELECTION_CHANGE due to not changed "
+       "actually", this));
     return NS_OK;
   }
 
+  MOZ_LOG(sIMECOLog, LogLevel::Info,
+    ("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), "
+     "sending NOTIFY_IME_OF_SELECTION_CHANGE... newSelChangeData=%s",
+     this, SelectionChangeDataToString(newSelChangeData).get()));
+
   IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
   notification.SetData(mIMEContentObserver->mSelectionData,
                        mCausedByComposition, mCausedBySelectionEvent);
   IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget);
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), "
+     "sent NOTIFY_IME_OF_SELECTION_CHANGE", this));
   return NS_OK;
 }
 
 /******************************************************************************
  * mozilla::IMEContentObserver::TextChangeEvent
  ******************************************************************************/
 
 NS_IMETHODIMP
 IMEContentObserver::TextChangeEvent::Run()
 {
   if (!CanNotifyIME()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::TextChangeEvent::Run(), FAILED, "
+       "due to impossible to notify IME of text change", this));
     return NS_OK;
   }
 
   if (!IsSafeToNotifyIME()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p   IMEContentObserver::TextChangeEvent::Run(), retrying to "
+       "send NOTIFY_IME_OF_TEXT_CHANGE...", this));
     mIMEContentObserver->PostTextChangeNotification(mTextChangeData);
     return NS_OK;
   }
 
+  MOZ_LOG(sIMECOLog, LogLevel::Info,
+    ("IMECO: 0x%p IMEContentObserver::TextChangeEvent::Run(), "
+     "sending NOTIFY_IME_OF_TEXT_CHANGE... mTextChangeData=%s",
+     this, TextChangeDataToString(mTextChangeData).get()));
+
   IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
   notification.SetData(mTextChangeData);
   IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget);
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::TextChangeEvent::Run(), "
+     "sent NOTIFY_IME_OF_TEXT_CHANGE", this));
   return NS_OK;
 }
 
 /******************************************************************************
  * mozilla::IMEContentObserver::PositionChangeEvent
  ******************************************************************************/
 
 NS_IMETHODIMP
 IMEContentObserver::PositionChangeEvent::Run()
 {
   if (!CanNotifyIME()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::PositionChangeEvent::Run(), FAILED, "
+       "due to impossible to notify IME of position change", this));
     return NS_OK;
   }
 
   if (!IsSafeToNotifyIME()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p   IMEContentObserver::PositionChangeEvent::Run(), "
+       "retrying to send NOTIFY_IME_OF_POSITION_CHANGE...", this));
     mIMEContentObserver->PostPositionChangeNotification();
     return NS_OK;
   }
 
+  MOZ_LOG(sIMECOLog, LogLevel::Info,
+    ("IMECO: 0x%p IMEContentObserver::PositionChangeEvent::Run(), "
+     "sending NOTIFY_IME_OF_POSITION_CHANGE...", this));
+
   IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE),
                              mIMEContentObserver->mWidget);
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::PositionChangeEvent::Run(), "
+     "sent NOTIFY_IME_OF_POSITION_CHANGE", this));
   return NS_OK;
 }
 
 /******************************************************************************
  * mozilla::IMEContentObserver::AsyncMergeableNotificationsFlusher
  ******************************************************************************/
 
 NS_IMETHODIMP
 IMEContentObserver::AsyncMergeableNotificationsFlusher::Run()
 {
   if (!CanNotifyIME()) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::AsyncMergeableNotificationsFlusher::"
+       "Run(), FAILED, due to impossible to flush pending notifications",
+       this));
     return NS_OK;
   }
 
+  MOZ_LOG(sIMECOLog, LogLevel::Info,
+    ("IMECO: 0x%p IMEContentObserver::AsyncMergeableNotificationsFlusher::"
+     "Run(), calling FlushMergeableNotifications()...", this));
+
   mIMEContentObserver->FlushMergeableNotifications();
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::AsyncMergeableNotificationsFlusher::"
+     "Run(), called FlushMergeableNotifications()", this));
   return NS_OK;
 }
 
 } // namespace mozilla
--- a/dom/events/IMEContentObserver.h
+++ b/dom/events/IMEContentObserver.h
@@ -61,16 +61,18 @@ public:
   NS_DECL_NSIREFLOWOBSERVER
 
   // nsIScrollObserver
   virtual void ScrollPositionChanged() override;
 
   bool OnMouseButtonEvent(nsPresContext* aPresContext,
                           WidgetMouseEvent* aMouseEvent);
 
+  nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
+
   void Init(nsIWidget* aWidget, nsPresContext* aPresContext,
             nsIContent* aContent, nsIEditor* aEditor);
   void Destroy();
   /**
    * IMEContentObserver is stored by EventStateManager during observing.
    * DisconnectFromEventStateManager() is called when EventStateManager stops
    * storing the instance.
    */
@@ -89,24 +91,18 @@ public:
   bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent);
   bool IsEditorHandlingEventForComposition() const;
   bool KeepAliveDuringDeactive() const
   {
     return mUpdatePreference.WantDuringDeactive();
   }
   nsIWidget* GetWidget() const { return mWidget; }
   nsIEditor* GetEditor() const { return mEditor; }
-  void SuppressNotifyingIME() { mSuppressNotifications++; }
-  void UnsuppressNotifyingIME()
-  {
-    if (!mSuppressNotifications || --mSuppressNotifications) {
-      return;
-    }
-    FlushMergeableNotifications();
-  }
+  void SuppressNotifyingIME();
+  void UnsuppressNotifyingIME();
   nsPresContext* GetPresContext() const;
   nsresult GetSelectionAndRoot(nsISelection** aSelection,
                                nsIContent** aRoot) const;
 
 private:
   ~IMEContentObserver() {}
 
   enum State {
@@ -117,42 +113,25 @@ private:
   };
   State GetState() const;
   bool IsObservingContent(nsPresContext* aPresContext,
                           nsIContent* aContent) const;
   bool IsReflowLocked() const;
   bool IsSafeToNotifyIME() const;
 
   void PostFocusSetNotification();
-  void MaybeNotifyIMEOfFocusSet()
-  {
-    PostFocusSetNotification();
-    FlushMergeableNotifications();
-  }
+  void MaybeNotifyIMEOfFocusSet();
   void PostTextChangeNotification(const TextChangeDataBase& aTextChangeData);
-  void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData)
-  {
-    PostTextChangeNotification(aTextChangeData);
-    FlushMergeableNotifications();
-  }
+  void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData);
   void PostSelectionChangeNotification(bool aCausedByComposition,
                                        bool aCausedBySelectionEvent);
   void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition,
-                                       bool aCausedBySelectionEvent)
-  {
-    PostSelectionChangeNotification(aCausedByComposition,
-                                    aCausedBySelectionEvent);
-    FlushMergeableNotifications();
-  }
+                                       bool aCausedBySelectionEvent);
   void PostPositionChangeNotification();
-  void MaybeNotifyIMEOfPositionChange()
-  {
-    PostPositionChangeNotification();
-    FlushMergeableNotifications();
-  }
+  void MaybeNotifyIMEOfPositionChange();
 
   void NotifyContentAdded(nsINode* aContainer, int32_t aStart, int32_t aEnd);
   void ObserveEditableNode();
   /**
    *  NotifyIMEOfBlur() notifies IME of blur.
    */
   void NotifyIMEOfBlur();
   /**
@@ -173,16 +152,20 @@ private:
    * This should be called only when IsSafeToNotifyIME() returns true.
    *
    * Note that this does nothing if mUpdatePreference.WantSelectionChange()
    * returns false.
    */
   bool UpdateSelectionCache();
 
   nsCOMPtr<nsIWidget> mWidget;
+  // mFocusedWidget has the editor observed by the instance.  E.g., if the
+  // focused editor is in XUL panel, this should be the widget of the panel.
+  // On the other hand, mWidget is its parent which handles IME.
+  nsCOMPtr<nsIWidget> mFocusedWidget;
   nsCOMPtr<nsISelection> mSelection;
   nsCOMPtr<nsIContent> mRootContent;
   nsCOMPtr<nsINode> mEditableNode;
   nsCOMPtr<nsIDocShell> mDocShell;
   nsCOMPtr<nsIEditor> mEditor;
 
   /**
    * FlatTextCache stores flat text length from start of the content to
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -109,16 +109,17 @@ TextComposition::CloneAndDispatchAs(
   MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->widget),
              "Should be called only when it's safe to dispatch an event");
 
   WidgetCompositionEvent compositionEvent(aCompositionEvent->mFlags.mIsTrusted,
                                           aMessage, aCompositionEvent->widget);
   compositionEvent.time = aCompositionEvent->time;
   compositionEvent.timeStamp = aCompositionEvent->timeStamp;
   compositionEvent.mData = aCompositionEvent->mData;
+  compositionEvent.mOriginalMessage = aCompositionEvent->mMessage;
   compositionEvent.mFlags.mIsSynthesizedForTests =
     aCompositionEvent->mFlags.mIsSynthesizedForTests;
 
   nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault;
   nsEventStatus* status = aStatus ? aStatus : &dummyStatus;
   if (aMessage == NS_COMPOSITION_UPDATE) {
     mLastData = compositionEvent.mData;
   }
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -784,24 +784,24 @@ DispatchSuccessEvent(ResultHelper* aResu
     transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
   }
 }
 
 class WorkerPermissionChallenge;
 
 // This class calles WorkerPermissionChallenge::OperationCompleted() in the
 // worker thread.
-class WorkerPermissionOperationCompleted final : public WorkerRunnable
+class WorkerPermissionOperationCompleted final : public WorkerControlRunnable
 {
   nsRefPtr<WorkerPermissionChallenge> mChallenge;
 
 public:
   WorkerPermissionOperationCompleted(WorkerPrivate* aWorkerPrivate,
                                      WorkerPermissionChallenge* aChallenge)
-    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
     , mChallenge(aChallenge)
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   virtual bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
 };
@@ -897,21 +897,17 @@ public:
 
   void
   OperationCompleted()
   {
     if (NS_IsMainThread()) {
       nsRefPtr<WorkerPermissionOperationCompleted> runnable =
         new WorkerPermissionOperationCompleted(mWorkerPrivate, this);
 
-      if (!runnable->Dispatch(nullptr)) {
-        NS_WARNING("Failed to dispatch a runnable to the worker thread.");
-        return;
-      }
-
+      MOZ_ALWAYS_TRUE(runnable->Dispatch(nullptr));
       return;
     }
 
     MOZ_ASSERT(mActor);
     mActor->AssertIsOnOwningThread();
 
     MaybeCollectGarbageOnIPCMessage();
 
@@ -1379,17 +1375,17 @@ BackgroundFactoryRequestChild::RecvPermi
 
     nsRefPtr<WorkerPermissionChallenge> challenge =
       new WorkerPermissionChallenge(workerPrivate, this, mFactory,
                                     aPrincipalInfo);
 
     JSContext* cx = workerPrivate->GetJSContext();
     MOZ_ASSERT(cx);
 
-    if (!workerPrivate->AddFeature(cx, challenge)) {
+    if (NS_WARN_IF(!workerPrivate->AddFeature(cx, challenge))) {
       return false;
     }
 
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(challenge)));
     return true;
   }
 
   nsresult rv;
@@ -1401,17 +1397,19 @@ BackgroundFactoryRequestChild::RecvPermi
 
   if (XRE_IsParentProcess()) {
     nsCOMPtr<nsPIDOMWindow> window = mFactory->GetParentObject();
     MOZ_ASSERT(window);
 
     nsCOMPtr<Element> ownerElement =
       do_QueryInterface(window->GetChromeEventHandler());
     if (NS_WARN_IF(!ownerElement)) {
-      return false;
+      // If this fails, the page was navigated. Fail the permission check by
+      // forcing an immediate retry.
+      return SendPermissionRetry();
     }
 
     nsRefPtr<PermissionRequestMainProcessHelper> helper =
       new PermissionRequestMainProcessHelper(this, mFactory, ownerElement, principal);
 
     PermissionRequestBase::PermissionValue permission;
     if (NS_WARN_IF(NS_FAILED(helper->PromptIfNeeded(&permission)))) {
       return false;
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -98,21 +98,18 @@ public:
 
   // Return true if the media layer supports seeking.
   virtual bool IsTransportSeekable() = 0;
 
   // Return true if the transport layer supports seeking.
   virtual bool IsMediaSeekable() = 0;
 
   virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags, MediaDecoderEventVisibility aEventVisibility) = 0;
-  virtual void QueueMetadata(const media::TimeUnit& aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) = 0;
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, MediaDecoderEventVisibility aEventVisibility) = 0;
 
-  virtual void RemoveMediaTracks() = 0;
-
   // May be called by the reader to notify this decoder that the metadata from
   // the media file has been read. Call on the decode thread only.
   virtual void OnReadMetadataCompleted() = 0;
 
   // Returns the owner of this media decoder. The owner should only be used
   // on the main thread.
   virtual MediaDecoderOwner* GetOwner() = 0;
 
@@ -216,51 +213,12 @@ public:
 
   NS_IMETHOD Run() override
   {
     mDecoder->FirstFrameLoaded(mInfo, mEventVisibility);
     return NS_OK;
   }
 };
 
-class MetadataUpdatedEventRunner : public nsRunnable, private MetadataContainer
-{
-public:
-  MetadataUpdatedEventRunner(AbstractMediaDecoder* aDecoder,
-                             nsAutoPtr<MediaInfo> aInfo,
-                             nsAutoPtr<MetadataTags> aTags,
-                             MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable)
-    : MetadataContainer(aDecoder, aInfo, aTags, aEventVisibility)
-  {}
-
-  NS_IMETHOD Run() override
-  {
-    nsAutoPtr<MediaInfo> info(new MediaInfo());
-    *info = *mInfo;
-    mDecoder->MetadataLoaded(info, mTags, mEventVisibility);
-    mDecoder->FirstFrameLoaded(mInfo, mEventVisibility);
-    return NS_OK;
-  }
-};
-
-class RemoveMediaTracksEventRunner : public nsRunnable
-{
-public:
-  explicit RemoveMediaTracksEventRunner(AbstractMediaDecoder* aDecoder)
-    : mDecoder(aDecoder)
-  {}
-
-  NS_IMETHOD Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    mDecoder->RemoveMediaTracks();
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<AbstractMediaDecoder> mDecoder;
-};
-
 } // namespace mozilla
 
 #endif
 
--- a/dom/media/MP3Demuxer.cpp
+++ b/dom/media/MP3Demuxer.cpp
@@ -396,20 +396,26 @@ MP3TrackDemuxer::FindNextFrame() {
 
   while (frameBeg == bufferEnd) {
     if ((!mParser.FirstFrame().Length() &&
          mOffset - mParser.ID3Header().Size() > MAX_SKIPPED_BYTES) ||
         (read = Read(buffer, mOffset, BUFFER_SIZE)) == 0) {
       // This is not a valid MPEG audio stream or we've reached EOS, give up.
       break;
     }
-    MOZ_ASSERT(mOffset + read > mOffset);
+    NS_ENSURE_TRUE(mOffset + read > mOffset, MediaByteRange(0, 0));
     mOffset += read;
     bufferEnd = buffer + read;
-    frameBeg = mParser.Parse(buffer, bufferEnd);
+    const FrameParserResult parseResults = mParser.Parse(buffer, bufferEnd);
+    frameBeg = parseResults.mBufferPos;
+
+    // If mBytesToSkip is > 0, this skips the rest of an ID3 tag which stretches
+    // beyond the current buffer.
+    NS_ENSURE_TRUE(mOffset + parseResults.mBytesToSkip >= mOffset, MediaByteRange(0, 0));
+    mOffset += parseResults.mBytesToSkip;
   }
 
   if (frameBeg == bufferEnd || !mParser.CurrentFrame().Length()) {
     MP3DEMUXER_LOG("FindNext() Exit frameBeg=%p bufferEnd=%p "
                 "mParser.CurrentFrame().Length()=%d ",
                 frameBeg, bufferEnd, mParser.CurrentFrame().Length());
     return { 0, 0 };
   }
@@ -600,47 +606,55 @@ FrameParser::ID3Header() const {
   return mID3Parser.Header();
 }
 
 const FrameParser::VBRHeader&
 FrameParser::VBRInfo() const {
   return mVBRHeader;
 }
 
-const uint8_t*
+FrameParserResult
 FrameParser::Parse(const uint8_t* aBeg, const uint8_t* aEnd) {
   if (!aBeg || !aEnd || aBeg >= aEnd) {
-    return aEnd;
+    return { aEnd, 0 };
   }
 
   if (!mID3Parser.Header().Size() && !mFirstFrame.Length()) {
     // No MP3 frames have been parsed yet, look for ID3v2 headers at file begin.
     // ID3v1 tags may only be at file end.
     // TODO: should we try to read ID3 tags at end of file/mid-stream, too?
     const uint8_t* id3Beg = mID3Parser.Parse(aBeg, aEnd);
     if (id3Beg != aEnd) {
-      // ID3 headers found, skip past them.
-      aBeg = id3Beg + ID3Parser::ID3Header::SIZE + mID3Parser.Header().Size();
+      // ID3 tag found, skip past it.
+      const uint32_t tagSize = ID3Parser::ID3Header::SIZE + mID3Parser.Header().Size() +
+                               mID3Parser.Header().FooterSize();
+      const uint32_t remainingBuffer = aEnd - id3Beg;
+      if (tagSize > remainingBuffer) {
+        // Skipping across the ID3 tag would take us past the end of the buffer, therefore we
+        // return immediately and let the calling function handle skipping the rest of the tag.
+        return { aEnd, tagSize - remainingBuffer };
+      }
+      aBeg = id3Beg + tagSize;
     }
   }
 
   while (aBeg < aEnd && !mFrame.ParseNext(*aBeg)) {
     ++aBeg;
   }
 
   if (mFrame.Length()) {
     // MP3 frame found.
     if (!mFirstFrame.Length()) {
       mFirstFrame = mFrame;
     }
     // Move to the frame header begin to allow for whole-frame parsing.
     aBeg -= FrameHeader::SIZE;
-    return aBeg;
+    return { aBeg, 0 };
   }
-  return aEnd;
+  return { aEnd, 0 };
 }
 
 // FrameParser::Header
 
 FrameParser::FrameHeader::FrameHeader()
 {
   Reset();
 }
@@ -785,29 +799,30 @@ FrameParser::FrameHeader::ParseNext(uint
       Reset();
     }
   }
   return IsValid();
 }
 
 bool
 FrameParser::FrameHeader::IsValid(int aPos) const {
-  if (IsValid()) {
+  if (aPos >= SIZE) {
     return true;
   }
   if (aPos == frame_header::SYNC1) {
     return Sync1() == 0xFF;
   }
   if (aPos == frame_header::SYNC2_VERSION_LAYER_PROTECTION) {
     return Sync2() == 7 &&
            RawVersion() != 1 &&
            RawLayer() != 0;
   }
   if (aPos == frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE) {
-    return RawBitrate() != 0xF;
+    return RawBitrate() != 0xF && RawBitrate() != 0 &&
+           RawSampleRate() != 3;
   }
   return true;
 }
 
 bool
 FrameParser::FrameHeader::IsValid() const {
   return mPos >= SIZE;
 }
@@ -1012,30 +1027,38 @@ ID3Parser::ID3Header::Flags() const {
   return mRaw[id3_header::FLAGS_END - id3_header::FLAGS_LEN];
 }
 
 uint32_t
 ID3Parser::ID3Header::Size() const {
   return mSize;
 }
 
+uint8_t
+ID3Parser::ID3Header::FooterSize() const {
+  if (Flags() & (1 << 4)) {
+    return SIZE;
+  }
+  return 0;
+}
+
 bool
 ID3Parser::ID3Header::ParseNext(uint8_t c) {
   if (!Update(c)) {
     Reset();
     if (!Update(c)) {
       Reset();
     }
   }
   return IsValid();
 }
 
 bool
 ID3Parser::ID3Header::IsValid(int aPos) const {
-  if (IsValid()) {
+  if (aPos >= SIZE) {
     return true;
   }
   const uint8_t c = mRaw[aPos];
   switch (aPos) {
     case 0: case 1: case 2:
       // Expecting "ID3".
       return id3_header::ID[aPos] == c;
     case 3:
--- a/dom/media/MP3Demuxer.h
+++ b/dom/media/MP3Demuxer.h
@@ -56,19 +56,22 @@ public:
 
     // The ID3 tags are versioned like this: ID3vMajorVersion.MinorVersion.
     uint8_t MajorVersion() const;
     uint8_t MinorVersion() const;
 
     // The ID3 flags field.
     uint8_t Flags() const;
 
-    // The derived size based on the provides size fields.
+    // The derived size based on the provided size fields.
     uint32_t Size() const;
 
+    // Returns the size of an ID3v2.4 footer if present and zero otherwise.
+    uint8_t FooterSize() const;
+
     // Returns whether the parsed data is a valid ID3 header up to the given
     // byte position.
     bool IsValid(int aPos) const;
 
     // Returns whether the parsed data is a complete and valid ID3 header.
     bool IsValid() const;
 
     // Parses the next provided byte.
@@ -103,16 +106,21 @@ public:
   // Resets the state to allow for a new parsing session.
   void Reset();
 
 private:
   // The currently parsed ID3 header. Reset via Reset, updated via Parse.
   ID3Header mHeader;
 };
 
+struct FrameParserResult {
+  const uint8_t* mBufferPos;
+  const uint32_t mBytesToSkip;
+};
+
 // MPEG audio frame parser.
 // The MPEG frame header has the following format (one bit per character):
 // 11111111 111VVLLC BBBBSSPR MMEETOHH
 // {   sync   } - 11 sync bits
 //   VV         - MPEG audio version ID (0->2.5, 1->reserved, 2->2, 3->1)
 //   LL         - Layer description (0->reserved, 1->III, 2->II, 3->I)
 //   C          - CRC protection bit (0->protected, 1->not protected)
 //   BBBB       - Bitrate index (see table in implementation)
@@ -277,19 +285,21 @@ public:
   void Reset();
 
   // Clear the last parsed frame to allow for next frame parsing, i.e.:
   // - sets PrevFrame to CurrentFrame
   // - resets the CurrentFrame
   // - resets ID3Header if no valid header was parsed yet
   void EndFrameSession();
 
-  // Parses given buffer [aBeg, aEnd) for a valid frame header.
-  // Returns begin of frame header if a frame header was found or aEnd otherwise.
-  const uint8_t* Parse(const uint8_t* aBeg, const uint8_t* aEnd);
+  // Parses given buffer [aBeg, aEnd) for a valid frame header and returns a FrameParserResult.
+  // FrameParserResult.mBufferPos points to begin of frame header if a frame header was found
+  // or to aEnd otherwise. FrameParserResult.mBytesToSkip indicates whether additional bytes need to
+  // be skipped in order to jump across an ID3 tag that stretches beyond the given buffer.
+  FrameParserResult Parse(const uint8_t* aBeg, const uint8_t* aEnd);
 
   // Parses given buffer [aBeg, aEnd) for a valid VBR header.
   // Returns whether a valid VBR header was found.
   bool ParseVBRHeader(const uint8_t* aBeg, const uint8_t* aEnd);
 
 private:
   // ID3 header parser.
   ID3Parser mID3Parser;
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -458,16 +458,17 @@ void MediaDecoder::Shutdown()
 
   mShuttingDown = true;
 
   // This changes the decoder state to SHUTDOWN and does other things
   // necessary to unblock the state machine thread if it's blocked, so
   // the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
   if (mDecoderStateMachine) {
     mDecoderStateMachine->DispatchShutdown();
+    mTimedMetadataListener.Disconnect();
   }
 
   // Force any outstanding seek and byterange requests to complete
   // to prevent shutdown from deadlocking.
   if (mResource) {
     mResource->Close();
   }
 
@@ -540,16 +541,18 @@ nsresult MediaDecoder::InitializeStateMa
 }
 
 void MediaDecoder::SetStateMachineParameters()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mMinimizePreroll) {
     mDecoderStateMachine->DispatchMinimizePrerollUntilPlaybackStarts();
   }
+  mTimedMetadataListener = mDecoderStateMachine->TimedMetadataEvent().Connect(
+    AbstractThread::MainThread(), this, &MediaDecoder::OnMetadataUpdate);
 }
 
 void MediaDecoder::SetMinimizePrerollUntilPlaybackStarts()
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG("SetMinimizePrerollUntilPlaybackStarts()");
   mMinimizePreroll = true;
 
@@ -630,23 +633,25 @@ double MediaDecoder::GetCurrentTime()
 }
 
 already_AddRefed<nsIPrincipal> MediaDecoder::GetCurrentPrincipal()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mResource ? mResource->GetCurrentPrincipal() : nullptr;
 }
 
-void MediaDecoder::QueueMetadata(const TimeUnit& aPublishTime,
-                                 nsAutoPtr<MediaInfo> aInfo,
-                                 nsAutoPtr<MetadataTags> aTags)
+void MediaDecoder::OnMetadataUpdate(TimedMetadata&& aMetadata)
 {
-  MOZ_ASSERT(OnDecodeTaskQueue());
-  GetReentrantMonitor().AssertCurrentThreadIn();
-  mDecoderStateMachine->QueueMetadata(aPublishTime, aInfo, aTags);
+  MOZ_ASSERT(NS_IsMainThread());
+  RemoveMediaTracks();
+  MetadataLoaded(nsAutoPtr<MediaInfo>(new MediaInfo(*aMetadata.mInfo)),
+                 Move(aMetadata.mTags),
+                 MediaDecoderEventVisibility::Observable);
+  FirstFrameLoaded(Move(aMetadata.mInfo),
+                   MediaDecoderEventVisibility::Observable);
 }
 
 void MediaDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
                                   nsAutoPtr<MetadataTags> aTags,
                                   MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -179,37 +179,40 @@ state machine once its threads are shutd
 
 The owning object of a MediaDecoder object *MUST* call Shutdown when
 destroying the MediaDecoder object.
 
 */
 #if !defined(MediaDecoder_h_)
 #define MediaDecoder_h_
 
+#ifdef MOZ_EME
+#include "mozilla/CDMProxy.h"
+#endif
+
 #include "mozilla/MozPromise.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/StateMirroring.h"
 #include "mozilla/StateWatching.h"
 
 #include "mozilla/dom/AudioChannelBinding.h"
 
-#include "nsISupports.h"
+#include "necko-config.h"
+#include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
-#include "nsAutoPtr.h"
+#include "nsISupports.h"
 #include "nsITimer.h"
-#include "MediaResource.h"
-#include "MediaDecoderOwner.h"
-#include "MediaStreamGraph.h"
+
 #include "AbstractMediaDecoder.h"
-#include "DecodedStream.h"
-#include "necko-config.h"
-#ifdef MOZ_EME
-#include "mozilla/CDMProxy.h"
-#endif
+#include "MediaDecoderOwner.h"
+#include "MediaEventSource.h"
+#include "MediaMetadataManager.h"
+#include "MediaResource.h"
+#include "MediaStreamGraph.h"
 #include "TimeUnits.h"
 
 class nsIStreamListener;
 class nsIPrincipal;
 
 namespace mozilla {
 
 class VideoFrameContainer;
@@ -574,23 +577,16 @@ public:
 
   // Returns true if we can play the entire media through without stopping
   // to buffer, given the current download and playback rates.
   bool CanPlayThrough();
 
   void SetAudioChannel(dom::AudioChannel aChannel) { mAudioChannel = aChannel; }
   dom::AudioChannel GetAudioChannel() { return mAudioChannel; }
 
-  // Send a new set of metadata to the state machine, to be dispatched to the
-  // main thread to be presented when the |currentTime| of the media is greater
-  // or equal to aPublishTime.
-  void QueueMetadata(const media::TimeUnit& aPublishTime,
-                     nsAutoPtr<MediaInfo> aInfo,
-                     nsAutoPtr<MetadataTags> aTags) override;
-
   /******
    * The following methods must only be called on the main
    * thread.
    ******/
 
   // Change to a new play state. This updates the mState variable and
   // notifies any thread blocking on this object's monitor of the
   // change. Call on the main thread only.
@@ -613,17 +609,17 @@ public:
 
   // Called from MetadataLoaded(). Creates audio tracks and adds them to its
   // owner's audio track list, and implies to video tracks respectively.
   // Call on the main thread only.
   void ConstructMediaTracks();
 
   // Removes all audio tracks and video tracks that are previously added into
   // the track list. Call on the main thread only.
-  virtual void RemoveMediaTracks() override;
+  void RemoveMediaTracks();
 
   // Called when the video has completed playing.
   // Call on the main thread only.
   void PlaybackEnded();
 
   void OnSeekRejected()
   {
     MOZ_ASSERT(NS_IsMainThread());
@@ -984,16 +980,18 @@ protected:
   // Ensures our media stream has been pinned.
   void PinForSeek();
 
   // Ensures our media stream has been unpinned.
   void UnpinForSeek();
 
   const char* PlayStateStr();
 
+  void OnMetadataUpdate(TimedMetadata&& aMetadata);
+
   // This should only ever be accessed from the main thread.
   // It is set in Init and cleared in Shutdown when the element goes away.
   // The decoder does not add a reference the element.
   MediaDecoderOwner* mOwner;
 
   // Counters related to decode and presentation of frames.
   FrameStatistics mFrameStats;
 
@@ -1054,16 +1052,19 @@ protected:
   const int mHeuristicDormantTimeout;
 
   // True if MediaDecoder is in dormant by heuristic.
   bool mIsHeuristicDormant;
 
   // Timer to schedule updating dormant state.
   nsCOMPtr<nsITimer> mDormantTimer;
 
+  // A listener to receive metadata updates from MDSM.
+  MediaEventListener mTimedMetadataListener;
+
 protected:
   // Whether the state machine is shut down.
   Mirror<bool> mStateMachineIsShutdown;
 
   // Buffered range, mirrored from the reader.
   Mirror<media::TimeIntervals> mBuffered;
 
   // NextFrameStatus, mirrored from the state machine.
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -6,16 +6,17 @@
 #if !defined(MediaDecoderReader_h_)
 #define MediaDecoderReader_h_
 
 #include "mozilla/MozPromise.h"
 
 #include "AbstractMediaDecoder.h"
 #include "MediaInfo.h"
 #include "MediaData.h"
+#include "MediaMetadataManager.h"
 #include "MediaQueue.h"
 #include "MediaTimer.h"
 #include "AudioCompactor.h"
 #include "Intervals.h"
 #include "TimeUnits.h"
 
 namespace mozilla {
 
@@ -322,16 +323,20 @@ public:
   virtual bool IsAsync() const { return false; }
 
   // Returns true if this decoder reader uses hardware accelerated video
   // decoding.
   virtual bool VideoIsHardwareAccelerated() const { return false; }
 
   virtual void DisableHardwareAcceleration() {}
 
+  TimedMetadataEventSource& TimedMetadataEvent() {
+    return mTimedMetadataEvent;
+  }
+
 protected:
   virtual ~MediaDecoderReader();
 
   // Overrides of this function should decodes an unspecified amount of
   // audio data, enqueuing the audio data in mAudioQueue. Returns true
   // when there's more audio to decode, false if the audio is finished,
   // end of file has been reached, or an un-recoverable read error has
   // occured. This function blocks until the decode is complete.
@@ -413,16 +418,19 @@ protected:
 
   // This is a quick-and-dirty way for DecodeAudioData implementations to
   // communicate the presence of a decoding error to RequestAudioData. We should
   // replace this with a promise-y mechanism as we make this stuff properly
   // async.
   bool mHitAudioDecodeError;
   bool mShutdown;
 
+  // Used to send TimedMetadata to the listener.
+  TimedMetadataEventProducer mTimedMetadataEvent;
+
 private:
   // Promises used only for the base-class (sync->async adapter) implementation
   // of Request{Audio,Video}Data.
   MozPromiseHolder<AudioDataPromise> mBaseAudioPromise;
   MozPromiseHolder<VideoDataPromise> mBaseVideoPromise;
 
   bool mTaskQueueIsBorrowed;
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -11,16 +11,17 @@
 
 #include "mozilla/DebugOnly.h"
 #include <stdint.h>
 
 #include "MediaDecoderStateMachine.h"
 #include "MediaTimer.h"
 #include "mediasink/DecodedAudioDataSink.h"
 #include "mediasink/AudioSinkWrapper.h"
+#include "mediasink/DecodedStream.h"
 #include "nsTArray.h"
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/mozalloc.h"
 #include "VideoUtils.h"
 #include "TimeUnits.h"
 #include "nsDeque.h"
@@ -34,17 +35,16 @@
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/TaskQueue.h"
 #include "nsIEventTarget.h"
 #include "prenv.h"
 #include "mozilla/Preferences.h"
 #include "gfx2DGlue.h"
 #include "nsPrintfCString.h"
 #include "DOMMediaStream.h"
-#include "DecodedStream.h"
 #include "mozilla/Logging.h"
 
 #include <algorithm>
 
 namespace mozilla {
 
 using namespace mozilla::dom;
 using namespace mozilla::layers;
@@ -216,17 +216,17 @@ MediaDecoderStateMachine::MediaDecoderSt
   mDropVideoUntilNextDiscontinuity(false),
   mDecodeToSeekTarget(false),
   mCurrentTimeBeforeSeek(0),
   mCorruptFrames(60),
   mDecodingFirstFrame(true),
   mSentLoadedMetadataEvent(false),
   mSentFirstFrameLoadedEvent(false),
   mSentPlaybackEndedEvent(false),
-  mDecodedStream(new DecodedStream(mTaskQueue, mAudioQueue, mVideoQueue)),
+  mStreamSink(new DecodedStream(mTaskQueue, mAudioQueue, mVideoQueue)),
   mResource(aDecoder->GetResource()),
   mBuffered(mTaskQueue, TimeIntervals(),
             "MediaDecoderStateMachine::mBuffered (Mirror)"),
   mEstimatedDuration(mTaskQueue, NullableTimeUnit(),
                     "MediaDecoderStateMachine::mEstimatedDuration (Mirror)"),
   mExplicitDuration(mTaskQueue, Maybe<double>(),
                     "MediaDecoderStateMachine::mExplicitDuration (Mirror)"),
   mPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_LOADING,
@@ -284,16 +284,18 @@ MediaDecoderStateMachine::MediaDecoderSt
   timeBeginPeriod(1);
 #endif
 
   mAudioQueueListener = AudioQueue().PopEvent().Connect(
     mTaskQueue, this, &MediaDecoderStateMachine::OnAudioPopped);
   mVideoQueueListener = VideoQueue().PopEvent().Connect(
     mTaskQueue, this, &MediaDecoderStateMachine::OnVideoPopped);
 
+  mMetadataManager.Connect(mReader->TimedMetadataEvent(), OwnerThread());
+
   nsRefPtr<MediaDecoderStateMachine> self = this;
   auto audioSinkCreator = [self] () {
     MOZ_ASSERT(self->OnTaskQueue());
     return new DecodedAudioDataSink(
       self->mAudioQueue, self->GetMediaTime(),
       self->mInfo.mAudio, self->mDecoder->GetAudioChannel());
   };
   mAudioSink = new AudioSinkWrapper(mTaskQueue, audioSinkCreator);
@@ -1094,17 +1096,17 @@ void MediaDecoderStateMachine::UpdatePla
 }
 
 void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   UpdatePlaybackPositionInternal(aTime);
 
   bool fragmentEnded = mFragmentEndTime >= 0 && GetMediaTime() >= mFragmentEndTime;
-  mMetadataManager.DispatchMetadataIfNeeded(mDecoder, TimeUnit::FromMicroseconds(aTime));
+  mMetadataManager.DispatchMetadataIfNeeded(TimeUnit::FromMicroseconds(aTime));
 
   if (fragmentEnded) {
     StopPlayback();
   }
 }
 
 void MediaDecoderStateMachine::ClearPositionChangeFlag()
 {
@@ -1146,17 +1148,17 @@ void MediaDecoderStateMachine::SetState(
   mSentPlaybackEndedEvent = false;
 }
 
 void MediaDecoderStateMachine::VolumeChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   mAudioSink->SetVolume(mVolume);
-  mDecodedStream->SetVolume(mVolume);
+  mStreamSink->SetVolume(mVolume);
 }
 
 void MediaDecoderStateMachine::RecomputeDuration()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   TimeUnit duration;
@@ -1400,17 +1402,17 @@ void MediaDecoderStateMachine::Logically
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::SameOriginMediaChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  mDecodedStream->SetSameOrigin(mSameOriginMedia);
+  mStreamSink->SetSameOrigin(mSameOriginMedia);
 }
 
 void MediaDecoderStateMachine::BufferedRangeUpdated()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // While playing an unseekable stream of unknown duration, mObservedDuration
   // is updated (in AdvanceFrame()) as we play. But if data is being downloaded
@@ -1754,48 +1756,54 @@ MediaDecoderStateMachine::StartAudioSink
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   if (mAudioCaptured) {
     MOZ_ASSERT(!mAudioSink->IsStarted());
     return;
   }
 
-  if (HasAudio() && !mAudioSink->IsStarted()) {
+  if (!mAudioSink->IsStarted()) {
     mAudioCompleted = false;
     mAudioSink->Start(GetMediaTime(), mInfo);
 
-    mAudioSinkPromise.Begin(
-      mAudioSink->OnEnded(TrackInfo::kAudioTrack)->Then(
+    auto promise = mAudioSink->OnEnded(TrackInfo::kAudioTrack);
+    if (promise) {
+      mAudioSinkPromise.Begin(promise->Then(
         OwnerThread(), __func__, this,
         &MediaDecoderStateMachine::OnAudioSinkComplete,
         &MediaDecoderStateMachine::OnAudioSinkError));
+    }
   }
 }
 
 void
 MediaDecoderStateMachine::StopDecodedStream()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
-  mDecodedStream->StopPlayback();
-  mDecodedStreamPromise.DisconnectIfExists();
+
+  if (mStreamSink->IsStarted()) {
+    mStreamSink->Stop();
+    mDecodedStreamPromise.DisconnectIfExists();
+  }
 }
 
 void
 MediaDecoderStateMachine::StartDecodedStream()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
   // Tell DecodedStream to start playback with specified start time and media
   // info. This is consistent with how we create AudioSink in StartAudioThread().
-  if (mAudioCaptured && !mDecodedStreamPromise.Exists()) {
+  if (mAudioCaptured && !mStreamSink->IsStarted()) {
+    mStreamSink->Start(GetMediaTime(), mInfo);
     mDecodedStreamPromise.Begin(
-      mDecodedStream->StartPlayback(GetMediaTime(), mInfo)->Then(
+      mStreamSink->OnEnded(TrackInfo::kAudioTrack)->Then(
         OwnerThread(), __func__, this,
         &MediaDecoderStateMachine::OnDecodedStreamFinish,
         &MediaDecoderStateMachine::OnDecodedStreamError));
   }
 }
 
 int64_t MediaDecoderStateMachine::AudioDecodedUsecs()
 {
@@ -2178,28 +2186,38 @@ public:
 
 private:
   virtual ~DecoderDisposer() {}
   nsRefPtr<MediaDecoder> mDecoder;
   nsRefPtr<MediaDecoderStateMachine> mStateMachine;
 };
 
 void
+MediaDecoderStateMachine::DispatchShutdown()
+{
+  mStreamSink->BeginShutdown();
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::Shutdown);
+  OwnerThread()->Dispatch(runnable.forget());
+}
+
+void
 MediaDecoderStateMachine::FinishShutdown()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // The reader's listeners hold references to the state machine,
   // creating a cycle which keeps the state machine and its shared
   // thread pools alive. So break it here.
 
   // Prevent dangling pointers by disconnecting the listeners.
   mAudioQueueListener.Disconnect();
   mVideoQueueListener.Disconnect();
+  mMetadataManager.Disconnect();
 
   // Disconnect canonicals and mirrors before shutting down our task queue.
   mBuffered.DisconnectIfConnected();
   mEstimatedDuration.DisconnectIfConnected();
   mExplicitDuration.DisconnectIfConnected();
   mPlayState.DisconnectIfConnected();
   mNextPlayState.DisconnectIfConnected();
   mLogicallySeeking.DisconnectIfConnected();
@@ -2356,17 +2374,17 @@ nsresult MediaDecoderStateMachine::RunSt
       if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying()) {
         StopPlayback();
       }
       // Play the remaining media. We want to run AdvanceFrame() at least
       // once to ensure the current playback position is advanced to the
       // end of the media, and so that we update the readyState.
       if (VideoQueue().GetSize() > 1 ||
           (HasAudio() && !mAudioCompleted) ||
-          (mAudioCaptured && !mDecodedStream->IsFinished()))
+          (mAudioCaptured && !mStreamSink->IsFinished()))
       {
         // Start playback if necessary to play the remaining media.
         MaybeStartPlayback();
         UpdateRenderedVideoFrames();
         NS_ASSERTION(!IsPlaying() ||
                      mLogicallySeeking ||
                      IsStateMachineScheduled(),
                      "Must have timer scheduled");
@@ -2548,86 +2566,34 @@ void MediaDecoderStateMachine::RenderVid
                 frame->mTime, frame->mFrameID,
                 VideoQueue().GetSize() + mReader->SizeOfVideoQueueInFrames(),
                 VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames());
   }
 
   container->SetCurrentFrames(frames[0]->As<VideoData>()->mDisplay, images);
 }
 
-void MediaDecoderStateMachine::ResyncAudioClock()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  AssertCurrentThreadInMonitor();
-  if (IsPlaying()) {
-    SetPlayStartTime(TimeStamp::Now());
-    mPlayDuration = GetAudioClock();
-  }
-}
-
-int64_t
-MediaDecoderStateMachine::GetAudioClock() const
-{
-  MOZ_ASSERT(OnTaskQueue());
-  // We must hold the decoder monitor while using the audio stream off the
-  // audio sink to ensure that it doesn't get destroyed on the audio sink
-  // while we're using it.
-  AssertCurrentThreadInMonitor();
-  MOZ_ASSERT(HasAudio() && !mAudioCompleted && IsPlaying());
-  // Since this function is called while we are playing and AudioSink is
-  // started once playback starts, IsStarted() is guaranteed to be true.
-  MOZ_ASSERT(mAudioSink->IsStarted());
-  return mAudioSink->GetPosition();
-}
-
-int64_t MediaDecoderStateMachine::GetStreamClock() const
-{
-  MOZ_ASSERT(OnTaskQueue());
-  AssertCurrentThreadInMonitor();
-  return mDecodedStream->GetPosition();
-}
-
-int64_t MediaDecoderStateMachine::GetVideoStreamPosition(TimeStamp aTimeStamp) const
-{
-  MOZ_ASSERT(OnTaskQueue());
-  AssertCurrentThreadInMonitor();
-
-  if (!IsPlaying()) {
-    return mPlayDuration;
-  }
-
-  // Time elapsed since we started playing.
-  int64_t delta = DurationToUsecs(aTimeStamp - mPlayStartTime);
-  // Take playback rate into account.
-  delta *= mPlaybackRate;
-  return mPlayDuration + delta;
-}
-
 int64_t MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
   // Determine the clock time. If we've got audio, and we've not reached
   // the end of the audio, use the audio clock. However if we've finished
   // audio, or don't have audio, use the system clock. If our output is being
   // fed to a MediaStream, use that stream as the source of the clock.
   int64_t clock_time = -1;
   TimeStamp t;
   if (!IsPlaying()) {
     clock_time = mPlayDuration;
   } else {
     if (mAudioCaptured) {
-      clock_time = GetStreamClock();
-    } else if (HasAudio() && !mAudioCompleted) {
-      clock_time = GetAudioClock();
+      clock_time = mStreamSink->GetPosition(&t);
     } else {
-      t = TimeStamp::Now();
-      // Audio is disabled on this system. Sync to the system clock.
-      clock_time = GetVideoStreamPosition(t);
+      clock_time = mAudioSink->GetPosition(&t);
     }
     NS_ASSERTION(GetMediaTime() <= clock_time, "Clock should go forwards.");
   }
   if (aTimeStamp) {
     *aTimeStamp = t.IsNull() ? TimeStamp::Now() : t;
   }
 
   return clock_time;
@@ -2910,20 +2876,17 @@ void MediaDecoderStateMachine::StartBuff
 
 void MediaDecoderStateMachine::SetPlayStartTime(const TimeStamp& aTimeStamp)
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   mPlayStartTime = aTimeStamp;
 
   mAudioSink->SetPlaying(!mPlayStartTime.IsNull());
-  // Have DecodedStream remember the playing state so it doesn't need to
-  // ask MDSM about IsPlaying(). Note we have to do this even before capture
-  // happens since capture could happen in the middle of playback.
-  mDecodedStream->SetPlaying(!mPlayStartTime.IsNull());
+  mStreamSink->SetPlaying(!mPlayStartTime.IsNull());
 }
 
 void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   DispatchAudioDecodeTaskIfNeeded();
   DispatchVideoDecodeTaskIfNeeded();
@@ -2990,27 +2953,16 @@ MediaDecoderStateMachine::LogicalPlaybac
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   if (mLogicalPlaybackRate == 0) {
     // This case is handled in MediaDecoder by pausing playback.
     return;
   }
 
-  // AudioStream will handle playback rate change when we have audio.
-  // Do nothing while we are not playing. Change in playback rate will
-  // take effect next time we start playing again.
-  if (!HasAudio() && IsPlaying()) {
-    // Remember how much time we've spent in playing the media
-    // for playback rate will change from now on.
-    TimeStamp now = TimeStamp::Now();
-    mPlayDuration = GetVideoStreamPosition(now);
-    SetPlayStartTime(now);
-  }
-
   mPlaybackRate = mLogicalPlaybackRate;
   mAudioSink->SetPlaybackRate(mPlaybackRate);
 
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::PreservesPitchChanged()
 {
@@ -3020,62 +2972,47 @@ void MediaDecoderStateMachine::Preserves
 }
 
 bool MediaDecoderStateMachine::IsShutdown()
 {
   MOZ_ASSERT(OnTaskQueue());
   return mIsShutdown;
 }
 
-void MediaDecoderStateMachine::QueueMetadata(const TimeUnit& aPublishTime,
-                                             nsAutoPtr<MediaInfo> aInfo,
-                                             nsAutoPtr<MetadataTags> aTags)
-{
-  MOZ_ASSERT(OnDecodeTaskQueue());
-  AssertCurrentThreadInMonitor();
-  TimedMetadata* metadata = new TimedMetadata;
-  metadata->mPublishTime = aPublishTime;
-  metadata->mInfo = aInfo.forget();
-  metadata->mTags = aTags.forget();
-  mMetadataManager.QueueMetadata(metadata);
-}
-
 int64_t
 MediaDecoderStateMachine::AudioEndTime() const
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   if (mAudioSink->IsStarted()) {
     return mAudioSink->GetEndTime(TrackInfo::kAudioTrack);
   } else if (mAudioCaptured) {
-    return mDecodedStream->AudioEndTime();
+    return mStreamSink->GetEndTime(TrackInfo::kAudioTrack);
   }
   MOZ_ASSERT(!HasAudio());
   return -1;
 }
 
 void MediaDecoderStateMachine::OnAudioSinkComplete()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MOZ_ASSERT(!mAudioCaptured, "Should be disconnected when capturing audio.");
 
   mAudioSinkPromise.Complete();
-  ResyncAudioClock();
   mAudioCompleted = true;
 }
 
 void MediaDecoderStateMachine::OnAudioSinkError()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MOZ_ASSERT(!mAudioCaptured, "Should be disconnected when capturing audio.");
 
   mAudioSinkPromise.Complete();
-  ResyncAudioClock();
   mAudioCompleted = true;
 
   // Make the best effort to continue playback when there is video.
   if (HasVideo()) {
     return;
   }
 
   // Otherwise notify media decoder/element about this error for it makes
@@ -3160,26 +3097,26 @@ void MediaDecoderStateMachine::DispatchA
   OwnerThread()->Dispatch(r.forget());
 }
 
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
                                                bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG("AddOutputStream aStream=%p!", aStream);
-  mDecodedStream->AddOutput(aStream, aFinishWhenEnded);
+  mStreamSink->AddOutput(aStream, aFinishWhenEnded);
   DispatchAudioCaptured();
 }
 
 void MediaDecoderStateMachine::RemoveOutputStream(MediaStream* aStream)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG("RemoveOutputStream=%p!", aStream);
-  mDecodedStream->RemoveOutput(aStream);
-  if (!mDecodedStream->HasConsumers()) {
+  mStreamSink->RemoveOutput(aStream);
+  if (!mStreamSink->HasConsumers()) {
     DispatchAudioUncaptured();
   }
 }
 
 } // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef LOG
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -89,26 +89,26 @@ hardware (via AudioStream).
 
 #include "nsThreadUtils.h"
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "MediaDecoderOwner.h"
 #include "MediaEventSource.h"
 #include "MediaMetadataManager.h"
 #include "MediaTimer.h"
-#include "DecodedStream.h"
 #include "ImageContainer.h"
 
 namespace mozilla {
 
 namespace media {
 class MediaSink;
 }
 
 class AudioSegment;
+class DecodedStream;
 class TaskQueue;
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 extern PRLogModuleInfo* gMediaSampleLog;
 
 /*
   The state machine class. This manages the decoding and seeking in the
   MediaDecoderReader on the decode task queue, and A/V sync on the shared
@@ -152,35 +152,33 @@ public:
 
   void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
   // Remove an output stream added with AddOutputStream.
   void RemoveOutputStream(MediaStream* aStream);
 
   // Set/Unset dormant state.
   void SetDormant(bool aDormant);
 
+  TimedMetadataEventSource& TimedMetadataEvent() {
+    return mMetadataManager.TimedMetadataEvent();
+  }
+
 private:
   // Initialization that needs to happen on the task queue. This is the first
   // task that gets run on the task queue, and is dispatched from the MDSM
   // constructor immediately after the task queue is created.
   void InitializationTask();
 
   void DispatchAudioCaptured();
   void DispatchAudioUncaptured();
 
   void Shutdown();
 public:
 
-  void DispatchShutdown()
-  {
-    mDecodedStream->Shutdown();
-    nsCOMPtr<nsIRunnable> runnable =
-      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::Shutdown);
-    OwnerThread()->Dispatch(runnable.forget());
-  }
+  void DispatchShutdown();
 
   void FinishShutdown();
 
   // Immutable after construction - may be called on any thread.
   bool IsRealTime() const { return mRealTime; }
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
@@ -328,20 +326,16 @@ public:
   void DiscardStreamData();
   bool HaveEnoughDecodedAudio(int64_t aAmpleAudioUSecs);
   bool HaveEnoughDecodedVideo();
 
   // Returns true if the state machine has shutdown or is in the process of
   // shutting down. The decoder monitor must be held while calling this.
   bool IsShutdown();
 
-  void QueueMetadata(const media::TimeUnit& aPublishTime,
-                     nsAutoPtr<MediaInfo> aInfo,
-                     nsAutoPtr<MetadataTags> aTags);
-
   // Returns true if we're currently playing. The decoder monitor must
   // be held.
   bool IsPlaying() const;
 
   // Called when the reader may have acquired the hardware resources required
   // to begin decoding.
   void NotifyWaitingForResourcesStatusChanged();
 
@@ -452,32 +446,16 @@ protected:
 
   // Returns true if we recently exited "quick buffering" mode.
   bool JustExitedQuickBuffering();
 
   // Recomputes mNextFrameStatus, possibly dispatching notifications to interested
   // parties.
   void UpdateNextFrameStatus();
 
-  // Called when AudioSink reaches the end. |mPlayStartTime| and
-  // |mPlayDuration| are updated to provide a good base for calculating video
-  // stream time.
-  void ResyncAudioClock();
-
-  // Returns the audio clock, if we have audio, or -1 if we don't.
-  // Called on the state machine thread.
-  int64_t GetAudioClock() const;
-
-  int64_t GetStreamClock() const;
-
-  // Get the video stream position, taking the |playbackRate| change into
-  // account. This is a position in the media, not the duration of the playback
-  // so far. Returns the position for the given time aTimeStamp.
-  int64_t GetVideoStreamPosition(TimeStamp aTimeStamp) const;
-
   // Return the current time, either the audio clock if available (if the media
   // has audio, and the playback is possible), or a clock for the video.
   // Called on the state machine thread.
   // If aTimeStamp is non-null, set *aTimeStamp to the TimeStamp corresponding
   // to the returned stream time.
   int64_t GetClock(TimeStamp* aTimeStamp = nullptr) const;
 
   nsresult DropAudioUpToSeekTarget(AudioData* aSample);
@@ -1282,17 +1260,17 @@ private:
 
   bool mSentPlaybackEndedEvent;
 
   // The SourceMediaStream we are using to feed the mOutputStreams. This stream
   // is never exposed outside the decoder.
   // Only written on the main thread while holding the monitor. Therefore it
   // can be read on any thread while holding the monitor, or on the main thread
   // without holding the monitor.
-  nsRefPtr<DecodedStream> mDecodedStream;
+  nsRefPtr<DecodedStream> mStreamSink;
 
   // Media data resource from the decoder.
   nsRefPtr<MediaResource> mResource;
 
   MozPromiseRequestHolder<GenericPromise> mAudioSinkPromise;
   MozPromiseRequestHolder<GenericPromise> mDecodedStreamPromise;
 
   MediaEventListener mAudioQueueListener;
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1373,16 +1373,18 @@ MediaManager::IsInMediaThread()
       (sSingleton->mMediaThread->thread_id() == PlatformThread::CurrentId()) :
       false;
 }
 #endif
 
 // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
 // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
 // from MediaManager thread.
+
+// Guaranteed never to return nullptr.
 /* static */  MediaManager*
 MediaManager::Get() {
   if (!sSingleton) {
     NS_ASSERTION(NS_IsMainThread(), "Only create MediaManager on main thread");
 #ifdef DEBUG
     static int timesCreated = 0;
     timesCreated++;
     MOZ_ASSERT(timesCreated == 1);
@@ -2061,29 +2063,40 @@ MediaManager::EnumerateDevices(nsPIDOMWi
                                nsIDOMGetUserMediaErrorCallback* aOnFailure)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE(!sInShutdown, NS_ERROR_FAILURE);
   nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
   uint64_t windowId = aWindow->WindowID();
 
-  AddWindowID(windowId);
+  StreamListeners* listeners = AddWindowID(windowId);
+
+  // Create a disabled listener to act as a placeholder
+  nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
+    new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowId);
+
+  // No need for locking because we always do this in the main thread.
+  listeners->AppendElement(listener);
 
   bool fake = Preferences::GetBool("media.navigator.streams.fake");
 
   nsRefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId,
                                                      dom::MediaSourceEnum::Camera,
                                                      dom::MediaSourceEnum::Microphone,
                                                      fake);
-  p->Then([onSuccess](SourceSet*& aDevices) mutable {
+  p->Then([onSuccess, windowId, listener](SourceSet*& aDevices) mutable {
     ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
+    nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
+    mgr->RemoveFromWindowList(windowId, listener);
     nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
     onSuccess->OnSuccess(array);
-  }, [onFailure](MediaStreamError& reason) mutable {
+  }, [onFailure, windowId, listener](MediaStreamError& reason) mutable {
+    nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
+    mgr->RemoveFromWindowList(windowId, listener);
     onFailure->OnError(&reason);
   });
   return NS_OK;
 }
 
 /*
  * GetUserMediaDevices - called by the UI-part of getUserMedia from chrome JS.
  */
--- a/dom/media/MediaMetadataManager.h
+++ b/dom/media/MediaMetadataManager.h
@@ -2,73 +2,105 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(MediaMetadataManager_h__)
 #define MediaMetadataManager_h__
 
+#include "mozilla/AbstractThread.h"
 #include "mozilla/LinkedList.h"
 
 #include "nsAutoPtr.h"
 #include "AbstractMediaDecoder.h"
+#include "MediaEventSource.h"
 #include "TimeUnits.h"
 #include "VideoUtils.h"
 
 namespace mozilla {
 
+class TimedMetadata;
+typedef MediaEventProducer<TimedMetadata, ListenerMode::Exclusive>
+        TimedMetadataEventProducer;
+typedef MediaEventSource<TimedMetadata, ListenerMode::Exclusive>
+        TimedMetadataEventSource;
+
 // A struct that contains the metadata of a media, and the time at which those
 // metadata should start to be reported.
 class TimedMetadata : public LinkedListElement<TimedMetadata> {
 public:
+  TimedMetadata(const media::TimeUnit& aPublishTime,
+                nsAutoPtr<MetadataTags>&& aTags,
+                nsAutoPtr<MediaInfo>&& aInfo)
+    : mPublishTime(aPublishTime)
+    , mTags(Move(aTags))
+    , mInfo(Move(aInfo)) {}
+
+  // Define our move constructor because we don't want to move the members of
+  // LinkedListElement to change the list.
+  TimedMetadata(TimedMetadata&& aOther)
+    : mPublishTime(aOther.mPublishTime)
+    , mTags(Move(aOther.mTags))
+    , mInfo(Move(aOther.mInfo)) {}
+
   // The time, in microseconds, at which those metadata should be available.
   media::TimeUnit mPublishTime;
   // The metadata. The ownership is transfered to the element when dispatching to
   // the main threads.
   nsAutoPtr<MetadataTags> mTags;
   // The media info, including the info of audio tracks and video tracks.
   // The ownership is transfered to MediaDecoder when dispatching to the
   // main thread.
   nsAutoPtr<MediaInfo> mInfo;
 };
 
 // This class encapsulate the logic to give the metadata from the reader to
 // the content, at the right time.
-class MediaMetadataManager
-{
+class MediaMetadataManager {
 public:
   ~MediaMetadataManager() {
     TimedMetadata* element;
     while((element = mMetadataQueue.popFirst()) != nullptr) {
       delete element;
     }
   }
 
-  void QueueMetadata(TimedMetadata* aMetadata) {
-    mMetadataQueue.insertBack(aMetadata);
+  // Connect to an event source to receive TimedMetadata events.
+  void Connect(TimedMetadataEventSource& aEvent, AbstractThread* aThread) {
+    mListener = aEvent.Connect(
+      aThread, this, &MediaMetadataManager::OnMetadataQueued);
   }
 
-  void DispatchMetadataIfNeeded(AbstractMediaDecoder* aDecoder, const media::TimeUnit& aCurrentTime) {
+  // Stop receiving TimedMetadata events.
+  void Disconnect() {
+    mListener.Disconnect();
+  }
+
+  // Return an event source through which we will send TimedMetadata events
+  // when playback position reaches the publish time.
+  TimedMetadataEventSource& TimedMetadataEvent() {
+    return mTimedMetadataEvent;
+  }
+
+  void DispatchMetadataIfNeeded(const media::TimeUnit& aCurrentTime) {
     TimedMetadata* metadata = mMetadataQueue.getFirst();
     while (metadata && aCurrentTime >= metadata->mPublishTime) {
-      // Remove all media tracks from the list first.
-      nsCOMPtr<nsIRunnable> removeTracksEvent =
-        new RemoveMediaTracksEventRunner(aDecoder);
-      NS_DispatchToMainThread(removeTracksEvent);
-
-      nsCOMPtr<nsIRunnable> metadataUpdatedEvent =
-        new MetadataUpdatedEventRunner(aDecoder,
-                                       metadata->mInfo,
-                                       metadata->mTags);
-      NS_DispatchToMainThread(metadataUpdatedEvent);
+      // Our listener will figure out what to do with TimedMetadata.
+      mTimedMetadataEvent.Notify(Move(*metadata));
       delete mMetadataQueue.popFirst();
       metadata = mMetadataQueue.getFirst();
     }
   }
 
 protected:
+  void OnMetadataQueued(TimedMetadata&& aMetadata) {
+    mMetadataQueue.insertBack(new TimedMetadata(Move(aMetadata)));
+  }
+
   LinkedList<TimedMetadata> mMetadataQueue;
+  MediaEventListener mListener;
+  TimedMetadataEventProducer mTimedMetadataEvent;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -536,17 +536,17 @@ private:
     MediaStreamGraph* gm = mRecorder->GetSourceMediaStream()->Graph();
     mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
     MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
 
     mTrackUnionStream->SetAutofinish(true);
 
     // Bind this Track Union Stream with Source Media.
     mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->GetSourceMediaStream(),
-                                                      MediaInputPort::FLAG_BLOCK_OUTPUT);
+                                                      0);
 
     DOMMediaStream* domStream = mRecorder->Stream();
     if (domStream) {
       // Get the track type hint from DOM media stream.
       TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(this);
       domStream->OnTracksAvailable(tracksAvailableCallback);
     } else {
       // Web Audio node has only audio.
@@ -787,17 +787,17 @@ MediaRecorder::MediaRecorder(AudioNode& 
     AudioNodeEngine* engine = new AudioNodeEngine(nullptr);
     AudioNodeStream::Flags flags =
       AudioNodeStream::EXTERNAL_OUTPUT |
       AudioNodeStream::NEED_MAIN_THREAD_FINISHED;
     mPipeStream = AudioNodeStream::Create(ctx->Graph(), engine, flags);
     AudioNodeStream* ns = aSrcAudioNode.GetStream();
     if (ns) {
       mInputPort = mPipeStream->AllocateInputPort(aSrcAudioNode.GetStream(),
-                                                  MediaInputPort::FLAG_BLOCK_INPUT,
+                                                  0,
                                                   0,
                                                   aSrcOutput);
     }
   }
   mAudioNode = &aSrcAudioNode;
   if (!gMediaRecorderLog) {
     gMediaRecorderLog = PR_NewLogModule("MediaRecorder");
   }
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -20,17 +20,21 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/TimeStamp.h"
 #include "nsThreadUtils.h"
 #include <algorithm>
 
 // For HTTP seeking, if number of bytes needing to be
 // seeked forward is less than this value then a read is
 // done rather than a byte range request.
-static const int64_t SEEK_VS_READ_THRESHOLD = 32*1024;
+//
+// If we assume a 100Mbit connection, and assume reissuing an HTTP seek causes
+// a delay of 200ms, then in that 200ms we could have simply read ahead 2MB. So
+// setting SEEK_VS_READ_THRESHOLD to 1MB sounds reasonable.
+static const int64_t SEEK_VS_READ_THRESHOLD = 1 * 1024 * 1024;
 
 static const uint32_t HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE = 416;
 
 // Number of bytes we have accumulated before we assume the connection download
 // rate can be reliably calculated. 57 Segments at IW=3 allows slow start to
 // reach a CWND of 30 (See bug 831998)
 static const int64_t RELIABLE_DATA_THRESHOLD = 57 * 1460;
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -77,38 +77,35 @@ MediaStreamGraphImpl::FinishStream(Media
     return;
   STREAM_LOG(LogLevel::Debug, ("MediaStream %p will finish", aStream));
   aStream->mFinished = true;
   aStream->mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX);
 
   SetStreamOrderDirty();
 }
 
-static const GraphTime START_TIME_DELAYED = -1;
-
 void
 MediaStreamGraphImpl::AddStreamGraphThread(MediaStream* aStream)
 {
+  aStream->mBufferStartTime = mProcessedTime;
   // Check if we're adding a stream to a suspended context, in which case, we
-  // add it to mSuspendedStreams, and delay setting mBufferStartTime
+  // add it to mSuspendedStreams
   bool contextSuspended = false;
   if (aStream->AsAudioNodeStream()) {
     for (uint32_t i = 0; i < mSuspendedStreams.Length(); i++) {
       if (aStream->AudioContextId() == mSuspendedStreams[i]->AudioContextId()) {
         contextSuspended = true;
       }
     }
   }
 
   if (contextSuspended) {
-    aStream->mBufferStartTime = START_TIME_DELAYED;
     mSuspendedStreams.AppendElement(aStream);
     STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph, in the suspended stream array", aStream));
   } else {
-    aStream->mBufferStartTime = mProcessedTime;
     mStreams.AppendElement(aStream);
     STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph", aStream));
   }
 
   SetStreamOrderDirty();
 }
 
 void
@@ -354,71 +351,62 @@ MediaStreamGraphImpl::StreamReadyToFinis
     }
   }
 }
 
 void
 MediaStreamGraphImpl::UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime,
                                                   GraphTime aNextCurrentTime)
 {
-  nsTArray<MediaStream*>* runningAndSuspendedPair[2];
-  runningAndSuspendedPair[0] = &mStreams;
-  runningAndSuspendedPair[1] = &mSuspendedStreams;
-
-  for (uint32_t array = 0; array < 2; array++) {
-    for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) {
-      MediaStream* stream = (*runningAndSuspendedPair[array])[i];
-
-      // Calculate blocked time and fire Blocked/Unblocked events
-      GraphTime blockedTime = 0;
-      GraphTime t = aPrevCurrentTime;
-      // include |nextCurrentTime| to ensure NotifyBlockingChanged() is called
-      // before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime ==
-      // stream end time|
-      while (t <= aNextCurrentTime) {
-        GraphTime end;
-        bool blocked = stream->mBlocked.GetAt(t, &end);
-        if (blocked) {
-          blockedTime += std::min(end, aNextCurrentTime) - t;
+  for (MediaStream* stream : AllStreams()) {
+    // Calculate blocked time and fire Blocked/Unblocked events
+    GraphTime blockedTime = 0;
+    GraphTime t = aPrevCurrentTime;
+    // include |nextCurrentTime| to ensure NotifyBlockingChanged() is called
+    // before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime ==
+    // stream end time|
+    while (t <= aNextCurrentTime) {
+      GraphTime end;
+      bool blocked = stream->mBlocked.GetAt(t, &end);
+      if (blocked) {
+        blockedTime += std::min(end, aNextCurrentTime) - t;
+      }
+      if (blocked != stream->mNotifiedBlocked) {
+        for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
+          MediaStreamListener* l = stream->mListeners[j];
+          l->NotifyBlockingChanged(this, blocked
+                                           ? MediaStreamListener::BLOCKED
+                                           : MediaStreamListener::UNBLOCKED);
         }
-        if (blocked != stream->mNotifiedBlocked) {
-          for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
-            MediaStreamListener* l = stream->mListeners[j];
-            l->NotifyBlockingChanged(this, blocked
-                                             ? MediaStreamListener::BLOCKED
-                                             : MediaStreamListener::UNBLOCKED);
-          }
-          stream->mNotifiedBlocked = blocked;
-        }
-        t = end;
+        stream->mNotifiedBlocked = blocked;
       }
-
-      stream->AdvanceTimeVaryingValuesToCurrentTime(aNextCurrentTime,
-                                                    blockedTime);
-      // Advance mBlocked last so that AdvanceTimeVaryingValuesToCurrentTime
-      // can rely on the value of mBlocked.
-      stream->mBlocked.AdvanceCurrentTime(aNextCurrentTime);
-
-      if (runningAndSuspendedPair[array] == &mStreams) {
-        bool streamHasOutput = blockedTime < aNextCurrentTime - aPrevCurrentTime;
-        NS_ASSERTION(!streamHasOutput || !stream->mNotifiedFinished,
-          "Shouldn't have already notified of finish *and* have output!");
-
-        if (streamHasOutput) {
-          StreamNotifyOutput(stream);
-        }
-
-        if (stream->mFinished && !stream->mNotifiedFinished) {
-          StreamReadyToFinish(stream);
-        }
-      }
-      STREAM_LOG(LogLevel::Verbose,
-                 ("MediaStream %p bufferStartTime=%f blockedTime=%f", stream,
-                  MediaTimeToSeconds(stream->mBufferStartTime),
-                  MediaTimeToSeconds(blockedTime)));
+      t = end;
+    }
+
+    stream->AdvanceTimeVaryingValuesToCurrentTime(aNextCurrentTime,
+                                                  blockedTime);
+    // Advance mBlocked last so that AdvanceTimeVaryingValuesToCurrentTime
+    // can rely on the value of mBlocked.
+    stream->mBlocked.AdvanceCurrentTime(aNextCurrentTime);
+
+    STREAM_LOG(LogLevel::Verbose,
+               ("MediaStream %p bufferStartTime=%f blockedTime=%f", stream,
+                MediaTimeToSeconds(stream->mBufferStartTime),
+                MediaTimeToSeconds(blockedTime)));
+
+    bool streamHasOutput = blockedTime < aNextCurrentTime - aPrevCurrentTime;
+    NS_ASSERTION(!streamHasOutput || !stream->mNotifiedFinished,
+      "Shouldn't have already notified of finish *and* have output!");
+
+    if (streamHasOutput) {
+      StreamNotifyOutput(stream);
+    }
+
+    if (stream->mFinished && !stream->mNotifiedFinished) {
+      StreamReadyToFinish(stream);
     }
   }
 }
 
 bool
 MediaStreamGraphImpl::WillUnderrun(MediaStream* aStream, GraphTime aTime,
                                    GraphTime aEndBlockingDecisions, GraphTime* aEnd)
 {
@@ -737,36 +725,29 @@ MediaStreamGraphImpl::UpdateStreamOrder(
   MOZ_ASSERT(orderedStreamCount == mFirstCycleBreaker);
 }
 
 void
 MediaStreamGraphImpl::RecomputeBlocking(GraphTime aEndBlockingDecisions)
 {
   STREAM_LOG(LogLevel::Verbose, ("Media graph %p computing blocking for time %f",
                               this, MediaTimeToSeconds(mStateComputedTime)));
-  nsTArray<MediaStream*>* runningAndSuspendedPair[2];
-  runningAndSuspendedPair[0] = &mStreams;
-  runningAndSuspendedPair[1] = &mSuspendedStreams;
-
-  for (uint32_t array = 0; array < 2; array++) {
-    for (uint32_t i = 0; i < (*runningAndSuspendedPair[array]).Length(); ++i) {
-      MediaStream* stream = (*runningAndSuspendedPair[array])[i];
-      if (!stream->mInBlockingSet) {
-        // Compute a partition of the streams containing 'stream' such that we
-        // can
-        // compute the blocking status of each subset independently.
-        nsAutoTArray<MediaStream*, 10> streamSet;
-        AddBlockingRelatedStreamsToSet(&streamSet, stream);
-
-        GraphTime end;
-        for (GraphTime t = mStateComputedTime;
-             t < aEndBlockingDecisions; t = end) {
-          end = GRAPH_TIME_MAX;
-          RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end);
-        }
+  for (MediaStream* stream : AllStreams()) {
+    if (!stream->mInBlockingSet) {
+      // Compute a partition of the streams containing 'stream' such that we
+      // can
+      // compute the blocking status of each subset independently.
+      nsAutoTArray<MediaStream*, 10> streamSet;
+      AddBlockingRelatedStreamsToSet(&streamSet, stream);
+
+      GraphTime end;
+      for (GraphTime t = mStateComputedTime;
+           t < aEndBlockingDecisions; t = end) {
+        end = GRAPH_TIME_MAX;
+        RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end);
       }
     }
   }
   STREAM_LOG(LogLevel::Verbose, ("Media graph %p computed blocking for interval %f to %f",
                               this, MediaTimeToSeconds(mStateComputedTime),
                               MediaTimeToSeconds(aEndBlockingDecisions)));
 
   MOZ_ASSERT(aEndBlockingDecisions >= mProcessedTime);
@@ -860,16 +841,22 @@ MediaStreamGraphImpl::RecomputeBlockingA
     bool explicitBlock = stream->mExplicitBlockerCount.GetAt(aTime, &end) > 0;
     *aEnd = std::min(*aEnd, end);
     if (explicitBlock) {
       STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is blocked due to explicit blocker", stream));
       MarkStreamBlocking(stream);
       continue;
     }
 
+    if (StreamSuspended(stream)) {
+      STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is blocked due to being suspended", stream));
+      MarkStreamBlocking(stream);
+      continue;
+    }
+
     bool underrun = WillUnderrun(stream, aTime, aEndBlockingDecisions, aEnd);
     if (underrun) {
       // We'll block indefinitely
       MarkStreamBlocking(stream);
       *aEnd = std::min(*aEnd, aEndBlockingDecisions);
       continue;
     }
   }
@@ -1258,19 +1245,19 @@ MediaStreamGraphImpl::ShouldUpdateMainTh
 void
 MediaStreamGraphImpl::PrepareUpdatesToMainThreadState(bool aFinalUpdate)
 {
   mMonitor.AssertCurrentThreadOwns();
 
   // We don't want to frequently update the main thread about timing update
   // when we are not running in realtime.
   if (aFinalUpdate || ShouldUpdateMainThread()) {
-    mStreamUpdates.SetCapacity(mStreamUpdates.Length() + mStreams.Length());
-    for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-      MediaStream* stream = mStreams[i];
+    mStreamUpdates.SetCapacity(mStreamUpdates.Length() + mStreams.Length() +
+        mSuspendedStreams.Length());
+    for (MediaStream* stream : AllStreams()) {
       if (!stream->MainThreadNeedsUpdates()) {
         continue;
       }
       StreamUpdate* update = mStreamUpdates.AppendElement();
       update->mStream = stream;
       update->mNextMainThreadCurrentTime =
         GraphTimeToStreamTime(stream, mProcessedTime);
       update->mNextMainThreadFinished = stream->mNotifiedFinished;
@@ -1324,19 +1311,18 @@ MediaStreamGraphImpl::ProduceDataForStre
     t = next;
   }
   NS_ASSERTION(t == aTo, "Something went wrong with rounding to block boundaries");
 }
 
 bool
 MediaStreamGraphImpl::AllFinishedStreamsNotified()
 {
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-    MediaStream* s = mStreams[i];
-    if (s->mFinished && !s->mNotifiedFinished) {
+  for (MediaStream* stream : AllStreams()) {
+    if (stream->mFinished && !stream->mNotifiedFinished) {
       return false;
     }
   }
   return true;
 }
 
 void
 MediaStreamGraphImpl::UpdateGraph(GraphTime aEndBlockingDecision)
@@ -1471,18 +1457,18 @@ MediaStreamGraphImpl::Process(GraphTime 
 bool
 MediaStreamGraphImpl::OneIteration(GraphTime aStateEnd)
 {
   {
     MonitorAutoLock lock(mMemoryReportMonitor);
     if (mNeedsMemoryReport) {
       mNeedsMemoryReport = false;
 
-      for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-        AudioNodeStream* stream = mStreams[i]->AsAudioNodeStream();
+      for (MediaStream* s : AllStreams()) {
+        AudioNodeStream* stream = s->AsAudioNodeStream();
         if (stream) {
           AudioNodeSizes usage;
           stream->SizeOfAudioNodesIncludingThis(MallocSizeOf, usage);
           mAudioStreamSizes.AppendElement(usage);
         }
       }
 
       lock.Notify();
@@ -1591,18 +1577,18 @@ public:
       mGraph->Destroy();
     } else {
       // The graph is not empty.  We must be in a forced shutdown, or a
       // non-realtime graph that has finished processing.  Some later
       // AppendMessage will detect that the manager has been emptied, and
       // delete it.
       NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime,
                    "Not in forced shutdown?");
-      for (uint32_t i = 0; i < mGraph->mStreams.Length(); ++i) {
-        DOMMediaStream* s = mGraph->mStreams[i]->GetWrapper();
+      for (MediaStream* stream : mGraph->AllStreams()) {
+        DOMMediaStream* s = stream->GetWrapper();
         if (s) {
           s->NotifyMediaStreamGraphShutdown();
         }
       }
 
       mGraph->mLifecycleState =
         MediaStreamGraphImpl::LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION;
     }
@@ -3187,29 +3173,22 @@ MediaStreamGraph::NotifyWhenGraphStarted
     graphImpl->AppendMessage(new GraphStartedNotificationControlMessage(aStream));
   }
 }
 
 void
 MediaStreamGraphImpl::ResetVisitedStreamState()
 {
   // Reset the visited/consumed/blocked state of the streams.
-  nsTArray<MediaStream*>* runningAndSuspendedPair[2];
-  runningAndSuspendedPair[0] = &mStreams;
-  runningAndSuspendedPair[1] = &mSuspendedStreams;
-
-  for (uint32_t array = 0; array < 2; array++) {
-    for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) {
-      ProcessedMediaStream* ps =
-        (*runningAndSuspendedPair[array])[i]->AsProcessedStream();
-      if (ps) {
-        ps->mCycleMarker = NOT_VISITED;
-        ps->mIsConsumed = false;
-        ps->mInBlockingSet = false;
-      }
+  for (MediaStream* stream : AllStreams()) {
+    ProcessedMediaStream* ps = stream->AsProcessedStream();
+    if (ps) {
+      ps->mCycleMarker = NOT_VISITED;
+      ps->mIsConsumed = false;
+      ps->mInBlockingSet = false;
     }
   }
 }
 
 void
 MediaStreamGraphImpl::StreamSetForAudioContext(dom::AudioContext::AudioContextId aAudioContextId,
                                   mozilla::LinkedList<MediaStream>& aStreamSet)
 {
@@ -3245,23 +3224,16 @@ MediaStreamGraphImpl::MoveStreams(AudioC
     // It is posible to not find the stream here, if there has been two
     // suspend/resume/close calls in a row.
     auto i = from.IndexOf(stream);
     if (i != from.NoIndex) {
       from.RemoveElementAt(i);
       to.AppendElement(stream);
     }
 
-    // If streams got added during a period where an AudioContext was suspended,
-    // set their buffer start time to the appropriate value now:
-    if (aAudioContextOperation == AudioContextOperation::Resume &&
-        stream->mBufferStartTime == START_TIME_DELAYED) {
-      stream->mBufferStartTime = mProcessedTime;
-    }
-
     stream->remove();
   }
   STREAM_LOG(LogLevel::Debug, ("Moving streams between suspended and running"
       "state: mStreams: %d, mSuspendedStreams: %d\n", mStreams.Length(),
       mSuspendedStreams.Length()));
 #ifdef DEBUG
   // The intersection of the two arrays should be null.
   for (uint32_t i = 0; i < mStreams.Length(); i++) {
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -523,16 +523,66 @@ public:
 
   // Capture Stream API. This allows to get a mixed-down output for a window.
   void RegisterCaptureStreamForWindow(uint64_t aWindowId,
                                       ProcessedMediaStream* aCaptureStream);
   void UnregisterCaptureStreamForWindow(uint64_t aWindowId);
   already_AddRefed<MediaInputPort>
   ConnectToCaptureStream(uint64_t aWindowId, MediaStream* aMediaStream);
 
+  class StreamSet {
+  public:
+    class iterator {
+    public:
+      explicit iterator(MediaStreamGraphImpl& aGraph)
+        : mGraph(&aGraph), mArrayNum(-1), mArrayIndex(0)
+      {
+        ++(*this);
+      }
+      iterator() : mGraph(nullptr), mArrayNum(2), mArrayIndex(0) {}
+      MediaStream* operator*()
+      {
+        return Array()->ElementAt(mArrayIndex);
+      }
+      iterator operator++()
+      {
+        ++mArrayIndex;
+        while (mArrayNum < 2 &&
+          (mArrayNum < 0 || mArrayIndex >= Array()->Length())) {
+          ++mArrayNum;
+          mArrayIndex = 0;
+        }
+        return *this;
+      }
+      bool operator==(const iterator& aOther) const
+      {
+        return mArrayNum == aOther.mArrayNum && mArrayIndex == aOther.mArrayIndex;
+      }
+      bool operator!=(const iterator& aOther) const
+      {
+        return !(*this == aOther);
+      }
+    private:
+      nsTArray<MediaStream*>* Array()
+      {
+        return mArrayNum == 0 ? &mGraph->mStreams : &mGraph->mSuspendedStreams;
+      }
+      MediaStreamGraphImpl* mGraph;
+      int mArrayNum;
+      uint32_t mArrayIndex;
+    };
+
+    explicit StreamSet(MediaStreamGraphImpl& aGraph) : mGraph(aGraph) {}
+    iterator begin() { return iterator(mGraph); }
+    iterator end() { return iterator(); }
+  private:
+    MediaStreamGraphImpl& mGraph;
+  };
+  StreamSet AllStreams() { return StreamSet(*this); }
+
   // Data members
   //
   /**
    * Graphs own owning references to their driver, until shutdown. When a driver
    * switch occur, previous driver is either deleted, or it's ownership is
    * passed to a event that will take care of the asynchronous cleanup, as
    * audio stream can take some time to shut down.
    */
--- a/dom/media/XiphExtradata.cpp
+++ b/dom/media/XiphExtradata.cpp
@@ -64,17 +64,17 @@ bool XiphExtradataToHeaders(nsTArray<uns
     // to underflow.
     if (aAvailable - total < headerLen) {
       return false;
     }
     aHeaderLens.AppendElement(headerLen);
     // Since we know aAvailable >= total + headerLen, this can't overflow.
     total += headerLen;
   }
-  aHeaderLens.AppendElement(aAvailable);
+  aHeaderLens.AppendElement(aAvailable - total);
   for (int i = 0; i < nHeaders; i++) {
     aHeaders.AppendElement(aData);
     aData += aHeaderLens[i];
   }
   return true;
 }
 
 } // namespace mozilla
--- a/dom/media/mediasink/AudioSinkWrapper.cpp
+++ b/dom/media/mediasink/AudioSinkWrapper.cpp
@@ -29,19 +29,19 @@ AudioSinkWrapper::GetPlaybackParams() co
   return mParams;
 }
 
 void
 AudioSinkWrapper::SetPlaybackParams(const PlaybackParams& aParams)
 {
   AssertOwnerThread();
   if (mAudioSink) {
-    mAudioSink->SetVolume(aParams.volume);
-    mAudioSink->SetPlaybackRate(aParams.playbackRate);
-    mAudioSink->SetPreservesPitch(aParams.preservesPitch);
+    mAudioSink->SetVolume(aParams.mVolume);
+    mAudioSink->SetPlaybackRate(aParams.mPlaybackRate);
+    mAudioSink->SetPreservesPitch(aParams.mPreservesPitch);
   }
   mParams = aParams;
 }
 
 nsRefPtr<GenericPromise>
 AudioSinkWrapper::OnEnded(TrackType aType)
 {
   AssertOwnerThread();
@@ -59,55 +59,94 @@ AudioSinkWrapper::GetEndTime(TrackType a
   MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
   if (aType == TrackInfo::kAudioTrack && mAudioSink) {
     return mAudioSink->GetEndTime();
   }
   return -1;
 }
 
 int64_t
-AudioSinkWrapper::GetPosition() const
+AudioSinkWrapper::GetVideoPosition(TimeStamp aNow) const
+{
+  AssertOwnerThread();
+  MOZ_ASSERT(!mPlayStartTime.IsNull());
+  // Time elapsed since we started playing.
+  int64_t delta = (aNow - mPlayStartTime).ToMicroseconds();
+  // Take playback rate into account.
+  return mPlayDuration + delta * mParams.mPlaybackRate;
+}
+
+int64_t
+AudioSinkWrapper::GetPosition(TimeStamp* aTimeStamp) const
 {
   AssertOwnerThread();
   MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
-  return mAudioSink->GetPosition();
+
+  int64_t pos = -1;
+  TimeStamp t = TimeStamp::Now();
+
+  if (!mAudioEnded) {
+    // Rely on the audio sink to report playback position when it is not ended.
+    pos = mAudioSink->GetPosition();
+  } else if (!mPlayStartTime.IsNull()) {
+    // Calculate playback position using system clock if we are still playing.
+    pos = GetVideoPosition(t);
+  } else {
+    // Return how long we've played if we are not playing.
+    pos = mPlayDuration;
+  }
+
+  if (aTimeStamp) {
+    *aTimeStamp = t;
+  }
+
+  return pos;
 }
 
 bool
 AudioSinkWrapper::HasUnplayedFrames(TrackType aType) const
 {
   AssertOwnerThread();
   return mAudioSink ? mAudioSink->HasUnplayedFrames() : false;
 }
 
 void
 AudioSinkWrapper::SetVolume(double aVolume)
 {
   AssertOwnerThread();
-  mParams.volume = aVolume;
+  mParams.mVolume = aVolume;
   if (mAudioSink) {
     mAudioSink->SetVolume(aVolume);
   }
 }
 
 void
 AudioSinkWrapper::SetPlaybackRate(double aPlaybackRate)
 {
   AssertOwnerThread();
-  mParams.playbackRate = aPlaybackRate;
-  if (mAudioSink) {
+  mParams.mPlaybackRate = aPlaybackRate;
+  if (!mAudioEnded) {
+    // Pass the playback rate to the audio sink. The underlying AudioStream
+    // will handle playback rate changes and report correct audio position.
     mAudioSink->SetPlaybackRate(aPlaybackRate);
+  } else if (!mPlayStartTime.IsNull()) {
+    // Adjust playback duration and start time when we are still playing.
+    TimeStamp now = TimeStamp::Now();
+    mPlayDuration = GetVideoPosition(now);
+    mPlayStartTime = now;
   }
+  // Do nothing when not playing. Changes in playback rate will be taken into
+  // account by GetVideoPosition().
 }
 
 void
 AudioSinkWrapper::SetPreservesPitch(bool aPreservesPitch)
 {
   AssertOwnerThread();
-  mParams.preservesPitch = aPreservesPitch;
+  mParams.mPreservesPitch = aPreservesPitch;
   if (mAudioSink) {
     mAudioSink->SetPreservesPitch(aPreservesPitch);
   }
 }
 
 void
 AudioSinkWrapper::SetPlaying(bool aPlaying)
 {
@@ -116,45 +155,85 @@ AudioSinkWrapper::SetPlaying(bool aPlayi
   // Resume/pause matters only when playback started.
   if (!mIsStarted) {
     return;
   }
 
   if (mAudioSink) {
     mAudioSink->SetPlaying(aPlaying);
   }
+
+  if (aPlaying) {
+    MOZ_ASSERT(mPlayStartTime.IsNull());
+    mPlayStartTime = TimeStamp::Now();
+  } else {
+    // Remember how long we've played.
+    mPlayDuration = GetPosition();
+    // mPlayStartTime must be updated later since GetPosition()
+    // depends on the value of mPlayStartTime.
+    mPlayStartTime = TimeStamp();
+  }
 }
 
 void
 AudioSinkWrapper::Start(int64_t aStartTime, const MediaInfo& aInfo)
 {
   AssertOwnerThread();
   MOZ_ASSERT(!mIsStarted, "playback already started.");
 
   mIsStarted = true;
+  mPlayDuration = aStartTime;
+  mPlayStartTime = TimeStamp::Now();
 
-  mAudioSink = mCreator->Create();
-  mEndPromise = mAudioSink->Init();
-  SetPlaybackParams(mParams);
+  // no audio is equivalent to audio ended before video starts.
+  mAudioEnded = !aInfo.HasAudio();
+
+  if (aInfo.HasAudio()) {
+    mAudioSink = mCreator->Create();
+    mEndPromise = mAudioSink->Init();
+    SetPlaybackParams(mParams);
+
+    mAudioSinkPromise.Begin(mEndPromise->Then(
+      mOwnerThread.get(), __func__, this,
+      &AudioSinkWrapper::OnAudioEnded,
+      &AudioSinkWrapper::OnAudioEnded));
+  }
 }
 
 void
 AudioSinkWrapper::Stop()
 {
   AssertOwnerThread();
   MOZ_ASSERT(mIsStarted, "playback not started.");
 
   mIsStarted = false;
-  mAudioSink->Shutdown();
-  mAudioSink = nullptr;
-  mEndPromise = nullptr;
+  mAudioEnded = true;
+
+  if (mAudioSink) {
+    mAudioSinkPromise.DisconnectIfExists();
+    mAudioSink->Shutdown();
+    mAudioSink = nullptr;
+    mEndPromise = nullptr;
+  }
 }
 
 bool
 AudioSinkWrapper::IsStarted() const
 {
   AssertOwnerThread();
   return mIsStarted;
 }
 
+void
+AudioSinkWrapper::OnAudioEnded()
+{
+  AssertOwnerThread();
+  mAudioSinkPromise.Complete();
+  mPlayDuration = GetPosition();
+  if (!mPlayStartTime.IsNull()) {
+    mPlayStartTime = TimeStamp::Now();
+  }
+  mAudioEnded = true;
+}
+
 } // namespace media
 } // namespace mozilla
 
--- a/dom/media/mediasink/AudioSinkWrapper.h
+++ b/dom/media/mediasink/AudioSinkWrapper.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef AudioSinkWrapper_h_
 #define AudioSinkWrapper_h_
 
 #include "mozilla/AbstractThread.h"
 #include "mozilla/dom/AudioChannelBinding.h"
 #include "mozilla/nsRefPtr.h"
+#include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 
 #include "MediaSink.h"
 
 namespace mozilla {
 
 class MediaData;
 template <class T> class MediaQueue;
@@ -45,24 +46,27 @@ class AudioSinkWrapper : public MediaSin
   };
 
 public:
   template <typename Function>
   AudioSinkWrapper(AbstractThread* aOwnerThread, const Function& aFunc)
     : mOwnerThread(aOwnerThread)
     , mCreator(new CreatorImpl<Function>(aFunc))
     , mIsStarted(false)
+    // Give an insane value to facilitate debug if used before playback starts.
+    , mPlayDuration(INT64_MAX)
+    , mAudioEnded(true)
   {}
 
   const PlaybackParams& GetPlaybackParams() const override;
   void SetPlaybackParams(const PlaybackParams& aParams) override;
 
   nsRefPtr<GenericPromise> OnEnded(TrackType aType) override;
   int64_t GetEndTime(TrackType aType) const override;
-  int64_t GetPosition() const override;
+  int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const override;
   bool HasUnplayedFrames(TrackType aType) const override;
 
   void SetVolume(double aVolume) override;
   void SetPlaybackRate(double aPlaybackRate) override;
   void SetPreservesPitch(bool aPreservesPitch) override;
   void SetPlaying(bool aPlaying) override;
 
   void Start(int64_t aStartTime, const MediaInfo& aInfo) override;
@@ -73,21 +77,31 @@ public:
 
 private:
   virtual ~AudioSinkWrapper();
 
   void AssertOwnerThread() const {
     MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   }
 
+  int64_t GetVideoPosition(TimeStamp aNow) const;
+
+  void OnAudioEnded();
+
   const nsRefPtr<AbstractThread> mOwnerThread;
   UniquePtr<Creator> mCreator;
   nsRefPtr<AudioSink> mAudioSink;
   nsRefPtr<GenericPromise> mEndPromise;
 
   bool mIsStarted;
   PlaybackParams mParams;
+
+  TimeStamp mPlayStartTime;
+  int64_t mPlayDuration;
+
+  bool mAudioEnded;
+  MozPromiseRequestHolder<GenericPromise> mAudioSinkPromise;
 };
 
 } // namespace media
 } // namespace mozilla
 
 #endif //AudioSinkWrapper_h_
rename from dom/media/DecodedStream.cpp
rename to dom/media/mediasink/DecodedStream.cpp
--- a/dom/media/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -1,14 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/CheckedInt.h"
+#include "mozilla/gfx/Point.h"
+
 #include "AudioSegment.h"
 #include "DecodedStream.h"
 #include "MediaData.h"
 #include "MediaQueue.h"
 #include "MediaStreamGraph.h"
 #include "SharedBuffer.h"
 #include "VideoSegment.h"
 #include "VideoUtils.h"
@@ -245,20 +248,17 @@ OutputStreamData::Init(OutputStreamManag
 
 void
 OutputStreamData::Connect(MediaStream* aStream)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mPort, "Already connected?");
   MOZ_ASSERT(!mStream->IsDestroyed(), "Can't connect a destroyed stream.");
 
-  // The output stream must stay in sync with the input stream, so if
-  // either stream is blocked, we block the other.
-  mPort = mStream->AllocateInputPort(aStream,
-    MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT);
+  mPort = mStream->AllocateInputPort(aStream, 0);
   // Unblock the output stream now. The input stream is responsible for
   // controlling blocking from now on.
   mStream->ChangeExplicitBlockerCount(-1);
 }
 
 bool
 OutputStreamData::Disconnect()
 {
@@ -353,43 +353,73 @@ OutputStreamManager::Disconnect()
 }
 
 DecodedStream::DecodedStream(AbstractThread* aOwnerThread,
                              MediaQueue<MediaData>& aAudioQueue,
                              MediaQueue<MediaData>& aVideoQueue)
   : mOwnerThread(aOwnerThread)
   , mShuttingDown(false)
   , mPlaying(false)
-  , mVolume(1.0)
   , mSameOrigin(false)
   , mAudioQueue(aAudioQueue)
   , mVideoQueue(aVideoQueue)
 {
 }
 
 DecodedStream::~DecodedStream()
 {
   MOZ_ASSERT(mStartTime.isNothing(), "playback should've ended.");
 }
 
+const media::MediaSink::PlaybackParams&
+DecodedStream::GetPlaybackParams() const
+{
+  AssertOwnerThread();
+  return mParams;
+}
+
 void
-DecodedStream::Shutdown()
+DecodedStream::SetPlaybackParams(const PlaybackParams& aParams)
+{
+  AssertOwnerThread();
+  mParams = aParams;
+}
+
+nsRefPtr<GenericPromise>
+DecodedStream::OnEnded(TrackType aType)
+{
+  AssertOwnerThread();
+  MOZ_ASSERT(mStartTime.isSome());
+
+  if (aType == TrackInfo::kAudioTrack) {
+    // TODO: we should return a promise which is resolved when the audio track
+    // is finished. For now this promise is resolved when the whole stream is
+    // finished.
+    return mFinishPromise;
+  }
+  // TODO: handle video track.
+  return nullptr;
+}
+
+void
+DecodedStream::BeginShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mShuttingDown = true;
 }
 
-nsRefPtr<GenericPromise>
-DecodedStream::StartPlayback(int64_t aStartTime, const MediaInfo& aInfo)
+void
+DecodedStream::Start(int64_t aStartTime, const MediaInfo& aInfo)
 {
   AssertOwnerThread();
   MOZ_ASSERT(mStartTime.isNothing(), "playback already started.");
 
   mStartTime.emplace(aStartTime);
   mInfo = aInfo;
+  mPlaying = true;
   ConnectListener();
 
   class R : public nsRunnable {
     typedef MozPromiseHolder<GenericPromise> Promise;
     typedef void(DecodedStream::*Method)(Promise&&);
   public:
     R(DecodedStream* aThis, Method aMethod, Promise&& aPromise)
       : mThis(aThis), mMethod(aMethod)
@@ -403,40 +433,43 @@ DecodedStream::StartPlayback(int64_t aSt
     }
   private:
     nsRefPtr<DecodedStream> mThis;
     Method mMethod;
     Promise mPromise;
   };
 
   MozPromiseHolder<GenericPromise> promise;
-  nsRefPtr<GenericPromise> rv = promise.Ensure(__func__);
+  mFinishPromise = promise.Ensure(__func__);
   nsCOMPtr<nsIRunnable> r = new R(this, &DecodedStream::CreateData, Move(promise));
   AbstractThread::MainThread()->Dispatch(r.forget());
-
-  return rv.forget();
 }
 
-void DecodedStream::StopPlayback()
+void
+DecodedStream::Stop()
 {
   AssertOwnerThread();
-
-  // Playback didn't even start at all.
-  if (mStartTime.isNothing()) {
-    return;
-  }
+  MOZ_ASSERT(mStartTime.isSome(), "playback not started.");
 
   mStartTime.reset();
   DisconnectListener();
+  mFinishPromise = nullptr;
 
   // Clear mData immediately when this playback session ends so we won't
   // send data to the wrong stream in SendData() in next playback session.
   DestroyData(Move(mData));
 }
 
+bool
+DecodedStream::IsStarted() const
+{
+  AssertOwnerThread();
+  return mStartTime.isSome();
+}
+
 void
 DecodedStream::DestroyData(UniquePtr<DecodedStreamData> aData)
 {
   AssertOwnerThread();
 
   if (!aData) {
     return;
   }
@@ -526,27 +559,47 @@ DecodedStream::RemoveOutput(MediaStream*
 {
   mOutputStreamManager.Remove(aStream);
 }
 
 void
 DecodedStream::SetPlaying(bool aPlaying)
 {
   AssertOwnerThread();
+
+  // Resume/pause matters only when playback started.
+  if (mStartTime.isNothing()) {
+    return;
+  }
+
   mPlaying = aPlaying;
   if (mData) {
     mData->SetPlaying(aPlaying);
   }
 }
 
 void
 DecodedStream::SetVolume(double aVolume)
 {
   AssertOwnerThread();
-  mVolume = aVolume;
+  mParams.mVolume = aVolume;
+}
+
+void
+DecodedStream::SetPlaybackRate(double aPlaybackRate)
+{
+  AssertOwnerThread();
+  mParams.mPlaybackRate = aPlaybackRate;
+}
+
+void
+DecodedStream::SetPreservesPitch(bool aPreservesPitch)
+{
+  AssertOwnerThread();
+  mParams.mPreservesPitch = aPreservesPitch;
 }
 
 void
 DecodedStream::SetSameOrigin(bool aSameOrigin)
 {
   AssertOwnerThread();
   mSameOrigin = aSameOrigin;
 }
@@ -810,50 +863,55 @@ DecodedStream::SendData()
   }
 
   // Nothing to do when the stream is finished.
   if (mData->mHaveSentFinish) {
     return;
   }
 
   InitTracks();
-  SendAudio(mVolume, mSameOrigin);
+  SendAudio(mParams.mVolume, mSameOrigin);
   SendVideo(mSameOrigin);
   AdvanceTracks();
 
   bool finished = (!mInfo.HasAudio() || mAudioQueue.IsFinished()) &&
                   (!mInfo.HasVideo() || mVideoQueue.IsFinished());
 
   if (finished && !mData->mHaveSentFinish) {
     mData->mHaveSentFinish = true;
     mData->mStream->Finish();
   }
 }
 
 int64_t
-DecodedStream::AudioEndTime() const
+DecodedStream::GetEndTime(TrackType aType) const
 {
   AssertOwnerThread();
-  if (mStartTime.isSome() && mInfo.HasAudio() && mData) {
+  if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio() && mData) {
     CheckedInt64 t = mStartTime.ref() +
       FramesToUsecs(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
     if (t.isValid()) {
       return t.value();
     }
+  } else if (aType == TrackInfo::kVideoTrack && mData) {
+    return mData->mNextVideoTime;
   }
   return -1;
 }
 
 int64_t
-DecodedStream::GetPosition() const
+DecodedStream::GetPosition(TimeStamp* aTimeStamp) const
 {
   AssertOwnerThread();
   // This is only called after MDSM starts playback. So mStartTime is
   // guaranteed to be something.
   MOZ_ASSERT(mStartTime.isSome());
+  if (aTimeStamp) {
+    *aTimeStamp = TimeStamp::Now();
+  }
   return mStartTime.ref() + (mData ? mData->GetPosition() : 0);
 }
 
 bool
 DecodedStream::IsFinished() const
 {
   AssertOwnerThread();
   return mData && mData->IsFinished();
rename from dom/media/DecodedStream.h
rename to dom/media/mediasink/DecodedStream.h
--- a/dom/media/DecodedStream.h
+++ b/dom/media/mediasink/DecodedStream.h
@@ -5,38 +5,36 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DecodedStream_h_
 #define DecodedStream_h_
 
 #include "nsTArray.h"
 #include "MediaEventSource.h"
 #include "MediaInfo.h"
+#include "MediaSink.h"
 
 #include "mozilla/AbstractThread.h"
-#include "mozilla/CheckedInt.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/nsRefPtr.h"
-#include "mozilla/ReentrantMonitor.h"
 #include "mozilla/UniquePtr.h"
-#include "mozilla/gfx/Point.h"
 
 namespace mozilla {
 
 class DecodedStream;
 class DecodedStreamData;
 class MediaData;
 class MediaInputPort;
 class MediaStream;
 class MediaStreamGraph;
 class OutputStreamListener;
 class OutputStreamManager;
 class ProcessedMediaStream;
-class ReentrantMonitor;
+class TimeStamp;
 
 template <class T> class MediaQueue;
 
 namespace layers {
 class Image;
 } // namespace layers
 
 class OutputStreamData {
@@ -94,44 +92,51 @@ public:
 
 private:
   // Keep the input stream so we can connect the output streams that
   // are added after Connect().
   nsRefPtr<MediaStream> mInputStream;
   nsTArray<OutputStreamData> mStreams;
 };
 
-class DecodedStream {
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodedStream);
+class DecodedStream : public media::MediaSink {
+  using media::MediaSink::PlaybackParams;
+
 public:
   DecodedStream(AbstractThread* aOwnerThread,
                 MediaQueue<MediaData>& aAudioQueue,
                 MediaQueue<MediaData>& aVideoQueue);
 
-  void Shutdown();
+  // MediaSink functions.
+  const PlaybackParams& GetPlaybackParams() const override;
+  void SetPlaybackParams(const PlaybackParams& aParams) override;
 
-  // Mimic MDSM::StartAudioThread.
-  // Must be called before any calls to SendData().
-  //
-  // Return a promise which will be resolved when the stream is finished
-  // or rejected if any error.
-  nsRefPtr<GenericPromise> StartPlayback(int64_t aStartTime,
-                                         const MediaInfo& aInfo);
-  // Mimic MDSM::StopAudioThread.
-  void StopPlayback();
+  nsRefPtr<GenericPromise> OnEnded(TrackType aType) override;
+  int64_t GetEndTime(TrackType aType) const override;
+  int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const override;
+  bool HasUnplayedFrames(TrackType aType) const override
+  {
+    // TODO: implement this.
+    return false;
+  }
 
+  void SetVolume(double aVolume) override;
+  void SetPlaybackRate(double aPlaybackRate) override;
+  void SetPreservesPitch(bool aPreservesPitch) override;
+  void SetPlaying(bool aPlaying) override;
+
+  void Start(int64_t aStartTime, const MediaInfo& aInfo) override;
+  void Stop() override;
+  bool IsStarted() const override;
+
+  // TODO: fix these functions that don't fit into the interface of MediaSink.
+  void BeginShutdown();
   void AddOutput(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
   void RemoveOutput(MediaStream* aStream);
-
-  void SetPlaying(bool aPlaying);
-  void SetVolume(double aVolume);
   void SetSameOrigin(bool aSameOrigin);
-
-  int64_t AudioEndTime() const;
-  int64_t GetPosition() const;
   bool IsFinished() const;
   bool HasConsumers() const;
 
 protected:
   virtual ~DecodedStream();
 
 private:
   void CreateData(MozPromiseHolder<GenericPromise>&& aPromise);
@@ -159,20 +164,21 @@ private:
   OutputStreamManager mOutputStreamManager;
   // True if MDSM has begun shutdown.
   bool mShuttingDown;
 
   /*
    * Worker thread only members.
    */
   UniquePtr<DecodedStreamData> mData;
+  nsRefPtr<GenericPromise> mFinishPromise;
 
   bool mPlaying;
-  double mVolume;
   bool mSameOrigin;
+  PlaybackParams mParams;
 
   Maybe<int64_t> mStartTime;
   MediaInfo mInfo;
 
   MediaQueue<MediaData>& mAudioQueue;
   MediaQueue<MediaData>& mVideoQueue;
 
   MediaEventListener mAudioPushListener;
--- a/dom/media/mediasink/MediaSink.h
+++ b/dom/media/mediasink/MediaSink.h
@@ -8,16 +8,19 @@
 #define MediaSink_h_
 
 #include "mozilla/nsRefPtr.h"
 #include "mozilla/MozPromise.h"
 #include "nsISupportsImpl.h"
 #include "MediaInfo.h"
 
 namespace mozilla {
+
+class TimeStamp;
+
 namespace media {
 
 /**
  * A consumer of audio/video data which plays audio and video tracks and
  * manages A/V sync between them.
  *
  * A typical sink sends audio/video outputs to the speaker and screen.
  * However, there are also sinks which capture the output of an media element
@@ -32,20 +35,20 @@ namespace media {
  */
 class MediaSink {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaSink);
   typedef mozilla::TrackInfo::TrackType TrackType;
 
   struct PlaybackParams {
     PlaybackParams()
-      : volume(1.0) , playbackRate(1.0) , preservesPitch(true) {}
-    double volume;
-    double playbackRate;
-    bool preservesPitch;
+      : mVolume(1.0) , mPlaybackRate(1.0) , mPreservesPitch(true) {}
+    double mVolume;
+    double mPlaybackRate;
+    bool mPreservesPitch;
   };
 
   // Return the playback parameters of this sink.
   // Can be called in any state.
   virtual const PlaybackParams& GetPlaybackParams() const = 0;
 
   // Set the playback parameters of this sink.
   // Can be called in any state.
@@ -59,18 +62,20 @@ public:
   // Return the end time of the audio/video data that has been consumed
   // or -1 if no such track.
   // Must be called after playback starts.
   virtual int64_t GetEndTime(TrackType aType) const = 0;
 
   // Return playback position of the media.
   // Since A/V sync is always maintained by this sink, there is no need to
   // specify whether we want to get audio or video position.
+  // aTimeStamp returns the timeStamp corresponding to the returned position
+  // which is used by the compositor to derive the render time of video frames.
   // Must be called after playback starts.
-  virtual int64_t GetPosition() const = 0;
+  virtual int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const = 0;
 
   // Return true if there are data consumed but not played yet.
   // Can be called in any state.
   virtual bool HasUnplayedFrames(TrackType aType) const = 0;
 
   // Set volume of the audio track.
   // Do nothing if this sink has no audio track.
   // Can be called in any state.
--- a/dom/media/mediasink/moz.build
+++ b/dom/media/mediasink/moz.build
@@ -2,11 +2,12 @@
 # vim: set filetype=python:
 # 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/.
 
 UNIFIED_SOURCES += [
     'AudioSinkWrapper.cpp',
     'DecodedAudioDataSink.cpp',
+    'DecodedStream.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
--- a/dom/media/mediasource/SourceBufferDecoder.cpp
+++ b/dom/media/mediasource/SourceBufferDecoder.cpp
@@ -90,30 +90,16 @@ SourceBufferDecoder::MetadataLoaded(nsAu
 
 void
 SourceBufferDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
                                       MediaDecoderEventVisibility aEventVisibility)
 {
   MSE_DEBUG("UNIMPLEMENTED");
 }
 
-void
-SourceBufferDecoder::QueueMetadata(const media::TimeUnit& aTime,
-                                   nsAutoPtr<MediaInfo> aInfo,
-                                   nsAutoPtr<MetadataTags> aTags)
-{
-  MSE_DEBUG("UNIMPLEMENTED");
-}
-
-void
-SourceBufferDecoder::RemoveMediaTracks()
-{
-  MSE_DEBUG("UNIMPLEMENTED");
-}
-
 bool
 SourceBufferDecoder::HasInitializationData()
 {
   return true;
 }
 
 void
 SourceBufferDecoder::OnReadMetadataCompleted()
--- a/dom/media/mediasource/SourceBufferDecoder.h
+++ b/dom/media/mediasource/SourceBufferDecoder.h
@@ -46,18 +46,16 @@ public:
                               MediaDecoderEventVisibility aEventVisibility) final override;
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
                                 MediaDecoderEventVisibility aEventVisibility) final override;
   virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) final override;
   virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset, bool aThrottleUpdates) final override;
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded, uint32_t aDropped) final override;
   virtual void NotifyWaitingForResourcesStatusChanged() final override;
   virtual void OnReadMetadataCompleted() final override;
-  virtual void QueueMetadata(const media::TimeUnit& aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) final override;
-  virtual void RemoveMediaTracks() final override;
   virtual void SetMediaSeekable(bool aMediaSeekable) final override;
   virtual bool HasInitializationData() final override;
 
   // SourceBufferResource specific interface below.
   int64_t GetTimestampOffset() const { return mTimestampOffset; }
   void SetTimestampOffset(int64_t aOffset)  { mTimestampOffset = aOffset; }
 
   // Warning: this mirrors GetBuffered in MediaDecoder, but this class's base is
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -102,17 +102,16 @@ EXPORTS += [
     'AudioCompactor.h',
     'AudioMixer.h',
     'AudioPacketizer.h',
     'AudioSampleFormat.h',
     'AudioSegment.h',
     'AudioStream.h',
     'BufferMediaResource.h',
     'CubebUtils.h',
-    'DecodedStream.h',
     'DecoderTraits.h',
     'DOMMediaStream.h',
     'EncodedBufferCache.h',
     'FileBlockCache.h',
     'FlushableTaskQueue.h',
     'Intervals.h',
     'Latency.h',
     'MediaCache.h',
@@ -197,17 +196,16 @@ UNIFIED_SOURCES += [
     'AudioCompactor.cpp',
     'AudioSegment.cpp',
     'AudioStream.cpp',
     'AudioStreamTrack.cpp',
     'AudioTrack.cpp',
     'AudioTrackList.cpp',
     'CanvasCaptureMediaStream.cpp',
     'CubebUtils.cpp',
-    'DecodedStream.cpp',
     'DOMMediaStream.cpp',
     'EncodedBufferCache.cpp',
     'FileBlockCache.cpp',
     'FlushableTaskQueue.cpp',
     'GetUserMediaRequest.cpp',
     'GraphDriver.cpp',
     'Latency.cpp',
     'MediaCache.cpp',
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -809,20 +809,21 @@ bool OggReader::ReadOggChain()
 
     chained = true;
     tags = newOpusState->GetTags();
   }
 
   if (chained) {
     SetChained(true);
     {
-      nsAutoPtr<MediaInfo> info(new MediaInfo(mInfo));
-      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
       auto t = mDecodedAudioFrames * USECS_PER_S / mInfo.mAudio.mRate;
-      mDecoder->QueueMetadata(media::TimeUnit::FromMicroseconds(t), info, tags);
+      mTimedMetadataEvent.Notify(
+        TimedMetadata(media::TimeUnit::FromMicroseconds(t),
+                      Move(tags),
+                      nsAutoPtr<MediaInfo>(new MediaInfo(mInfo))));
     }
     return true;
   }
 
   return false;
 }
 
 nsresult OggReader::DecodeTheora(ogg_packet* aPacket, int64_t aTimeThreshold)
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -852,21 +852,16 @@ AudioContext::Suspend(ErrorResult& aRv)
 
   if (mAudioContextState == AudioContextState::Suspended) {
     promise->MaybeResolve(JS::UndefinedHandleValue);
     return promise.forget();
   }
 
   Destination()->Suspend();
 
-  MediaStream* ds = DestinationStream();
-  if (ds) {
-    ds->BlockStreamIfNeeded();
-  }
-
   mPromiseGripArray.AppendElement(promise);
   Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
                                       AudioContextOperation::Suspend, promise);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
@@ -892,21 +887,16 @@ AudioContext::Resume(ErrorResult& aRv)
 
   if (mAudioContextState == AudioContextState::Running) {
     promise->MaybeResolve(JS::UndefinedHandleValue);
     return promise.forget();
   }
 
   Destination()->Resume();
 
-  MediaStream* ds = DestinationStream();
-  if (ds) {
-    ds->UnblockStreamIfNeeded();
-  }
-
   mPromiseGripArray.AppendElement(promise);
   Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
                                       AudioContextOperation::Resume, promise);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
@@ -938,20 +928,16 @@ AudioContext::Close(ErrorResult& aRv)
   mPromiseGripArray.AppendElement(promise);
 
   // This can be called when freeing a document, and the streams are dead at
   // this point, so we need extra null-checks.
   MediaStream* ds = DestinationStream();
   if (ds) {
     Graph()->ApplyAudioContextOperation(ds->AsAudioNodeStream(),
                                         AudioContextOperation::Close, promise);
-
-    if (ds) {
-      ds->BlockStreamIfNeeded();
-    }
   }
   return promise.forget();
 }
 
 void
 AudioContext::UpdateNodeCount(int32_t aDelta)
 {
   bool firstNode = mNodeCount == 0;
--- a/dom/media/webaudio/AudioNode.cpp
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -220,17 +220,17 @@ AudioNode::Connect(AudioNode& aDestinati
   input->mInputPort = aInput;
   input->mOutputPort = aOutput;
   AudioNodeStream* destinationStream = aDestination.mStream;
   if (mStream && destinationStream) {
     // Connect streams in the MediaStreamGraph
     MOZ_ASSERT(aInput <= UINT16_MAX, "Unexpected large input port number");
     MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
     input->mStreamPort = destinationStream->
-      AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT,
+      AllocateInputPort(mStream, 0,
                             static_cast<uint16_t>(aInput),
                             static_cast<uint16_t>(aOutput));
   }
   aDestination.NotifyInputsChanged();
 
   // This connection may have connected a panner and a source.
   Context()->UpdatePannerSource();
 }
@@ -263,18 +263,17 @@ AudioNode::Connect(AudioParam& aDestinat
 
   MediaStream* stream = aDestination.Stream();
   MOZ_ASSERT(stream->AsProcessedStream());
   ProcessedMediaStream* ps = static_cast<ProcessedMediaStream*>(stream);
   if (mStream) {
     // Setup our stream as an input to the AudioParam's stream
     MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
     input->mStreamPort =
-      ps->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT,
-                            0, static_cast<uint16_t>(aOutput));
+      ps->AllocateInputPort(mStream, 0, 0, static_cast<uint16_t>(aOutput));
   }
 }
 
 void
 AudioNode::SendDoubleParameterToStream(uint32_t aIndex, double aValue)
 {
   MOZ_ASSERT(mStream, "How come we don't have a stream here?");
   mStream->SetDoubleParameter(aIndex, aValue);
--- a/dom/media/webaudio/AudioParam.cpp
+++ b/dom/media/webaudio/AudioParam.cpp
@@ -109,18 +109,17 @@ AudioParam::Stream()
   // Mark as an AudioParam helper stream
   stream->SetAudioParamHelperStream();
 
   mStream = stream.forget();
 
   // Setup the AudioParam's stream as an input to the owner AudioNode's stream
   AudioNodeStream* nodeStream = mNode->GetStream();
   if (nodeStream) {
-    mNodeStreamPort =
-      nodeStream->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT);
+    mNodeStreamPort = nodeStream->AllocateInputPort(mStream, 0);
   }
 
   // Let the MSG's copy of AudioParamTimeline know about the change in the stream
   mCallback(mNode);
 
   return mStream;
 }
 
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -125,28 +125,16 @@ BufferDecoder::MetadataLoaded(nsAutoPtr<
 
 void
 BufferDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, MediaDecoderEventVisibility aEventVisibility)
 {
   // ignore
 }
 
 void
-BufferDecoder::QueueMetadata(const media::TimeUnit& aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags)
-{
-  // ignore
-}
-
-void
-BufferDecoder::RemoveMediaTracks()
-{
-  // ignore
-}
-
-void
 BufferDecoder::OnReadMetadataCompleted()
 {
   // ignore
 }
 
 void
 BufferDecoder::NotifyWaitingForResourcesStatusChanged()
 {
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -53,22 +53,19 @@ public:
 
   virtual bool IsTransportSeekable() final override;
 
   virtual bool IsMediaSeekable() final override;
 
   virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
                               nsAutoPtr<MetadataTags> aTags,
                               MediaDecoderEventVisibility aEventVisibility) final override;
-  virtual void QueueMetadata(const media::TimeUnit& aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) final override;
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
                                 MediaDecoderEventVisibility aEventVisibility) final override;
 
-  virtual void RemoveMediaTracks() final override;
-
   virtual void OnReadMetadataCompleted() final override;
 
   virtual MediaDecoderOwner* GetOwner() final override;
 
   virtual void NotifyWaitingForResourcesStatusChanged() final override;
 
   virtual void NotifyDataArrived(uint32_t, int64_t, bool) final override {};
 
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -36,18 +36,17 @@ MediaStreamAudioSourceNode::MediaStreamA
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers),
     mInputStream(aMediaStream)
 {
   AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this);
   mStream = AudioNodeExternalInputStream::Create(aContext->Graph(), engine);
   ProcessedMediaStream* outputStream = static_cast<ProcessedMediaStream*>(mStream.get());
-  mInputPort = outputStream->AllocateInputPort(aMediaStream->GetStream(),
-                                               MediaInputPort::FLAG_BLOCK_INPUT);
+  mInputPort = outputStream->AllocateInputPort(aMediaStream->GetStream(), 0);
   mInputStream->AddConsumerToKeepAlive(static_cast<nsIDOMEventTarget*>(this));
 
   PrincipalChanged(mInputStream); // trigger enabling/disabling of the connector
   mInputStream->AddPrincipalChangeObserver(this);
 }
 
 MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode()
 {
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
@@ -157,16 +157,59 @@ MediaEngineCameraVideoSource::LogConstra
        c.mHeight.mIdeal.WasPassed()? c.mHeight.mIdeal.Value() : 0));
   LOG(((c.mFrameRate.mIdeal.WasPassed()?
         "             frameRate: { min: %f, max: %f, ideal: %f }" :
         "             frameRate: { min: %f, max: %f }"),
        c.mFrameRate.mMin, c.mFrameRate.mMax,
        c.mFrameRate.mIdeal.WasPassed()? c.mFrameRate.mIdeal.Value() : 0));
 }
 
+void
+MediaEngineCameraVideoSource::LogCapability(const char* aHeader,
+    const webrtc::CaptureCapability &aCapability, uint32_t aDistance)
+{
+  // RawVideoType and VideoCodecType media/webrtc/trunk/webrtc/common_types.h
+  static const char* const types[] = {
+    "I420",
+    "YV12",
+    "YUY2",
+    "UYVY",
+    "IYUV",
+    "ARGB",
+    "RGB24",
+    "RGB565",
+    "ARGB4444",
+    "ARGB1555",
+    "MJPEG",
+    "NV12",
+    "NV21",
+    "BGRA",
+    "Unknown type"
+  };
+
+  static const char* const codec[] = {
+    "VP8",
+    "VP9",
+    "H264",
+    "I420",
+    "RED",
+    "ULPFEC",
+    "Generic codec",
+    "Unknown codec"
+  };
+
+  LOG(("%s: %4u x %4u x %2u maxFps, %s, %s. Distance = %lu",
+       aHeader, aCapability.width, aCapability.height, aCapability.maxFPS,
+       types[std::min(std::max(uint32_t(0), uint32_t(aCapability.rawType)),
+                      uint32_t(sizeof(types) / sizeof(*types) - 1))],
+       codec[std::min(std::max(uint32_t(0), uint32_t(aCapability.codecType)),
+                      uint32_t(sizeof(codec) / sizeof(*codec) - 1))],
+       aDistance));
+}
+
 bool
 MediaEngineCameraVideoSource::ChooseCapability(
     const MediaTrackConstraints &aConstraints,
     const MediaEnginePrefs &aPrefs,
     const nsString& aDeviceId)
 {
   if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
     LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps",
@@ -190,16 +233,17 @@ MediaEngineCameraVideoSource::ChooseCapa
 
   // First, filter capabilities by required constraints (min, max, exact).
 
   for (size_t i = 0; i < candidateSet.Length();) {
     auto& candidate = candidateSet[i];
     webrtc::CaptureCapability cap;
     GetCapability(candidate.mIndex, cap);
     candidate.mDistance = GetFitnessDistance(cap, aConstraints, false, aDeviceId);
+    LogCapability("Capability", cap, candidate.mDistance);
     if (candidate.mDistance == UINT32_MAX) {
       candidateSet.RemoveElementAt(i);
     } else {
       ++i;
     }
   }
 
   // Filter further with all advanced constraints (that don't overconstrain).
@@ -229,16 +273,17 @@ MediaEngineCameraVideoSource::ChooseCapa
   }
 
   // Remaining algorithm is up to the UA.
 
   TrimLessFitCandidates(candidateSet);
 
   // Any remaining multiples all have the same distance. A common case of this
   // occurs when no ideal is specified. Lean toward defaults.
+  uint32_t sameDistance = candidateSet[0].mDistance;
   {
     MediaTrackConstraintSet prefs;
     prefs.mWidth.SetAsLong() = aPrefs.GetWidth();
     prefs.mHeight.SetAsLong() = aPrefs.GetHeight();
     prefs.mFrameRate.SetAsDouble() = aPrefs.mFPS;
 
     for (auto& candidate : candidateSet) {
       webrtc::CaptureCapability cap;
@@ -263,19 +308,17 @@ MediaEngineCameraVideoSource::ChooseCapa
       found = true;
       break;
     }
   }
   if (!found) {
     GetCapability(candidateSet[0].mIndex, mCapability);
   }
 
-  LOG(("chose cap %dx%d @%dfps codec %d raw %d",
-       mCapability.width, mCapability.height, mCapability.maxFPS,
-       mCapability.codecType, mCapability.rawType));
+  LogCapability("Chosen capability", mCapability, sameDistance);
   return true;
 }
 
 void
 MediaEngineCameraVideoSource::SetName(nsString aName)
 {
   mDeviceName = aName;
   bool hasFacingMode = false;
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.h
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.h
@@ -79,16 +79,19 @@ protected:
                              StreamTime delta);
   uint32_t GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
                               const dom::MediaTrackConstraintSet &aConstraints,
                               bool aAdvanced,
                               const nsString& aDeviceId);
   static void TrimLessFitCandidates(CapabilitySet& set);
   static void LogConstraints(const dom::MediaTrackConstraintSet& aConstraints,
                              bool aAdvanced);
+static void LogCapability(const char* aHeader,
+                          const webrtc::CaptureCapability &aCapability,
+                          uint32_t aDistance);
   virtual size_t NumCapabilities();
   virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut);
   bool ChooseCapability(const dom::MediaTrackConstraints &aConstraints,
                         const MediaEnginePrefs &aPrefs,
                         const nsString& aDeviceId);
   void SetName(nsString aName);
   void SetUUID(const char* aUUID);
   const nsCString& GetUUID(); // protected access
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -135,25 +135,29 @@ nsSpeechTask::~nsSpeechTask()
 
   if (mPort) {
     mPort->Destroy();
     mPort = nullptr;
   }
 }
 
 void
-nsSpeechTask::Init(ProcessedMediaStream* aStream)
+nsSpeechTask::InitDirectAudio()
 {
-  if (aStream) {
-    mStream = aStream->Graph()->CreateSourceStream(nullptr);
-    mPort = aStream->AllocateInputPort(mStream, 0);
-    mIndirectAudio = false;
-  } else {
-    mIndirectAudio = true;
-  }
+  mStream = MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
+                                          AudioChannel::Normal)->
+    CreateSourceStream(nullptr);
+  mIndirectAudio = false;
+  mInited = true;
+}
+
+void
+nsSpeechTask::InitIndirectAudio()
+{
+  mIndirectAudio = true;
   mInited = true;
 }
 
 void
 nsSpeechTask::SetChosenVoiceURI(const nsAString& aUri)
 {
   mChosenVoiceURI = aUri;
 }
--- a/dom/media/webspeech/synth/nsSpeechTask.h
+++ b/dom/media/webspeech/synth/nsSpeechTask.h
@@ -43,17 +43,18 @@ public:
   virtual void ForceEnd();
 
   float GetCurrentTime();
 
   uint32_t GetCurrentCharOffset();
 
   void SetSpeechSynthesis(SpeechSynthesis* aSpeechSynthesis);
 
-  void Init(ProcessedMediaStream* aStream);
+  void InitDirectAudio();
+  void InitIndirectAudio();
 
   void SetChosenVoiceURI(const nsAString& aUri);
 
   virtual void SetAudioOutputVolume(float aVolume);
 
   bool IsPreCanceled()
   {
     return mPreCanceled;
--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
+++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
@@ -172,24 +172,16 @@ nsSynthVoiceRegistry::nsSynthVoiceRegist
 
 nsSynthVoiceRegistry::~nsSynthVoiceRegistry()
 {
   LOG(LogLevel::Debug, ("~nsSynthVoiceRegistry"));
 
   // mSpeechSynthChild's lifecycle is managed by the Content protocol.
   mSpeechSynthChild = nullptr;
 
-  if (mStream) {
-    if (!mStream->IsDestroyed()) {
-      mStream->Destroy();
-    }
-
-    mStream = nullptr;
-  }
-
   mUriVoiceMap.Clear();
 }
 
 nsSynthVoiceRegistry*
 nsSynthVoiceRegistry::GetInstance()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -770,23 +762,18 @@ nsSynthVoiceRegistry::SpeakImpl(VoiceDat
        aRate, aPitch));
 
   SpeechServiceType serviceType;
 
   DebugOnly<nsresult> rv = aVoice->mService->GetServiceType(&serviceType);
   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to get speech service type");
 
   if (serviceType == nsISpeechService::SERVICETYPE_INDIRECT_AUDIO) {
-    aTask->Init(nullptr);
+    aTask->InitIndirectAudio();
   } else {
-    if (!mStream) {
-      mStream =
-        MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
-                                      AudioChannel::Normal)->CreateTrackUnionStream(nullptr);
-    }
-    aTask->Init(mStream);
+    aTask->InitDirectAudio();
   }
 
   aVoice->mService->Speak(aText, aVoice->mUri, aVolume, aRate, aPitch, aTask);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.h
+++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.h
@@ -90,18 +90,16 @@ private:
   nsTArray<nsRefPtr<VoiceData>> mVoices;
 
   nsTArray<nsRefPtr<VoiceData>> mDefaultVoices;
 
   nsRefPtrHashtable<nsStringHashKey, VoiceData> mUriVoiceMap;
 
   SpeechSynthesisChild* mSpeechSynthChild;
 
-  nsRefPtr<ProcessedMediaStream> mStream;
-
   bool mUseGlobalQueue;
 
   nsTArray<nsRefPtr<GlobalQueueItem>> mGlobalQueue;
 
   bool mIsSpeaking;
 };
 
 } // namespace dom
--- a/dom/svg/SVGSVGElement.cpp
+++ b/dom/svg/SVGSVGElement.cpp
@@ -506,20 +506,20 @@ SVGSVGElement::SetCurrentScaleTranslate(
 
   // now dispatch the appropriate event if we are the root element
   nsIDocument* doc = GetUncomposedDoc();
   if (doc) {
     nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
     if (presShell && IsRoot()) {
       nsEventStatus status = nsEventStatus_eIgnore;
       if (mPreviousScale != mCurrentScale) {
-        InternalSVGZoomEvent svgZoomEvent(true, NS_SVG_ZOOM);
+        InternalSVGZoomEvent svgZoomEvent(true, eSVGZoom);
         presShell->HandleDOMEventWithTarget(this, &svgZoomEvent, &status);
       } else {
-        WidgetEvent svgScrollEvent(true, NS_SVG_SCROLL);
+        WidgetEvent svgScrollEvent(true, eSVGScroll);
         presShell->HandleDOMEventWithTarget(this, &svgScrollEvent, &status);
       }
       InvalidateTransformNotifyFrame();
     }
   }
 }
 
 void
@@ -585,17 +585,17 @@ SVGSVGElement::IsAttributeMapped(const n
 }
 
 //----------------------------------------------------------------------
 // nsIContent methods:
 
 nsresult
 SVGSVGElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
 {
-  if (aVisitor.mEvent->mMessage == NS_SVG_LOAD) {
+  if (aVisitor.mEvent->mMessage == eSVGLoad) {
     if (mTimedDocumentRoot) {
       mTimedDocumentRoot->Begin();
       // Set 'resample needed' flag, so that if any script calls a DOM method
       // that requires up-to-date animations before our first sample callback,
       // we'll force a synchronous sample.
       AnimationNeedsResample();
     }
   }
--- a/dom/svg/SVGZoomEvent.cpp
+++ b/dom/svg/SVGZoomEvent.cpp
@@ -26,17 +26,17 @@ NS_IMPL_RELEASE_INHERITED(SVGZoomEvent, 
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGZoomEvent)
 NS_INTERFACE_MAP_END_INHERITING(UIEvent)
 
 SVGZoomEvent::SVGZoomEvent(EventTarget* aOwner,
                            nsPresContext* aPresContext,
                            InternalSVGZoomEvent* aEvent)
   : UIEvent(aOwner, aPresContext,
-            aEvent ? aEvent : new InternalSVGZoomEvent(false, NS_SVG_ZOOM))
+            aEvent ? aEvent : new InternalSVGZoomEvent(false, eSVGZoom))
   , mPreviousScale(0)
   , mNewScale(0)
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
--- a/editor/libeditor/nsEditor.cpp
+++ b/editor/libeditor/nsEditor.cpp
@@ -1852,16 +1852,19 @@ nsEditor::NotifyEditorObservers(Notifica
 
       if (!mDispatchInputEvent) {
         return;
       }
 
       FireInputEvent();
       break;
     case eNotifyEditorObserversOfBefore:
+      if (NS_WARN_IF(mIsInEditAction)) {
+        break;
+      }
       mIsInEditAction = true;
       for (auto& observer : observers) {
         observer->BeforeEditAction();
       }
       break;
     case eNotifyEditorObserversOfCancel:
       mIsInEditAction = false;
       for (auto& observer : observers) {
--- a/editor/libeditor/nsPlaintextEditor.cpp
+++ b/editor/libeditor/nsPlaintextEditor.cpp
@@ -880,20 +880,21 @@ nsPlaintextEditor::UpdateIMEComposition(
     rv = InsertText(compositionChangeEvent->mData);
 
     if (caretP) {
       caretP->SetSelection(selection);
     }
   }
 
   // If still composing, we should fire input event via observer.
-  // Note that if committed, we don't need to notify it since it will be
-  // notified at followed compositionend event.
+  // Note that if the composition will be committed by the following
+  // compositionend event, we don't need to notify editor observes of this
+  // change.
   // NOTE: We must notify after the auto batch will be gone.
-  if (IsIMEComposing()) {
+  if (!compositionChangeEvent->IsFollowedByCompositionEnd()) {
     NotifyEditorObservers(eNotifyEditorObserversOfEnd);
   }
 
   return rv;
 }
 
 already_AddRefed<nsIContent>
 nsPlaintextEditor::GetInputEventTargetContent()
--- a/gfx/harfbuzz/src/Makefile.am
+++ b/gfx/harfbuzz/src/Makefile.am
@@ -15,16 +15,18 @@ DISTCHECK_CONFIGURE_FLAGS = --enable-int
 
 # Convenience targets:
 lib: $(BUILT_SOURCES) libharfbuzz.la
 
 lib_LTLIBRARIES = libharfbuzz.la
 
 HBCFLAGS =
 HBLIBS =
+HBNONPCLIBS =
+HBDEPS =
 HBSOURCES =  \
 	hb-atomic-private.hh \
 	hb-blob.cc \
 	hb-buffer-deserialize-json.hh \
 	hb-buffer-deserialize-text.hh \
 	hb-buffer-private.hh \
 	hb-buffer-serialize.cc \
 	hb-buffer.cc \
@@ -34,16 +36,17 @@ HBSOURCES =  \
 	hb-face.cc \
 	hb-font-private.hh \
 	hb-font.cc \
 	hb-mutex-private.hh \
 	hb-object-private.hh \
 	hb-open-file-private.hh \
 	hb-open-type-private.hh \
 	hb-ot-cmap-table.hh \
+	hb-ot-glyf-table.hh \
 	hb-ot-head-table.hh \
 	hb-ot-hhea-table.hh \
 	hb-ot-hmtx-table.hh \
 	hb-ot-maxp-table.hh \
 	hb-ot-name-table.hh \
 	hb-ot-tag.cc \
 	hb-private.hh \
 	hb-set-private.hh \
@@ -88,16 +91,17 @@ HBSOURCES += \
 	hb-ot-layout-gsub-table.hh \
 	hb-ot-layout-jstf-table.hh \
 	hb-ot-layout-private.hh \
 	hb-ot-map.cc \
 	hb-ot-map-private.hh \
 	hb-ot-shape.cc \
 	hb-ot-shape-complex-arabic.cc \
 	hb-ot-shape-complex-arabic-fallback.hh \
+	hb-ot-shape-complex-arabic-private.hh \
 	hb-ot-shape-complex-arabic-table.hh \
 	hb-ot-shape-complex-arabic-win1256.hh \
 	hb-ot-shape-complex-default.cc \
 	hb-ot-shape-complex-hangul.cc \
 	hb-ot-shape-complex-hebrew.cc \
 	hb-ot-shape-complex-indic.cc \
 	hb-ot-shape-complex-indic-machine.hh \
 	hb-ot-shape-complex-indic-private.hh \
@@ -127,65 +131,75 @@ HBHEADERS += \
 endif
 
 if HAVE_FALLBACK
 HBSOURCES += hb-fallback-shape.cc
 endif
 
 if HAVE_PTHREAD
 HBCFLAGS += $(PTHREAD_CFLAGS)
-HBLIBS   += $(PTHREAD_LIBS)
+HBNONPCLIBS += $(PTHREAD_LIBS)
 endif
 
 if HAVE_GLIB
 HBCFLAGS += $(GLIB_CFLAGS)
 HBLIBS   += $(GLIB_LIBS)
+HBDEPS   += $(GLIB_DEPS)
 HBSOURCES += hb-glib.cc
 HBHEADERS += hb-glib.h
 endif
 
 if HAVE_FREETYPE
 HBCFLAGS += $(FREETYPE_CFLAGS)
 HBLIBS   += $(FREETYPE_LIBS)
+# XXX
+# The following creates a recursive dependency on FreeType if FreeType is
+# built with HarfBuzz support enabled.  Newer pkg-config handles that just
+# fine but pkg-config 0.26 as shipped in Ubuntu 14.04 crashes.  Remove
+# in a year or two, or otherwise work around it...
+#HBDEPS   += $(FREETYPE_DEPS)
 HBSOURCES += hb-ft.cc
 HBHEADERS += hb-ft.h
 endif
 
 if HAVE_GRAPHITE2
 HBCFLAGS += $(GRAPHITE2_CFLAGS)
 HBLIBS   += $(GRAPHITE2_LIBS)
+HBDEPS   += $(GRAPHITE2_DEPS)
 HBSOURCES += hb-graphite2.cc
 HBHEADERS += hb-graphite2.h
 endif
 
 if HAVE_UNISCRIBE
 HBCFLAGS += $(UNISCRIBE_CFLAGS)
-HBLIBS   += $(UNISCRIBE_LIBS)
+HBNONPCLIBS += $(UNISCRIBE_LIBS)
 HBSOURCES += hb-uniscribe.cc
 HBHEADERS += hb-uniscribe.h
 endif
 
 if HAVE_CORETEXT
 HBCFLAGS += $(CORETEXT_CFLAGS)
-HBLIBS   += $(CORETEXT_LIBS)
+HBNONPCLIBS += $(CORETEXT_LIBS)
 HBSOURCES += hb-coretext.cc
 HBHEADERS += hb-coretext.h
 endif
 
 if HAVE_UCDN
 SUBDIRS += hb-ucdn
 HBCFLAGS += -I$(srcdir)/hb-ucdn
 HBLIBS   += hb-ucdn/libhb-ucdn.la
 HBSOURCES += hb-ucdn.cc
 endif
 DIST_SUBDIRS += hb-ucdn
 
 
 # Put the library together
 
+HBLIBS += $(HBNONPCLIBS)
+
 if OS_WIN32
 export_symbols = -export-symbols harfbuzz.def
 harfbuzz_def_dependency = harfbuzz.def
 libharfbuzz_la_LINK = $(CXXLINK) $(libharfbuzz_la_LDFLAGS)
 else
 # Use a C linker for GCC, not C++; Don't link to libstdc++
 if HAVE_GCC
 libharfbuzz_la_LINK = $(LINK) $(libharfbuzz_la_LDFLAGS)
@@ -250,31 +264,33 @@ EXTRA_DIST += \
 
 
 %.pc: %.pc.in $(top_builddir)/config.status
 	$(AM_V_GEN) \
 	$(SED)	-e 's@%prefix%@$(prefix)@g' \
 		-e 's@%exec_prefix%@$(exec_prefix)@g' \
 		-e 's@%libdir%@$(libdir)@g' \
 		-e 's@%includedir%@$(includedir)@g' \
+		-e 's@%libs_private%@$(HBNONPCLIBS)@g' \
+		-e 's@%requires_private%@$(HBDEPS)@g' \
 		-e 's@%VERSION%@$(VERSION)@g' \
 	"$<" > "$@" \
 	|| ($(RM) "$@"; false)
 
 CLEANFILES += $(pkgconfig_DATA)
 
 
 CLEANFILES += harfbuzz.def
 harfbuzz.def: $(HBHEADERS) $(HBNODISTHEADERS)
 	$(AM_V_GEN) (echo EXPORTS; \
 	(cat $^ || echo 'hb_ERROR ()' ) | \
 	$(EGREP) '^hb_.* \(' | \
 	sed -e 's/ (.*//' | \
 	LANG=C sort; \
-	echo LIBRARY libharfbuzz-$(HB_VERSION_MAJOR).dll; \
+	echo LIBRARY libharfbuzz-0.dll; \
 	) >"$@"
 	@ ! grep -q hb_ERROR "$@" \
 	|| ($(RM) "$@"; false)
 
 
 GENERATORS = \
 	gen-arabic-table.py \
 	gen-indic-table.py \
@@ -365,17 +381,17 @@ TESTS_ENVIRONMENT = \
 	MAKE="$(MAKE) $(AM_MAKEFLAGS)" \
 	HBSOURCES="$(HBSOURCES)" \
 	HBHEADERS="$(HBHEADERS) $(HBNODISTHEADERS)" \
 	$(NULL)
 
 if HAVE_INTROSPECTION
 
 -include $(INTROSPECTION_MAKEFILE)
-INTROSPECTION_GIRS = HarfBuzz-$(HB_VERSION_MAJOR).0.gir # What does the 0 mean anyway?!
+INTROSPECTION_GIRS = HarfBuzz-0.0.gir # What does the 0 mean anyway?!
 INTROSPECTION_SCANNER_ARGS = -I$(srcdir) -n hb --identifier-prefix=hb_ --warn-all
 INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir)
 INTROSPECTION_SCANNER_ENV = CC="$(CC)"
 
 HarfBuzz-0.0.gir: libharfbuzz.la libharfbuzz-gobject.la
 HarfBuzz_0_0_gir_INCLUDES = GObject-2.0
 HarfBuzz_0_0_gir_CFLAGS = \
 	$(INCLUDES) \
--- a/gfx/harfbuzz/src/harfbuzz.pc.in
+++ b/gfx/harfbuzz/src/harfbuzz.pc.in
@@ -3,9 +3,11 @@ exec_prefix=%exec_prefix%
 libdir=%libdir%
 includedir=%includedir%
 
 Name: harfbuzz
 Description: HarfBuzz text shaping library
 Version: %VERSION%
 
 Libs: -L${libdir} -lharfbuzz
+Libs.private: %libs_private%
+Requires.private: %requires_private%
 Cflags: -I${includedir}/harfbuzz
--- a/gfx/harfbuzz/src/hb-buffer-private.hh
+++ b/gfx/harfbuzz/src/hb-buffer-private.hh
@@ -196,16 +196,18 @@ struct hb_buffer_t {
 
   HB_INTERNAL bool make_room_for (unsigned int num_in, unsigned int num_out);
   HB_INTERNAL bool shift_forward (unsigned int count);
 
   typedef long scratch_buffer_t;
   HB_INTERNAL scratch_buffer_t *get_scratch_buffer (unsigned int *size);
 
   inline void clear_context (unsigned int side) { context_len[side] = 0; }
+
+  HB_INTERNAL void sort (unsigned int start, unsigned int end, int(*compar)(const hb_glyph_info_t *, const hb_glyph_info_t *));
 };
 
 
 #define HB_BUFFER_XALLOCATE_VAR(b, func, var, owner) \
   b->func (offsetof (hb_glyph_info_t, var) - offsetof(hb_glyph_info_t, var1), \
 	   sizeof (b->info[0].var), owner)
 #define HB_BUFFER_ALLOCATE_VAR(b, var) \
 	HB_BUFFER_XALLOCATE_VAR (b, allocate_var, var (), #var)
--- a/gfx/harfbuzz/src/hb-buffer-serialize.cc
+++ b/gfx/harfbuzz/src/hb-buffer-serialize.cc
@@ -94,17 +94,18 @@ static unsigned int
 				  unsigned int end,
 				  char *buf,
 				  unsigned int buf_size,
 				  unsigned int *buf_consumed,
 				  hb_font_t *font,
 				  hb_buffer_serialize_flags_t flags)
 {
   hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
-  hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
+  hb_glyph_position_t *pos = (flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS) ?
+			     NULL : hb_buffer_get_glyph_positions (buffer, NULL);
 
   *buf_consumed = 0;
   for (unsigned int i = start; i < end; i++)
   {
     char b[1024];
     char *p = b;
 
     /* In the following code, we know b is large enough that no overflow can happen. */
@@ -139,16 +140,26 @@ static unsigned int
     if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS))
     {
       p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"dx\":%d,\"dy\":%d",
 		     pos[i].x_offset, pos[i].y_offset);
       p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"ax\":%d,\"ay\":%d",
 		     pos[i].x_advance, pos[i].y_advance);
     }
 
+    if (flags & HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS)
+    {
+      hb_glyph_extents_t extents;
+      hb_font_get_glyph_extents(font, info[i].codepoint, &extents);
+      p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"xb\":%d,\"yb\":%d",
+        extents.x_bearing, extents.y_bearing));
+      p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"w\":%d,\"h\":%d",
+        extents.width, extents.height));
+    }
+
     *p++ = '}';
 
     unsigned int l = p - b;
     if (buf_size > l)
     {
       memcpy (buf, b, l);
       buf += l;
       buf_size -= l;
@@ -167,17 +178,18 @@ static unsigned int
 				  unsigned int end,
 				  char *buf,
 				  unsigned int buf_size,
 				  unsigned int *buf_consumed,
 				  hb_font_t *font,
 				  hb_buffer_serialize_flags_t flags)
 {
   hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
-  hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
+  hb_glyph_position_t *pos = (flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS) ?
+			     NULL : hb_buffer_get_glyph_positions (buffer, NULL);
 
   *buf_consumed = 0;
   for (unsigned int i = start; i < end; i++)
   {
     char b[1024];
     char *p = b;
 
     /* In the following code, we know b is large enough that no overflow can happen. */
@@ -203,16 +215,23 @@ static unsigned int
 	p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), "@%d,%d", pos[i].x_offset, pos[i].y_offset));
 
       *p++ = '+';
       p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), "%d", pos[i].x_advance));
       if (pos[i].y_advance)
 	p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",%d", pos[i].y_advance));
     }
 
+    if (flags & HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS)
+    {
+      hb_glyph_extents_t extents;
+      hb_font_get_glyph_extents(font, info[i].codepoint, &extents);
+      p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), "<%d,%d,%d,%d>", extents.x_bearing, extents.y_bearing, extents.width, extents.height));
+    }
+
     unsigned int l = p - b;
     if (buf_size > l)
     {
       memcpy (buf, b, l);
       buf += l;
       buf_size -= l;
       *buf_consumed += l;
       *buf = '\0';
--- a/gfx/harfbuzz/src/hb-buffer.cc
+++ b/gfx/harfbuzz/src/hb-buffer.cc
@@ -1631,26 +1631,26 @@ normalize_glyphs_cluster (hb_buffer_t *b
   }
 
   if (backward)
   {
     /* Transfer all cluster advance to the last glyph. */
     pos[end - 1].x_advance = total_x_advance;
     pos[end - 1].y_advance = total_y_advance;
 
-    hb_bubble_sort (buffer->info + start, end - start - 1, compare_info_codepoint, buffer->pos + start);
+    hb_stable_sort (buffer->info + start, end - start - 1, compare_info_codepoint, buffer->pos + start);
   } else {
     /* Transfer all cluster advance to the first glyph. */
     pos[start].x_advance += total_x_advance;
     pos[start].y_advance += total_y_advance;
     for (unsigned int i = start + 1; i < end; i++) {
       pos[i].x_offset -= total_x_advance;
       pos[i].y_offset -= total_y_advance;
     }
-    hb_bubble_sort (buffer->info + start + 1, end - start - 1, compare_info_codepoint, buffer->pos + start + 1);
+    hb_stable_sort (buffer->info + start + 1, end - start - 1, compare_info_codepoint, buffer->pos + start + 1);
   }
 }
 
 /**
  * hb_buffer_normalize_glyphs:
  * @buffer: a buffer.
  *
  * 
@@ -1673,8 +1673,29 @@ hb_buffer_normalize_glyphs (hb_buffer_t 
   unsigned int end;
   for (end = start + 1; end < count; end++)
     if (info[start].cluster != info[end].cluster) {
       normalize_glyphs_cluster (buffer, start, end, backward);
       start = end;
     }
   normalize_glyphs_cluster (buffer, start, end, backward);
 }
+
+void
+hb_buffer_t::sort (unsigned int start, unsigned int end, int(*compar)(const hb_glyph_info_t *, const hb_glyph_info_t *))
+{
+  assert (!have_positions);
+  for (unsigned int i = start + 1; i < end; i++)
+  {
+    unsigned int j = i;
+    while (j > start && compar (&info[j - 1], &info[i]) > 0)
+      j--;
+    if (i == j)
+      continue;
+    /* Move item i to occupy place for item j, shift what's in between. */
+    merge_clusters (j, i + 1);
+    {
+      hb_glyph_info_t t = info[i];
+      memmove (&info[j + 1], &info[j], (i - j) * sizeof (hb_glyph_info_t));
+      info[j] = t;
+    }
+  }
+}
--- a/gfx/harfbuzz/src/hb-buffer.h
+++ b/gfx/harfbuzz/src/hb-buffer.h
@@ -318,17 +318,18 @@ hb_buffer_normalize_glyphs (hb_buffer_t 
 /*
  * Serialize
  */
 
 typedef enum { /*< flags >*/
   HB_BUFFER_SERIALIZE_FLAG_DEFAULT		= 0x00000000u,
   HB_BUFFER_SERIALIZE_FLAG_NO_CLUSTERS		= 0x00000001u,
   HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS		= 0x00000002u,
-  HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES	= 0x00000004u
+  HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES	= 0x00000004u,
+  HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS	= 0x00000008u
 } hb_buffer_serialize_flags_t;
 
 typedef enum {
   HB_BUFFER_SERIALIZE_FORMAT_TEXT	= HB_TAG('T','E','X','T'),
   HB_BUFFER_SERIALIZE_FORMAT_JSON	= HB_TAG('J','S','O','N'),
   HB_BUFFER_SERIALIZE_FORMAT_INVALID	= HB_TAG_NONE
 } hb_buffer_serialize_format_t;
 
--- a/gfx/harfbuzz/src/hb-coretext.cc
+++ b/gfx/harfbuzz/src/hb-coretext.cc
@@ -151,16 +151,17 @@ hb_coretext_shaper_font_data_t *
   hb_coretext_shaper_font_data_t *data = (hb_coretext_shaper_font_data_t *) calloc (1, sizeof (hb_coretext_shaper_font_data_t));
   if (unlikely (!data))
     return NULL;
 
   hb_face_t *face = font->face;
   hb_coretext_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face);
 
   /* Choose a CoreText font size and calculate multipliers to convert to HarfBuzz space. */
+  /* TODO: use upem instead of 36? */
   CGFloat font_size = 36.; /* Default... */
   /* No idea if the following is even a good idea. */
   if (font->y_ppem)
     font_size = font->y_ppem;
 
   if (font_size < 0)
     font_size = -font_size;
   data->x_mult = (CGFloat) font->x_scale / font_size;
@@ -910,18 +911,18 @@ retry:
 	      }
 	      if (buffer->unicode->is_default_ignorable (ch))
 	        continue;
 
 	      info->codepoint = notdef;
 	      info->cluster = log_clusters[j];
 
 	      info->mask = advance;
-	      info->var1.u32 = x_offset;
-	      info->var2.u32 = y_offset;
+	      info->var1.i32 = x_offset;
+	      info->var2.i32 = y_offset;
 
 	      info++;
 	      buffer->len++;
 	  }
 	  if (HB_DIRECTION_IS_BACKWARD (buffer->props.direction))
 	    buffer->reverse_range (old_len, buffer->len);
 	  advances_so_far += run_advance;
 	  continue;
@@ -997,72 +998,82 @@ retry:
 	  for (unsigned int j = 0; j < num_glyphs; j++)
 	  {
 	    double advance;
 	    if (likely (j + 1 < num_glyphs))
 	      advance = positions[j + 1].x - positions[j].x;
 	    else /* last glyph */
 	      advance = run_advance - (positions[j].x - positions[0].x);
 	    info->mask = advance * x_mult;
-	    info->var1.u32 = x_offset;
-	    info->var2.u32 = positions[j].y * y_mult;
+	    info->var1.i32 = x_offset;
+	    info->var2.i32 = positions[j].y * y_mult;
 	    info++;
 	  }
 	}
 	else
 	{
 	  hb_position_t y_offset = (positions[0].y - advances_so_far) * y_mult;
 	  for (unsigned int j = 0; j < num_glyphs; j++)
 	  {
 	    double advance;
 	    if (likely (j + 1 < num_glyphs))
 	      advance = positions[j + 1].y - positions[j].y;
 	    else /* last glyph */
 	      advance = run_advance - (positions[j].y - positions[0].y);
 	    info->mask = advance * y_mult;
-	    info->var1.u32 = positions[j].x * x_mult;
-	    info->var2.u32 = y_offset;
+	    info->var1.i32 = positions[j].x * x_mult;
+	    info->var2.i32 = y_offset;
 	    info++;
 	  }
 	}
 	SCRATCH_RESTORE();
 	advances_so_far += run_advance;
       }
 #undef SCRATCH_RESTORE
 #undef SCRATCH_SAVE
 #undef USE_PTR
 #undef ALLOCATE_ARRAY
 
       buffer->len += num_glyphs;
     }
 
-    /* Make sure all runs had the expected direction. */
-    bool backward = HB_DIRECTION_IS_BACKWARD (buffer->props.direction);
-    assert (bool (status_and & kCTRunStatusRightToLeft) == backward);
-    assert (bool (status_or  & kCTRunStatusRightToLeft) == backward);
+    /* Mac OS 10.6 doesn't have kCTTypesetterOptionForcedEmbeddingLevel,
+     * or if it does, it doesn't resepct it.  So we get runs with wrong
+     * directions.  As such, disable the assert...  It wouldn't crash, but
+     * cursoring will be off...
+     *
+     * http://crbug.com/419769
+     */
+    if (0)
+    {
+      /* Make sure all runs had the expected direction. */
+      bool backward = HB_DIRECTION_IS_BACKWARD (buffer->props.direction);
+      assert (bool (status_and & kCTRunStatusRightToLeft) == backward);
+      assert (bool (status_or  & kCTRunStatusRightToLeft) == backward);
+    }
 
     buffer->clear_positions ();
 
     unsigned int count = buffer->len;
     hb_glyph_info_t *info = buffer->info;
     hb_glyph_position_t *pos = buffer->pos;
     if (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction))
       for (unsigned int i = 0; i < count; i++)
       {
 	pos->x_advance = info->mask;
-	pos->x_offset = info->var1.u32;
-	pos->y_offset = info->var2.u32;
+	pos->x_offset = info->var1.i32;
+	pos->y_offset = info->var2.i32;
 	info++, pos++;
       }
     else
       for (unsigned int i = 0; i < count; i++)
       {
 	pos->y_advance = info->mask;
-	pos->x_offset = info->var1.u32;
-	pos->y_offset = info->var2.u32;
+	pos->x_offset = info->var1.i32;
+	pos->y_offset = info->var2.i32;
 	info++, pos++;
       }
 
     /* Fix up clusters so that we never return out-of-order indices;
      * if core text has reordered glyphs, we'll merge them to the
      * beginning of the reordered cluster.  CoreText is nice enough
      * to tell us whenever it has produced nonmonotonic results...
      * Note that we assume the input clusters were nonmonotonic to
--- a/gfx/harfbuzz/src/hb-font.h
+++ b/gfx/harfbuzz/src/hb-font.h
@@ -75,22 +75,23 @@ void
 hb_font_funcs_make_immutable (hb_font_funcs_t *ffuncs);
 
 hb_bool_t
 hb_font_funcs_is_immutable (hb_font_funcs_t *ffuncs);
 
 
 /* glyph extents */
 
+/* Note that height is negative in coordinate systems that grow up. */
 typedef struct hb_glyph_extents_t
 {
-  hb_position_t x_bearing;
-  hb_position_t y_bearing;
-  hb_position_t width;
-  hb_position_t height;
+  hb_position_t x_bearing; /* left side of glyph from origin. */
+  hb_position_t y_bearing; /* top side of glyph from origin. */
+  hb_position_t width; /* distance from left to right side. */
+  hb_position_t height; /* distance from top to bottom side. */
 } hb_glyph_extents_t;
 
 
 /* func types */
 
 typedef hb_bool_t (*hb_font_get_glyph_func_t) (hb_font_t *font, void *font_data,
 					       hb_codepoint_t unicode, hb_codepoint_t variation_selector,
 					       hb_codepoint_t *glyph,
--- a/gfx/harfbuzz/src/hb-object-private.hh
+++ b/gfx/harfbuzz/src/hb-object-private.hh
@@ -42,30 +42,32 @@
 
 #ifndef HB_DEBUG_OBJECT
 #define HB_DEBUG_OBJECT (HB_DEBUG+0)
 #endif
 
 
 /* reference_count */
 
-#define HB_REFERENCE_COUNT_INVALID_VALUE -1
-#define HB_REFERENCE_COUNT_INIT {HB_ATOMIC_INT_INIT(HB_REFERENCE_COUNT_INVALID_VALUE)}
+#define HB_REFERENCE_COUNT_INERT_VALUE -1
+#define HB_REFERENCE_COUNT_POISON_VALUE -0x0000DEAD
+#define HB_REFERENCE_COUNT_INIT {HB_ATOMIC_INT_INIT(HB_REFERENCE_COUNT_INERT_VALUE)}
 
 struct hb_reference_count_t
 {
   hb_atomic_int_t ref_count;
 
   inline void init (int v) { ref_count.set_unsafe (v); }
   inline int get_unsafe (void) const { return ref_count.get_unsafe (); }
   inline int inc (void) { return ref_count.inc (); }
   inline int dec (void) { return ref_count.dec (); }
-  inline void finish (void) { ref_count.set_unsafe (HB_REFERENCE_COUNT_INVALID_VALUE); }
+  inline void finish (void) { ref_count.set_unsafe (HB_REFERENCE_COUNT_POISON_VALUE); }
 
-  inline bool is_invalid (void) const { return ref_count.get_unsafe () == HB_REFERENCE_COUNT_INVALID_VALUE; }
+  inline bool is_inert (void) const { return ref_count.get_unsafe () == HB_REFERENCE_COUNT_INERT_VALUE; }
+  inline bool is_valid (void) const { return ref_count.get_unsafe () > 0; }
 };
 
 
 /* user_data */
 
 #define HB_USER_DATA_ARRAY_INIT {HB_MUTEX_INIT, HB_LOCKABLE_SET_INIT}
 struct hb_user_data_array_t
 {
@@ -137,55 +139,64 @@ template <typename Type>
 static inline void hb_object_init (Type *obj)
 {
   obj->header.ref_count.init (1);
   obj->header.user_data.init ();
 }
 template <typename Type>
 static inline bool hb_object_is_inert (const Type *obj)
 {
-  return unlikely (obj->header.ref_count.is_invalid ());
+  return unlikely (obj->header.ref_count.is_inert ());
+}
+template <typename Type>
+static inline bool hb_object_is_valid (const Type *obj)
+{
+  return likely (obj->header.ref_count.is_valid ());
 }
 template <typename Type>
 static inline Type *hb_object_reference (Type *obj)
 {
   hb_object_trace (obj, HB_FUNC);
   if (unlikely (!obj || hb_object_is_inert (obj)))
     return obj;
+  assert (hb_object_is_valid (obj));
   obj->header.ref_count.inc ();
   return obj;
 }
 template <typename Type>
 static inline bool hb_object_destroy (Type *obj)
 {
   hb_object_trace (obj, HB_FUNC);
   if (unlikely (!obj || hb_object_is_inert (obj)))
     return false;
+  assert (hb_object_is_valid (obj));
   if (obj->header.ref_count.dec () != 1)
     return false;
 
   obj->header.ref_count.finish (); /* Do this before user_data */
   obj->header.user_data.finish ();
   return true;
 }
 template <typename Type>
 static inline bool hb_object_set_user_data (Type               *obj,
 					    hb_user_data_key_t *key,
 					    void *              data,
 					    hb_destroy_func_t   destroy,
 					    hb_bool_t           replace)
 {
   if (unlikely (!obj || hb_object_is_inert (obj)))
     return false;
+  assert (hb_object_is_valid (obj));
   return obj->header.user_data.set (key, data, destroy, replace);
 }
 
 template <typename Type>
 static inline void *hb_object_get_user_data (Type               *obj,
 					     hb_user_data_key_t *key)
 {
   if (unlikely (!obj || hb_object_is_inert (obj)))
     return NULL;
+  assert (hb_object_is_valid (obj));
   return obj->header.user_data.get (key);
 }
 
 
 #endif /* HB_OBJECT_PRIVATE_HH */
--- a/gfx/harfbuzz/src/hb-open-type-private.hh
+++ b/gfx/harfbuzz/src/hb-open-type-private.hh
@@ -531,16 +531,30 @@ struct Supplier
 /*
  * Int types
  */
 
 
 template <typename Type, int Bytes> struct BEInt;
 
 template <typename Type>
+struct BEInt<Type, 1>
+{
+  public:
+  inline void set (Type V)
+  {
+    v = V;
+  }
+  inline operator Type (void) const
+  {
+    return v;
+  }
+  private: uint8_t v;
+};
+template <typename Type>
 struct BEInt<Type, 2>
 {
   public:
   inline void set (Type V)
   {
     v[0] = (V >>  8) & 0xFF;
     v[1] = (V      ) & 0xFF;
   }
@@ -613,17 +627,17 @@ struct IntType
     return TRACE_RETURN (likely (c->check_struct (this)));
   }
   protected:
   BEInt<Type, Size> v;
   public:
   DEFINE_SIZE_STATIC (Size);
 };
 
-typedef		uint8_t	     BYTE;	/* 8-bit unsigned integer. */
+typedef	IntType<uint8_t	, 1> BYTE;	/* 8-bit unsigned integer. */
 typedef IntType<uint16_t, 2> USHORT;	/* 16-bit unsigned integer. */
 typedef IntType<int16_t,  2> SHORT;	/* 16-bit signed integer. */
 typedef IntType<uint32_t, 4> ULONG;	/* 32-bit unsigned integer. */
 typedef IntType<int32_t,  4> LONG;	/* 32-bit signed integer. */
 typedef IntType<uint32_t, 3> UINT24;	/* 24-bit unsigned integer. */
 
 /* 16-bit signed integer (SHORT) that describes a quantity in FUnits. */
 typedef SHORT FWORD;
--- a/gfx/harfbuzz/src/hb-ot-font.cc
+++ b/gfx/harfbuzz/src/hb-ot-font.cc
@@ -26,16 +26,18 @@
 
 #include "hb-private.hh"
 
 #include "hb-ot.h"
 
 #include "hb-font-private.hh"
 
 #include "hb-ot-cmap-table.hh"
+#include "hb-ot-glyf-table.hh"
+#include "hb-ot-head-table.hh"
 #include "hb-ot-hhea-table.hh"
 #include "hb-ot-hmtx-table.hh"
 
 
 struct hb_ot_face_metrics_accelerator_t
 {
   unsigned int num_metrics;
   unsigned int num_advances;
@@ -71,31 +73,104 @@ struct hb_ot_face_metrics_accelerator_t
     hb_blob_destroy (this->blob);
   }
 
   inline unsigned int get_advance (hb_codepoint_t glyph) const
   {
     if (unlikely (glyph >= this->num_metrics))
     {
       /* If this->num_metrics is zero, it means we don't have the metrics table
-       * for this direction: return one EM.  Otherwise, it means that the glyph
-       * index is out of bound: return zero. */
+       * for this direction: return default advance.  Otherwise, it means that the
+       * glyph index is out of bound: return zero. */
       if (this->num_metrics)
 	return 0;
       else
 	return this->default_advance;
     }
 
     if (glyph >= this->num_advances)
       glyph = this->num_advances - 1;
 
     return this->table->longMetric[glyph].advance;
   }
 };
 
+struct hb_ot_face_glyf_accelerator_t
+{
+  bool short_offset;
+  unsigned int num_glyphs;
+  const OT::loca *loca;
+  const OT::glyf *glyf;
+  hb_blob_t *loca_blob;
+  hb_blob_t *glyf_blob;
+  unsigned int glyf_len;
+
+  inline void init (hb_face_t *face)
+  {
+    hb_blob_t *head_blob = OT::Sanitizer<OT::head>::sanitize (face->reference_table (HB_OT_TAG_head));
+    const OT::head *head = OT::Sanitizer<OT::head>::lock_instance (head_blob);
+    if ((unsigned int) head->indexToLocFormat > 1 || head->glyphDataFormat != 0)
+    {
+      /* Unknown format.  Leave num_glyphs=0, that takes care of disabling us. */
+      hb_blob_destroy (head_blob);
+      return;
+    }
+    this->short_offset = 0 == head->indexToLocFormat;
+    hb_blob_destroy (head_blob);
+
+    this->loca_blob = OT::Sanitizer<OT::loca>::sanitize (face->reference_table (HB_OT_TAG_loca));
+    this->loca = OT::Sanitizer<OT::loca>::lock_instance (this->loca_blob);
+    this->glyf_blob = OT::Sanitizer<OT::glyf>::sanitize (face->reference_table (HB_OT_TAG_glyf));
+    this->glyf = OT::Sanitizer<OT::glyf>::lock_instance (this->glyf_blob);
+
+    this->num_glyphs = MAX (1u, hb_blob_get_length (this->loca_blob) / (this->short_offset ? 2 : 4)) - 1;
+    this->glyf_len = hb_blob_get_length (this->glyf_blob);
+  }
+
+  inline void fini (void)
+  {
+    hb_blob_destroy (this->loca_blob);
+    hb_blob_destroy (this->glyf_blob);
+  }
+
+  inline bool get_extents (hb_codepoint_t glyph,
+			   hb_glyph_extents_t *extents) const
+  {
+    if (unlikely (glyph >= this->num_glyphs))
+      return false;
+
+    unsigned int start_offset, end_offset;
+    if (this->short_offset)
+    {
+      start_offset = 2 * this->loca->u.shortsZ[glyph];
+      end_offset   = 2 * this->loca->u.shortsZ[glyph + 1];
+    }
+    else
+    {
+      start_offset = this->loca->u.longsZ[glyph];
+      end_offset   = this->loca->u.longsZ[glyph + 1];
+    }
+
+    if (start_offset > end_offset || end_offset > this->glyf_len)
+      return false;
+
+    if (end_offset - start_offset < OT::glyfGlyphHeader::static_size)
+      return true; /* Empty glyph; zero extents. */
+
+    const OT::glyfGlyphHeader &glyph_header = OT::StructAtOffset<OT::glyfGlyphHeader> (this->glyf, start_offset);
+
+    extents->x_bearing = MIN (glyph_header.xMin, glyph_header.xMax);
+    extents->y_bearing = MAX (glyph_header.yMin, glyph_header.yMax);
+    extents->width     = MAX (glyph_header.xMin, glyph_header.xMax) - extents->x_bearing;
+    extents->height    = MIN (glyph_header.yMin, glyph_header.yMax) - extents->y_bearing;
+
+    return true;
+  }
+};
+
 struct hb_ot_face_cmap_accelerator_t
 {
   const OT::CmapSubtable *table;
   const OT::CmapSubtable *uvs_table;
   hb_blob_t *blob;
 
   inline void init (hb_face_t *face)
   {
@@ -153,16 +228,17 @@ struct hb_ot_face_cmap_accelerator_t
 };
 
 
 struct hb_ot_font_t
 {
   hb_ot_face_cmap_accelerator_t cmap;
   hb_ot_face_metrics_accelerator_t h_metrics;
   hb_ot_face_metrics_accelerator_t v_metrics;
+  hb_ot_face_glyf_accelerator_t glyf;
 };
 
 
 static hb_ot_font_t *
 _hb_ot_font_create (hb_font_t *font)
 {
   hb_ot_font_t *ot_font = (hb_ot_font_t *) calloc (1, sizeof (hb_ot_font_t));
   hb_face_t *face = font->face;
@@ -170,16 +246,17 @@ static hb_ot_font_t *
   if (unlikely (!ot_font))
     return NULL;
 
   unsigned int upem = face->get_upem ();
 
   ot_font->cmap.init (face);
   ot_font->h_metrics.init (face, HB_OT_TAG_hhea, HB_OT_TAG_hmtx, upem>>1);
   ot_font->v_metrics.init (face, HB_OT_TAG_vhea, HB_OT_TAG_vmtx, upem); /* TODO Can we do this lazily? */
+  ot_font->glyf.init (face);
 
   return ot_font;
 }
 
 static void
 _hb_ot_font_destroy (hb_ot_font_t *ot_font)
 {
   ot_font->cmap.fini ();
@@ -271,18 +348,23 @@ hb_ot_get_glyph_v_kerning (hb_font_t *fo
 
 static hb_bool_t
 hb_ot_get_glyph_extents (hb_font_t *font HB_UNUSED,
 			 void *font_data,
 			 hb_codepoint_t glyph,
 			 hb_glyph_extents_t *extents,
 			 void *user_data HB_UNUSED)
 {
-  /* TODO */
-  return false;
+  const hb_ot_font_t *ot_font = (const hb_ot_font_t *) font_data;
+  bool ret = ot_font->glyf.get_extents (glyph, extents);
+  extents->x_bearing = font->em_scale_x (extents->x_bearing);
+  extents->y_bearing = font->em_scale_y (extents->y_bearing);
+  extents->width     = font->em_scale_x (extents->width);
+  extents->height    = font->em_scale_y (extents->height);
+  return ret;
 }
 
 static hb_bool_t
 hb_ot_get_glyph_contour_point (hb_font_t *font HB_UNUSED,
 			       void *font_data,
 			       hb_codepoint_t glyph,
 			       unsigned int point_index,
 			       hb_position_t *x,
--- a/gfx/harfbuzz/src/hb-ot-head-table.hh
+++ b/gfx/harfbuzz/src/hb-ot-head-table.hh
@@ -133,19 +133,20 @@ struct head
 					 * Bits 7-15: Reserved (set to 0). */
   USHORT	lowestRecPPEM;		/* Smallest readable size in pixels. */
   SHORT		fontDirectionHint;	/* Deprecated (Set to 2).
 					 * 0: Fully mixed directional glyphs;
 					 * 1: Only strongly left to right;
 					 * 2: Like 1 but also contains neutrals;
 					 * -1: Only strongly right to left;
 					 * -2: Like -1 but also contains neutrals. */
+  public:
   SHORT		indexToLocFormat;	/* 0 for short offsets, 1 for long. */
   SHORT		glyphDataFormat;	/* 0 for current format. */
-  public:
+
   DEFINE_SIZE_STATIC (54);
 };
 
 
 } /* namespace OT */
 
 
 #endif /* HB_OT_HEAD_TABLE_HH */
--- a/gfx/harfbuzz/src/hb-ot-layout-gpos-table.hh
+++ b/gfx/harfbuzz/src/hb-ot-layout-gpos-table.hh
@@ -878,16 +878,19 @@ struct EntryExitRecord
   OffsetTo<Anchor>
 		exitAnchor;		/* Offset to ExitAnchor table--from
 					 * beginning of CursivePos
 					 * subtable--may be NULL */
   public:
   DEFINE_SIZE_STATIC (4);
 };
 
+static void
+reverse_cursive_minor_offset (hb_glyph_position_t *pos, unsigned int i, hb_direction_t direction, unsigned int new_parent);
+
 struct CursivePosFormat1
 {
   inline void collect_glyphs (hb_collect_glyphs_context_t *c) const
   {
     TRACE_COLLECT_GLYPHS (this);
     (this+coverage).add_coverage (c->input);
   }
 
@@ -955,30 +958,49 @@ struct CursivePosFormat1
 	pos[j].y_advance  =  entry_y;
 	break;
       case HB_DIRECTION_INVALID:
       default:
 	break;
     }
 
     /* Cross-direction adjustment */
-    if  (c->lookup_props & LookupFlag::RightToLeft) {
-      pos[i].cursive_chain() = j - i;
-      if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction)))
-	pos[i].y_offset = entry_y - exit_y;
-      else
-	pos[i].x_offset = entry_x - exit_x;
-    } else {
-      pos[j].cursive_chain() = i - j;
-      if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction)))
-	pos[j].y_offset = exit_y - entry_y;
-      else
-	pos[j].x_offset = exit_x - entry_x;
+
+    /* We attach child to parent (think graph theory and rooted trees whereas
+     * the root stays on baseline and each node aligns itself against its
+     * parent.
+     *
+     * Optimize things for the case of RightToLeft, as that's most common in
+     * Arabinc. */
+    unsigned int child  = i;
+    unsigned int parent = j;
+    hb_position_t x_offset = entry_x - exit_x;
+    hb_position_t y_offset = entry_y - exit_y;
+    if  (!(c->lookup_props & LookupFlag::RightToLeft))
+    {
+      unsigned int k = child;
+      child = parent;
+      parent = k;
+      x_offset = -x_offset;
+      y_offset = -y_offset;
     }
 
+    /* If child was already connected to someone else, walk through its old
+     * chain and reverse the link direction, such that the whole tree of its
+     * previous connection now attaches to new parent.  Watch out for case
+     * where new parent is on the path from old chain...
+     */
+    reverse_cursive_minor_offset (pos, child, c->direction, parent);
+
+    pos[child].cursive_chain() = parent - child;
+    if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction)))
+      pos[child].y_offset = y_offset;
+    else
+      pos[child].x_offset = x_offset;
+
     buffer->idx = j;
     return TRACE_RETURN (true);
   }
 
   inline bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
     return TRACE_RETURN (coverage.sanitize (c, this) && entryExitRecord.sanitize (c, this));
@@ -1478,16 +1500,40 @@ struct GPOS : GSUBGPOS
     return TRACE_RETURN (list.sanitize (c, this));
   }
   public:
   DEFINE_SIZE_STATIC (10);
 };
 
 
 static void
+reverse_cursive_minor_offset (hb_glyph_position_t *pos, unsigned int i, hb_direction_t direction, unsigned int new_parent)
+{
+  unsigned int j = pos[i].cursive_chain();
+  if (likely (!j))
+    return;
+
+  j += i;
+
+  pos[i].cursive_chain() = 0;
+
+  /* Stop if we see new parent in the chain. */
+  if (j == new_parent)
+    return;
+
+  reverse_cursive_minor_offset (pos, j, direction, new_parent);
+
+  if (HB_DIRECTION_IS_HORIZONTAL (direction))
+    pos[j].y_offset = -pos[i].y_offset;
+  else
+    pos[j].x_offset = -pos[i].x_offset;
+
+  pos[j].cursive_chain() = i - j;
+}
+static void
 fix_cursive_minor_offset (hb_glyph_position_t *pos, unsigned int i, hb_direction_t direction)
 {
   unsigned int j = pos[i].cursive_chain();
   if (likely (!j))
     return;
 
   j += i;
 
@@ -1563,18 +1609,21 @@ template <typename context_t>
   return l.dispatch (c);
 }
 
 /*static*/ inline bool PosLookup::apply_recurse_func (hb_apply_context_t *c, unsigned int lookup_index)
 {
   const GPOS &gpos = *(hb_ot_layout_from_face (c->face)->gpos);
   const PosLookup &l = gpos.get_lookup (lookup_index);
   unsigned int saved_lookup_props = c->lookup_props;
-  c->set_lookup (l);
+  unsigned int saved_lookup_index = c->lookup_index;
+  c->set_lookup_index (lookup_index);
+  c->set_lookup_props (l.get_props ());
   bool ret = l.dispatch (c);
+  c->set_lookup_index (saved_lookup_index);
   c->set_lookup_props (saved_lookup_props);
   return ret;
 }
 
 
 #undef attach_lookback
 #undef cursive_chain
 
--- a/gfx/harfbuzz/src/hb-ot-layout-gsub-table.hh
+++ b/gfx/harfbuzz/src/hb-ot-layout-gsub-table.hh
@@ -1306,18 +1306,21 @@ template <typename context_t>
   return l.dispatch (c);
 }
 
 /*static*/ inline bool SubstLookup::apply_recurse_func (hb_apply_context_t *c, unsigned int lookup_index)
 {
   const GSUB &gsub = *(hb_ot_layout_from_face (c->face)->gsub);
   const SubstLookup &l = gsub.get_lookup (lookup_index);
   unsigned int saved_lookup_props = c->lookup_props;
-  c->set_lookup (l);
+  unsigned int saved_lookup_index = c->lookup_index;
+  c->set_lookup_index (lookup_index);
+  c->set_lookup_props (l.get_props ());
   bool ret = l.dispatch (c);
+  c->set_lookup_index (saved_lookup_index);
   c->set_lookup_props (saved_lookup_props);
   return ret;
 }
 
 
 } /* namespace OT */
 
 
--- a/gfx/harfbuzz/src/hb-ot-layout-gsubgpos-private.hh
+++ b/gfx/harfbuzz/src/hb-ot-layout-gsubgpos-private.hh
@@ -261,17 +261,18 @@ struct hb_add_coverage_context_t
 
 #ifndef HB_DEBUG_APPLY
 #define HB_DEBUG_APPLY (HB_DEBUG+0)
 #endif
 
 #define TRACE_APPLY(this) \
 	hb_auto_trace_t<HB_DEBUG_APPLY, bool> trace \
 	(&c->debug_depth, c->get_name (), this, HB_FUNC, \
-	 "idx %d codepoint %u", c->buffer->idx, c->buffer->cur().codepoint);
+	 "idx %d gid %u lookup %d", \
+	 c->buffer->idx, c->buffer->cur().codepoint, (int) c->lookup_index);
 
 struct hb_apply_context_t
 {
   struct matcher_t
   {
     inline matcher_t (void) :
 	     lookup_props (0),
 	     ignore_zwnj (false),
@@ -476,16 +477,17 @@ struct hb_apply_context_t
   hb_mask_t lookup_mask;
   bool auto_zwj;
   recurse_func_t recurse_func;
   unsigned int nesting_level_left;
   unsigned int lookup_props;
   const GDEF &gdef;
   bool has_glyph_classes;
   skipping_iterator_t iter_input, iter_context;
+  unsigned int lookup_index;
   unsigned int debug_depth;
 
 
   hb_apply_context_t (unsigned int table_index_,
 		      hb_font_t *font_,
 		      hb_buffer_t *buffer_) :
 			table_index (table_index_),
 			font (font_), face (font->face), buffer (buffer_),
@@ -494,22 +496,23 @@ struct hb_apply_context_t
 			auto_zwj (true),
 			recurse_func (NULL),
 			nesting_level_left (MAX_NESTING_LEVEL),
 			lookup_props (0),
 			gdef (*hb_ot_layout_from_face (face)->gdef),
 			has_glyph_classes (gdef.has_glyph_classes ()),
 			iter_input (),
 			iter_context (),
+			lookup_index ((unsigned int) -1),
 			debug_depth (0) {}
 
   inline void set_lookup_mask (hb_mask_t mask) { lookup_mask = mask; }
   inline void set_auto_zwj (bool auto_zwj_) { auto_zwj = auto_zwj_; }
   inline void set_recurse_func (recurse_func_t func) { recurse_func = func; }
-  inline void set_lookup (const Lookup &l) { set_lookup_props (l.get_props ()); }
+  inline void set_lookup_index (unsigned int lookup_index_) { lookup_index = lookup_index_; }
   inline void set_lookup_props (unsigned int lookup_props_)
   {
     lookup_props = lookup_props_;
     iter_input.init (this, false);
     iter_context.init (this, true);
   }
 
   inline bool
--- a/gfx/harfbuzz/src/hb-ot-layout.cc
+++ b/gfx/harfbuzz/src/hb-ot-layout.cc
@@ -954,17 +954,17 @@ apply_string (OT::hb_apply_context_t *c,
 	      const typename Proxy::Lookup &lookup,
 	      const hb_ot_layout_lookup_accelerator_t &accel)
 {
   hb_buffer_t *buffer = c->buffer;
 
   if (unlikely (!buffer->len || !c->lookup_mask))
     return;
 
-  c->set_lookup (lookup);
+  c->set_lookup_props (lookup.get_props ());
 
   if (likely (!lookup.is_reverse ()))
   {
     /* in/out forward substitution/positioning */
     if (Proxy::table_index == 0)
       buffer->clear_output ();
     buffer->idx = 0;
 
@@ -1005,17 +1005,30 @@ inline void hb_ot_map_t::apply (const Pr
   unsigned int i = 0;
   OT::hb_apply_context_t c (table_index, font, buffer);
   c.set_recurse_func (Proxy::Lookup::apply_recurse_func);
 
   for (unsigned int stage_index = 0; stage_index < stages[table_index].len; stage_index++) {
     const stage_map_t *stage = &stages[table_index][stage_index];
     for (; i < stage->last_lookup; i++)
     {
+#if 0
+      char buf[4096];
+      hb_buffer_serialize_glyphs (buffer, 0, buffer->len,
+				  buf, sizeof (buf), NULL,
+				  font,
+				  HB_BUFFER_SERIALIZE_FORMAT_TEXT,
+				  Proxy::table_index == 0 ?
+				  HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS :
+				  HB_BUFFER_SERIALIZE_FLAG_DEFAULT);
+      printf ("buf: [%s]\n", buf);
+#endif
+
       unsigned int lookup_index = lookups[table_index][i].index;
+      c.set_lookup_index (lookup_index);
       c.set_lookup_mask (lookups[table_index][i].mask);
       c.set_auto_zwj (lookups[table_index][i].auto_zwj);
       apply_string<Proxy> (&c,
 			   proxy.table.get_lookup (lookup_index),
 			   proxy.accels[lookup_index]);
     }
 
     if (stage->pause_func)
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-arabic-fallback.hh
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-arabic-fallback.hh
@@ -70,19 +70,19 @@ arabic_fallback_synthesize_lookup_single
     substitutes[num_glyphs].set (s_glyph);
 
     num_glyphs++;
   }
 
   if (!num_glyphs)
     return NULL;
 
-  /* Bubble-sort!
+  /* Bubble-sort or something equally good!
    * May not be good-enough for presidential candidate interviews, but good-enough for us... */
-  hb_bubble_sort (&glyphs[0], num_glyphs, OT::GlyphID::cmp, &substitutes[0]);
+  hb_stable_sort (&glyphs[0], num_glyphs, OT::GlyphID::cmp, &substitutes[0]);
 
   OT::Supplier<OT::GlyphID> glyphs_supplier      (glyphs, num_glyphs);
   OT::Supplier<OT::GlyphID> substitutes_supplier (substitutes, num_glyphs);
 
   /* Each glyph takes four bytes max, and there's some overhead. */
   char buf[(SHAPING_TABLE_LAST - SHAPING_TABLE_FIRST + 1) * 4 + 128];
   OT::hb_serialize_context_t c (buf, sizeof (buf));
   OT::SubstLookup *lookup = c.start_serialize<OT::SubstLookup> ();
@@ -121,17 +121,17 @@ arabic_fallback_synthesize_lookup_ligatu
     hb_codepoint_t first_glyph;
     if (!hb_font_get_glyph (font, first_u, 0, &first_glyph))
       continue;
     first_glyphs[num_first_glyphs].set (first_glyph);
     ligature_per_first_glyph_count_list[num_first_glyphs] = 0;
     first_glyphs_indirection[num_first_glyphs] = first_glyph_idx;
     num_first_glyphs++;
   }
-  hb_bubble_sort (&first_glyphs[0], num_first_glyphs, OT::GlyphID::cmp, &first_glyphs_indirection[0]);
+  hb_stable_sort (&first_glyphs[0], num_first_glyphs, OT::GlyphID::cmp, &first_glyphs_indirection[0]);
 
   /* Now that the first-glyphs are sorted, walk again, populate ligatures. */
   for (unsigned int i = 0; i < num_first_glyphs; i++)
   {
     unsigned int first_glyph_idx = first_glyphs_indirection[i];
 
     for (unsigned int second_glyph_idx = 0; second_glyph_idx < ARRAY_LENGTH (ligature_table[0].ligatures); second_glyph_idx++)
     {
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-hangul.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-hangul.cc
@@ -200,21 +200,21 @@ preprocess_text_hangul (const hb_ot_shap
        * I didn't bother for now.
        */
       if (start < end && end == buffer->out_len)
       {
 	/* Tone mark follows a valid syllable; move it in front, unless it's zero width. */
 	buffer->next_glyph ();
 	if (!is_zero_width_char (font, u))
 	{
+	  buffer->merge_out_clusters (start, end + 1);
 	  hb_glyph_info_t *info = buffer->out_info;
 	  hb_glyph_info_t tone = info[end];
 	  memmove (&info[start + 1], &info[start], (end - start) * sizeof (hb_glyph_info_t));
 	  info[start] = tone;
-	  buffer->merge_out_clusters (start, end + 1);
 	}
       }
       else
       {
 	/* No valid syllable as base for tone mark; try to insert dotted circle. */
 	if (font->has_glyph (0x25CCu))
 	{
 	  hb_codepoint_t chars[2];
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-indic.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-indic.cc
@@ -1007,30 +1007,34 @@ initial_reordering_consonant_syllable (c
 
   {
     /* Use syllable() for sort accounting temporarily. */
     unsigned int syllable = info[start].syllable();
     for (unsigned int i = start; i < end; i++)
       info[i].syllable() = i - start;
 
     /* Sit tight, rock 'n roll! */
-    hb_bubble_sort (info + start, end - start, compare_indic_order);
+    hb_stable_sort (info + start, end - start, compare_indic_order);
     /* Find base again */
     base = end;
     for (unsigned int i = start; i < end; i++)
       if (info[i].indic_position() == POS_BASE_C)
       {
 	base = i;
 	break;
       }
     /* Things are out-of-control for post base positions, they may shuffle
      * around like crazy.  In old-spec mode, we move halants around, so in
      * that case merge all clusters after base.  Otherwise, check the sort
      * order and merge as needed.
-     * For pre-base stuff, we handle cluster issues in final reordering. */
+     * For pre-base stuff, we handle cluster issues in final reordering.
+     *
+     * We could use buffer->sort() for this, if there was no special
+     * reordering of pre-base stuff happening later...
+     */
     if (indic_plan->is_old_spec || end - base > 127)
       buffer->merge_clusters (base, end);
     else
     {
       /* Note!  syllable() is a one-byte field. */
       for (unsigned int i = base; i < end; i++)
         if (info[i].syllable() != 255)
 	{
@@ -1399,22 +1403,27 @@ final_reordering_syllable (const hb_ot_s
 
     if (start < new_pos && info[new_pos].indic_position () != POS_PRE_M)
     {
       /* Now go see if there's actually any matras... */
       for (unsigned int i = new_pos; i > start; i--)
 	if (info[i - 1].indic_position () == POS_PRE_M)
 	{
 	  unsigned int old_pos = i - 1;
+	  if (old_pos < base && base <= new_pos) /* Shouldn't actually happen. */
+	    base--;
+
 	  hb_glyph_info_t tmp = info[old_pos];
 	  memmove (&info[old_pos], &info[old_pos + 1], (new_pos - old_pos) * sizeof (info[0]));
 	  info[new_pos] = tmp;
-	  if (old_pos < base && base <= new_pos) /* Shouldn't actually happen. */
-	    base--;
+
+	  /* Note: this merge_clusters() is intentionally *after* the reordering.
+	   * Indic matra reordering is special and tricky... */
 	  buffer->merge_clusters (new_pos, MIN (end, base + 1));
+
 	  new_pos--;
 	}
     } else {
       for (unsigned int i = start; i < base; i++)
 	if (info[i].indic_position () == POS_PRE_M) {
 	  buffer->merge_clusters (i, MIN (end, base + 1));
 	  break;
 	}
@@ -1557,22 +1566,22 @@ final_reordering_syllable (const hb_ot_s
 	    new_reph_pos--;
 	  }
       }
       goto reph_move;
     }
 
     reph_move:
     {
+      /* Move */
       buffer->merge_clusters (start, new_reph_pos + 1);
-
-      /* Move */
       hb_glyph_info_t reph = info[start];
       memmove (&info[start], &info[start + 1], (new_reph_pos - start) * sizeof (info[0]));
       info[new_reph_pos] = reph;
+
       if (start < base && base <= new_reph_pos)
 	base--;
     }
   }
 
 
   /*   o Reorder pre-base reordering consonants:
    *
@@ -1635,20 +1644,22 @@ final_reordering_syllable (const hb_ot_s
 	  {
 	    /* -> If ZWJ or ZWNJ follow this halant, position is moved after it. */
 	    if (new_pos < end && is_joiner (info[new_pos]))
 	      new_pos++;
 	  }
 
 	  {
 	    unsigned int old_pos = i;
+
 	    buffer->merge_clusters (new_pos, old_pos + 1);
 	    hb_glyph_info_t tmp = info[old_pos];
 	    memmove (&info[new_pos + 1], &info[new_pos], (old_pos - new_pos) * sizeof (info[0]));
 	    info[new_pos] = tmp;
+
 	    if (new_pos <= base && base < old_pos)
 	      base++;
 	  }
 	}
 
         break;
       }
   }
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar.cc
@@ -386,19 +386,18 @@ initial_reordering_consonant_syllable (h
         pos = POS_AFTER_SUB;
 	info[i].myanmar_position() = pos;
 	continue;
       }
       info[i].myanmar_position() = pos;
     }
   }
 
-  buffer->merge_clusters (start, end);
   /* Sit tight, rock 'n roll! */
-  hb_bubble_sort (info + start, end - start, compare_myanmar_order);
+  buffer->sort (start, end, compare_myanmar_order);
 }
 
 static void
 initial_reordering_syllable (const hb_ot_shape_plan_t *plan,
 			     hb_face_t *face,
 			     hb_buffer_t *buffer,
 			     unsigned int start, unsigned int end)
 {
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-thai.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-thai.cc
@@ -348,17 +348,17 @@ preprocess_text_thai (const hb_ot_shape_
 	       buffer->out_info + start,
 	       sizeof (buffer->out_info[0]) * (end - start - 2));
       buffer->out_info[start] = t;
     }
     else
     {
       /* Since we decomposed, and NIKHAHIT is combining, merge clusters with the
        * previous cluster. */
-      if (start)
+      if (start && buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES)
 	buffer->merge_out_clusters (start - 1, end);
     }
   }
   buffer->swap_buffers ();
 
   /* If font has Thai GSUB, we are done. */
   if (plan->props.script == HB_SCRIPT_THAI && !plan->map.found_script[0])
     do_thai_pua_shaping (plan, buffer, font);
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-use.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-complex-use.cc
@@ -233,24 +233,16 @@ enum syllable_type_t {
   numeral_cluster,
   symbol_cluster,
   broken_cluster,
 };
 
 #include "hb-ot-shape-complex-use-machine.hh"
 
 
-static inline void
-set_use_properties (hb_glyph_info_t &info)
-{
-  hb_codepoint_t u = info.codepoint;
-  info.use_category() = hb_use_get_categories (u);
-}
-
-
 static void
 setup_masks_use (const hb_ot_shape_plan_t *plan,
 		 hb_buffer_t              *buffer,
 		 hb_font_t                *font HB_UNUSED)
 {
   const use_shape_plan_t *use_plan = (const use_shape_plan_t *) plan->data;
 
   /* Do this before allocating use_category(). */
@@ -439,20 +431,20 @@ reorder_syllable (hb_buffer_t *buffer, u
       if (FLAG_UNSAFE (info[i].use_category()) & (HALANT_FLAGS | BASE_FLAGS))
       {
 	/* If we hit a halant, move before it; otherwise it's a base: move to it's
 	 * place, and shift things in between backward. */
 
 	if (info[i].use_category() == USE_H)
 	  i--;
 
+	buffer->merge_clusters (start, i + 1);
 	hb_glyph_info_t t = info[start];
 	memmove (&info[start], &info[start + 1], (i - start) * sizeof (info[0]));
 	info[i] = t;
-	buffer->merge_clusters (start, i + 1);
 
 	break;
       }
   }
 
   /* Move things back. */
   unsigned int j = end;
   for (unsigned int i = start; i < end; i++)
@@ -467,20 +459,20 @@ reorder_syllable (hb_buffer_t *buffer, u
       else
 	j = i;
     }
     else if (((flag) & (FLAG (USE_VPre) | FLAG (USE_VMPre))) &&
 	     /* Only move the first component of a MultipleSubst. */
 	     0 == _hb_glyph_info_get_lig_comp (&info[i]) &&
 	     j < i)
     {
+      buffer->merge_clusters (j, i + 1);
       hb_glyph_info_t t = info[i];
       memmove (&info[j + 1], &info[j], (i - j) * sizeof (info[0]));
       info[j] = t;
-      buffer->merge_clusters (j, i + 1);
     }
   }
 }
 
 static inline void
 insert_dotted_circles (const hb_ot_shape_plan_t *plan HB_UNUSED,
 		       hb_font_t *font,
 		       hb_buffer_t *buffer)
--- a/gfx/harfbuzz/src/hb-ot-shape-normalize.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape-normalize.cc
@@ -339,25 +339,23 @@ void
     if (_hb_glyph_info_get_modified_combining_class (&buffer->info[i]) == 0)
       continue;
 
     unsigned int end;
     for (end = i + 1; end < count; end++)
       if (_hb_glyph_info_get_modified_combining_class (&buffer->info[end]) == 0)
         break;
 
-    /* We are going to do a bubble-sort.  Only do this if the
-     * sequence is short.  Doing it on long sequences can result
-     * in an O(n^2) DoS. */
+    /* We are going to do a O(n^2).  Only do this if the sequence is short. */
     if (end - i > 10) {
       i = end;
       continue;
     }
 
-    hb_bubble_sort (buffer->info + i, end - i, compare_combining_class);
+    buffer->sort (i, end, compare_combining_class);
 
     i = end;
   }
 
 
   if (mode == HB_OT_SHAPE_NORMALIZATION_MODE_NONE ||
       mode == HB_OT_SHAPE_NORMALIZATION_MODE_DECOMPOSED)
     return;
--- a/gfx/harfbuzz/src/hb-ot-shape.cc
+++ b/gfx/harfbuzz/src/hb-ot-shape.cc
@@ -297,25 +297,26 @@ hb_ensure_native_direction (hb_buffer_t 
      * Since form_clusters() merged clusters already, we don't merge. */
     unsigned int base = 0;
     unsigned int count = buffer->len;
     hb_glyph_info_t *info = buffer->info;
     for (unsigned int i = 1; i < count; i++)
     {
       if (likely (!HB_UNICODE_GENERAL_CATEGORY_IS_MARK (_hb_glyph_info_get_general_category (&info[i]))))
       {
-	buffer->reverse_range (base, i);
 	if (buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
 	  buffer->merge_clusters (base, i);
+	buffer->reverse_range (base, i);
+
 	base = i;
       }
     }
-    buffer->reverse_range (base, count);
     if (buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
       buffer->merge_clusters (base, count);
+    buffer->reverse_range (base, count);
 
     buffer->reverse ();
 
     buffer->props.direction = HB_DIRECTION_REVERSE (buffer->props.direction);
   }
 }
 
 
@@ -508,16 +509,18 @@ hb_ot_hide_default_ignorables (hb_ot_sha
 static inline void
 hb_ot_map_glyphs_fast (hb_buffer_t  *buffer)
 {
   /* Normalization process sets up glyph_index(), we just copy it. */
   unsigned int count = buffer->len;
   hb_glyph_info_t *info = buffer->info;
   for (unsigned int i = 0; i < count; i++)
     info[i].codepoint = info[i].glyph_index();
+
+  buffer->content_type = HB_BUFFER_CONTENT_TYPE_GLYPHS;
 }
 
 static inline void
 hb_synthesize_glyph_classes (hb_ot_shape_context_t *c)
 {
   unsigned int count = c->buffer->len;
   hb_glyph_info_t *info = c->buffer->info;
   for (unsigned int i = 0; i < count; i++)
--- a/gfx/harfbuzz/src/hb-private.hh
+++ b/gfx/harfbuzz/src/hb-private.hh
@@ -850,52 +850,44 @@ hb_in_ranges (T u, T lo1, T hi1, T lo2, 
  */
 #define FLAG(x) (ASSERT_STATIC_EXPR_ZERO ((x) < 32) + (1U << (x)))
 #define FLAG_SAFE(x) (1U << (x))
 #define FLAG_UNSAFE(x) ((x) < 32 ? FLAG_SAFE(x) : 0)
 #define FLAG_RANGE(x,y) (ASSERT_STATIC_EXPR_ZERO ((x) < (y)) + FLAG(y+1) - FLAG(x))
 
 
 template <typename T, typename T2> static inline void
-hb_bubble_sort (T *array, unsigned int len, int(*compar)(const T *, const T *), T2 *array2)
+hb_stable_sort (T *array, unsigned int len, int(*compar)(const T *, const T *), T2 *array2)
 {
-  if (unlikely (!len))
-    return;
-
-  unsigned int k = len - 1;
-  do {
-    unsigned int new_k = 0;
-
-    for (unsigned int j = 0; j < k; j++)
-      if (compar (&array[j], &array[j+1]) > 0)
-      {
-        {
-	  T t;
-	  t = array[j];
-	  array[j] = array[j + 1];
-	  array[j + 1] = t;
-	}
-        if (array2)
-        {
-	  T2 t;
-	  t = array2[j];
-	  array2[j] = array2[j + 1];
-	  array2[j + 1] = t;
-	}
-
-	new_k = j;
-      }
-    k = new_k;
-  } while (k);
+  for (unsigned int i = 1; i < len; i++)
+  {
+    unsigned int j = i;
+    while (j && compar (&array[j - 1], &array[i]) > 0)
+      j--;
+    if (i == j)
+      continue;
+    /* Move item i to occupy place for item j, shift what's in between. */
+    {
+      T t = array[i];
+      memmove (&array[j + 1], &array[j], (i - j) * sizeof (T));
+      array[j] = t;
+    }
+    if (array2)
+    {
+      T2 t = array2[i];
+      memmove (&array2[j + 1], &array2[j], (i - j) * sizeof (T2));
+      array2[j] = t;
+    }
+  }
 }
 
 template <typename T> static inline void
-hb_bubble_sort (T *array, unsigned int len, int(*compar)(const T *, const T *))
+hb_stable_sort (T *array, unsigned int len, int(*compar)(const T *, const T *))
 {
-  hb_bubble_sort (array, len, compar, (int *) NULL);
+  hb_stable_sort (array, len, compar, (int *) NULL);
 }
 
 static inline hb_bool_t
 hb_codepoint_parse (const char *s, unsigned int len, int base, hb_codepoint_t *out)
 {
   /* Pain because we don't know whether s is nul-terminated. */
   char buf[64];
   len = MIN (ARRAY_LENGTH (buf) - 1, len);
--- a/gfx/harfbuzz/src/hb-uniscribe.cc
+++ b/gfx/harfbuzz/src/hb-uniscribe.cc
@@ -481,24 +481,26 @@ void
  * shaper font data
  */
 
 struct hb_uniscribe_shaper_font_data_t {
   HDC hdc;
   LOGFONTW log_font;
   HFONT hfont;
   SCRIPT_CACHE script_cache;
+  double x_mult, y_mult; /* From LOGFONT space to HB space. */
 };
 
 static bool
 populate_log_font (LOGFONTW  *lf,
-		   hb_font_t *font)
+		   hb_font_t *font,
+		   unsigned int font_size)
 {
   memset (lf, 0, sizeof (*lf));
-  lf->lfHeight = -font->y_scale;
+  lf->lfHeight = -font_size;
   lf->lfCharSet = DEFAULT_CHARSET;
 
   hb_face_t *face = font->face;
   hb_uniscribe_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face);
 
   memcpy (lf->lfFaceName, face_data->face_name, sizeof (lf->lfFaceName));
 
   return true;
@@ -508,19 +510,29 @@ hb_uniscribe_shaper_font_data_t *
 _hb_uniscribe_shaper_font_data_create (hb_font_t *font)
 {
   if (unlikely (!hb_uniscribe_shaper_face_data_ensure (font->face))) return NULL;
 
   hb_uniscribe_shaper_font_data_t *data = (hb_uniscribe_shaper_font_data_t *) calloc (1, sizeof (hb_uniscribe_shaper_font_data_t));
   if (unlikely (!data))
     return NULL;
 
+  int font_size = font->face->get_upem (); /* Default... */
+  /* No idea if the following is even a good idea. */
+  if (font->y_ppem)
+    font_size = font->y_ppem;
+
+  if (font_size < 0)
+    font_size = -font_size;
+  data->x_mult = (double) font->x_scale / font_size;
+  data->y_mult = (double) font->y_scale / font_size;
+
   data->hdc = GetDC (NULL);
 
-  if (unlikely (!populate_log_font (&data->log_font, font))) {
+  if (unlikely (!populate_log_font (&data->log_font, font, font_size))) {
     DEBUG_MSG (UNISCRIBE, font, "Font populate_log_font() failed");
     _hb_uniscribe_shaper_font_data_destroy (data);
     return NULL;
   }
 
   data->hfont = CreateFontIndirectW (&data->log_font);
   if (unlikely (!data->hfont)) {
     DEBUG_MSG (UNISCRIBE, font, "Font CreateFontIndirectW() failed");
@@ -989,31 +1001,32 @@ retry:
   {
     hb_glyph_info_t *info = &buffer->info[buffer->len++];
 
     info->codepoint = glyphs[i];
     info->cluster = vis_clusters[i];
 
     /* The rest is crap.  Let's store position info there for now. */
     info->mask = advances[i];
-    info->var1.u32 = offsets[i].du;
-    info->var2.u32 = offsets[i].dv;
+    info->var1.i32 = offsets[i].du;
+    info->var2.i32 = offsets[i].dv;
   }
 
   /* Set glyph positions */
   buffer->clear_positions ();
+  double x_mult = font_data->x_mult, y_mult = font_data->y_mult;
   for (unsigned int i = 0; i < glyphs_len; i++)
   {
     hb_glyph_info_t *info = &buffer->info[i];
     hb_glyph_position_t *pos = &buffer->pos[i];
 
     /* TODO vertical */
-    pos->x_advance = info->mask;
-    pos->x_offset = backward ? -info->var1.u32 : info->var1.u32;
-    pos->y_offset = info->var2.u32;
+    pos->x_advance = x_mult * info->mask;
+    pos->x_offset = x_mult * (backward ? -info->var1.i32 : info->var1.i32);
+    pos->y_offset = y_mult * info->var2.i32;
   }
 
   if (backward)
     hb_buffer_reverse (buffer);
 
   /* Wow, done! */
   return true;
 }
--- a/gfx/harfbuzz/src/hb-version.h
+++ b/gfx/harfbuzz/src/hb-version.h
@@ -33,19 +33,19 @@
 
 #include "hb-common.h"
 
 HB_BEGIN_DECLS
 
 
 #define HB_VERSION_MAJOR 1
 #define HB_VERSION_MINOR 0
-#define HB_VERSION_MICRO 1
+#define HB_VERSION_MICRO 3
 
-#define HB_VERSION_STRING "1.0.1"
+#define HB_VERSION_STRING "1.0.3"
 
 #define HB_VERSION_ATLEAST(major,minor,micro) \
 	((major)*10000+(minor)*100+(micro) <= \
 	 HB_VERSION_MAJOR*10000+HB_VERSION_MINOR*100+HB_VERSION_MICRO)
 
 
 void
 hb_version (unsigned int *major,
--- a/gfx/thebes/gfxMacPlatformFontList.h
+++ b/gfx/thebes/gfxMacPlatformFontList.h
@@ -62,16 +62,17 @@ protected:
     static void DestroyBlobFunc(void* aUserData);
 
     CGFontRef mFontRef; // owning reference to the CGFont, released on destruction
 
     bool mFontRefInitialized;
     bool mRequiresAAT;
     bool mIsCFF;
     bool mIsCFFInitialized;
+    nsTHashtable<nsUint32HashKey> mAvailableTables;
 };
 
 class gfxMacPlatformFontList : public gfxPlatformFontList {
 public:
     static gfxMacPlatformFontList* PlatformFontList() {
         return static_cast<gfxMacPlatformFontList*>(sPlatformFontList);
     }
 
--- a/gfx/thebes/gfxMacPlatformFontList.mm
+++ b/gfx/thebes/gfxMacPlatformFontList.mm
@@ -370,30 +370,36 @@ MacOSFontEntry::GetFontTable(uint32_t aT
     }
 
     return nullptr;
 }
 
 bool
 MacOSFontEntry::HasFontTable(uint32_t aTableTag)
 {
-    nsAutoreleasePool localPool;
+    if (mAvailableTables.Count() == 0) {
+        nsAutoreleasePool localPool;
 
-    CGFontRef fontRef = GetFontRef();
-    if (!fontRef) {
-        return false;
+        CGFontRef fontRef = GetFontRef();
+        if (!fontRef) {
+            return false;
+        }
+        CFArrayRef tags = ::CGFontCopyTableTags(fontRef);
+        if (!tags) {
+            return false;
+        }
+        int numTags = (int) ::CFArrayGetCount(tags);
+        for (int t = 0; t < numTags; t++) {
+            uint32_t tag = (uint32_t)(uintptr_t)::CFArrayGetValueAtIndex(tags, t);
+            mAvailableTables.PutEntry(tag);
+        }
+        ::CFRelease(tags);
     }
 
-    CFDataRef tableData = ::CGFontCopyTableForTag(fontRef, aTableTag);
-    if (!tableData) {
-        return false;
-    }
-
-    ::CFRelease(tableData);
-    return true;
+    return mAvailableTables.GetEntry(aTableTag);
 }
 
 void
 MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                                        FontListSizes* aSizes) const
 {
     aSizes->mFontListSize += aMallocSizeOf(this);
     AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
--- a/js/src/asmjs/AsmJSFrameIterator.cpp
+++ b/js/src/asmjs/AsmJSFrameIterator.cpp
@@ -245,17 +245,17 @@ GenerateProfilingEpilogue(MacroAssembler
         // and the async interrupt exit. Since activation.fp can be read at any
         // time and still points to the current frame, be careful to only update
         // sp after activation.fp has been repointed to the caller's frame.
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS32)
         masm.loadPtr(Address(masm.getStackPointer(), 0), scratch2);
         masm.storePtr(scratch2, Address(scratch, AsmJSActivation::offsetOfFP()));
         DebugOnly<uint32_t> prePop = masm.currentOffset();
         masm.addToStackPtr(Imm32(sizeof(void *)));
-        MOZ_ASSERT(PostStorePrePopFP == masm.currentOffset() - prePop);
+        MOZ_ASSERT_IF(!masm.oom(), PostStorePrePopFP == masm.currentOffset() - prePop);
 #else
         masm.pop(Address(scratch, AsmJSActivation::offsetOfFP()));
         MOZ_ASSERT(PostStorePrePopFP == 0);
 #endif
 
         masm.bind(profilingReturn);
         masm.ret();
     }
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -12381,17 +12381,18 @@ CheckModule(ExclusiveContext* cx, AsmJSP
 #endif
 
     m.startFunctionBodies();
 
     ScopedJSDeletePtr<ModuleCompileResults> mcd;
     if (!CheckFunctions(m, &mcd))
         return false;
 
-    m.finishFunctionBodies(&mcd);
+    if (!m.finishFunctionBodies(&mcd))
+        return false;
 
     if (!CheckFuncPtrTables(m))
         return false;
 
     if (!CheckModuleReturn(m))
         return false;
 
     TokenKind tk;
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2918,17 +2918,18 @@ BytecodeEmitter::emitNumberOp(double dva
             return emit1(JSOP_ZERO);
         if (ival == 1)
             return emit1(JSOP_ONE);
         if ((int)(int8_t)ival == ival)
             return emit2(JSOP_INT8, uint8_t(int8_t(ival)));
 
         uint32_t u = uint32_t(ival);
         if (u < JS_BIT(16)) {
-            emitUint16Operand(JSOP_UINT16, u);
+            if (!emitUint16Operand(JSOP_UINT16, u))
+                return false;
         } else if (u < JS_BIT(24)) {
             ptrdiff_t off;
             if (!emitN(JSOP_UINT24, 3, &off))
                 return false;
             SET_UINT24(code(off), u);
         } else {
             ptrdiff_t off;
             if (!emitN(JSOP_INT32, 4, &off))
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -477,16 +477,18 @@ class FullParseHandler
         ParseNode* makeGen = new_<NullaryNode>(PNK_GENERATOR, yieldPos);
         if (!makeGen)
             return false;
 
         MOZ_ASSERT(genName->getOp() == JSOP_GETNAME);
         genName->setOp(JSOP_SETNAME);
         genName->markAsAssigned();
         ParseNode* genInit = newBinary(PNK_ASSIGN, genName, makeGen);
+        if (!genInit)
+            return false;
 
         ParseNode* initialYield = newYieldExpression(yieldPos.begin, nullptr, genInit,
                                                      JSOP_INITIALYIELD);
         if (!initialYield)
             return false;
 
         stmtList->prepend(initialYield);
         return true;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/sharedbuf/typedarray-from-sharedtypedarray-with-overridden-length.js
@@ -0,0 +1,23 @@
+if (!this.SharedArrayBuffer)
+    quit(0);
+
+// This would hit an assertion in debug builds due to an incorrect
+// type guard in the code that copies data from STA to TA.
+
+// Original test case
+
+var x = new SharedInt32Array(4);
+x.__proto__ = (function(){});
+new Uint8Array(x);		// Should assert here
+
+// Supposedly equivalent test case, provoking the error directly
+
+var x = new SharedInt32Array(4);
+Object.defineProperty(x, "length", { value: 0 });
+new Uint8Array(x);		// Should assert here
+
+// Derived test case - should not tickle the bug, though.
+
+var x = new SharedInt32Array(4);
+Object.defineProperty(x, "length", { value: 1 << 20 });
+new Uint8Array(x);
--- a/js/src/jit/AtomicOperations-inl.h
+++ b/js/src/jit/AtomicOperations-inl.h
@@ -7,17 +7,17 @@
 #ifndef jit_AtomicOperations_inl_h
 #define jit_AtomicOperations_inl_h
 
 #if defined(JS_CODEGEN_ARM)
 # include "jit/arm/AtomicOperations-arm.h"
 #elif defined(JS_CODEGEN_ARM64)
 # include "jit/arm64/AtomicOperations-arm64.h"
 #elif defined(JS_CODEGEN_MIPS32)
-# include "jit/mips32/AtomicOperations-mips32.h"
+# include "jit/mips-shared/AtomicOperations-mips-shared.h"
 #elif defined(JS_CODEGEN_NONE)
 # include "jit/none/AtomicOperations-none.h"
 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
 # include "jit/x86-shared/AtomicOperations-x86-shared.h"
 #else
 # error "Atomic operations must be defined for this platform"
 #endif
 
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -401,19 +401,23 @@ class JitCompartment
     }
 
     JitCode* getStubCode(uint32_t key) {
         ICStubCodeMap::AddPtr p = stubCodes_->lookupForAdd(key);
         if (p)
             return p->value();
         return nullptr;
     }
-    bool putStubCode(uint32_t key, Handle<JitCode*> stubCode) {
+    bool putStubCode(JSContext* cx, uint32_t key, Handle<JitCode*> stubCode) {
         MOZ_ASSERT(stubCode);
-        return stubCodes_->putNew(key, stubCode.get());
+        if (!stubCodes_->putNew(key, stubCode.get())) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+        return true;
     }
     void initBaselineCallReturnAddr(void* addr, bool constructing) {
         MOZ_ASSERT(baselineCallReturnAddrs_[constructing] == nullptr);
         baselineCallReturnAddrs_[constructing] = addr;
     }
     void* baselineCallReturnAddr(bool constructing) {
         MOZ_ASSERT(baselineCallReturnAddrs_[constructing] != nullptr);
         return baselineCallReturnAddrs_[constructing];
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -723,17 +723,17 @@ ICStubCompiler::getStubCode()
     if (!postGenerateStubCode(masm, newStubCode))
         return nullptr;
 
     // All barriers are emitted off-by-default, enable them if needed.
     if (cx->zone()->needsIncrementalBarrier())
         newStubCode->togglePreBarriers(true);
 
     // Cache newly compiled stubcode.
-    if (!comp->putStubCode(stubKey, newStubCode))
+    if (!comp->putStubCode(cx, stubKey, newStubCode))
         return nullptr;
 
     MOZ_ASSERT(entersStubFrame_ == ICStub::CanMakeCalls(kind));
     MOZ_ASSERT(!inStubFrame_);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(newStubCode, "BaselineIC");
 #endif
--- a/js/src/jit/Snapshots.cpp
+++ b/js/src/jit/Snapshots.cpp
@@ -656,18 +656,20 @@ SnapshotWriter::add(const RValueAllocati
 {
     MOZ_ASSERT(allocMap_.initialized());
 
     uint32_t offset;
     RValueAllocMap::AddPtr p = allocMap_.lookupForAdd(alloc);
     if (!p) {
         offset = allocWriter_.length();
         alloc.write(allocWriter_);
-        if (!allocMap_.add(p, alloc, offset))
+        if (!allocMap_.add(p, alloc, offset)) {
+            allocWriter_.setOOM();
             return false;
+        }
     } else {
         offset = p->value();
     }
 
     if (JitSpewEnabled(JitSpew_IonSnapshots)) {
         JitSpewHeader(JitSpew_IonSnapshots);
         Fprinter& out = JitSpewPrinter();
         out.printf("    slot %u (%d): ", allocWritten_, offset);
--- a/js/src/jit/arm/Architecture-arm.h
+++ b/js/src/jit/arm/Architecture-arm.h
@@ -234,47 +234,47 @@ class FloatRegisters
         d27,
         d28,
         d29,
         d30,
         d31,
         invalid_freg
     };
 
-    typedef FPRegisterID Code;
+    typedef uint32_t Code;
     typedef FPRegisterID Encoding;
 
     // Content spilled during bailouts.
     union RegisterContent {
         double d;
     };
 
-    static const char* GetDoubleName(Code code) {
+    static const char* GetDoubleName(Encoding code) {
         static const char * const Names[] = { "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
                                               "d8", "d9", "d10", "d11", "d12", "d13", "d14", "d15",
                                               "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23",
                                               "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31"};
         return Names[code];
     }
-    static const char* GetSingleName(Code code) {
+    static const char* GetSingleName(Encoding code) {
         static const char * const Names[] = { "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
                                               "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15",
                                               "s16", "s17", "s18", "s19", "s20", "s21", "s22", "s23",
                                               "s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31"};
         return Names[code];
     }
 
     static const char* GetName(uint32_t i) {
         MOZ_ASSERT(i < Total);
-        return GetName(Code(i));
+        return GetName(Encoding(i));
     }
 
     static Code FromName(const char* name);
 
-    static const Code Invalid = invalid_freg;
+    static const Encoding Invalid = invalid_freg;
     static const uint32_t Total = 48;
     static const uint32_t TotalDouble = 16;
     static const uint32_t TotalSingle = 32;
     static const uint32_t Allocatable = 45;
     // There are only 32 places that we can put values.
     static const uint32_t TotalPhys = 32;
     static uint32_t ActualTotalPhys();
 
@@ -427,35 +427,35 @@ class VFPRegister
         MOZ_ASSERT(!_isInvalid && !_isMissing);
         // This should only be used in areas where we only have doubles and
         // singles.
         MOZ_ASSERT(isFloat());
         return Code(code_ | (kind << 5));
     }
     Encoding encoding() const {
         MOZ_ASSERT(!_isInvalid && !_isMissing);
-        return Code(code_ | (kind << 5));
+        return Encoding(code_);
     }
     uint32_t id() const {
         return code_;
     }
     static VFPRegister FromCode(uint32_t i) {
         uint32_t code = i & 31;
         uint32_t kind = i >> 5;
         return VFPRegister(code, RegType(kind));
     }
     bool volatile_() const {
         if (isDouble())
             return !!((1 << (code_ >> 1)) & FloatRegisters::VolatileMask);
         return !!((1 << code_) & FloatRegisters::VolatileMask);
     }
     const char* name() const {
         if (isDouble())
-            return FloatRegisters::GetDoubleName(Code(code_));
-        return FloatRegisters::GetSingleName(Code(code_));
+            return FloatRegisters::GetDoubleName(Encoding(code_));
+        return FloatRegisters::GetSingleName(Encoding(code_));
     }
     bool operator != (const VFPRegister& other) const {
         return other.kind != kind || code_ != other.code_;
     }
     bool aliases(const VFPRegister& other) {
         if (kind == other.kind)
             return code_ == other.code_;
         return doubleOverlay() == other.doubleOverlay();
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -471,17 +471,17 @@ private:
         while (iter.more()) {
             startFloatTransferM(ls, rm, mode, WriteBack);
             int32_t reg = (*iter).code();
             do {
                 offset += delta;
                 if ((*iter).isDouble())
                     offset += delta;
                 transferFloatReg(*iter);
-            } while ((++iter).more() && (*iter).code() == (reg += sign));
+            } while ((++iter).more() && int32_t((*iter).code()) == (reg += sign));
             finishFloatTransfer();
         }
         return offset;
     }
 };
 
 class MacroAssembler;
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit/mips-shared/AtomicOperations-mips-shared.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* For documentation, see jit/AtomicOperations.h */
+
+#ifndef jit_mips_shared_AtomicOperations_mips_shared_h
+#define jit_mips_shared_AtomicOperations_mips_shared_h
+
+#include "jit/AtomicOperations.h"
+
+#if defined(__clang__) || defined(__GNUC__)
+
+// The default implementation tactic for gcc/clang is to use the newer
+// __atomic intrinsics added for use in C++11 <atomic>.  Where that
+// isn't available, we use GCC's older __sync functions instead.
+//
+// ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS is kept as a backward
+// compatible option for older compilers: enable this to use GCC's old
+// __sync functions instead of the newer __atomic functions.  This
+// will be required for GCC 4.6.x and earlier, and probably for Clang
+// 3.1, should we need to use those versions.
+
+//#define ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+
+inline bool
+js::jit::AtomicOperations::isLockfree8()
+{
+# ifndef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    MOZ_ASSERT(__atomic_always_lock_free(sizeof(int8_t), 0));
+    MOZ_ASSERT(__atomic_always_lock_free(sizeof(int16_t), 0));
+    MOZ_ASSERT(__atomic_always_lock_free(sizeof(int32_t), 0));
+#  if _MIPS_SIM == _ABI64
+    MOZ_ASSERT(__atomic_always_lock_free(sizeof(int64_t), 0));
+#  endif
+    return true;
+# else
+    return false;
+# endif
+}
+
+inline void
+js::jit::AtomicOperations::fenceSeqCst()
+{
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    __sync_synchronize();
+# else
+    __atomic_thread_fence(__ATOMIC_SEQ_CST);
+# endif
+}
+
+template<typename T>
+inline T
+js::jit::AtomicOperations::loadSeqCst(T* addr)
+{
+    MOZ_ASSERT(sizeof(T) < 8 || isLockfree8());
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    __sync_synchronize();
+    T v = *addr;
+    __sync_synchronize();
+# else
+    T v;
+    __atomic_load(addr, &v, __ATOMIC_SEQ_CST);
+# endif
+    return v;
+}
+
+template<typename T>
+inline void
+js::jit::AtomicOperations::storeSeqCst(T* addr, T val)
+{
+    MOZ_ASSERT(sizeof(T) < 8 || isLockfree8());
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    __sync_synchronize();
+    *addr = val;
+    __sync_synchronize();
+# else
+    __atomic_store(addr, &val, __ATOMIC_SEQ_CST);
+# endif
+}
+
+template<typename T>
+inline T
+js::jit::AtomicOperations::compareExchangeSeqCst(T* addr, T oldval, T newval)
+{
+    MOZ_ASSERT(sizeof(T) < 8 || isLockfree8());
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    return __sync_val_compare_and_swap(addr, oldval, newval);
+# else
+    __atomic_compare_exchange(addr, &oldval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
+    return oldval;
+# endif
+}
+
+template<typename T>
+inline T
+js::jit::AtomicOperations::fetchAddSeqCst(T* addr, T val)
+{
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    return __sync_fetch_and_add(addr, val);
+# else
+    return __atomic_fetch_add(addr, val, __ATOMIC_SEQ_CST);
+# endif
+}
+
+template<typename T>
+inline T
+js::jit::AtomicOperations::fetchSubSeqCst(T* addr, T val)
+{
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    return __sync_fetch_and_sub(addr, val);
+# else
+    return __atomic_fetch_sub(addr, val, __ATOMIC_SEQ_CST);
+# endif
+}
+
+template<typename T>
+inline T
+js::jit::AtomicOperations::fetchAndSeqCst(T* addr, T val)
+{
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    return __sync_fetch_and_and(addr, val);
+# else
+    return __atomic_fetch_and(addr, val, __ATOMIC_SEQ_CST);
+# endif
+}
+
+template<typename T>
+inline T
+js::jit::AtomicOperations::fetchOrSeqCst(T* addr, T val)
+{
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    return __sync_fetch_and_or(addr, val);
+# else
+    return __atomic_fetch_or(addr, val, __ATOMIC_SEQ_CST);
+# endif
+}
+
+template<typename T>
+inline T
+js::jit::AtomicOperations::fetchXorSeqCst(T* addr, T val)
+{
+    static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    return __sync_fetch_and_xor(addr, val);
+# else
+    return __atomic_fetch_xor(addr, val, __ATOMIC_SEQ_CST);
+# endif
+}
+
+template<typename T>
+inline T
+js::jit::AtomicOperations::exchangeSeqCst(T* addr, T val)
+{
+    MOZ_ASSERT(sizeof(T) < 8 || isLockfree8());
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    T v;
+    __sync_synchronize();
+    do {
+	v = *addr;
+    } while (__sync_val_compare_and_swap(addr, v, val) != v);
+    return v;
+# else
+    T v;
+    __atomic_exchange(addr, &val, &v, __ATOMIC_SEQ_CST);
+    return v;
+# endif
+}
+
+template<size_t nbytes>
+inline void
+js::jit::RegionLock::acquire(void* addr)
+{
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    while (!__sync_bool_compare_and_swap(&spinlock, 0, 1))
+        ;
+# else
+    uint32_t zero = 0;
+    uint32_t one = 1;
+    while (!__atomic_compare_exchange(&spinlock, &zero, &one, false, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE))
+        continue;
+# endif
+}
+
+template<size_t nbytes>
+inline void
+js::jit::RegionLock::release(void* addr)
+{
+    MOZ_ASSERT(AtomicOperations::loadSeqCst(&spinlock) == 1, "releasing unlocked region lock");
+# ifdef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+    __sync_sub_and_fetch(&spinlock, 1);
+# else
+    uint32_t zero = 0;
+    __atomic_store(&spinlock, &zero, __ATOMIC_SEQ_CST);
+# endif
+}
+
+# undef ATOMICS_IMPLEMENTED_WITH_SYNC_INTRINSICS
+
+#elif defined(ENABLE_SHARED_ARRAY_BUFFER)
+
+# error "Either disable JS shared memory, use GCC or Clang, or add code here"
+
+#endif
+
+#endif // jit_mips_shared_AtomicOperations_mips_shared_h
deleted file mode 100644
--- a/js/src/jit/mips32/AtomicOperations-mips32.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=8 sts=4 et sw=4 tw=99:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* For documentation, see jit/AtomicOperations.h */
-
-#ifndef jit_mips32_AtomicOperations_mips32_h
-#define jit_mips32_AtomicOperations_mips32_h
-
-#include "jit/AtomicOperations.h"
-
-inline bool
-js::jit::AtomicOperations::isLockfree8()
-{
-    // Don't crash this one, since it may be read during
-    // initialization, to cache the value.
-    return false;
-}
-
-inline void
-js::jit::AtomicOperations::fenceSeqCst()
-{
-    MOZ_CRASH();
-}
-
-template<typename T>
-inline T
-js::jit::AtomicOperations::loadSeqCst(T* addr)
-{
-    MOZ_CRASH();
-}
-
-template<typename T>
-inline void
-js::jit::AtomicOperations::storeSeqCst(T* addr, T val)
-{
-    MOZ_CRASH();
-}
-
-template<typename T>
-inline T
-js::jit::AtomicOperations::compareExchangeSeqCst(T* addr, T oldval, T newval)
-{
-    MOZ_CRASH();
-}
-
-template<typename T>
-inline T
-js::jit::AtomicOperations::fetchAddSeqCst(T* addr, T val)
-{
-    MOZ_CRASH();
-}
-
-template<typename T>
-inline T
-js::jit::AtomicOperations::fetchSubSeqCst(T* addr, T val)
-{
-    MOZ_CRASH();
-}
-
-template<typename T>
-inline T
-js::jit::AtomicOperations::fetchAndSeqCst(T* addr, T val)
-{
-    MOZ_CRASH();
-}
-
-template<typename T>
-inline T
-js::jit::AtomicOperations::fetchOrSeqCst(T* addr, T val)
-{
-    MOZ_CRASH();
-}
-
-template<typename T>
-inline T
-js::jit::AtomicOperations::fetchXorSeqCst(T* addr, T val)
-{
-    MOZ_CRASH();
-}
-
-template<typename T>
-inline T
-js::jit::AtomicOperations::exchangeSeqCst(T* addr, T val)
-{
-    MOZ_CRASH();
-}
-
-template<size_t nbytes>
-inline void
-js::jit::RegionLock::acquire(void* addr)
-{
-    MOZ_CRASH();
-}
-
-template<size_t nbytes>
-inline void
-js::jit::RegionLock::release(void* addr)
-{
-    MOZ_CRASH();
-}
-
-#endif // jit_mips32_AtomicOperations_mips32_h
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -3453,19 +3453,19 @@ MacroAssembler::callAndPushReturnAddress
     as_addiu(StackPointer, StackPointer, -sizeof(intptr_t));
     as_jalr(callee);
     as_sw(ra, StackPointer, 0);
 }
 
 void
 MacroAssembler::callAndPushReturnAddress(Label* label)
 {
-    // Push return address during jalr delay slot.
+    // Push return address during bal delay slot.
     as_addiu(StackPointer, StackPointer, -sizeof(intptr_t));
-    as_jalr(label);
+    ma_bal(label, DontFillDelaySlot);
     as_sw(ra, StackPointer, 0);
 }
 
 // ===============================================================
 // ABI function calls.
 
 void
 MacroAssembler::setupUnalignedABICall(Register scratch)
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -583,17 +583,17 @@ CodeGeneratorShared::encode(LSnapshot* s
     }
     snapshots_.trackSnapshot(pcOpcode, mirOpcode, mirId, lirOpcode, lirId);
 #endif
 
     uint32_t allocIndex = 0;
     for (LRecoverInfo::OperandIter it(recoverInfo); !it; ++it) {
         DebugOnly<uint32_t> allocWritten = snapshots_.allocWritten();
         encodeAllocation(snapshot, *it, &allocIndex);
-        MOZ_ASSERT(allocWritten + 1 == snapshots_.allocWritten());
+        MOZ_ASSERT_IF(!snapshots_.oom(), allocWritten + 1 == snapshots_.allocWritten());
     }
 
     MOZ_ASSERT(allocIndex == snapshot->numSlots());
     snapshots_.endSnapshot();
     snapshot->setSnapshotOffset(offset);
     masm.propagateOOM(!snapshots_.oom());
 }
 
--- a/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h
+++ b/js/src/jit/shared/IonAssemblerBufferWithConstantPools.h
@@ -513,17 +513,19 @@ struct AssemblerBufferWithConstantPools 
 
             inhibitNops_ = false;
         }
     }
 
     void markNextAsBranch() {
         // If the previous thing inserted was the last instruction of the node,
         // then whoops, we want to mark the first instruction of the next node.
-        this->ensureSpace(InstSize);
+        if (!this->ensureSpace(InstSize))
+            return;
+
         MOZ_ASSERT(this->getTail() != nullptr);
         this->getTail()->markNextAsBranch();
     }
 
     bool isNextBranch() const {
         MOZ_ASSERT(this->getTail() != nullptr);
         return this->getTail()->isNextBranch();
     }
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -265,27 +265,31 @@ JSCompartment::checkWrapperMapAfterMovin
 #endif
 
 bool
 JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, const js::Value& wrapper)
 {
     MOZ_ASSERT(wrapped.wrapped);
     MOZ_ASSERT_IF(wrapped.kind == CrossCompartmentKey::StringWrapper, wrapper.isString());
     MOZ_ASSERT_IF(wrapped.kind != CrossCompartmentKey::StringWrapper, wrapper.isObject());
-    bool success = crossCompartmentWrappers.put(wrapped, ReadBarriered<Value>(wrapper));
 
     /* There's no point allocating wrappers in the nursery since we will tenure them anyway. */
     MOZ_ASSERT(!IsInsideNursery(static_cast<gc::Cell*>(wrapper.toGCThing())));
 
-    if (success && (IsInsideNursery(wrapped.wrapped) || IsInsideNursery(wrapped.debugger))) {
+    if (!crossCompartmentWrappers.put(wrapped, ReadBarriered<Value>(wrapper))) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    if (IsInsideNursery(wrapped.wrapped) || IsInsideNursery(wrapped.debugger)) {
         WrapperMapRef ref(&crossCompartmentWrappers, wrapped);
         cx->runtime()->gc.storeBuffer.putGeneric(ref);
     }
 
-    return success;
+    return true;
 }
 
 static JSString*
 CopyStringPure(JSContext* cx, JSString* str)
 {
     /*
      * Directly allocate the copy in the destination compartment, rather than
      * first flattening it (and possibly allocating in source compartment),
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -515,18 +515,20 @@ NewPropertyIteratorObject(JSContext* cx,
 
 NativeIterator*
 NativeIterator::allocateIterator(JSContext* cx, uint32_t numGuards, const AutoIdVector& props)
 {
     JS_STATIC_ASSERT(sizeof(ReceiverGuard) == 2 * sizeof(void*));
 
     size_t plength = props.length();
     NativeIterator* ni = cx->zone()->pod_malloc_with_extra<NativeIterator, void*>(plength + numGuards * 2);
-    if (!ni)
+    if (!ni) {
+        ReportOutOfMemory(cx);
         return nullptr;
+    }
 
     AutoValueVector strings(cx);
     ni->props_array = ni->props_cursor = reinterpret_cast<HeapPtrFlatString*>(ni + 1);
     ni->props_end = ni->props_array + plength;
     if (plength) {
         for (size_t i = 0; i < plength; i++) {
             JSFlatString* str = IdToString(cx, props[i]);
             if (!str || !strings.append(StringValue(str)))
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -263,19 +263,19 @@ class BytecodeParser
         // in the array is the offset of the opcode that defined the
         // corresponding stack slot.  The top of the stack is at position
         // |stackDepth - 1|.
         uint32_t* offsetStack;
 
         bool captureOffsetStack(LifoAlloc& alloc, const uint32_t* stack, uint32_t depth) {
             stackDepth = depth;
             offsetStack = alloc.newArray<uint32_t>(stackDepth);
+            if (!offsetStack)
+                return false;
             if (stackDepth) {
-                if (!offsetStack)
-                    return false;
                 for (uint32_t n = 0; n < stackDepth; n++)
                     offsetStack[n] = stack[n];
             }
             return true;
         }
 
         // When control-flow merges, intersect the stacks, marking slots that
         // are defined by different offsets with the UINT32_MAX sentinel.
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -3242,18 +3242,20 @@ js::detail::CopyScript(JSContext* cx, Ha
     uint32_t ntrynotes = src->hasTrynotes() ? src->trynotes()->length : 0;
     uint32_t nblockscopes = src->hasBlockScopes() ? src->blockScopes()->length : 0;
     uint32_t nyieldoffsets = src->hasYieldOffsets() ? src->yieldOffsets().length() : 0;
 
     /* Script data */
 
     size_t size = src->dataSize();
     uint8_t* data = AllocScriptData(cx->zone(), size);
-    if (size && !data)
+    if (size && !data) {
+        ReportOutOfMemory(cx);
         return false;
+    }
 
     /* Bindings */
 
     Rooted<Bindings> bindings(cx);
     if (!Bindings::clone(cx, &bindings, data, src))
         return false;
 
     /* Objects */
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -549,20 +549,22 @@ ObjectGroup::defaultNewGroup(ExclusiveCo
     }
 
     ObjectGroupCompartment::newTablePostBarrier(cx, table, clasp, proto, associated);
 
     if (proto.isObject()) {
         RootedObject obj(cx, proto.toObject());
 
         if (associated) {
-            if (associated->is<JSFunction>())
-                TypeNewScript::make(cx->asJSContext(), group, &associated->as<JSFunction>());
-            else
+            if (associated->is<JSFunction>()) {
+                if (!TypeNewScript::make(cx->asJSContext(), group, &associated->as<JSFunction>()))
+                    return nullptr;
+            } else {
                 group->setTypeDescr(&associated->as<TypeDescr>());
+            }
         }
 
         /*
          * Some builtin objects have slotful native properties baked in at
          * creation via the Shape::{insert,get}initialShape mechanism. Since
          * these properties are never explicitly defined on new objects, update
          * the type information for them here.
          */
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -2164,28 +2164,28 @@ class MOZ_STACK_CLASS AutoInitGCManagedO
             rt->handlingInitFailure = true;
             ptr_.reset(nullptr);
             rt->handlingInitFailure = false;
         }
 #endif
     }
 
     T& operator*() const {
-        return *ptr_.get();
+        return *get();
     }
 
     T* operator->() const {
-        return ptr_.get();
+        return get();
     }
 
     explicit operator bool() const {
-        return ptr_.get() != nullptr;
+        return get() != nullptr;
     }
 
-    T* get() {
+    T* get() const {
         return ptr_.get();
     }
 
     T* release() {
         return ptr_.release();
     }
 
     AutoInitGCManagedObject(const AutoInitGCManagedObject<T>& other) = delete;
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -1105,23 +1105,32 @@ ScopeIter::incrementStaticScopeIter()
         ssi_++;
 }
 
 void
 ScopeIter::settle()
 {
     // Check for trying to iterate a function frame before the prologue has
     // created the CallObject, in which case we have to skip.
-    if (frame_ && frame_.isNonEvalFunctionFrame() &&
-        frame_.fun()->needsCallObject() && !frame_.hasCallObj())
+    if (frame_ && frame_.isNonEvalFunctionFrame() && frame_.fun()->needsCallObject() &&
+        !frame_.hasCallObj())
     {
         MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Function);
         incrementStaticScopeIter();
     }
 
+    // Check for trying to iterate a strict eval frame before the prologue has
+    // created the CallObject.
+    if (frame_ && frame_.isStrictEvalFrame() && !frame_.hasCallObj() && !ssi_.done()) {
+        MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Block);
+        incrementStaticScopeIter();
+        MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Eval);
+        incrementStaticScopeIter();
+    }
+
     // Check if we have left the extent of the initial frame after we've
     // settled on a static scope.
     if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope()))
         frame_ = NullFramePtr();
 
 #ifdef DEBUG
     if (!ssi_.done() && hasAnyScopeObject()) {
         switch (ssi_.type()) {
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3483,39 +3483,40 @@ PreliminaryObjectArrayWithTemplate::mayb
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeNewScript
 /////////////////////////////////////////////////////////////////////
 
 // Make a TypeNewScript for |group|, and set it up to hold the preliminary
 // objects created with the group.
-/* static */ void
+/* static */ bool
 TypeNewScript::make(JSContext* cx, ObjectGroup* group, JSFunction* fun)
 {
     MOZ_ASSERT(cx->zone()->types.activeAnalysis);
     MOZ_ASSERT(!group->newScript());
     MOZ_ASSERT(!group->maybeUnboxedLayout());
 
     if (group->unknownProperties())
-        return;
+        return true;
 
     ScopedJSDeletePtr<TypeNewScript> newScript(cx->new_<TypeNewScript>());
     if (!newScript)
-        return;
+        return false;
 
     newScript->function_ = fun;
 
     newScript->preliminaryObjects = group->zone()->new_<PreliminaryObjectArray>();
     if (!newScript->preliminaryObjects)
-        return;
+        return true;
 
     group->setNewScript(newScript.forget());
 
     gc::TraceTypeNewScript(group);
+    return true;
 }
 
 // Make a TypeNewScript with the same initializer list as |newScript| but with
 // a new template object.
 /* static */ TypeNewScript*
 TypeNewScript::makeNativeVersion(JSContext* cx, TypeNewScript* newScript,
                                  PlainObject* templateObject)
 {
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -959,17 +959,17 @@ class TypeNewScript
     void trace(JSTracer* trc);
     void sweep();
 
     void registerNewObject(PlainObject* res);
     bool maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, bool force = false);
 
     bool rollbackPartiallyInitializedObjects(JSContext* cx, ObjectGroup* group);
 
-    static void make(JSContext* cx, ObjectGroup* group, JSFunction* fun);
+    static bool make(JSContext* cx, ObjectGroup* group, JSFunction* fun);
     static TypeNewScript* makeNativeVersion(JSContext* cx, TypeNewScript* newScript,
                                             PlainObject* templateObject);
 
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 };
 
 /* Is this a reasonable PC to be doing inlining on? */
 inline bool isInlinableCall(jsbytecode* pc);
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -2038,22 +2038,25 @@ js::TryConvertToUnboxedLayout(ExclusiveC
     // Accumulate a list of all the values in each preliminary object, and
     // update their shapes.
     AutoValueVector values(cx);
     for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
         JSObject* obj = objects->get(i);
         if (!obj)
             continue;
 
-        if (isArray) {
-            if (!GetValuesFromPreliminaryArrayObject(&obj->as<ArrayObject>(), values))
-                return false;
-        } else {
-            if (!GetValuesFromPreliminaryPlainObject(&obj->as<PlainObject>(), values))
-                return false;
+        bool ok;
+        if (isArray)
+            ok = GetValuesFromPreliminaryArrayObject(&obj->as<ArrayObject>(), values);
+        else
+            ok = GetValuesFromPreliminaryPlainObject(&obj->as<PlainObject>(), values);
+
+        if (!ok) {
+            cx->recoverFromOutOfMemory();
+            return false;
         }
     }
 
     if (TypeNewScript* newScript = group->newScript())
         layout->setNewScript(newScript);
 
     for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
         if (JSObject* obj = objects->get(i))
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-matching/font-shorthand-stretch-1.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+/* load 9 faces of DejaVu Sans in the "dvs" family, with appropriate style descriptors */
+@font-face {
+  font-family: dvs;
+  src: url(../fonts/dejavu-sans/DejaVuSans.ttf);
+}
+@font-face {
+  font-family: dvs;
+  font-weight: bold;
+  src: url(../fonts/dejavu-sans/DejaVuSans-Bold.ttf);
+}
+@font-face {
+  font-family: dvs;
+  font-style: italic;
+  src: url(../fonts/dejavu-sans/DejaVuSans-Oblique.ttf);
+}
+@font-face {
+  font-family: dvs;
+  font-style: italic;
+  font-weight: bold;
+  src: url(../fonts/dejavu-sans/DejaVuSans-BoldOblique.ttf);
+}
+@font-face { /* note that there is no ExtraLight Condensed or Oblique */
+  font-family: dvs;
+  font-weight: 200;
+  src: url(../fonts/dejavu-sans/DejaVuSans-ExtraLight.ttf);
+}
+@font-face {
+  font-family: dvs;
+  font-stretch: condensed;
+  src: url(../fonts/dejavu-sans/DejaVuSansCondensed.ttf);
+}
+@font-face {
+  font-family: dvs;
+  font-weight: bold;
+  font-stretch: condensed;
+  src: url(../fonts/dejavu-sans/DejaVuSansCondensed-Bold.ttf);
+}
+@font-face {
+  font-family: dvs;
+  font-style: italic;
+  font-stretch: condensed;
+  src: url(../fonts/dejavu-sans/DejaVuSansCondensed-Oblique.ttf);
+}
+@font-face {
+  font-family: dvs;
+  font-style: italic;
+  font-weight: bold;
+  font-stretch: condensed;
+  src: url(../fonts/dejavu-sans/DejaVuSansCondensed-BoldOblique.ttf);
+}
+
+body {
+  font-family: dvs, serif;
+  font-size: 24px;
+}
+.l {
+  font-weight: 200;
+}
+</style>
+</head>
+<body>
+<!-- all 4 levels of "condensed" come out the same; "condensed" takes priority over "light" -->
+<div style="font: ultra-condensed 24px dvs, serif">ultra-condensed <i>italic</i> <b>bold <i>italic</i></b> <span class="l">light <i>italic</i></span></div>
+<div style="font: extra-condensed 24px dvs, serif">extra-condensed <i>italic</i> <b>bold <i>italic</i></b> <span class="l">light <i>italic</i></span></div>
+<div style="font: condensed 24px dvs, serif">condensed <i>italic</i> <b>bold <i>italic</i></b> <span class="l">light <i>italic</i></span></div>
+<div style="font: semi-condensed 24px dvs, serif">semi-condensed <i>italic</i> <b>bold <i>italic</i></b> <span class="l">light <i>italic</i></span></div>
+<!-- "normal" and all 4 levels of "expanded" come out the same; "light" is available, but only in upright, not italic -->
+<div style="font: 24px dvs">normal <i>italic</i> <b>bold <i>italic</i></b> <span class="l">light <i>italic</i></span></div>
+<div style="font: semi-expanded 24px dvs, serif">semi-expanded <i>italic</i> <b>bold <i>italic</i></b> <span class="l">light <i>italic</i></span></div>
+<div style="font: expanded 24px dvs, serif">expanded <i>italic</i> <b>bold <i>italic</i></b> <span class="l">light <i>italic</i></span></div>
+<div style="font: extra-expanded 24px dvs, serif">extra-expanded <i>italic</i> <b>bold <i>italic</i></b> <span class="l">light <i>italic</i></span></div>
+<div style="font: ultra-expanded 24px dvs, serif">ultra-expanded <i>italic</i> <b>bold <i>italic</i></b> <span class="l">light <i>italic</i></span></div>
+</body>
+</html>
+
--- a/layout/reftests/font-matching/reftest.list
+++ b/layout/reftests/font-matching/reftest.list
@@ -67,16 +67,17 @@ HTTP(..) == weightmapping-12579.html wei
 
 skip-if(B2G||Mulet) HTTP(..) == stretchmapping-all.html stretchmapping-all-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) HTTP(..) == stretchmapping-reverse.html stretchmapping-reverse-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(Android,4,8) HTTP(..) == stretchmapping-35.html stretchmapping-35-ref.html
 HTTP(..) == stretchmapping-137.html stretchmapping-137-ref.html
 
 # test for font-stretch using @font-face
 skip-if(B2G||Mulet) skip-if(Android&&AndroidVersion>15) HTTP(..) == font-stretch-1.html font-stretch-1-ref.html # bugs 773482, 927602 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) skip-if(Android&&AndroidVersion>15) HTTP(..) == font-shorthand-stretch-1.html font-stretch-1-ref.html # bugs 773482, 927602 # Initial mulet triage: parity with B2G/B2G Desktop
 
 # bug 724231 - applying synthetic styles to a single @font-face font
 # should apply artificial obliquing, not switch to a true styled face
 != synthetic-style-1.html synthetic-style-1-notref.html
 != synthetic-style-2.html synthetic-style-2-notref.html
 
 # Bug 765906 - synthetic bold should be used if necessary together with system fallback.
 # **NOTE** we skip these on Linux because of bug 769659.
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -578,19 +578,17 @@ Declaration::GetValue(nsCSSProperty aPro
           // This can't be represented as a shorthand.
           return;
         }
         systemFont->AppendToString(eCSSProperty__x_system_font, aValue,
                                    aSerialization);
       } else {
         // properties reset by this shorthand property to their
         // initial values but not represented in its syntax
-        if (stretch->GetUnit() != eCSSUnit_Enumerated ||
-            stretch->GetIntValue() != NS_STYLE_FONT_STRETCH_NORMAL ||
-            sizeAdjust->GetUnit() != eCSSUnit_None ||
+        if (sizeAdjust->GetUnit() != eCSSUnit_None ||
             featureSettings->GetUnit() != eCSSUnit_Normal ||
             languageOverride->GetUnit() != eCSSUnit_Normal ||
             fontKerning->GetIntValue() != NS_FONT_KERNING_AUTO ||
             fontSynthesis->GetUnit() != eCSSUnit_Enumerated ||
             fontSynthesis->GetIntValue() !=
               (NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE) ||
             fontVariantAlternates->GetUnit() != eCSSUnit_Normal ||
             fontVariantEastAsian->GetUnit() != eCSSUnit_Normal ||
@@ -620,16 +618,22 @@ Declaration::GetValue(nsCSSProperty aPro
           aValue.Append(char16_t(' '));
         }
         if (weight->GetUnit() != eCSSUnit_Enumerated ||
             weight->GetIntValue() != NS_FONT_WEIGHT_NORMAL) {
           weight->AppendToString(eCSSProperty_font_weight, aValue,
                                  aSerialization);
           aValue.Append(char16_t(' '));
         }
+        if (stretch->GetUnit() != eCSSUnit_Enumerated ||
+            stretch->GetIntValue() != NS_FONT_STRETCH_NORMAL) {
+          stretch->AppendToString(eCSSProperty_font_stretch, aValue,
+                                  aSerialization);
+          aValue.Append(char16_t(' '));
+        }
         size->AppendToString(eCSSProperty_font_size, aValue, aSerialization);
         if (lh->GetUnit() != eCSSUnit_Normal) {
           aValue.Append(char16_t('/'));
           lh->AppendToString(eCSSProperty_line_height, aValue, aSerialization);
         }
         aValue.Append(char16_t(' '));
         family->AppendToString(eCSSProperty_font_family, aValue,
                                aSerialization);
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -12165,22 +12165,16 @@ CSSParserImpl::ParseCursor()
   AppendValue(eCSSProperty_cursor, value);
   return true;
 }
 
 
 bool
 CSSParserImpl::ParseFont()
 {
-  static const nsCSSProperty fontIDs[] = {
-    eCSSProperty_font_style,
-    eCSSProperty_font_variant_caps,
-    eCSSProperty_font_weight
-  };
-
   nsCSSValue  family;
   if (ParseVariant(family, VARIANT_HK, nsCSSProps::kFontKTable)) {
     if (eCSSUnit_Inherit == family.GetUnit() ||
         eCSSUnit_Initial == family.GetUnit() ||
         eCSSUnit_Unset == family.GetUnit()) {
       AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
       AppendValue(eCSSProperty_font_family, family);
       AppendValue(eCSSProperty_font_style, family);
@@ -12219,45 +12213,70 @@ CSSParserImpl::ParseFont()
       AppendValue(eCSSProperty_font_variant_east_asian, systemFont);
       AppendValue(eCSSProperty_font_variant_ligatures, systemFont);
       AppendValue(eCSSProperty_font_variant_numeric, systemFont);
       AppendValue(eCSSProperty_font_variant_position, systemFont);
     }
     return true;
   }
 
-  // Get optional font-style, font-variant and font-weight (in any order)
-  const int32_t numProps = 3;
+  // Get optional font-style, font-variant, font-weight, font-stretch
+  // (in any order)
+
+  // Indexes into fontIDs[] and values[] arrays.
+  const int kFontStyleIndex = 0;
+  const int kFontVariantIndex = 1;
+  const int kFontWeightIndex = 2;
+  const int kFontStretchIndex = 3;
+
+  // The order of the initializers here must match the order of the indexes
+  // defined above!
+  static const nsCSSProperty fontIDs[] = {
+    eCSSProperty_font_style,
+    eCSSProperty_font_variant_caps,
+    eCSSProperty_font_weight,
+    eCSSProperty_font_stretch
+  };
+
+  const int32_t numProps = MOZ_ARRAY_LENGTH(fontIDs);
   nsCSSValue  values[numProps];
   int32_t found = ParseChoice(values, fontIDs, numProps);
   if (found < 0 ||
-      eCSSUnit_Inherit == values[0].GetUnit() ||
-      eCSSUnit_Initial == values[0].GetUnit() ||
-      eCSSUnit_Unset == values[0].GetUnit()) { // illegal data
-    return false;
-  }
-  if ((found & 1) == 0) {
+      eCSSUnit_Inherit == values[kFontStyleIndex].GetUnit() ||
+      eCSSUnit_Initial == values[kFontStyleIndex].GetUnit() ||
+      eCSSUnit_Unset == values[kFontStyleIndex].GetUnit()) { // illegal data
+    return false;
+  }
+  if ((found & (1 << kFontStyleIndex)) == 0) {
     // Provide default font-style
-    values[0].SetIntValue(NS_FONT_STYLE_NORMAL, eCSSUnit_Enumerated);
-  }
-  if ((found & 2) == 0) {
+    values[kFontStyleIndex].SetIntValue(NS_FONT_STYLE_NORMAL,
+                                        eCSSUnit_Enumerated);
+  }
+  if ((found & (1 << kFontVariantIndex)) == 0) {
     // Provide default font-variant
-    values[1].SetNormalValue();
+    values[kFontVariantIndex].SetNormalValue();
   } else {
-    if (values[1].GetUnit() == eCSSUnit_Enumerated &&
-        values[1].GetIntValue() != NS_FONT_VARIANT_CAPS_SMALLCAPS) {
+    if (values[kFontVariantIndex].GetUnit() == eCSSUnit_Enumerated &&
+        values[kFontVariantIndex].GetIntValue() !=
+        NS_FONT_VARIANT_CAPS_SMALLCAPS) {
       // only normal or small-caps is allowed in font shorthand
       // this also assumes other values for font-variant-caps never overlap
       // possible values for style or weight
       return false;
     }
   }
-  if ((found & 4) == 0) {
+  if ((found & (1 << kFontWeightIndex)) == 0) {
     // Provide default font-weight
-    values[2].SetIntValue(NS_FONT_WEIGHT_NORMAL, eCSSUnit_Enumerated);
+    values[kFontWeightIndex].SetIntValue(NS_FONT_WEIGHT_NORMAL,
+                                         eCSSUnit_Enumerated);
+  }
+  if ((found & (1 << kFontStretchIndex)) == 0) {
+    // Provide default font-stretch
+    values[kFontStretchIndex].SetIntValue(NS_FONT_STRETCH_NORMAL,
+                                          eCSSUnit_Enumerated);
   }
 
   // Get mandatory font-size
   nsCSSValue  size;
   if (! ParseNonNegativeVariant(size, VARIANT_KEYWORD | VARIANT_LP,
                                 nsCSSProps::kFontSizeKTable)) {
     return false;
   }
@@ -12278,23 +12297,22 @@ CSSParserImpl::ParseFont()
   // Get final mandatory font-family
   nsAutoParseCompoundProperty compound(this);
   if (ParseFamily(family)) {
     if (eCSSUnit_Inherit != family.GetUnit() &&
         eCSSUnit_Initial != family.GetUnit() &&
         eCSSUnit_Unset != family.GetUnit()) {
       AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
       AppendValue(eCSSProperty_font_family, family);
-      AppendValue(eCSSProperty_font_style, values[0]);
-      AppendValue(eCSSProperty_font_variant_caps, values[1]);
-      AppendValue(eCSSProperty_font_weight, values[2]);
+      AppendValue(eCSSProperty_font_style, values[kFontStyleIndex]);
+      AppendValue(eCSSProperty_font_variant_caps, values[kFontVariantIndex]);
+      AppendValue(eCSSProperty_font_weight, values[kFontWeightIndex]);
       AppendValue(eCSSProperty_font_size, size);
       AppendValue(eCSSProperty_line_height, lineHeight);
-      AppendValue(eCSSProperty_font_stretch,
-                  nsCSSValue(NS_FONT_STRETCH_NORMAL, eCSSUnit_Enumerated));
+      AppendValue(eCSSProperty_font_stretch, values[kFontStretchIndex]);
       AppendValue(eCSSProperty_font_size_adjust, nsCSSValue(eCSSUnit_None));
       AppendValue(eCSSProperty_font_feature_settings, nsCSSValue(eCSSUnit_Normal));
       AppendValue(eCSSProperty_font_language_override, nsCSSValue(eCSSUnit_Normal));
       AppendValue(eCSSProperty_font_kerning,
                   nsCSSValue(NS_FONT_KERNING_AUTO, eCSSUnit_Enumerated));
       AppendValue(eCSSProperty_font_synthesis,
                   nsCSSValue(NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE,
                              eCSSUnit_Enumerated));
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -2485,17 +2485,17 @@ var gCSSProperties = {
     domProp: "font",
     inherited: true,
     type: CSS_TYPE_TRUE_SHORTHAND,
     subproperties: [ "font-style", "font-variant", "font-weight", "font-size", "line-height", "font-family", "font-stretch",
                      "font-size-adjust", "font-feature-settings", "font-language-override",
                      "font-kerning", "font-synthesis", "font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
                      "font-variant-ligatures", "font-variant-numeric", "font-variant-position" ],
     initial_values: [ (gInitialFontFamilyIsSansSerif ? "medium sans-serif" : "medium serif") ],
-    other_values: [ "large serif", "9px fantasy", "bold italic small-caps 24px/1.4 Times New Roman, serif", "small inherit roman", "small roman inherit",
+    other_values: [ "large serif", "9px fantasy", "condensed bold italic small-caps 24px/1.4 Times New Roman, serif", "small inherit roman", "small roman inherit",
       // system fonts
       "caption", "icon", "menu", "message-box", "small-caption", "status-bar",
       // Gecko-specific system fonts
       "-moz-window", "-moz-document", "-moz-desktop", "-moz-info", "-moz-dialog", "-moz-button", "-moz-pull-down-menu", "-moz-list", "-moz-field", "-moz-workspace",
     ],
     invalid_values: [ "9 fantasy", "-2px fantasy" ]
   },
   "font-family": {
--- a/layout/style/test/test_bug377947.html
+++ b/layout/style/test/test_bug377947.html
@@ -51,18 +51,18 @@ is(s.getPropertyValue("list-style"), "",
 is(s.getPropertyValue("font"), "",
    "font shorthand should start off empty");
 var all_but_one = {
   "font-family": "serif",
   "font-style": "normal",
   "font-variant": "normal",
   "font-weight": "bold",
   "font-size": "small",
+  "font-stretch": "normal",
   "font-size-adjust": "none", // has to be default value
-  "font-stretch": "normal", // has to be default value
   "font-feature-settings": "normal", // has to be default value
   "font-language-override": "normal", // has to be default value
   "font-kerning": "auto", // has to be default value
   "font-synthesis": "weight style", // has to be default value
   "font-variant-alternates": "normal", // has to be default value
   "font-variant-caps": "normal", // has to be default value
   "font-variant-east-asian": "normal", // has to be default value
   "font-variant-ligatures": "normal", // has to be default value
@@ -73,20 +73,20 @@ var all_but_one = {
 for (var prop in all_but_one) {
   s.setProperty(prop, all_but_one[prop], "");
 }
 is(s.getPropertyValue("font"), "",
    "font shorthand should be empty when some subproperties specified");
 s.setProperty("line-height", "1.5", "");
 isnot(s.getPropertyValue("font"), "",
       "font shorthand should produce value when all subproperties set");
-s.setProperty("font-stretch", "condensed", "");
+s.setProperty("font-size-adjust", "0.5", "");
 is(s.getPropertyValue("font"), "",
-   "font shorthand should be empty when font-stretch is non-default");
-s.setProperty("font-stretch", "normal", "");
+   "font shorthand should be empty when font-size-adjust is non-default");
+s.setProperty("font-size-adjust", "none", "");
 isnot(s.getPropertyValue("font"), "",
       "font shorthand should produce value when all subproperties set");
 s.removeProperty("font");
 is(s.getPropertyValue("font"), "",
    "font shorthand be empty after removal");
 s.font="medium serif";
 isnot(s.getPropertyValue("font"), "",
       "font shorthand should produce value when shorthand set");
--- a/layout/style/test/test_shorthand_property_getters.html
+++ b/layout/style/test/test_shorthand_property_getters.html
@@ -108,17 +108,17 @@ is(e.style.cssText, "border-radius: 1px 
 
 // Test that we refuse to serialize the 'background' and 'font'
 // shorthands when some subproperties that can't be expressed in the
 // shorthand syntax are present.
 e.setAttribute("style", "font: medium serif");
 isnot(e.style.font, "", "should have font shorthand");
 e.setAttribute("style", "font: medium serif; font-size-adjust: 0.45");
 is(e.style.font, "", "should not have font shorthand");
-e.setAttribute("style", "font: medium serif; font-stretch: condensed");
+e.setAttribute("style", "font: medium serif; font-feature-settings: 'liga' off");
 is(e.style.font, "", "should not have font shorthand");
 
 // Test that all combinations of background-clip and background-origin
 // can be expressed in the shorthand (which wasn't the case previously).
 e.setAttribute("style", "background: red");
 isnot(e.style.background, "", "should have background shorthand");
 e.setAttribute("style", "background: red; background-origin: border-box");
 isnot(e.style.background, "", "should have background shorthand (origin:border-box)");
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -354,18 +354,20 @@ class RemoteReftest(RefTest):
         prefs["reftest.remote"] = True
         # Set a future policy version to avoid the telemetry prompt.
         prefs["toolkit.telemetry.prompted"] = 999
         prefs["toolkit.telemetry.notifiedOptOut"] = 999
         prefs["reftest.uri"] = "%s" % reftestlist
         prefs["datareporting.policy.dataSubmissionPolicyBypassAcceptance"] = True
 
         # Point the url-classifier to the local testing server for fast failures
-        prefs["browser.safebrowsing.gethashURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/gethash"
-        prefs["browser.safebrowsing.updateURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/update"
+        prefs["browser.safebrowsing.provider.google.gethashURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/gethash"
+        prefs["browser.safebrowsing.provider.google.updateURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/update"
+        prefs["browser.safebrowsing.provider.mozilla.gethashURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/gethash"
+        prefs["browser.safebrowsing.provider.mozilla.updateURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/update"
         # Point update checks to the local testing server for fast failures
         prefs["extensions.update.url"] = "http://127.0.0.1:8888/extensions-dummy/updateURL"
         prefs["extensions.update.background.url"] = "http://127.0.0.1:8888/extensions-dummy/updateBackgroundURL"
         prefs["extensions.blocklist.url"] = "http://127.0.0.1:8888/extensions-dummy/blocklistURL"
         prefs["extensions.hotfix.url"] = "http://127.0.0.1:8888/extensions-dummy/hotfixURL"
         # Turn off extension updates so they don't bother tests
         prefs["extensions.update.enabled"] = False
         # Make sure opening about:addons won't hit the network
--- a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
@@ -850,16 +850,19 @@ status_t MPEG4Extractor::parseChunk(off6
                     sp<MPEG4DataSource> cachedSource =
                         new MPEG4DataSource(mDataSource);
 
                     if (cachedSource->setCachedRange(*offset, chunk_size) == OK) {
                         mDataSource = cachedSource;
                     }
                 }
 
+                if (!mLastTrack) {
+                  return ERROR_MALFORMED;
+                }
                 mLastTrack->sampleTable = new SampleTable(mDataSource);
             }
 
             bool isTrack = false;
             if (chunk_type == FOURCC('t', 'r', 'a', 'k')) {
                 isTrack = true;
 
                 Track *track = new Track;
@@ -975,39 +978,48 @@ status_t MPEG4Extractor::parseChunk(off6
                     return ERROR_IO;
                 }
                 entriesoffset += 4; // ignore media_rate_integer and media_rate_fraction.
                 if (media_time == -1 && i) {
                     ALOGW("ignoring invalid empty edit", i);
                     break;
                 } else if (media_time == -1) {
                     // Starting offsets for tracks (streams) are represented by an initial empty edit.
+                    if (!mLastTrack) {
+                      return ERROR_MALFORMED;
+                    }
                     mLastTrack->empty_duration = segment_duration;
                     continue;
                 } else if (i > 1) {
                     // we only support a single non-empty entry at the moment, for gapless playback
                     ALOGW("multiple edit list entries, A/V sync will be wrong");
                     break;
                 }
+                if (!mLastTrack) {
+                  return ERROR_MALFORMED;
+                }
                 mLastTrack->segment_duration = segment_duration;
                 mLastTrack->media_time = media_time;
             }
             storeEditList();
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('f', 'r', 'm', 'a'):
         {
             uint32_t original_fourcc;
             if (mDataSource->readAt(data_offset, &original_fourcc, 4) < 4) {
                 return ERROR_IO;
             }
             original_fourcc = ntohl(original_fourcc);
             ALOGV("read original format: %d", original_fourcc);
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(original_fourcc));
             uint32_t num_channels = 0;
             uint32_t sample_rate = 0;
             if (AdjustChannelsAndRate(original_fourcc, &num_channels, &sample_rate)) {
                 mLastTrack->meta->setInt32(kKeyChannelCount, num_channels);
                 mLastTrack->meta->setInt32(kKeySampleRate, sample_rate);
             }
             *offset += chunk_size;
@@ -1062,16 +1074,19 @@ status_t MPEG4Extractor::parseChunk(off6
             }
 
             uint8_t defaultKeyId[16];
 
             if (mDataSource->readAt(data_offset + 8, &defaultKeyId, 16) < 16) {
                 return ERROR_IO;
             }
 
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             mLastTrack->meta->setInt32(kKeyCryptoMode, defaultAlgorithmId);
             mLastTrack->meta->setInt32(kKeyCryptoDefaultIVSize, defaultIVSize);
             mLastTrack->meta->setData(kKeyCryptoKey, 'tenc', defaultKeyId, 16);
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('t', 'r', 'e', 'x'):
@@ -1151,16 +1166,19 @@ status_t MPEG4Extractor::parseChunk(off6
 
             uint32_t timescale;
             if (mDataSource->readAt(
                         timescale_offset, &timescale, sizeof(timescale))
                     < (ssize_t)sizeof(timescale)) {
                 return ERROR_IO;
             }
 
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             mLastTrack->timescale = ntohl(timescale);
 
             // Now that we've parsed the media timescale, we can interpret
             // the edit list data.
             storeEditList();
 
             int64_t duration = 0;
             if (version == 1) {
@@ -1243,16 +1261,19 @@ status_t MPEG4Extractor::parseChunk(off6
             uint32_t entry_count = U32_AT(&buffer[4]);
 
             if (entry_count > 1) {
                 // For 3GPP timed text, there could be multiple tx3g boxes contain
                 // multiple text display formats. These formats will be used to
                 // display the timed text.
                 // For encrypted files, there may also be more than one entry.
                 const char *mime;
+                if (!mLastTrack) {
+                  return ERROR_MALFORMED;
+                }
                 CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));
                 if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) &&
                         strcasecmp(mime, "application/octet-stream")) {
                     // For now we only support a single type of media per track.
                     mLastTrack->skipTrack = true;
                     *offset += chunk_size;
                     break;
                 }
@@ -1297,16 +1318,19 @@ status_t MPEG4Extractor::parseChunk(off6
 
             uint16_t data_ref_index = U16_AT(&buffer[6]);
             uint16_t qt_version = U16_AT(&buffer[8]);
             uint32_t num_channels = U16_AT(&buffer[16]);
 
             uint16_t sample_size = U16_AT(&buffer[18]);
             uint32_t sample_rate = U32_AT(&buffer[24]) >> 16;
 
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             if (chunk_type != FOURCC('e', 'n', 'c', 'a')) {
                 // if the chunk type is enca, we'll get the type from the sinf/frma box later
                 mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
                 AdjustChannelsAndRate(chunk_type, &num_channels, &sample_rate);
             }
             ALOGV("*** coding='%s' %d channels, size %d, rate %d\n",
                    chunk, num_channels, sample_size, sample_rate);
             mLastTrack->meta->setInt32(kKeyChannelCount, num_channels);
@@ -1392,16 +1416,19 @@ status_t MPEG4Extractor::parseChunk(off6
             // let the decoder figure out the actual width and height (and thus
             // be prepared for INFO_FOMRAT_CHANGED event).
             if (width == 0)  width  = 352;
             if (height == 0) height = 288;
 
             // printf("*** coding='%s' width=%d height=%d\n",
             //        chunk, width, height);
 
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             if (chunk_type != FOURCC('e', 'n', 'c', 'v')) {
                 // if the chunk type is encv, we'll get the type from the sinf/frma box later
                 mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
             }
             mLastTrack->meta->setInt32(kKeyWidth, width);
             mLastTrack->meta->setInt32(kKeyHeight, height);
 
             off64_t stop_offset = *offset + chunk_size;
@@ -1422,45 +1449,54 @@ status_t MPEG4Extractor::parseChunk(off6
                 return ERROR_MALFORMED;
             }
             break;
         }
 
         case FOURCC('s', 't', 'c', 'o'):
         case FOURCC('c', 'o', '6', '4'):
         {
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             status_t err =
                 mLastTrack->sampleTable->setChunkOffsetParams(
                         chunk_type, data_offset, chunk_data_size);
 
             if (err != OK) {
                 return err;
             }
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('s', 't', 's', 'c'):
         {
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             status_t err =
                 mLastTrack->sampleTable->setSampleToChunkParams(
                         data_offset, chunk_data_size);
 
             if (err != OK) {
                 return err;
             }
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('s', 't', 's', 'z'):
         case FOURCC('s', 't', 'z', '2'):
         {
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             status_t err =
                 mLastTrack->sampleTable->setSampleSizeParams(
                         chunk_type, data_offset, chunk_data_size);
 
             if (err != OK) {
                 return err;
             }
 
@@ -1505,72 +1541,87 @@ status_t MPEG4Extractor::parseChunk(off6
                 }
             }
 
             break;
         }
 
         case FOURCC('s', 't', 't', 's'):
         {
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             status_t err =
                 mLastTrack->sampleTable->setTimeToSampleParams(
                         data_offset, chunk_data_size);
 
             if (err != OK) {
                 return err;
             }
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('c', 't', 't', 's'):
         {
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             status_t err =
                 mLastTrack->sampleTable->setCompositionTimeToSampleParams(
                         data_offset, chunk_data_size);
 
             if (err != OK) {
                 return err;
             }
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('s', 't', 's', 's'):
         {
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             status_t err =
                 mLastTrack->sampleTable->setSyncSampleParams(
                         data_offset, chunk_data_size);
 
             if (err != OK) {
                 return err;
             }
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('s', 'a', 'i', 'z'):
         {
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             status_t err =
                 mLastTrack->sampleTable->setSampleAuxiliaryInformationSizeParams(
                         data_offset, chunk_data_size, mDrmScheme);
 
             if (err != OK) {
                 return err;
             }
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('s', 'a', 'i', 'o'):
         {
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             status_t err =
                 mLastTrack->sampleTable->setSampleAuxiliaryInformationOffsetParams(
                         data_offset, chunk_data_size, mDrmScheme);
 
             if (err != OK) {
                 return err;
             }
 
@@ -1629,16 +1680,19 @@ status_t MPEG4Extractor::parseChunk(off6
                 return ERROR_IO;
             }
 
             if (U32_AT(buffer) != 0) {
                 // Should be version 0, flags 0.
                 return ERROR_MALFORMED;
             }
 
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             mLastTrack->meta->setData(
                     kKeyESDS, kTypeESDS, &buffer[4], chunk_data_size - 4);
 
             if (mPath.size() >= 2
                     && (mPath[mPath.size() - 2] == FOURCC('m', 'p', '4', 'a') ||
                        (mPath[mPath.size() - 2] == FOURCC('e', 'n', 'c', 'a')))) {
                 // Information from the ESDS must be relied on for proper
                 // setup of sample rate and channel count for MPEG4 Audio.
@@ -1661,16 +1715,19 @@ status_t MPEG4Extractor::parseChunk(off6
         {
             sp<ABuffer> buffer = new ABuffer(chunk_data_size);
 
             if (mDataSource->readAt(
                         data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
                 return ERROR_IO;
             }
 
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             mLastTrack->meta->setData(
                     kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size);
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('d', '2', '6', '3'):
@@ -1693,16 +1750,19 @@ status_t MPEG4Extractor::parseChunk(off6
                 return ERROR_MALFORMED;
             }
 
             if (mDataSource->readAt(
                     data_offset, buffer, chunk_data_size) < chunk_data_size) {
                 return ERROR_IO;
             }
 
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             mLastTrack->meta->setData(kKeyD263, kTypeD263, buffer, chunk_data_size);
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('m', 'e', 't', 'a'):
         {
@@ -1855,25 +1915,31 @@ status_t MPEG4Extractor::parseChunk(off6
                 return ERROR_IO;
             }
 
             uint32_t type = ntohl(buffer);
             // For the 3GPP file format, the handler-type within the 'hdlr' box
             // shall be 'text'. We also want to support 'sbtl' handler type
             // for a practical reason as various MPEG4 containers use it.
             if (type == FOURCC('t', 'e', 'x', 't') || type == FOURCC('s', 'b', 't', 'l')) {
+                if (!mLastTrack) {
+                  return ERROR_MALFORMED;
+                }
                 mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP);
             }
 
             *offset += chunk_size;
             break;
         }
 
         case FOURCC('t', 'x', '3', 'g'):
         {
+            if (!mLastTrack) {
+              return ERROR_MALFORMED;
+            }
             uint32_t type;
             const void *data;
             size_t size = 0;
             if (!mLastTrack->meta->findData(
                     kKeyTextFormatData, &type, &data, &size)) {
                 size = 0;
             }
 
@@ -1965,16 +2031,17 @@ status_t MPEG4Extractor::parseChunk(off6
     }
 
     return OK;
 }
 
 void MPEG4Extractor::storeEditList()
 {
   if (mHeaderTimescale == 0 ||
+      !mLastTrack ||
       mLastTrack->timescale == 0) {
     return;
   }
 
   uint64_t segment_duration = (mLastTrack->segment_duration * 1000000) / mHeaderTimescale;
   // media_time is measured in media time scale units.
   int64_t media_time = (mLastTrack->media_time * 1000000) / mLastTrack->timescale;
   // empty_duration is in the Movie Header Box's timescale.
@@ -2104,16 +2171,19 @@ status_t MPEG4Extractor::parseSegmentInd
         se.mSize = d1 & 0x7fffffff;
         se.mDurationUs = 1000000LL * d2 / timeScale;
         mSidxEntries.add(se);
     }
 
     mSidxDuration = total_duration * 1000000 / timeScale;
     ALOGV("duration: %lld", mSidxDuration);
 
+    if (!mLastTrack) {
+      return ERROR_MALFORMED;
+    }
     int64_t metaDuration;
     if (!mLastTrack->meta->findInt64(kKeyDuration, &metaDuration) || metaDuration == 0) {
         mLastTrack->meta->setInt64(kKeyDuration, mSidxDuration);
     }
     return OK;
 }
 
 status_t MPEG4Extractor::parseTrackExtends(
@@ -2173,16 +2243,19 @@ status_t MPEG4Extractor::parseTrackHeade
         ctime = U32_AT(&buffer[4]);
         mtime = U32_AT(&buffer[8]);
         id = U32_AT(&buffer[12]);
         duration = U32_AT(&buffer[20]);
     } else {
         return ERROR_UNSUPPORTED;
     }
 
+    if (!mLastTrack) {
+      return ERROR_MALFORMED;
+    }
     mLastTrack->meta->setInt32(kKeyTrackID, id);
 
     size_t matrixOffset = dynSize + 16;
     int32_t a00 = U32_AT(&buffer[matrixOffset]);
     int32_t a01 = U32_AT(&buffer[matrixOffset + 4]);
     int32_t dx = U32_AT(&buffer[matrixOffset + 8]);
     int32_t a10 = U32_AT(&buffer[matrixOffset + 12]);
     int32_t a11 = U32_AT(&buffer[matrixOffset + 16]);
@@ -2354,16 +2427,19 @@ status_t MPEG4Extractor::parseMetaData(o
                 (mLastCommentName.length() != 0) &&
                 (mLastCommentData.length() != 0)) {
 
                 if (mLastCommentMean == "com.apple.iTunes"
                         && mLastCommentName == "iTunSMPB") {
                     int32_t delay, padding;
                     if (sscanf(mLastCommentData,
                                " %*x %x %x %*x", &delay, &padding) == 2) {
+                        if (!mLastTrack) {
+                          return ERROR_MALFORMED;
+                        }
                         mLastTrack->meta->setInt32(kKeyEncoderDelay, delay);
                         mLastTrack->meta->setInt32(kKeyEncoderPadding, padding);
                     }
                 }
 
                 mLastCommentMean.clear();
                 mLastCommentName.clear();
                 mLastCommentData.clear();
@@ -2475,22 +2551,28 @@ status_t MPEG4Extractor::updateAudioTrac
 
     uint8_t objectTypeIndication;
     if (esds.getObjectTypeIndication(&objectTypeIndication) != OK) {
         return ERROR_MALFORMED;
     }
 
     if (objectTypeIndication == 0xe1) {
         // This isn't MPEG4 audio at all, it's QCELP 14k...
+        if (!mLastTrack) {
+          return ERROR_MALFORMED;
+        }
         mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_QCELP);
         return OK;
     }
 
     if (objectTypeIndication  == 0x6b || objectTypeIndication  == 0x69) {
         // The media subtype is MP3 audio
+        if (!mLastTrack) {
+          return ERROR_MALFORMED;
+        }
         mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
     }
 
     const uint8_t *csd;
     size_t csd_size;
     if (esds.getCodecSpecificInfo(
                 (const void **)&csd, &csd_size) != OK) {
         return ERROR_MALFORMED;
@@ -2516,17 +2598,20 @@ status_t MPEG4Extractor::updateAudioTrac
     ABitReader br(csd, csd_size);
     uint32_t objectType = br.getBits(5);
 
     if (objectType == 31) {  // AAC-ELD => additional 6 bits
         objectType = 32 + br.getBits(6);
     }
 
     if (objectType >= 1 && objectType <= 4) {
-      mLastTrack->meta->setInt32(kKeyAACProfile, objectType);
+        if (!mLastTrack) {
+          return ERROR_MALFORMED;
+        }
+        mLastTrack->meta->setInt32(kKeyAACProfile, objectType);
     }
 
     uint32_t freqIndex = br.getBits(4);
 
     int32_t sampleRate = 0;
     int32_t numChannels = 0;
     if (freqIndex == 15) {
         if (csd_size < 5) {
@@ -2560,16 +2645,19 @@ status_t MPEG4Extractor::updateAudioTrac
             sampleRate = kSamplingRate[freqIndex];
         }
     }
 
     if (numChannels == 0) {
         return ERROR_UNSUPPORTED;
     }
 
+    if (!mLastTrack) {
+      return ERROR_MALFORMED;
+    }
     int32_t prevSampleRate;
     CHECK(mLastTrack->meta->findInt32(kKeySampleRate, &prevSampleRate));
 
     if (prevSampleRate != sampleRate) {
         ALOGV("mpeg4 audio sample rate different from previous setting. "
              "was: %d, now: %d", prevSampleRate, sampleRate);
     }
 
--- a/media/libstagefright/gtest/TestParser.cpp
+++ b/media/libstagefright/gtest/TestParser.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 #include "MediaData.h"
+#include "mozilla/ArrayUtils.h"
 #include "mp4_demuxer/BufferStream.h"
 #include "mp4_demuxer/MP4Metadata.h"
 #include "mp4_demuxer/MoofParser.h"
 
 using namespace mozilla;
 using namespace mp4_demuxer;
 
 TEST(stagefright_MP4Metadata, EmptyStream)
@@ -37,18 +38,18 @@ TEST(stagefright_MP4Metadata, EmptyStrea
   EXPECT_FALSE(metadata.Crypto().valid);
 }
 
 TEST(stagefright_MoofParser, EmptyStream)
 {
   nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(0);
   nsRefPtr<BufferStream> stream = new BufferStream(buffer);
 
-  mozilla::Monitor monitor("MP4Metadata::gtest");
-  mozilla::MonitorAutoLock mon(monitor);
+  Monitor monitor("MP4Metadata::gtest");
+  MonitorAutoLock mon(monitor);
   MoofParser parser(stream, 0, false, &monitor);
   EXPECT_EQ(0u, parser.mOffset);
   EXPECT_TRUE(parser.ReachedEnd());
 
   nsTArray<MediaByteRange> byteRanges;
   byteRanges.AppendElement(MediaByteRange(0, 0));
   EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
 
@@ -57,8 +58,349 @@ TEST(stagefright_MoofParser, EmptyStream
   EXPECT_EQ(0u, parser.mOffset);
   EXPECT_TRUE(parser.ReachedEnd());
   EXPECT_FALSE(parser.HasMetadata());
   nsRefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
   EXPECT_FALSE(metadataBuffer);
   EXPECT_TRUE(parser.FirstCompleteMediaSegment().IsNull());
   EXPECT_TRUE(parser.FirstCompleteMediaHeader().IsNull());
 }
+
+uint8_t test_case_mp4[] = {
+  0x00, 0x00, 0x00, 0x20,  'f',  't',  'y',  'p',  'i',  's',  'o',  'm',
+  0x00, 0x00, 0x02, 0x00,  'i',  's',  'o',  'm',  'i',  's',  'o',  '2',
+   'a',  'v',  'c',  '1',  'm',  'p',  '4',  '1', 0x00, 0x00, 0x00, 0x08,
+   'f',  'r',  'e',  'e', 0x00, 0x00, 0x07, 0xcc,  'm',  'd',  'a',  't',
+  0x00, 0x00, 0x02, 0xaf, 0x06, 0x05, 0xff, 0xff, 0xab, 0xdc, 0x45, 0xe9,
+  0xbd, 0xe6, 0xd9, 0x48, 0xb7, 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee,
+  0xef, 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65,
+  0x20, 0x31, 0x34, 0x36, 0x20, 0x72, 0x32, 0x35, 0x33, 0x38, 0x20, 0x31,
+  0x32, 0x31, 0x33, 0x39, 0x36, 0x63, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32,
+  0x36, 0x34, 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56,
+  0x43, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f,
+  0x70, 0x79, 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, 0x33, 0x2d,
+  0x32, 0x30, 0x31, 0x35, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c,
+  0x61, 0x6e, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e,
+  0x68, 0x74, 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x31, 0x20,
+  0x72, 0x65, 0x66, 0x3d, 0x33, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63,
+  0x6b, 0x3d, 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c,
+  0x79, 0x73, 0x65, 0x3d, 0x30, 0x78, 0x33, 0x3a, 0x30, 0x78, 0x31, 0x31,
+  0x33, 0x20, 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62,
+  0x6d, 0x65, 0x3d, 0x37, 0x20, 0x70, 0x73, 0x79, 0x3d, 0x31, 0x20, 0x70,
+  0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x3a, 0x30,
+  0x2e, 0x30, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x72, 0x65,
+  0x66, 0x3d, 0x31, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65,
+  0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x6d,
+  0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69, 0x73, 0x3d,
+  0x31, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x31, 0x20, 0x63,
+  0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a, 0x6f, 0x6e,
+  0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x66, 0x61, 0x73, 0x74,
+  0x5f, 0x70, 0x73, 0x6b, 0x69, 0x70, 0x3d, 0x31, 0x20, 0x63, 0x68, 0x72,
+  0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65,
+  0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73,
+  0x3d, 0x31, 0x32, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61,
+  0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x31, 0x20,
+  0x73, 0x6c, 0x69, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61,
+  0x64, 0x73, 0x3d, 0x30, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, 0x64, 0x65,
+  0x63, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x69, 0x6e, 0x74,
+  0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x6c,
+  0x75, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x3d,
+  0x30, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x65,
+  0x64, 0x5f, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x62, 0x66,
+  0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x33, 0x20, 0x62, 0x5f, 0x70, 0x79,
+  0x72, 0x61, 0x6d, 0x69, 0x64, 0x3d, 0x32, 0x20, 0x62, 0x5f, 0x61, 0x64,
+  0x61, 0x70, 0x74, 0x3d, 0x31, 0x20, 0x62, 0x5f, 0x62, 0x69, 0x61, 0x73,
+  0x3d, 0x30, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x31, 0x20,
+  0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x62, 0x3d, 0x31, 0x20, 0x6f, 0x70,
+  0x65, 0x6e, 0x5f, 0x67, 0x6f, 0x70, 0x3d, 0x30, 0x20, 0x77, 0x65, 0x69,
+  0x67, 0x68, 0x74, 0x70, 0x3d, 0x32, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e,
+  0x74, 0x3d, 0x32, 0x35, 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74,
+  0x5f, 0x6d, 0x69, 0x6e, 0x3d, 0x32, 0x35, 0x20, 0x73, 0x63, 0x65, 0x6e,
+  0x65, 0x63, 0x75, 0x74, 0x3d, 0x34, 0x30, 0x20, 0x69, 0x6e, 0x74, 0x72,
+  0x61, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x3d, 0x30, 0x20,
+  0x72, 0x63, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64,
+  0x3d, 0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x63, 0x72, 0x66, 0x20, 0x6d,
+  0x62, 0x74, 0x72, 0x65, 0x65, 0x3d, 0x31, 0x20, 0x63, 0x72, 0x66, 0x3d,
+  0x32, 0x33, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f, 0x6d, 0x70, 0x3d, 0x30,
+  0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d, 0x30, 0x20,
+  0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x73,
+  0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x69, 0x70, 0x5f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, 0x20, 0x61, 0x71, 0x3d, 0x31,
+  0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80, 0x00, 0x00, 0x04, 0xfb, 0x65,
+  0x88, 0x84, 0x00, 0x25, 0xff, 0xd7, 0x2b, 0x47, 0x53, 0x79, 0x61, 0xc8,
+  0xdf, 0x88, 0x8e, 0xdc, 0xef, 0xc0, 0x85, 0x00, 0x99, 0x10, 0x58, 0x46,
+  0xf1, 0x28, 0xf2, 0x0c, 0xfc, 0x41, 0x82, 0xa0, 0x45, 0x81, 0xb5, 0x7e,
+  0xcc, 0x27, 0x62, 0x2f, 0xac, 0x5c, 0xe8, 0xa9, 0xcf, 0x0e, 0x54, 0x17,
+  0x68, 0x13, 0x48, 0x0d, 0xa1, 0x5d, 0xd1, 0x8b, 0xdf, 0x6e, 0xd0, 0xc8,
+  0x8f, 0xb3, 0xc2, 0xaf, 0xd1, 0xdc, 0x98, 0x74, 0x7e, 0x0f, 0x69, 0xbe,
+  0x54, 0xa2, 0x83, 0x4a, 0xc4, 0xf1, 0x79, 0x35, 0x60, 0x10, 0xb4, 0xfb,
+  0xa7, 0x52, 0x3a, 0x99, 0x5e, 0x39, 0x0d, 0x04, 0xad, 0x24, 0x49, 0xfd,
+  0xbe, 0x5f, 0x49, 0xdf, 0x36, 0x91, 0x1f, 0x62, 0x7f, 0x00, 0xa1, 0xba,
+  0x3c, 0xe9, 0xf3, 0x38, 0x08, 0xb7, 0x87, 0x9a, 0x11, 0xae, 0x5f, 0xf2,
+  0x66, 0x14, 0xc3, 0xa9, 0xf0, 0x76, 0x69, 0x5f, 0x1d, 0x20, 0x19, 0xc0,
+  0x50, 0x94, 0xb4, 0x7b, 0x44, 0xaf, 0x21, 0x24, 0x80, 0x3f, 0x2a, 0x81,
+  0x1b, 0xe4, 0xae, 0xfe, 0xb0, 0x99, 0xd3, 0xa5, 0x1d, 0x3f, 0x27, 0x0c,
+  0x61, 0x54, 0x4d, 0x39, 0x8b, 0x1d, 0x12, 0x65, 0xe1, 0xb3, 0x4d, 0xc1,
+  0x9d, 0xef, 0xfe, 0x48, 0xc6, 0xc9, 0xf4, 0x9a, 0x09, 0xc1, 0x3c, 0x5c,
+  0xe3, 0xa1, 0x16, 0xec, 0x83, 0xb6, 0xb9, 0xc2, 0x94, 0x64, 0xc9, 0xf2,
+  0x91, 0x51, 0x4c, 0xaf, 0xe5, 0xc3, 0x8a, 0x99, 0x1b, 0x22, 0x86, 0x55,
+  0x70, 0xbf, 0xc7, 0x99, 0xc3, 0x07, 0x15, 0xd6, 0xdb, 0x27, 0x46, 0x08,
+  0x19, 0x7e, 0x0a, 0x9b, 0x82, 0xe0, 0x15, 0x1d, 0x57, 0x8a, 0x2c, 0xc4,
+  0x05, 0xf8, 0x09, 0xfb, 0x3c, 0x47, 0x4f, 0x55, 0xc6, 0x3e, 0x0b, 0x9d,
+  0xe5, 0x03, 0x3e, 0xc9, 0x4c, 0xeb, 0xa2, 0x1b, 0x04, 0xfa, 0x99, 0x44,
+  0x29, 0x06, 0x2e, 0x57, 0x61, 0xa3, 0x63, 0x00, 0x53, 0x08, 0x20, 0xcb,
+  0x15, 0x50, 0x95, 0x13, 0x24, 0x63, 0xc9, 0x4f, 0xc0, 0xb0, 0x88, 0xe6,
+  0x38, 0xef, 0x96, 0x99, 0xd2, 0x96, 0x47, 0xc7, 0xc8, 0x61, 0xac, 0xcc,
+  0x24, 0x0a, 0x1f, 0x79, 0xce, 0x91, 0x88, 0xf5, 0xe9, 0xe2, 0x2d, 0x88,
+  0xaf, 0x37, 0xca, 0xa3, 0x90, 0x5b, 0x83, 0x1a, 0x9c, 0x92, 0xda, 0x33,
+  0x69, 0xe6, 0x7c, 0x14, 0xa6, 0x85, 0x00, 0x42, 0x46, 0x51, 0xcf, 0x50,
+  0xed, 0x34, 0xe8, 0x8e, 0xb5, 0x78, 0xeb, 0x09, 0x37, 0x6e, 0x82, 0xea,
+  0x7b, 0xe1, 0xfd, 0x45, 0x61, 0x77, 0x14, 0x6a, 0x75, 0xbf, 0xb8, 0x13,
+  0x10, 0xc0, 0x08, 0xca, 0x1c, 0x51, 0xc7, 0x3a, 0x0a, 0x2b, 0x89, 0x5d,
+  0x3c, 0x8d, 0xf4, 0x11, 0xc0, 0x3a, 0x90, 0x02, 0xc0, 0x0c, 0xe8, 0x59,
+  0x11, 0xac, 0xe0, 0x5d, 0xe2, 0x95, 0x78, 0x09, 0xd5, 0xfa, 0x26, 0xf5,
+  0x23, 0x30, 0x0a, 0xfd, 0x54, 0x0b, 0x73, 0x1c, 0x81, 0x22, 0x48, 0x8b,
+  0x79, 0xe0, 0xf9, 0xe8, 0xde, 0x7a, 0x79, 0xba, 0xef, 0xdd, 0x5a, 0xd6,
+  0x44, 0xb7, 0xa8, 0x41, 0xd0, 0xa1, 0xeb, 0x45, 0xf8, 0x4d, 0x7f, 0x97,
+  0x48, 0x82, 0xd2, 0x81, 0x61, 0x08, 0x15, 0x9e, 0xfe, 0x78, 0xba, 0xdf,
+  0xb0, 0x13, 0x92, 0x59, 0x4e, 0x4d, 0xfd, 0xff, 0x5d, 0x66, 0x14, 0xd4,
+  0x0b, 0x43, 0x9f, 0xd8, 0x62, 0x4e, 0x54, 0x83, 0xf9, 0x59, 0x48, 0x2f,
+  0x26, 0x21, 0xd4, 0xf0, 0x98, 0x6b, 0x14, 0x61, 0x4b, 0xf6, 0x00, 0xcf,
+  0xe3, 0x24, 0x4b, 0x2f, 0xd4, 0x5e, 0x6d, 0x40, 0x4b, 0x52, 0xea, 0xa5,
+  0x89, 0x94, 0x0f, 0xd2, 0xeb, 0x02, 0x54, 0x68, 0x26, 0xcf, 0x2d, 0x83,
+  0x0f, 0x62, 0x1b, 0x9e, 0x75, 0x81, 0x65, 0x30, 0xdd, 0x03, 0xb0, 0xc1,
+  0xda, 0x4a, 0xc9, 0xd8, 0x64, 0x29, 0x6a, 0xe2, 0x83, 0xf1, 0xb8, 0x4e,
+  0xc5, 0xaf, 0x8e, 0xf0, 0x8c, 0xbb, 0xda, 0xea, 0x8a, 0xbc, 0xcc, 0xa3,
+  0xf2, 0x19, 0xe8, 0xeb, 0x7e, 0x18, 0x64, 0x91, 0x1f, 0xdd, 0xc8, 0xc8,
+  0xf8, 0xad, 0xc8, 0x3b, 0xf9, 0x92, 0x74, 0x03, 0x9b, 0x90, 0x51, 0xb7,
+  0xb5, 0xe3, 0x80, 0x4f, 0x8a, 0x84, 0xfa, 0xa4, 0xd6, 0x9c, 0x53, 0x0b,
+  0x81, 0xc3, 0xee, 0x9e, 0x70, 0xa3, 0xbd, 0x3f, 0xbb, 0x60, 0x2d, 0x97,
+  0x65, 0xa7, 0x69, 0x2f, 0x22, 0x49, 0x65, 0xf4, 0xb0, 0x33, 0xbc, 0xd2,
+  0x80, 0x1b, 0x2c, 0x3a, 0xe3, 0x04, 0x8c, 0x49, 0x42, 0x25, 0xa0, 0x6d,
+  0x3c, 0xfe, 0xfa, 0x70, 0x90, 0x6a, 0x30, 0xf4, 0x0c, 0xe4, 0x3f, 0x78,
+  0xf9, 0xba, 0x55, 0xb9, 0xfa, 0xd7, 0xce, 0x05, 0xbc, 0xe9, 0xc9, 0xad,
+  0x4a, 0x37, 0xa0, 0xf7, 0x8d, 0x96, 0x22, 0xf6, 0x38, 0x8d, 0xf4, 0xf6,
+  0xe6, 0x8b, 0x45, 0xac, 0x13, 0xc5, 0xe6, 0x05, 0x1e, 0x09, 0xd7, 0x98,
+  0xb3, 0xb6, 0x59, 0xe4, 0x3b, 0x47, 0x16, 0x6e, 0xdf, 0xac, 0x7f, 0x38,
+  0x6e, 0xf9, 0xcf, 0xaa, 0x68, 0x98, 0xdb, 0x22, 0x89, 0x6e, 0xad, 0xbe,
+  0xed, 0xb1, 0x82, 0xa0, 0xc2, 0x9b, 0xd5, 0x79, 0x89, 0x96, 0xf6, 0xd9,
+  0x8f, 0x58, 0x77, 0x15, 0x2c, 0x73, 0xeb, 0x89, 0xcc, 0xf0, 0x37, 0x4d,
+  0x41, 0x70, 0xc5, 0x58, 0xae, 0x77, 0xab, 0x30, 0xcf, 0x6c, 0x7c, 0x1c,
+  0x52, 0x9a, 0x62, 0xf6, 0xf8, 0x0a, 0x65, 0x92, 0x83, 0x01, 0xc3, 0x60,
+  0xed, 0xfd, 0x4d, 0x9a, 0x4b, 0xd4, 0xa5, 0xe1, 0xc4, 0xe2, 0xe1, 0x8c,
+  0x64, 0xce, 0x54, 0x9c, 0xa9, 0x7f, 0xb9, 0x34, 0x88, 0xb1, 0x17, 0xde,
+  0x85, 0x27, 0x43, 0x81, 0x3b, 0x37, 0x27, 0x25, 0xbf, 0xba, 0x6c, 0x69,
+  0xb4, 0xce, 0xcd, 0xef, 0x7c, 0xee, 0x48, 0xb9, 0x8a, 0x09, 0x1f, 0x42,
+  0x8e, 0xc3, 0x14, 0xe4, 0xfd, 0xda, 0xa0, 0xfa, 0x7d, 0x0b, 0x68, 0x6a,
+  0x81, 0xb1, 0x96, 0xf9, 0x07, 0xf2, 0xed, 0x32, 0xf3, 0x52, 0x15, 0x00,
+  0x2f, 0x5f, 0x6e, 0x55, 0xc6, 0x85, 0x4b, 0xdc, 0x3d, 0x7c, 0xa6, 0xa7,
+  0xeb, 0x80, 0xab, 0xf3, 0xfe, 0x21, 0xe6, 0x1d, 0xbd, 0xca, 0x33, 0x29,
+  0x9c, 0x94, 0xa4, 0x7f, 0xec, 0x67, 0x6c, 0xc0, 0x0e, 0xea, 0x5f, 0x9a,
+  0x30, 0x54, 0x9c, 0xf2, 0x8f, 0xaa, 0x5f, 0xc3, 0x3e, 0x61, 0x54, 0x41,
+  0x8a, 0xbf, 0x7f, 0xff, 0x8a, 0xfb, 0x7f, 0x8c, 0x40, 0xe4, 0x5a, 0xbe,
+  0xe5, 0x39, 0xa4, 0xdd, 0x9b, 0xa6, 0xab, 0xd7, 0xee, 0x15, 0x32, 0x04,
+  0xd1, 0xbd, 0x7c, 0xe4, 0x98, 0x3f, 0x3f, 0x40, 0x87, 0xbc, 0x01, 0x36,
+  0xe7, 0xcd, 0x7b, 0xcf, 0xf7, 0xe9, 0x60, 0x1d, 0xea, 0xe2, 0x6e, 0x13,
+  0x3c, 0xd0, 0x28, 0x2d, 0xa8, 0x9d, 0x25, 0x06, 0x99, 0xf8, 0xdc, 0x9f,
+  0x9f, 0xc5, 0x5a, 0xfd, 0x8f, 0x31, 0xcf, 0xc3, 0xe5, 0xe6, 0xc9, 0x99,
+  0xae, 0x56, 0x72, 0xe2, 0x2f, 0xdf, 0x2c, 0x0e, 0x7d, 0x51, 0xc8, 0x35,
+  0x40, 0x23, 0x9e, 0x52, 0x44, 0x6f, 0x17, 0x40, 0x01, 0xd1, 0x85, 0x07,
+  0x55, 0xef, 0x10, 0xc9, 0xe5, 0xb9, 0xef, 0xbf, 0xf4, 0xe5, 0x38, 0xd3,
+  0x1f, 0x2b, 0x91, 0x51, 0x60, 0x75, 0xc8, 0x95, 0xcc, 0x9d, 0x5a, 0xfa,
+  0x69, 0xae, 0x9a, 0x16, 0x41, 0x07, 0x9c, 0x94, 0x40, 0xb7, 0xa9, 0xdd,
+  0x8d, 0xcb, 0xce, 0xc5, 0xf2, 0x5e, 0x59, 0x67, 0x69, 0x07, 0xd3, 0x04,
+  0xa4, 0x99, 0x56, 0xd9, 0xdc, 0x04, 0x9a, 0x66, 0x62, 0x7b, 0x67, 0xc8,
+  0xd0, 0x34, 0x69, 0x5b, 0x4a, 0xce, 0x6e, 0x53, 0x0e, 0x62, 0xf7, 0x85,
+  0xe0, 0xd7, 0xb7, 0x27, 0x55, 0x3a, 0x4d, 0x36, 0x47, 0xf2, 0x74, 0x02,
+  0xb3, 0x66, 0x2c, 0xda, 0xc3, 0xb3, 0x38, 0x94, 0x67, 0x82, 0x44, 0xf4,
+  0x12, 0xe4, 0x1c, 0x8f, 0x22, 0x4d, 0x32, 0x35, 0xf0, 0xe3, 0x41, 0x0a,
+  0x7d, 0xe4, 0xb4, 0x6e, 0x10, 0x4f, 0xa9, 0x46, 0xd5, 0xab, 0x90, 0x4c,
+  0xad, 0x2c, 0x30, 0xd0, 0x9e, 0x68, 0x2c, 0xc4, 0x3c, 0xf7, 0x05, 0xdf,
+  0x22, 0xaa, 0xb0, 0x82, 0xbb, 0x2c, 0x67, 0x8c, 0xfd, 0x1b, 0x04, 0x41,
+  0xf1, 0x4f, 0x77, 0xa4, 0xdb, 0xfb, 0xca, 0x1d, 0xd7, 0x61, 0x8a, 0x3e,
+  0x89, 0x40, 0x88, 0xf2, 0xda, 0x35, 0x2b, 0x9d, 0xbf, 0xd8, 0x98, 0x55,
+  0x4e, 0x60, 0xac, 0xc1, 0x1b, 0xd4, 0xe0, 0xb8, 0x6d, 0x13, 0xa0, 0xa3,
+  0x24, 0x80, 0xa0, 0xe6, 0x12, 0xad, 0x27, 0x36, 0xee, 0xd7, 0x55, 0x4b,
+  0xb4, 0x1a, 0xd2, 0x87, 0x31, 0x1a, 0x00, 0x53, 0xe9, 0x0f, 0xb7, 0x50,
+  0xeb, 0xdb, 0x63, 0xfe, 0xc3, 0xd0, 0xb1, 0x25, 0xdc, 0x63, 0x66, 0xcc,
+  0xe6, 0x99, 0xa3, 0x34, 0x0b, 0x1d, 0xdd, 0x84, 0x88, 0x3c, 0xfc, 0x79,
+  0xf5, 0x13, 0x0a, 0xe0, 0xca, 0x9e, 0x02, 0xeb, 0x06, 0xab, 0x6d, 0x80,
+  0xeb, 0x06, 0x3d, 0x9a, 0xbb, 0x97, 0xd5, 0xd2, 0x23, 0x22, 0x17, 0xca,
+  0x7a, 0x34, 0x09, 0xfe, 0x53, 0xfa, 0xc1, 0x34, 0x2a, 0x2c, 0xcb, 0x07,
+  0xd3, 0x92, 0x86, 0x9c, 0x7b, 0xd6, 0xdc, 0xe9, 0x5d, 0xa9, 0xcd, 0xb3,
+  0x72, 0xc1, 0x5d, 0xcd, 0x3f, 0xc2, 0x9b, 0xcf, 0x5a, 0x54, 0xbe, 0x50,
+  0x84, 0xbc, 0xe1, 0x33, 0xbd, 0xfd, 0xb6, 0x59, 0x49, 0x11, 0x25, 0xc9,
+  0x01, 0x57, 0x78, 0x8a, 0xef, 0x16, 0x7e, 0x15, 0x2d, 0x9d, 0x30, 0x4b,
+  0x68, 0xa4, 0x3b, 0x99, 0xbf, 0x11, 0x70, 0x0f, 0x17, 0x33, 0xd5, 0x6e,
+  0x31, 0x86, 0x7a, 0xea, 0x12, 0xdb, 0xb9, 0xbc, 0x67, 0x4e, 0x79, 0x58,
+  0x2f, 0x81, 0x00, 0x00, 0x00, 0x0e, 0x41, 0x9a, 0x21, 0x6c, 0x42, 0xdf,
+  0x18, 0xd3, 0x2d, 0x01, 0x43, 0x7f, 0x24, 0x38, 0x00, 0x00, 0x03, 0x1f,
+   'm',  'o',  'o',  'v', 0x00, 0x00, 0x00, 0x6c,  'm',  'v',  'h',  'd',
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x01, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x49,  't',  'r',  'a',  'k',
+  0x00, 0x00, 0x00, 0x5c,  't',  'k',  'h',  'd', 0x00, 0x00, 0x00, 0x03,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+  0x00, 0xa0, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
+   'e',  'd',  't',  's', 0x00, 0x00, 0x00, 0x1c,  'e',  'l',  's',  't',
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc1,
+   'm',  'd',  'i',  'a', 0x00, 0x00, 0x00, 0x20,  'm',  'd',  'h',  'd',
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x04, 0x00, 0x15, 0xc7, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x2d, 0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x64, 0x65,
+  0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01,
+  0x6c, 0x6d, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x14, 0x76, 0x6d, 0x68,
+  0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00,
+  0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x01, 0x2c, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00,
+  0xac, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x9c, 0x61, 0x76, 0x63, 0x31, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
+  0x5a, 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x36, 0x61, 0x76, 0x63, 0x43, 0x01,
+  0x64, 0x00, 0x0a, 0xff, 0xe1, 0x00, 0x1d, 0x67, 0x64, 0x00, 0x0a, 0xac,
+  0xd9, 0x42, 0x0d, 0xf9, 0x3f, 0xf0, 0x00, 0x50, 0x00, 0x41, 0x00, 0x00,
+  0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x32, 0x0f, 0x12, 0x25, 0x96,
+  0x01, 0x00, 0x06, 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, 0x00, 0x00, 0x00,
+  0x10, 0x70, 0x61, 0x73, 0x70, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+  0x04, 0x00, 0x00, 0x00, 0x18, 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02,
+  0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x73, 0x73, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x1c, 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x1c, 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x07,
+  0xb2, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x63,
+  0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x30, 0x00, 0x00, 0x00, 0x62, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00,
+  0x5a, 0x6d, 0x65, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x21, 0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x6d, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6c, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x69, 0x6c,
+  0x73, 0x74, 0x00, 0x00, 0x00, 0x25, 0xa9, 0x74, 0x6f, 0x6f, 0x00, 0x00,
+  0x00, 0x1d, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+  0x00, 0x00, 0x4c, 0x61, 0x76, 0x66, 0x35, 0x36, 0x2e, 0x33, 0x33, 0x2e,
+  0x31, 0x30, 0x31
+};
+static const size_t test_case_mp4_len = ArrayLength(test_case_mp4);
+
+TEST(stagefright_MPEG4Metadata, test_case_mp4)
+{
+  nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(test_case_mp4_len);
+  buffer->AppendElements(test_case_mp4, test_case_mp4_len);
+  nsRefPtr<BufferStream> stream = new BufferStream(buffer);
+
+  EXPECT_TRUE(MP4Metadata::HasCompleteMetadata(stream));
+  nsRefPtr<MediaByteBuffer> metadataBuffer = MP4Metadata::Metadata(stream);
+  EXPECT_TRUE(metadataBuffer);
+
+  MP4Metadata metadata(stream);
+  EXPECT_EQ(0u, metadata.GetNumberTracks(TrackInfo::kUndefinedTrack));
+  EXPECT_EQ(0u, metadata.GetNumberTracks(TrackInfo::kAudioTrack));
+  EXPECT_EQ(1u, metadata.GetNumberTracks(TrackInfo::kVideoTrack));
+  EXPECT_EQ(0u, metadata.GetNumberTracks(TrackInfo::kTextTrack));
+  EXPECT_EQ(0u, metadata.GetNumberTracks(static_cast<TrackInfo::TrackType>(-1)));
+  EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kUndefinedTrack, 0));
+  EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kAudioTrack, 0));
+  UniquePtr<TrackInfo> track = metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0);
+  EXPECT_TRUE(!!track);
+  EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0));
+  EXPECT_FALSE(metadata.GetTrackInfo(static_cast<TrackInfo::TrackType>(-1), 0));
+  // We can see anywhere in any MPEG4.
+  EXPECT_TRUE(metadata.CanSeek());
+  EXPECT_FALSE(metadata.Crypto().valid);
+}
+
+TEST(stagefright_MPEG4Metadata, test_case_mp4_skimming)
+{
+  static const size_t step = 4u;
+  nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(test_case_mp4_len);
+  buffer->AppendElements(test_case_mp4, test_case_mp4_len);
+  for (size_t offset = 0; offset < test_case_mp4_len - step; offset += step) {
+    nsRefPtr<BufferStream> stream = new BufferStream(buffer);
+
+    // Just exercizing the parser starting at different points through the file,
+    // making sure it doesn't crash.
+    // No checks because results would differ for each position.
+    MP4Metadata::HasCompleteMetadata(stream);
+    nsRefPtr<MediaByteBuffer> metadataBuffer = MP4Metadata::Metadata(stream);
+    MP4Metadata metadata(stream);
+
+    buffer->RemoveElementsAt(0, step);
+  }
+}
+
+TEST(stagefright_MoofParser, test_case_mp4)
+{
+  nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(test_case_mp4_len);
+  buffer->AppendElements(test_case_mp4, test_case_mp4_len);
+  nsRefPtr<BufferStream> stream = new BufferStream(buffer);
+
+  Monitor monitor("MP4Metadata::HasCompleteMetadata");
+  MonitorAutoLock mon(monitor);
+  MoofParser parser(stream, 0, false, &monitor);
+  EXPECT_EQ(0u, parser.mOffset);
+  EXPECT_FALSE(parser.ReachedEnd());
+
+  nsTArray<MediaByteRange> byteRanges;
+  byteRanges.AppendElement(MediaByteRange(0, 0));
+  EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
+
+  EXPECT_TRUE(parser.GetCompositionRange(byteRanges).IsNull());
+  EXPECT_TRUE(parser.mInitRange.IsNull());
+  EXPECT_EQ(0u, parser.mOffset);
+  EXPECT_FALSE(parser.ReachedEnd());
+  EXPECT_TRUE(parser.HasMetadata());
+  nsRefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
+  EXPECT_TRUE(metadataBuffer);
+  EXPECT_TRUE(parser.FirstCompleteMediaSegment().IsNull());
+  EXPECT_TRUE(parser.FirstCompleteMediaHeader().IsNull());
+}
+
+TEST(stagefright_MoofParser, test_case_mp4_skimming)
+{
+  const size_t step = 4u;
+  nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(test_case_mp4_len);
+  buffer->AppendElements(test_case_mp4, test_case_mp4_len);
+  Monitor monitor("MP4Metadata::HasCompleteMetadata");
+  MonitorAutoLock mon(monitor);
+  for (size_t offset = 0; offset < test_case_mp4_len - step; offset += step) {
+    nsRefPtr<BufferStream> stream = new BufferStream(buffer);
+
+    // Just exercizing the parser starting at different points through the file,
+    // making sure it doesn't crash.
+    // No checks because results would differ for each position.
+    MoofParser parser(stream, 0, false, &monitor);
+    nsTArray<MediaByteRange> byteRanges;
+    byteRanges.AppendElement(MediaByteRange(0, 0));
+    EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
+    parser.GetCompositionRange(byteRanges);
+    parser.HasMetadata();
+    nsRefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
+    parser.FirstCompleteMediaSegment();
+    parser.FirstCompleteMediaHeader();
+
+    buffer->RemoveElementsAt(0, step);
+  }
+}
--- a/memory/build/jemalloc_config.cpp
+++ b/memory/build/jemalloc_config.cpp
@@ -32,17 +32,17 @@
 #endif
 
 #ifdef DEBUG
 #define MOZ_MALLOC_BUILD_OPTIONS ",junk:true"
 #else
 #define MOZ_MALLOC_BUILD_OPTIONS ",junk:free"
 #endif
 
-#define MOZ_MALLOC_OPTIONS "narenas:1,lg_chunk:20,tcache:false"
+#define MOZ_MALLOC_OPTIONS "narenas:1,tcache:false"
 MFBT_DATA const char* je_(malloc_conf) =
   MOZ_MALLOC_OPTIONS MOZ_MALLOC_PLATFORM_OPTIONS MOZ_MALLOC_BUILD_OPTIONS;
 
 #ifdef ANDROID
 #include <android/log.h>
 
 static void
 _je_malloc_message(void* cbopaque, const char* s)
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -602,24 +602,30 @@ pref("shumway.disabled", true);
 #endif
 
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
 
 #ifdef MOZ_SAFE_BROWSING
 pref("browser.safebrowsing.enabled", true);
 pref("browser.safebrowsing.malware.enabled", true);
+pref("browser.safebrowsing.downloads.enabled", false);
+pref("browser.safebrowsing.downloads.remote.enabled", false);
+pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
 pref("browser.safebrowsing.debug", false);
 
-pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
-pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
+pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
+pref("browser.safebrowsing.provider.google.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
+
 pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
 pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
-pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 
 pref("browser.safebrowsing.id", @MOZ_APP_UA_NAME@);
 
 // Name of the about: page contributed by safebrowsing to handle display of error
 // pages on phishing/malware hits.  (bug 399233)
 pref("urlclassifier.alternate_error_page", "blocked");
 
 // The number of random entries to send with a gethash request.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1430,16 +1430,21 @@ pref("network.http.enforce-framing.http1
 pref("network.http.enforce-framing.soft", true);
 
 // Whether nsHttpChannel should use the PackagedAppService to load
 // resources from a package when directed to a URL
 // such as http://domain.com/package.pak!//resource.html
 // See http://www.w3.org/TR/web-packaging/#streamable-package-format
 pref("network.http.enable-packaged-apps", false);
 
+// Enable this pref to skip verification process. The packaged app
+// will be considered signed no matter the package has a valid/invalid
+// signature or no signature.
+pref("network.http.packaged-apps-developer-mode", false);
+
 // default values for FTP
 // in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
 // Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22)
 // per Section 4.7 "Low-Latency Data Service Class".
 pref("network.ftp.data.qos", 0);
 pref("network.ftp.control.qos", 0);
 
 // If this pref is false only one xpcom event will be served per poll
@@ -4758,18 +4763,20 @@ pref("urlclassifier.phishTable", "goog-p
 pref("urlclassifier.downloadBlockTable", "");
 pref("urlclassifier.downloadAllowTable", "");
 pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,goog-downloadwhite-digest256,mozpub-track-digest256,mozpub-trackwhite-digest256");
 
 // The table and update/gethash URLs for Safebrowsing phishing and malware
 // checks.
 pref("urlclassifier.trackingTable", "test-track-simple,mozpub-track-digest256");
 pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozpub-trackwhite-digest256");
-pref("browser.trackingprotection.updateURL", "https://tracking.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
-pref("browser.trackingprotection.gethashURL", "https://tracking.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+
+pref("browser.safebrowsing.provider.mozilla.lists", "mozpub-track-digest256,mozpub-trackwhite-digest256");
+pref("browser.safebrowsing.provider.mozilla.updateURL", "https://tracking.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://tracking.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 
 // Turn off Spatial navigation by default.
 pref("snav.enabled", false);
 
 // Original caret implementation on collapsed selection.
 pref("touchcaret.enabled", false);
 
 // This will inflate the size of the touch caret frame when checking if user
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -64,16 +64,17 @@ XPIDL_SOURCES += [
     'nsINetworkLinkService.idl',
     'nsINetworkPredictor.idl',
     'nsINetworkPredictorVerifier.idl',
     'nsINetworkProperties.idl',
     'nsINSSErrorsService.idl',
     'nsINullChannel.idl',
     'nsIPACGenerator.idl',
     'nsIPackagedAppService.idl',
+    'nsIPackagedAppVerifier.idl',
     'nsIParentChannel.idl',
     'nsIParentRedirectingChannel.idl',
     'nsIPermission.idl',
     'nsIPermissionManager.idl',
     'nsIPrivateBrowsingChannel.idl',
     'nsIProgressEventSink.idl',
     'nsIPrompt.idl',
     'nsIProtocolHandler.idl',
--- a/netwerk/base/nsIMultiPartChannel.idl
+++ b/netwerk/base/nsIMultiPartChannel.idl
@@ -7,17 +7,17 @@
 
 interface nsIChannel;
 
 /**
  * An interface to access the the base channel 
  * associated with a MultiPartChannel.
  */
 
-[scriptable, uuid(3c329c90-2ee0-11e5-a2cb-0800200c9a66)]
+[scriptable, uuid(4fefb490-5567-11e5-a837-0800200c9a66)]
 interface nsIMultiPartChannel : nsISupports
 {
     /**
      * readonly attribute to access the underlying channel
      */
     readonly attribute nsIChannel baseChannel;
 
     /**
@@ -33,9 +33,14 @@ interface nsIMultiPartChannel : nsISuppo
      */
     readonly attribute boolean isLastPart;
 
     /**
      * ASCII-encoding content prior to the first resource. Only valid for
      * content-type=application/package.
      */
     readonly attribute ACString preamble;
+
+    /**
+     * The original http response header in each part.
+     */
+    readonly attribute ACString originalResponseHeader;
 };
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsIPackagedAppVerifier.idl
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIStreamListener.idl"
+
+interface nsIURI;
+interface nsICacheEntry;
+interface nsIPackagedAppVerifierListener;
+
+/**
+ * nsIPackagedAppVerifier
+ *
+ * It inherits nsIStreamListener and all the data will be fed by
+ * onStartRequest/onDataAvailable/onStopRequest.
+ *
+ */
+[scriptable, uuid(16419a80-4cc3-11e5-b970-0800200c9a66)]
+interface nsIPackagedAppVerifier : nsIStreamListener
+{
+  // The package origin of either a signed or unsigned package.
+  readonly attribute ACString packageOrigin;
+
+  // Whether this package is signed.
+  readonly attribute boolean isPackageSigned;
+
+  /**
+   * @param aListener
+   *    an object implementing nsIPackagedAppVerifierListener as the bridge that
+   *    the client gets callback from the package verifier. The callback might be
+   *    sync or async depending on the implementation.
+   *
+   * @param aPackageOrigin
+   *    the origin of the package. It will be updated based on the package
+   *    identifier defined in the manifest.
+   *
+   * @param aSignature
+   *    the signature of the package we desire to verify against. See
+   *    https://wiki.mozilla.org/User:Ptheriault/Packagedprivilegedcontent#The_Signed_Manifest
+   *    for further information.
+   *
+   * @param aPackageCacheEntry
+   *    the cache entry of the package itself (not the resource's cache).
+   *    It will be used to store any necessary information like the signed
+   *    package origin.
+   *
+   * The verifier init function.
+   */
+  void init(in nsIPackagedAppVerifierListener aListener,
+            in ACString aPackageOrigin,
+            in ACString aSignature,
+            in nsICacheEntry aPackageCacheEntry);
+
+  /**
+   * @param aUri
+   *    the URI of the resource.
+   *
+   * @param aCacheEntry
+   *    the cache entry of the resource.
+   *
+   * @param aStatusCode
+   *    the status code of the resource we just finished download.
+   *
+   * @param aIsLastPart
+   *    whether this resource is the last one in the package.
+   *
+   * Create an object that we will pass to the verifier as a user context
+   * through onStartRequest. The main purpose of this function is to make
+   * nsIPackagedAppVerifier xpcshell-testable. See test_packaged_app_verifier.js.
+   *
+   */
+  nsISupports createResourceCacheInfo(in nsIURI aUri,
+                                      in nsICacheEntry aCacheEntry,
+                                      in nsresult aStatusCode,
+                                      in boolean aIsLastPart);
+};
+
+/**
+ * nsIPackagedAppVerifierListener
+ */
+[scriptable, uuid(092eba70-4cbf-11e5-b970-0800200c9a66)]
+interface nsIPackagedAppVerifierListener : nsISupports
+{
+  /**
+   * @param aIsManifest
+   *    indicate if this callback is for manifest or not. True for manifest and false
+   *    for resource.
+   *
+   * @param aUri
+   *    the URI of the resource that has just been verified.
+   *
+   * @param aCacheEntry
+   *    the cache entry of the resource that has just been verified.
+   *
+   * @param aStatusCode
+   *    the resource download status code from nsIMultipartChannel.
+   *
+   * @param aIsLastPart
+   *    indicate if the verified resource is that last one in the package.
+   *
+   * @param aVerificationSuccess
+   *    the verification result.
+   *
+   * Callback'ed when a manifest/resource is verified.
+   */
+  void onVerified(in boolean aIsManifest,
+                  in nsIURI aUri,
+                  in nsICacheEntry aCacheEntry,
+                  in nsresult aStatusCode,
+                  in boolean aIsLastPart,
+                  in boolean aVerificationSuccess);
+};
+
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -901,16 +901,25 @@
 #define NS_PACKAGEDAPPSERVICE_CID                      \
 {   /* adef6762-41b9-4470-a06a-dc29cf8de381 */         \
     0xadef6762,                                        \
     0x41b9,                                            \
     0x4470,                                            \
   { 0xa0, 0x6a, 0xdc, 0x29, 0xcf, 0x8d, 0xe3, 0x81 }   \
 }
 
+#define NS_PACKAGEDAPPVERIFIER_CONTRACTID \
+    "@mozilla.org/network/packaged-app-verifier;1"
+#define NS_PACKAGEDAPPVERIFIER_CID                      \
+{   /* 07242d20-4cae-11e5-b970-0800200c9a66 */         \
+    0x07242d20,                                        \
+    0x4cae,                                            \
+    0x11e5,                                            \
+  { 0xb9, 0x70, 0x08, 0x00, 0x20, 0x0c, 0x96, 0x66 }   \
+}
 
 /******************************************************************************
  * netwerk/cookie classes
  */
 
 // service implementing nsICookieManager and nsICookieManager2.
 #define NS_COOKIEMANAGER_CONTRACTID \
     "@mozilla.org/cookiemanager;1"
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -259,20 +259,22 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpAct
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpBasicAuth)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpDigestAuth)
 } // namespace net
 } // namespace mozilla
 #endif // !NECKO_PROTOCOL_http
 
 #include "mozilla/net/Dashboard.h"
 #include "mozilla/net/PackagedAppService.h"
+#include "mozilla/net/PackagedAppVerifier.h"
 namespace mozilla {
 namespace net {
   NS_GENERIC_FACTORY_CONSTRUCTOR(Dashboard)
   NS_GENERIC_FACTORY_CONSTRUCTOR(PackagedAppService)
+  NS_GENERIC_FACTORY_CONSTRUCTOR(PackagedAppVerifier)
 } // namespace net
 } // namespace mozilla
 #include "AppProtocolHandler.h"
 
 #ifdef NECKO_PROTOCOL_res
 // resource
 #include "nsResProtocolHandler.h"
 #include "ExtensionProtocolHandler.h"
@@ -727,16 +729,17 @@ NS_DEFINE_NAMED_CID(NS_AUTHURLPARSER_CID
 NS_DEFINE_NAMED_CID(NS_STANDARDURL_CID);
 NS_DEFINE_NAMED_CID(NS_ARRAYBUFFERINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_BUFFEREDINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_BUFFEREDOUTPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_MIMEINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_PROTOCOLPROXYSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_STREAMCONVERTERSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_PACKAGEDAPPSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PACKAGEDAPPVERIFIER_CID);
 NS_DEFINE_NAMED_CID(NS_DASHBOARD_CID);
 #ifdef NECKO_PROTOCOL_ftp
 NS_DEFINE_NAMED_CID(NS_FTPDIRLISTINGCONVERTER_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_NSINDEXEDTOHTMLCONVERTER_CID);
 NS_DEFINE_NAMED_CID(NS_DIRINDEXPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_MULTIMIXEDCONVERTER_CID);
 NS_DEFINE_NAMED_CID(NS_UNKNOWNDECODER_CID);
@@ -875,16 +878,17 @@ static const mozilla::Module::CIDEntry k
     { &kNS_STANDARDURL_CID, false, nullptr, nsStandardURLConstructor },
     { &kNS_ARRAYBUFFERINPUTSTREAM_CID, false, nullptr, ArrayBufferInputStreamConstructor },
     { &kNS_BUFFEREDINPUTSTREAM_CID, false, nullptr, nsBufferedInputStream::Create },
     { &kNS_BUFFEREDOUTPUTSTREAM_CID, false, nullptr, nsBufferedOutputStream::Create },
     { &kNS_MIMEINPUTSTREAM_CID, false, nullptr, nsMIMEInputStreamConstructor },
     { &kNS_PROTOCOLPROXYSERVICE_CID, true, nullptr, nsProtocolProxyServiceConstructor },
     { &kNS_STREAMCONVERTERSERVICE_CID, false, nullptr, CreateNewStreamConvServiceFactory },
     { &kNS_PACKAGEDAPPSERVICE_CID, false, NULL, mozilla::net::PackagedAppServiceConstructor },
+    { &kNS_PACKAGEDAPPVERIFIER_CID, false, NULL, mozilla::net::PackagedAppVerifierConstructor },
     { &kNS_DASHBOARD_CID, false, nullptr, mozilla::net::DashboardConstructor },
 #ifdef NECKO_PROTOCOL_ftp
     { &kNS_FTPDIRLISTINGCONVERTER_CID, false, nullptr, CreateNewFTPDirListingConv },
 #endif
     { &kNS_NSINDEXEDTOHTMLCONVERTER_CID, false, nullptr, nsIndexedToHTML::Create },
     { &kNS_DIRINDEXPARSER_CID, false, nullptr, nsDirIndexParserConstructor },
     { &kNS_MULTIMIXEDCONVERTER_CID, false, nullptr, CreateNewMultiMixedConvFactory },
     { &kNS_UNKNOWNDECODER_CID, false, nullptr, CreateNewUnknownDecoderFactory },
@@ -1025,16 +1029,17 @@ static const mozilla::Module::ContractID
     { NS_STANDARDURL_CONTRACTID, &kNS_STANDARDURL_CID },
     { NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID, &kNS_ARRAYBUFFERINPUTSTREAM_CID },
     { NS_BUFFEREDINPUTSTREAM_CONTRACTID, &kNS_BUFFEREDINPUTSTREAM_CID },
     { NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &kNS_BUFFEREDOUTPUTSTREAM_CID },
     { NS_MIMEINPUTSTREAM_CONTRACTID, &kNS_MIMEINPUTSTREAM_CID },
     { NS_PROTOCOLPROXYSERVICE_CONTRACTID, &kNS_PROTOCOLPROXYSERVICE_CID },
     { NS_STREAMCONVERTERSERVICE_CONTRACTID, &kNS_STREAMCONVERTERSERVICE_CID },
     { NS_PACKAGEDAPPSERVICE_CONTRACTID, &kNS_PACKAGEDAPPSERVICE_CID },
+    { NS_PACKAGEDAPPVERIFIER_CONTRACTID, &kNS_PACKAGEDAPPVERIFIER_CID },
     { NS_DASHBOARD_CONTRACTID, &kNS_DASHBOARD_CID },
 #ifdef NECKO_PROTOCOL_ftp
     { NS_ISTREAMCONVERTER_KEY FTP_TO_INDEX, &kNS_FTPDIRLISTINGCONVERTER_CID },
 #endif
     { NS_ISTREAMCONVERTER_KEY INDEX_TO_HTML, &kNS_NSINDEXEDTOHTMLCONVERTER_CID },
     { NS_DIRINDEXPARSER_CONTRACTID, &kNS_DIRINDEXPARSER_CID },
     { NS_ISTREAMCONVERTER_KEY MULTI_MIXED_X, &kNS_MULTIMIXEDCONVERTER_CID },
     { NS_ISTREAMCONVERTER_KEY MULTI_BYTERANGES, &kNS_MULTIMIXEDCONVERTER_CID },
--- a/netwerk/cache2/AppCacheStorage.cpp
+++ b/netwerk/cache2/AppCacheStorage.cpp
@@ -20,17 +20,17 @@
 
 namespace mozilla {
 namespace net {
 
 NS_IMPL_ISUPPORTS_INHERITED0(AppCacheStorage, CacheStorage)
 
 AppCacheStorage::AppCacheStorage(nsILoadContextInfo* aInfo,
                                  nsIApplicationCache* aAppCache)
-: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */)
+: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */, false /* skip size check */)
 , mAppCache(aAppCache)
 {
   MOZ_COUNT_CTOR(AppCacheStorage);
 }
 
 AppCacheStorage::~AppCacheStorage()
 {
   ProxyReleaseMainThread(mAppCache);
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -158,25 +158,27 @@ nsresult CacheEntry::Callback::OnAvailTh
 NS_IMPL_ISUPPORTS(CacheEntry,
                   nsICacheEntry,
                   nsIRunnable,
                   CacheFileListener)
 
 CacheEntry::CacheEntry(const nsACString& aStorageID,
                        nsIURI* aURI,
                        const nsACString& aEnhanceID,
-                       bool aUseDisk)
+                       bool aUseDisk,
+                       bool aSkipSizeCheck)
 : mFrecency(0)
 , mSortingExpirationTime(uint32_t(-1))
 , mLock("CacheEntry")
 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
 , mURI(aURI)
 , mEnhanceID(aEnhanceID)
 , mStorageID(aStorageID)
 , mUseDisk(aUseDisk)
+, mSkipSizeCheck(aSkipSizeCheck)
 , mIsDoomed(false)
 , mSecurityInfoLoaded(false)
 , mPreventCallbacks(false)
 , mHasData(false)
 , mState(NOTLOADED)
 , mRegistration(NEVERREGISTERED)
 , mWriter(nullptr)
 , mPredictedDataSize(0)
@@ -386,16 +388,17 @@ bool CacheEntry::Load(bool aTruncate, bo
         CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
     }
 
     LOG(("  performing load, file=%p", mFile.get()));
     if (NS_SUCCEEDED(rv)) {
       rv = mFile->Init(fileKey,
                        aTruncate,
                        !mUseDisk,
+                       mSkipSizeCheck,
                        aPriority,
                        directLoad ? nullptr : this);
     }
 
     if (NS_FAILED(rv)) {
       mFileStatus = rv;
       AsyncDoom(nullptr);
       return false;
@@ -481,16 +484,17 @@ already_AddRefed<CacheEntryHandle> Cache
   nsRefPtr<CacheEntry> newEntry;
   {
     mozilla::MutexAutoUnlock unlock(mLock);
 
     // The following call dooms this entry (calls DoomAlreadyRemoved on us)
     nsresult rv = CacheStorageService::Self()->AddStorageEntry(
       GetStorageID(), GetURI(), GetEnhanceID(),
       mUseDisk && !aMemoryOnly,
+      mSkipSizeCheck,
       true, // always create
       true, // truncate existing (this one)
       getter_AddRefs(handle));
 
     if (NS_SUCCEEDED(rv)) {
       newEntry = handle->Entry();
       LOG(("  exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
       newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
@@ -1137,17 +1141,17 @@ NS_IMETHODIMP CacheEntry::GetPredictedDa
 {
   *aPredictedDataSize = mPredictedDataSize;
   return NS_OK;
 }
 NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
 {
   mPredictedDataSize = aPredictedDataSize;
 
-  if (CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
+  if (!mSkipSizeCheck && CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
     LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this));
     AsyncDoom(nullptr);
 
     return NS_ERROR_FILE_TOO_BIG;
   }
 
   return NS_OK;
 }
--- a/netwerk/cache2/CacheEntry.h
+++ b/netwerk/cache2/CacheEntry.h
@@ -50,17 +50,17 @@ class CacheEntry final : public nsICache
                        , public CacheFileListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICACHEENTRY
   NS_DECL_NSIRUNNABLE
 
   CacheEntry(const nsACString& aStorageID, nsIURI* aURI, const nsACString& aEnhanceID,
-             bool aUseDisk);
+             bool aUseDisk, bool aSkipSizeCheck);
 
   void AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags);
 
   CacheEntryHandle* NewHandle();
 
 public:
   uint32_t GetMetadataMemoryConsumption();
   nsCString const &GetStorageID() const { return mStorageID; }
@@ -271,16 +271,19 @@ private:
   ::mozilla::Atomic<nsresult, ::mozilla::ReleaseAcquire> mFileStatus;
   nsCOMPtr<nsIURI> mURI;
   nsCString mEnhanceID;
   nsCString mStorageID;
 
   // Whether it's allowed to persist the data to disk
   bool const mUseDisk;
 
+  // Whether it should skip max size check.
+  bool const mSkipSizeCheck;
+
   // Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved().
   // Left as a standalone flag to not bother with locking (there is no need).
   bool mIsDoomed;
 
   // Following flags are all synchronized with the cache entry lock.
 
   // Whether security info has already been looked up in metadata.
   bool mSecurityInfoLoaded : 1;
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -178,16 +178,17 @@ NS_INTERFACE_MAP_BEGIN(CacheFile)
                                    mozilla::net::CacheFileChunkListener)
 NS_INTERFACE_MAP_END_THREADSAFE
 
 CacheFile::CacheFile()
   : mLock("CacheFile.mLock")
   , mOpeningFile(false)
   , mReady(false)
   , mMemoryOnly(false)
+  , mSkipSizeCheck(false)
   , mOpenAsMemoryOnly(false)
   , mPriority(false)
   , mDataAccessed(false)
   , mDataIsDirty(false)
   , mWritingMetadata(false)
   , mPreloadWithoutInputStreams(true)
   , mPreloadChunkCount(0)
   , mStatus(NS_OK)
@@ -207,26 +208,28 @@ CacheFile::~CacheFile()
     WriteMetadataIfNeededLocked(true);
   }
 }
 
 nsresult
 CacheFile::Init(const nsACString &aKey,
                 bool aCreateNew,
                 bool aMemoryOnly,
+                bool aSkipSizeCheck,
                 bool aPriority,
                 CacheFileListener *aCallback)
 {
   MOZ_ASSERT(!mListener);
   MOZ_ASSERT(!mHandle);
 
   nsresult rv;
 
   mKey = aKey;
   mOpenAsMemoryOnly = mMemoryOnly = aMemoryOnly;
+  mSkipSizeCheck = aSkipSizeCheck;
   mPriority = aPriority;
 
   // Some consumers (at least nsHTTPCompressConv) assume that Read() can read
   // such amount of data that was announced by Available().
   // CacheFileInputStream::Available() uses also preloaded chunks to compute
   // number of available bytes in the input stream, so we have to make sure the
   // preloadChunkCount won't change during CacheFile's lifetime since otherwise
   // we could potentially release some cached chunks that was used to calculate
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -51,16 +51,17 @@ class CacheFile final : public CacheFile
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   CacheFile();
 
   nsresult Init(const nsACString &aKey,
                 bool aCreateNew,
                 bool aMemoryOnly,
+                bool aSkipSizeCheck,
                 bool aPriority,
                 CacheFileListener *aCallback);
 
   NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
                               CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk) override;
@@ -188,16 +189,17 @@ private:
   void SetError(nsresult aStatus);
 
   nsresult InitIndexEntry();
 
   mozilla::Mutex mLock;
   bool           mOpeningFile;
   bool           mReady;
   bool           mMemoryOnly;
+  bool           mSkipSizeCheck;
   bool           mOpenAsMemoryOnly;
   bool           mPriority;
   bool           mDataAccessed;
   bool           mDataIsDirty;
   bool           mWritingMetadata;
   bool           mPreloadWithoutInputStreams;
   uint32_t       mPreloadChunkCount;
   nsresult       mStatus;
--- a/netwerk/cache2/CacheFileOutputStream.cpp
+++ b/netwerk/cache2/CacheFileOutputStream.cpp
@@ -89,17 +89,17 @@ CacheFileOutputStream::Write(const char 
 
   if (mClosed) {
     LOG(("CacheFileOutputStream::Write() - Stream is closed. [this=%p, "
          "status=0x%08x]", this, mStatus));
 
     return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
   }
 
-  if (CacheObserver::EntryIsTooBig(mPos + aCount, !mFile->mMemoryOnly)) {
+  if (!mFile->mSkipSizeCheck && CacheObserver::EntryIsTooBig(mPos + aCount, !mFile->mMemoryOnly)) {
     LOG(("CacheFileOutputStream::Write() - Entry is too big, failing and "
          "dooming the entry. [this=%p]", this));
 
     mFile->DoomLocked(nullptr);
     CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
     return NS_ERROR_FILE_TOO_BIG;
   }
 
--- a/netwerk/cache2/CacheStorage.cpp
+++ b/netwerk/cache2/CacheStorage.cpp
@@ -20,20 +20,22 @@
 
 namespace mozilla {
 namespace net {
 
 NS_IMPL_ISUPPORTS(CacheStorage, nsICacheStorage)
 
 CacheStorage::CacheStorage(nsILoadContextInfo* aInfo,
                            bool aAllowDisk,
-                           bool aLookupAppCache)
+                           bool aLookupAppCache,
+                           bool aSkipSizeCheck)
 : mLoadContextInfo(GetLoadContextInfo(aInfo))
 , mWriteToDisk(aAllowDisk)
 , mLookupAppCache(aLookupAppCache)
+, mSkipSizeCheck(aSkipSizeCheck)
 {
 }
 
 CacheStorage::~CacheStorage()
 {
 }
 
 NS_IMETHODIMP CacheStorage::AsyncOpenURI(nsIURI *aURI,
--- a/netwerk/cache2/CacheStorage.h
+++ b/netwerk/cache2/CacheStorage.h
@@ -47,29 +47,32 @@ private:
 class CacheStorage : public nsICacheStorage
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICACHESTORAGE
 
 public:
   CacheStorage(nsILoadContextInfo* aInfo,
                bool aAllowDisk,
-               bool aLookupAppCache);
+               bool aLookupAppCache,
+               bool aSkipSizeCheck);
 
 protected:
   virtual ~CacheStorage();
 
   nsresult ChooseApplicationCache(nsIURI* aURI, nsIApplicationCache** aCache);
 
   nsRefPtr<LoadContextInfo> mLoadContextInfo;
   bool mWriteToDisk : 1;
   bool mLookupAppCache : 1;
+  bool mSkipSizeCheck: 1;
 
 public:
   nsILoadContextInfo* LoadInfo() const { return mLoadContextInfo; }
   bool WriteToDisk() const { return mWriteToDisk && !mLoadContextInfo->IsPrivate(); }
   bool LookupAppCache() const { return mLookupAppCache; }
+  bool SkipSizeCheck() const { return mSkipSizeCheck; }
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -676,17 +676,17 @@ nsresult CacheStorageService::Dispatch(n
 NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(nsILoadContextInfo *aLoadContextInfo,
                                                       nsICacheStorage * *_retval)
 {
   NS_ENSURE_ARG(aLoadContextInfo);
   NS_ENSURE_ARG(_retval);
 
   nsCOMPtr<nsICacheStorage> storage;
   if (CacheObserver::UseNewCache()) {
-    storage = new CacheStorage(aLoadContextInfo, false, false);
+    storage = new CacheStorage(aLoadContextInfo, false, false, false);
   }
   else {
     storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
@@ -701,17 +701,17 @@ NS_IMETHODIMP CacheStorageService::DiskC
   // TODO save some heap granularity - cache commonly used storages.
 
   // When disk cache is disabled, still provide a storage, but just keep stuff
   // in memory.
   bool useDisk = CacheObserver::UseDiskCache();
 
   nsCOMPtr<nsICacheStorage> storage;
   if (CacheObserver::UseNewCache()) {
-    storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache);
+    storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache, false);
   }
   else {
     storage = new _OldStorage(aLoadContextInfo, useDisk, aLookupAppCache, false, nullptr);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
@@ -732,16 +732,34 @@ NS_IMETHODIMP CacheStorageService::AppCa
   else {
     storage = new _OldStorage(aLoadContextInfo, true, false, true, aApplicationCache);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
 
+NS_IMETHODIMP CacheStorageService::SynthesizedCacheStorage(nsILoadContextInfo *aLoadContextInfo,
+                                                           nsICacheStorage * *_retval)
+{
+  NS_ENSURE_ARG(aLoadContextInfo);
+  NS_ENSURE_ARG(_retval);
+
+  nsCOMPtr<nsICacheStorage> storage;
+  if (CacheObserver::UseNewCache()) {
+    storage = new CacheStorage(aLoadContextInfo, false, false, true /* skip size checks for synthesized cache */);
+  }
+  else {
+    storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
+  }
+
+  storage.forget(_retval);
+  return NS_OK;
+}
+
 NS_IMETHODIMP CacheStorageService::Clear()
 {
   nsresult rv;
 
   if (CacheObserver::UseNewCache()) {
     {
       mozilla::MutexAutoLock lock(mLock);
 
@@ -1334,25 +1352,27 @@ CacheStorageService::AddStorageEntry(Cac
   NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   NS_ENSURE_ARG(aStorage);
 
   nsAutoCString contextKey;
   CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
 
   return AddStorageEntry(contextKey, aURI, aIdExtension,
-                         aStorage->WriteToDisk(), aCreateIfNotExist, aReplace,
+                         aStorage->WriteToDisk(), aStorage->SkipSizeCheck(),
+                         aCreateIfNotExist, aReplace,
                          aResult);
 }
 
 nsresult
 CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
                                      nsIURI* aURI,
                                      const nsACString & aIdExtension,
                                      bool aWriteToDisk,
+                                     bool aSkipSizeCheck,
                                      bool aCreateIfNotExist,
                                      bool aReplace,
                                      CacheEntryHandle** aResult)
 {
   NS_ENSURE_ARG(aURI);
 
   nsresult rv;
 
@@ -1405,17 +1425,17 @@ CacheStorageService::AddStorageEntry(nsC
 
       entry = nullptr;
       entryExists = false;
     }
 
     // Ensure entry for the particular URL, if not read/only
     if (!entryExists && (aCreateIfNotExist || aReplace)) {
       // Entry is not in the hashtable or has just been truncated...
-      entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk);
+      entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk, aSkipSizeCheck);
       entries->Put(entryKey, entry);
       LOG(("  new entry %p for %s", entry.get(), entryKey.get()));
     }
 
     if (entry) {
       // Here, if this entry was not for a long time referenced by any consumer,
       // gets again first 'handles count' reference.
       handle = entry->NewHandle();
--- a/netwerk/cache2/CacheStorageService.h
+++ b/netwerk/cache2/CacheStorageService.h
@@ -273,16 +273,17 @@ private:
   nsresult DoomStorageEntries(nsCSubstring const& aContextKey,
                               nsILoadContextInfo* aContext,
                               bool aDiskStorage,
                               nsICacheEntryDoomCallback* aCallback);
   nsresult AddStorageEntry(nsCSubstring const& aContextKey,
                            nsIURI* aURI,
                            const nsACString & aIdExtension,
                            bool aWriteToDisk,
+                           bool aSkipSizeCheck,
                            bool aCreateIfNotExist,
                            bool aReplace,
                            CacheEntryHandle** aResult);
 
   static CacheStorageService* sSelf;
 
   mozilla::Mutex mLock;
   mozilla::Mutex mForcedValidEntriesLock;
--- a/netwerk/cache2/nsICacheStorageService.idl
+++ b/netwerk/cache2/nsICacheStorageService.idl
@@ -8,17 +8,17 @@ interface nsICacheStorage;
 interface nsILoadContextInfo;
 interface nsIApplicationCache;
 interface nsIEventTarget;
 interface nsICacheStorageConsumptionObserver;
 
 /**
  * Provides access to particual cache storages of the network URI cache.
  */
-[scriptable, uuid(44de2fa4-1b0e-4cd3-9e32-211e936f721e)]
+[scriptable, uuid(9c9dc1d6-533e-4716-9ad8-11e08c3763b3)]
 interface nsICacheStorageService : nsISupports
 {
   /**
    * Get storage where entries will only remain in memory, never written
    * to the disk.
    *
    * NOTE: Any existing disk entry for [URL|id-extension] will be doomed
    * prior opening an entry using this memory-only storage.  Result of
@@ -51,16 +51,23 @@ interface nsICacheStorageService : nsISu
    * @param aApplicationCache
    *    Optional reference to an existing appcache.  When left null, this will
    *    work with offline cache as a whole.
    */
   nsICacheStorage appCacheStorage(in nsILoadContextInfo aLoadContextInfo,
                                   in nsIApplicationCache aApplicationCache);
 
   /**
+   * Get storage for synthesized cache entries that we currently use for ServiceWorker interception in non-e10s mode.
+   *
+   * This cache storage has no limits on file size to allow the ServiceWorker to intercept large files.
+   */
+  nsICacheStorage synthesizedCacheStorage(in nsILoadContextInfo aLoadContextInfo);
+
+  /**
    * Evict the whole cache.
    */
   void clear();
 
   /**
    * Purge only data of disk backed entries.  Metadata are left for
    * performance purposes.
    */
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -50,16 +50,18 @@ FTPChannelParent::FTPChannelParent(const
   CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler);
   MOZ_ASSERT(handler, "no ftp handler");
 
   if (aIframeEmbedding.type() == PBrowserOrId::TPBrowserParent) {
     mTabParent = static_cast<dom::TabParent*>(aIframeEmbedding.get_PBrowserParent());
   }
 
   mObserver = new OfflineObserver(this);
+
+  mEventQ = new ChannelEventQueue(static_cast<nsIParentChannel*>(this));
 }
 
 FTPChannelParent::~FTPChannelParent()
 {
   gFtpHandler->Release();
   if (mObserver) {
     mObserver->RemoveObserver();
   }
@@ -238,16 +240,42 @@ FTPChannelParent::RecvSuspend()
 bool
 FTPChannelParent::RecvResume()
 {
   if (mChannel)
     mChannel->Resume();
   return true;
 }
 
+class FTPDivertDataAvailableEvent : public ChannelEvent
+{
+public:
+  FTPDivertDataAvailableEvent(FTPChannelParent* aParent,
+                              const nsCString& data,
+                              const uint64_t& offset,
+                              const uint32_t& count)
+  : mParent(aParent)
+  , mData(data)
+  , mOffset(offset)
+  , mCount(count)
+  {
+  }
+
+  void Run()
+  {
+    mParent->DivertOnDataAvailable(mData, mOffset, mCount);
+  }
+
+private:
+  FTPChannelParent* mParent;
+  nsCString mData;
+  uint64_t mOffset;
+  uint32_t mCount;
+};
+
 bool
 FTPChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
                                             const uint64_t& offset,
                                             const uint32_t& count)
 {
   if (NS_WARN_IF(!mDivertingFromChild)) {
     MOZ_ASSERT(mDivertingFromChild,
                "Cannot RecvDivertOnDataAvailable if diverting is not set!");
@@ -255,81 +283,185 @@ FTPChannelParent::RecvDivertOnDataAvaila
     return false;
   }
 
   // Drop OnDataAvailables if the parent was canceled already.
   if (NS_FAILED(mStatus)) {
     return true;
   }
 
+  if (mEventQ->ShouldEnqueue()) {
+    mEventQ->Enqueue(new FTPDivertDataAvailableEvent(this, data, offset,
+                                                     count));
+    return true;
+  }
+
+  DivertOnDataAvailable(data, offset, count);
+  return true;
+}
+
+void
+FTPChannelParent::DivertOnDataAvailable(const nsCString& data,
+                                        const uint64_t& offset,
+                                        const uint32_t& count)
+{
+  LOG(("FTPChannelParent::DivertOnDataAvailable [this=%p]\n", this));
+
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot DivertOnDataAvailable if diverting is not set!");
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  // Drop OnDataAvailables if the parent was canceled already.
+  if (NS_FAILED(mStatus)) {
+    return;
+  }
+
   nsCOMPtr<nsIInputStream> stringStream;
   nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
                                       count, NS_ASSIGNMENT_DEPEND);
   if (NS_FAILED(rv)) {
     if (mChannel) {
       mChannel->Cancel(rv);
     }
     mStatus = rv;
-    return true;
+    return;
   }
 
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
   rv = OnDataAvailable(mChannel, nullptr, stringStream, offset, count);
 
   stringStream->Close();
   if (NS_FAILED(rv)) {
     if (mChannel) {
       mChannel->Cancel(rv);
     }
     mStatus = rv;
   }
-  return true;
 }
 
+class FTPDivertStopRequestEvent : public ChannelEvent
+{
+public:
+  FTPDivertStopRequestEvent(FTPChannelParent* aParent,
+                            const nsresult& statusCode)
+  : mParent(aParent)
+  , mStatusCode(statusCode)
+  {
+  }
+
+  void Run() {
+    mParent->DivertOnStopRequest(mStatusCode);
+  }
+
+private:
+  FTPChannelParent* mParent;
+  nsresult mStatusCode;
+};
+
 bool
 FTPChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
 {
   if (NS_WARN_IF(!mDivertingFromChild)) {
     MOZ_ASSERT(mDivertingFromChild,
                "Cannot RecvDivertOnStopRequest if diverting is not set!");
     FailDiversion(NS_ERROR_UNEXPECTED);
     return false;
   }
 
+  if (mEventQ->ShouldEnqueue()) {
+    mEventQ->Enqueue(new FTPDivertStopRequestEvent(this, statusCode));
+    return true;
+  }
+
+  DivertOnStopRequest(statusCode);
+  return true;
+}
+
+void
+FTPChannelParent::DivertOnStopRequest(const nsresult& statusCode)
+{
+  LOG(("FTPChannelParent::DivertOnStopRequest [this=%p]\n", this));
+
+  if (NS_WARN_IF(!mDivertingFromChild)) {
+    MOZ_ASSERT(mDivertingFromChild,
+               "Cannot DivertOnStopRequest if diverting is not set!");
+    FailDiversion(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
   // Honor the channel's status even if the underlying transaction completed.
   nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
 
   // Reset fake pending status in case OnStopRequest has already been called.
   if (mChannel) {
     nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel);
     if (forcePendingIChan) {
       forcePendingIChan->ForcePending(false);
     }
   }
 
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
   OnStopRequest(mChannel, nullptr, status);
-  return true;
 }
 
+class FTPDivertCompleteEvent : public ChannelEvent
+{
+public:
+  explicit FTPDivertCompleteEvent(FTPChannelParent* aParent)
+  : mParent(aParent)
+  {
+  }
+
+  void Run() {
+    mParent->DivertComplete();
+  }
+
+private:
+  FTPChannelParent* mParent;
+};
+
 bool
 FTPChannelParent::RecvDivertComplete()
 {
   if (NS_WARN_IF(!mDivertingFromChild)) {
     MOZ_ASSERT(mDivertingFromChild,
                "Cannot RecvDivertComplete if diverting is not set!");