Merge inbound to m-c. a=merge FIREFOX_AURORA_37_BASE
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 12 Jan 2015 15:26:50 -0500
changeset 249212 2c951493eef5b50b8085ef78ffe0d7902ff3d593
parent 249139 34c1ede59bebf0bf01cb614718939c135ddf0bbf (current diff)
parent 249211 332fa08016f81abeceb85cd1265099e891e16b6c (diff)
child 249213 c8b4096b0934f8e8a864878a83b16e4c949a7aa9
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone37.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 inbound to m-c. a=merge
js/src/jit-test/tests/gc/nursery-finalize.js
--- a/build/upload.py
+++ b/build/upload.py
@@ -19,17 +19,18 @@
 #
 # All files to be uploaded should be passed as commandline arguments to this
 # script. The script takes one other parameter, --base-path, which you can use
 # to indicate that files should be uploaded including their paths relative
 # to the base path.
 
 import sys, os
 from optparse import OptionParser
-from subprocess import PIPE, Popen, check_call
+from subprocess import check_call, check_output
+import redo
 
 def RequireEnvironmentVariable(v):
     """Return the value of the environment variable named v, or print
     an error and exit if it's unset (or empty)."""
     if not v in os.environ or os.environ[v] == "":
         print "Error: required environment variable %s not set" % v
         sys.exit(1)
     return os.environ[v]
@@ -77,31 +78,35 @@ def AppendOptionalArgsToSSHCommandline(c
         cmdline.extend(["-o", "IdentityFile=%s" % ssh_key])
 
 def DoSSHCommand(command, user, host, port=None, ssh_key=None):
     """Execute command on user@host using ssh. Optionally use
     port and ssh_key, if provided."""
     cmdline = ["ssh"]
     AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key)
     cmdline.extend(["%s@%s" % (user, host), command])
-    cmd = Popen(cmdline, stdout=PIPE)
-    retcode = cmd.wait()
-    if retcode != 0:
-        raise Exception("Command %s returned non-zero exit code: %i" % \
-          (cmdline, retcode))
-    return cmd.stdout.read().strip()
+
+    with redo.retrying(check_output, sleeptime=10) as f:
+        output = f(cmdline).strip()
+        return output
+
+    raise Exception("Command %s returned non-zero exit code" % cmdline)
 
 def DoSCPFile(file, remote_path, user, host, port=None, ssh_key=None):
     """Upload file to user@host:remote_path using scp. Optionally use
     port and ssh_key, if provided."""
     cmdline = ["scp"]
     AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key)
     cmdline.extend([WindowsPathToMsysPath(file),
                     "%s@%s:%s" % (user, host, remote_path)])
-    check_call(cmdline)
+    with redo.retrying(check_call, sleeptime=10) as f:
+        f(cmdline)
+        return
+
+    raise Exception("Command %s returned non-zero exit code" % cmdline)
 
 def GetRemotePath(path, local_file, base_path):
     """Given a remote path to upload to, a full path to a local file, and an
     optional full path that is a base path of the local file, construct the
     full remote path to place the file in. If base_path is not None, include
     the relative path from base_path to file."""
     if base_path is None or not local_file.startswith(base_path):
         return path
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -19,8 +19,9 @@ mozilla.pth:dom/bindings/parser
 mozilla.pth:layout/tools/reftest
 moztreedocs.pth:tools/docs
 copy:build/buildconfig.py
 packages.txt:testing/mozbase/packages.txt
 objdir:build
 gyp.pth:media/webrtc/trunk/tools/gyp/pylib
 pyasn1.pth:python/pyasn1
 bitstring.pth:python/bitstring
+redo.pth:python/redo
--- a/dom/base/test/chrome/file_bug990812-2.xul
+++ b/dom/base/test/chrome/file_bug990812-2.xul
@@ -43,17 +43,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       var window = promiseMessage("window", messageManager);
       var group = promiseMessage("group", getGroupMessageManager("test"));
 
       var browser = document.querySelector("browser");
       browser.messageManager.loadFrameScript(FRAME_SCRIPT, true);
 
       Promise.all([global, window, group]).then(function () {
         opener.setTimeout("next()");
-        window.close();
+        self.close();
       });
     }
 
   ]]></script>
 
   <browser messagemanagergroup="test" type="content-targetable" src="about:mozilla" />
 
 </window>
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -280,16 +280,28 @@ class DescriptorProvider:
     def getDescriptor(self, interfaceName):
         """
         Gets the appropriate descriptor for the given interface name given the
         context of the current descriptor. This selects the appropriate
         implementation for cases like workers.
         """
         return self.config.getDescriptor(interfaceName, self.workers)
 
+def methodReturnsJSObject(method):
+    assert method.isMethod()
+    if method.returnsPromise():
+        return True
+
+    for signature in method.signatures():
+        returnType = signature[0]
+        if returnType.isObject() or returnType.isSpiderMonkeyInterface():
+            return True
+
+    return False
+
 class Descriptor(DescriptorProvider):
     """
     Represents a single descriptor for an interface. See Bindings.conf.
     """
     def __init__(self, config, interface, desc):
         DescriptorProvider.__init__(self, config, desc.get('workers', False))
         self.interface = interface
 
@@ -617,16 +629,21 @@ class Descriptor(DescriptorProvider):
                 (throws is not True and
                  ('Workers' not in throws or not self.workers) and
                  ('MainThread' not in throws or self.workers))):
                 attrs.append("infallible")
 
         name = member.identifier.name
         throws = self.interface.isJSImplemented() or member.getExtendedAttribute("Throws")
         if member.isMethod():
+            # JSObject-returning [NewObject] methods must be fallible,
+            # since they have to (fallibly) allocate the new JSObject.
+            if (member.getExtendedAttribute("NewObject") and
+                methodReturnsJSObject(member)):
+                throws = True
             attrs = self.extendedAttributes['all'].get(name, [])
             maybeAppendInfallibleToAttrs(attrs, throws)
             return attrs
 
         assert member.isAttr()
         assert bool(getter) != bool(setter)
         key = 'getterOnly' if getter else 'setterOnly'
         attrs = self.extendedAttributes['all'].get(name, []) + self.extendedAttributes[key].get(name, [])
--- a/dom/devicestorage/test/test_app_permissions.html
+++ b/dom/devicestorage/test/test_app_permissions.html
@@ -123,17 +123,17 @@ function TestEnumerate(iframe, data) {
 
   request.onerror = function(e) {
     isnot(data.shouldPass, true, "onfailure was called for type " + data.type + " Error: " + e.target.error.name);
     is(e.target.error.name, "SecurityError", "onerror should fire a SecurityError");
     testComplete(iframe, data);
   };
 }
 
-var gTestUri = "https://example.com/tests/dom/devicestorage/test/test_app_permissions.html"
+var gTestUri = "https://example.com/chrome/dom/devicestorage/test/test_app_permissions.html"
 
 var gData = [
 
   // Get
   // Web applications with no permissions
   {
     type: 'pictures',
     shouldPass: false,
--- a/dom/gamepad/Gamepad.cpp
+++ b/dom/gamepad/Gamepad.cpp
@@ -1,14 +1,15 @@
 /* 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 "Gamepad.h"
 #include "nsAutoPtr.h"
+#include "nsPIDOMWindow.h"
 #include "nsTArray.h"
 #include "nsVariant.h"
 #include "mozilla/dom/GamepadBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Gamepad)
@@ -16,32 +17,45 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(Gamepad
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Gamepad)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Gamepad, mParent, mButtons)
 
+void
+Gamepad::UpdateTimestamp()
+{
+  nsCOMPtr<nsPIDOMWindow> newWindow(do_QueryInterface(mParent));
+  if(newWindow) {
+    nsPerformance* perf = newWindow->GetPerformance();
+    if (perf) {
+      mTimestamp =  perf->GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now());
+    }
+  }
+}
+
 Gamepad::Gamepad(nsISupports* aParent,
                  const nsAString& aID, uint32_t aIndex,
                  GamepadMappingType aMapping,
                  uint32_t aNumButtons, uint32_t aNumAxes)
   : mParent(aParent),
     mID(aID),
     mIndex(aIndex),
     mMapping(aMapping),
     mConnected(true),
     mButtons(aNumButtons),
     mAxes(aNumAxes)
 {
   for (unsigned i = 0; i < aNumButtons; i++) {
     mButtons.InsertElementAt(i, new GamepadButton(mParent));
   }
   mAxes.InsertElementsAt(0, aNumAxes, 0.0f);
+  UpdateTimestamp();
 }
 
 void
 Gamepad::SetIndex(uint32_t aIndex)
 {
   mIndex = aIndex;
 }
 
@@ -52,26 +66,28 @@ Gamepad::SetConnected(bool aConnected)
 }
 
 void
 Gamepad::SetButton(uint32_t aButton, bool aPressed, double aValue)
 {
   MOZ_ASSERT(aButton < mButtons.Length());
   mButtons[aButton]->SetPressed(aPressed);
   mButtons[aButton]->SetValue(aValue);
+  UpdateTimestamp();
 }
 
 void
 Gamepad::SetAxis(uint32_t aAxis, double aValue)
 {
   MOZ_ASSERT(aAxis < mAxes.Length());
   if (mAxes[aAxis] != aValue) {
     mAxes[aAxis] = aValue;
     GamepadBinding::ClearCachedAxesValue(this);
   }
+  UpdateTimestamp();
 }
 
 void
 Gamepad::SyncState(Gamepad* aOther)
 {
   if (mButtons.Length() != aOther->mButtons.Length() ||
       mAxes.Length() != aOther->mAxes.Length()) {
     return;
@@ -85,16 +101,17 @@ Gamepad::SyncState(Gamepad* aOther)
   bool changed = false;
   for (uint32_t i = 0; i < mAxes.Length(); ++i) {
     changed = changed || (mAxes[i] != aOther->mAxes[i]);
     mAxes[i] = aOther->mAxes[i];
   }
   if (changed) {
     GamepadBinding::ClearCachedAxesValue(this);
   }
+  UpdateTimestamp();
 }
 
 already_AddRefed<Gamepad>
 Gamepad::Clone(nsISupports* aParent)
 {
   nsRefPtr<Gamepad> out =
     new Gamepad(aParent, mID, mIndex, mMapping,
                 mButtons.Length(), mAxes.Length());
--- a/dom/gamepad/Gamepad.h
+++ b/dom/gamepad/Gamepad.h
@@ -8,16 +8,17 @@
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/GamepadBinding.h"
 #include "mozilla/dom/GamepadButton.h"
 #include <stdint.h>
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
+#include "nsPerformance.h"
 
 namespace mozilla {
 namespace dom {
 
 // Per spec:
 // https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#remapping
 const int kStandardGamepadButtons = 17;
 const int kStandardGamepadAxes = 4;
@@ -60,16 +61,21 @@ public:
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   void GetId(nsAString& aID) const
   {
     aID = mID;
   }
 
+  DOMHighResTimeStamp Timestamp() const
+  {
+     return mTimestamp;
+  }
+
   GamepadMappingType Mapping()
   {
     return mMapping;
   }
 
   bool Connected() const
   {
     return mConnected;
@@ -87,29 +93,31 @@ public:
 
   void GetAxes(nsTArray<double>& aAxes) const
   {
     aAxes = mAxes;
   }
 
 private:
   virtual ~Gamepad() {}
+  void UpdateTimestamp();
 
 protected:
   nsCOMPtr<nsISupports> mParent;
   nsString mID;
   uint32_t mIndex;
 
   // The mapping in use.
   GamepadMappingType mMapping;
 
   // true if this gamepad is currently connected.
   bool mConnected;
 
   // Current state of buttons, axes.
   nsTArray<nsRefPtr<GamepadButton>> mButtons;
   nsTArray<double> mAxes;
+  DOMHighResTimeStamp mTimestamp;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_gamepad_Gamepad_h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4267,21 +4267,21 @@ HTMLMediaElement::VideoTracks()
 {
   if (!mVideoTrackList) {
     nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(OwnerDoc()->GetParentObject());
     mVideoTrackList = new VideoTrackList(window, this);
   }
   return mVideoTrackList;
 }
 
-/* readonly attribute TextTrackList textTracks; */
+/* readonly attribute TextTrackList? textTracks; */
 TextTrackList*
-HTMLMediaElement::TextTracks()
+HTMLMediaElement::GetTextTracks()
 {
-  return GetOrCreateTextTrackManager()->TextTracks();
+  return GetOrCreateTextTrackManager()->GetTextTracks();
 }
 
 already_AddRefed<TextTrack>
 HTMLMediaElement::AddTextTrack(TextTrackKind aKind,
                                const nsAString& aLabel,
                                const nsAString& aLanguage)
 {
   return
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -574,17 +574,17 @@ public:
   }
 
   void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
 
   AudioTrackList* AudioTracks();
 
   VideoTrackList* VideoTracks();
 
-  TextTrackList* TextTracks();
+  TextTrackList* GetTextTracks();
 
   already_AddRefed<TextTrack> AddTextTrack(TextTrackKind aKind,
                                            const nsAString& aLabel,
                                            const nsAString& aLanguage);
 
   void AddTextTrack(TextTrack* aTextTrack) {
     GetOrCreateTextTrackManager()->AddTextTrack(aTextTrack);
   }
--- a/dom/html/HTMLTrackElement.cpp
+++ b/dom/html/HTMLTrackElement.cpp
@@ -148,23 +148,22 @@ HTMLTrackElement::CreateTextTrack()
 
   TextTrackKind kind;
   if (const nsAttrValue* value = GetParsedAttr(nsGkAtoms::kind)) {
     kind = static_cast<TextTrackKind>(value->GetEnumValue());
   } else {
     kind = TextTrackKind::Subtitles;
   }
 
-  bool hasHadScriptObject = true;
-  nsIScriptGlobalObject* scriptObject =
-    OwnerDoc()->GetScriptHandlingObject(hasHadScriptObject);
+  nsISupports* parentObject =
+    OwnerDoc()->GetParentObject();
 
-  NS_ENSURE_TRUE_VOID(scriptObject || !hasHadScriptObject);
+  NS_ENSURE_TRUE_VOID(parentObject);
 
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(scriptObject);
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(parentObject);
   mTrack = new TextTrack(window, kind, label, srcLang,
                          TextTrackMode::Disabled,
                          TextTrackReadyState::NotLoaded,
                          TextTrackSource::Track);
   mTrack->SetTrackElement(this);
 
   if (mMediaParent) {
     mMediaParent->AddTextTrack(mTrack);
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -86,23 +86,22 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(TextTrac
 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextTrackManager)
 
 StaticRefPtr<nsIWebVTTParserWrapper> TextTrackManager::sParserWrapper;
 
 TextTrackManager::TextTrackManager(HTMLMediaElement *aMediaElement)
   : mMediaElement(aMediaElement)
   , performedTrackSelection(false)
 {
-  bool hasHadScriptObject = true;
-  nsIScriptGlobalObject* scriptObject =
-    mMediaElement->OwnerDoc()->GetScriptHandlingObject(hasHadScriptObject);
+  nsISupports* parentObject =
+    mMediaElement->OwnerDoc()->GetParentObject();
 
-  NS_ENSURE_TRUE_VOID(scriptObject || !hasHadScriptObject);
+  NS_ENSURE_TRUE_VOID(parentObject);
 
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(scriptObject);
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(parentObject);
   mNewCues = new TextTrackCueList(window);
   mTextTracks = new TextTrackList(window, this);
   mPendingTextTracks = new TextTrackList(window, this);
 
   if (!sParserWrapper) {
     nsCOMPtr<nsIWebVTTParserWrapper> parserWrapper =
       do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID);
     sParserWrapper = parserWrapper;
@@ -110,17 +109,17 @@ TextTrackManager::TextTrackManager(HTMLM
   }
 }
 
 TextTrackManager::~TextTrackManager()
 {
 }
 
 TextTrackList*
-TextTrackManager::TextTracks() const
+TextTrackManager::GetTextTracks() const
 {
   return mTextTracks;
 }
 
 already_AddRefed<TextTrack>
 TextTrackManager::AddTextTrack(TextTrackKind aKind, const nsAString& aLabel,
                                const nsAString& aLanguage,
                                TextTrackMode aMode,
--- a/dom/html/TextTrackManager.h
+++ b/dom/html/TextTrackManager.h
@@ -40,17 +40,17 @@ class TextTrackManager MOZ_FINAL : publi
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(TextTrackManager)
 
   NS_DECL_NSIDOMEVENTLISTENER
 
   explicit TextTrackManager(HTMLMediaElement* aMediaElement);
 
-  TextTrackList* TextTracks() const;
+  TextTrackList* GetTextTracks() const;
   already_AddRefed<TextTrack> AddTextTrack(TextTrackKind aKind,
                                            const nsAString& aLabel,
                                            const nsAString& aLanguage,
                                            TextTrackMode aMode,
                                            TextTrackReadyState aReadyState,
                                            TextTrackSource aTextTrackSource);
   void AddTextTrack(TextTrack* aTextTrack);
   void RemoveTextTrack(TextTrack* aTextTrack, bool aPendingListOnly);
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -526,16 +526,23 @@ bool MediaDecoderStateMachine::HaveEnoug
       GetDecodedAudioDuration() < aAmpleAudioUSecs) {
     return false;
   }
   if (!mAudioCaptured) {
     return true;
   }
 
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
+
+  // Since stream is initialized in SendStreamData() which is called when
+  // audio/video samples are received, we need to keep decoding to ensure
+  // the stream is initialized.
+  if (stream && !stream->mStreamInitialized) {
+    return false;
+  }
   if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishAudio) {
     if (!stream->mStream->HaveEnoughBuffered(kAudioTrack)) {
       return false;
     }
     stream->mStream->DispatchWhenNotEnoughBuffered(kAudioTrack,
         GetStateMachineThread(), GetWakeDecoderRunnable());
   }
 
@@ -546,16 +553,21 @@ bool MediaDecoderStateMachine::HaveEnoug
 {
   AssertCurrentThreadInMonitor();
 
   if (static_cast<uint32_t>(VideoQueue().GetSize()) < mAmpleVideoFrames * mPlaybackRate) {
     return false;
   }
 
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
+
+  if (stream && !stream->mStreamInitialized) {
+    return false;
+  }
+
   if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishVideo) {
     if (!stream->mStream->HaveEnoughBuffered(kVideoTrack)) {
       return false;
     }
     stream->mStream->DispatchWhenNotEnoughBuffered(kVideoTrack,
         GetStateMachineThread(), GetWakeDecoderRunnable());
   }
 
--- a/dom/tests/mochitest/gamepad/mochitest.ini
+++ b/dom/tests/mochitest/gamepad/mochitest.ini
@@ -1,15 +1,17 @@
 [DEFAULT]
 skip-if = e10s
 support-files =
   gamepad_frame.html
   gamepad_frame_state.html
   mock_gamepad.js
 
+[test_check_timestamp.html]
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_gamepad.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_gamepad_connect_events.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_gamepad_frame_state_sync.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_gamepad_hidden_frame.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/gamepad/test_check_timestamp.html
@@ -0,0 +1,51 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test Gamepad.timestamp</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script type="text/javascript" src="mock_gamepad.js"></script>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+var index = GamepadService.addGamepad("test gamepad", // id
+                                      SpecialPowers.Ci.nsIGamepadServiceTest.NO_MAPPING,
+                                      4, // buttons
+                                      2);// axes
+
+var timea=0;
+window.addEventListener("gamepadbuttondown", buttonpresshandler);
+
+var firstPress = true;
+GamepadService.newButtonEvent(index, 0, true);
+GamepadService.newButtonEvent(index, 0, true);
+
+function cleanup(){
+  SpecialPowers.executeSoon(function() {
+    GamepadService.removeGamepad(index);
+    SimpleTest.finish();
+  });
+}
+
+function buttonpresshandler(e) {
+  if(timea == 0){
+    timea = e.gamepad.timestamp;
+  }
+  else{
+    ok(timea <= e.gamepad.timestamp);
+  }
+  GamepadService.newButtonEvent(index, 0, false);
+  if (!firstPress) {
+    SpecialPowers.executeSoon(cleanup);
+  }
+  else {
+    firstPress = false;
+  }
+}
+
+</script>
+</body>
+</html>
--- a/dom/tests/mochitest/gamepad/test_gamepad.html
+++ b/dom/tests/mochitest/gamepad/test_gamepad.html
@@ -15,16 +15,17 @@ window.addEventListener("gamepadconnecte
 // Add a gamepad
 var index = GamepadService.addGamepad("test gamepad", // id
                                       SpecialPowers.Ci.nsIGamepadServiceTest.STANDARD_MAPPING,
                                       4, // buttons
                                       2);// axes
 // Press a button
 GamepadService.newButtonEvent(index, 0, true);
 function connecthandler(e) {
+  ok(e.gamepad.timestamp <= performance.now());
   is(e.gamepad.id, "test gamepad", "correct gamepad name");
   is(e.gamepad.mapping, "standard", "standard mapping");
   is(e.gamepad.buttons.length, 4, "correct number of buttons");
   is(e.gamepad.axes.length, 2, "correct number of axes");
   SimpleTest.executeSoon(function() {
     GamepadService.removeGamepad(index);
     SimpleTest.finish();
   });
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -335,19 +335,19 @@ var interfaceNamesInGlobalScope =
     "DeviceLightEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DeviceMotionEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DeviceOrientationEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DeviceProximityEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "DeviceStorage",
+    { name: "DeviceStorage", pref: "device.storage.enabled" },
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "DeviceStorageChangeEvent",
+    { name: "DeviceStorageChangeEvent", pref: "device.storage.enabled" },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Document",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DocumentFragment",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DocumentType",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "DOMConstructor", xbl: true},
--- a/dom/webidl/BluetoothAdapter2.webidl
+++ b/dom/webidl/BluetoothAdapter2.webidl
@@ -67,34 +67,34 @@ interface BluetoothAdapter : EventTarget
 
   /**
    * Enable/Disable a local bluetooth adapter by asynchronus methods and return
    * its result through a Promise.
    *
    * Several onattributechanged events would be triggered during processing the
    * request, and the last one indicates adapter.state becomes enabled/disabled.
    */
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> enable();
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> disable();
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> setName(DOMString aName);
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> setDiscoverable(boolean aDiscoverable);
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<BluetoothDiscoveryHandle> startDiscovery();
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> stopDiscovery();
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> pair(DOMString deviceAddress);
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> unpair(DOMString deviceAddress);
 
   sequence<BluetoothDevice> getPairedDevices();
 
   [NewObject, Throws, AvailableIn=CertifiedApps]
   DOMRequest getConnectedDevices(unsigned short serviceUuid);
 
   /**
--- a/dom/webidl/BluetoothDevice2.webidl
+++ b/dom/webidl/BluetoothDevice2.webidl
@@ -21,17 +21,17 @@ interface BluetoothDevice : EventTarget
 
   /**
    * Fetch the up-to-date UUID list of each bluetooth service that the device
    * provides and refresh the cache value of attribute uuids if it is updated.
    *
    * If the operation succeeds, the promise will be resolved with up-to-date
    * UUID list which is identical to attribute uuids.
    */
-  [NewObject, Throws]
+  [NewObject]
   Promise<sequence<DOMString>>              fetchUuids();
 };
 
 enum BluetoothDeviceType
 {
   "unknown",
   "classic",
   "le",
--- a/dom/webidl/BluetoothPairingHandle.webidl
+++ b/dom/webidl/BluetoothPairingHandle.webidl
@@ -8,13 +8,13 @@ interface BluetoothPairingHandle
 {
   /**
    * A 6-digit string ranging from decimal 000000 to 999999.
    * This attribute is an empty string for enterpincodereq and
    * pairingconsentreq.
    */
   readonly attribute DOMString passkey;
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> setPinCode(DOMString aPinCode);
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> setPairingConfirmation(boolean aConfirm);
 };
--- a/dom/webidl/DOMRequest.webidl
+++ b/dom/webidl/DOMRequest.webidl
@@ -14,14 +14,14 @@ interface DOMRequestShared {
 
   attribute EventHandler onsuccess;
   attribute EventHandler onerror;
 };
 
 interface DOMRequest : EventTarget {
   // The [TreatNonCallableAsNull] annotation is required since then() should do
   // nothing instead of throwing errors when non-callable arguments are passed.
-  [NewObject, Throws]
+  [NewObject]
   Promise<any> then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
                     [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
 };
 
 DOMRequest implements DOMRequestShared;
--- a/dom/webidl/DeviceStorage.webidl
+++ b/dom/webidl/DeviceStorage.webidl
@@ -2,16 +2,17 @@
 /* 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/. */
 
 dictionary DeviceStorageEnumerationParameters {
   Date since;
 };
 
+[Pref="device.storage.enabled"]
 interface DeviceStorage : EventTarget {
   attribute EventHandler onchange;
 
   [Throws]
   DOMRequest? add(Blob? aBlob);
   [Throws]
   DOMRequest? addNamed(Blob? aBlob, DOMString aName);
 
@@ -80,12 +81,12 @@ interface DeviceStorage : EventTarget {
 
   // Determines if this storage area is the one which will be used by default
   // for storing new files.
   readonly attribute boolean default;
 
   // Indicates if the storage area denoted by storageName is removable
   readonly attribute boolean isRemovable;
 
-  [NewObject, Throws]
+  [NewObject]
   // XXXbz what type does this really return?
   Promise<any> getRoot();
 };
--- a/dom/webidl/DeviceStorageChangeEvent.webidl
+++ b/dom/webidl/DeviceStorageChangeEvent.webidl
@@ -1,15 +1,16 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-[Constructor(DOMString type, optional DeviceStorageChangeEventInit eventInitDict)]
+[Constructor(DOMString type, optional DeviceStorageChangeEventInit eventInitDict),
+ Pref="device.storage.enabled"]
 interface DeviceStorageChangeEvent : Event
 {
   readonly attribute DOMString? path;
   readonly attribute DOMString? reason;
 };
 
 dictionary DeviceStorageChangeEventInit : EventInit
 {
--- a/dom/webidl/Directory.webidl
+++ b/dom/webidl/Directory.webidl
@@ -30,68 +30,68 @@ interface Directory {
    * a new file to replace the existing one;
    * If 'ifExists' is 'replace', the path already exists, but is a directory,
    * createFile must fail.
    * Otherwise, if no other error occurs, createFile will create a new file.
    * The 'data' property contains the new file's content.
    * @return If succeeds, the promise is resolved with the new created
    * File object. Otherwise, rejected with a DOM error.
    */
-  [NewObject, Throws]
+  [NewObject]
   Promise<File> createFile(DOMString path, optional CreateFileOptions options);
 
   /*
    * Creates a descendent directory. This method will create any intermediate
    * directories specified by the path segments.
    *
    * @param path The relative path of the new directory to current directory.
    * If path exists, createDirectory must fail.
    * @return If succeeds, the promise is resolved with the new created
    * Directory object. Otherwise, rejected with a DOM error.
    */
-  [NewObject, Throws]
+  [NewObject]
   Promise<Directory> createDirectory(DOMString path);
 
   /*
    * Gets a descendent file or directory with the given path.
    *
    * @param path The descendent entry's relative path to current directory.
    * @return If the path exists and no error occurs, the promise is resolved
    * with a File or Directory object, depending on the entry's type. Otherwise,
    * rejected with a DOM error.
    */
-  [NewObject, Throws]
+  [NewObject]
   Promise<(File or Directory)> get(DOMString path);
 
   /*
    * Deletes a file or an empty directory. The target must be a descendent of
    * current directory.
    * @param path If a DOM string is passed, it is the relative path of the
    * target. Otherwise, the File or Directory object of the target should be
    * passed.
    * @return If the target is a non-empty directory, or if deleting the target
    * fails, the promise is rejected with a DOM error. If the target did not
    * exist, the promise is resolved with boolean false. If the target did exist
    * and was successfully deleted, the promise is resolved with boolean true.
    */
-  [NewObject, Throws]
+  [NewObject]
   Promise<boolean> remove((DOMString or File or Directory) path);
 
   /*
    * Deletes a file or a directory recursively. The target should be a
    * descendent of current directory.
    * @param path If a DOM string is passed, it is the relative path of the
    * target. Otherwise, the File or Directory object of the target should be
    * passed.
    * @return If the target exists, but deleting the target fails, the promise is
    * rejected with a DOM error. If the target did not exist, the promise is
    * resolved with boolean false. If the target did exist and was successfully
    * deleted, the promise is resolved with boolean true.
    */
-  [NewObject, Throws]
+  [NewObject]
   Promise<boolean> removeDeep((DOMString or File or Directory) path);
 };
 
 enum CreateIfExistsMode { "replace", "fail" };
 
 dictionary CreateFileOptions {
   CreateIfExistsMode ifExists = "fail";
   (DOMString or Blob or ArrayBuffer or ArrayBufferView) data;
--- a/dom/webidl/Gamepad.webidl
+++ b/dom/webidl/Gamepad.webidl
@@ -46,9 +46,14 @@ interface Gamepad {
   readonly attribute sequence<GamepadButton> buttons;
 
   /**
    * The current position of all axes on the device, an
    * array of doubles.
    */
   [Pure, Cached, Frozen]
   readonly attribute sequence<double> axes;
+
+  /**
+   * Timestamp from when the data of this device was last updated.
+   */
+  readonly attribute DOMHighResTimeStamp timestamp;
 };
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -86,17 +86,17 @@ interface HTMLMediaElement : HTMLElement
 
   // TODO: Bug 847379
   // tracks
   [Pref="media.track.enabled"]
   readonly attribute AudioTrackList audioTracks;
   [Pref="media.track.enabled"]
   readonly attribute VideoTrackList videoTracks;
   [Pref="media.webvtt.enabled"]
-  readonly attribute TextTrackList textTracks;
+  readonly attribute TextTrackList? textTracks;
   [Pref="media.webvtt.enabled"]
   TextTrack addTextTrack(TextTrackKind kind,
                          optional DOMString label = "",
                          optional DOMString language = "");
 };
 
 // Mozilla extensions:
 partial interface HTMLMediaElement {
@@ -146,17 +146,17 @@ enum MediaWaitingFor {
 
 #ifdef MOZ_EME
 // Encrypted Media Extensions
 partial interface HTMLMediaElement {
   [Pref="media.eme.enabled"]
   readonly attribute MediaKeys? mediaKeys;
 
   // void, not any: https://www.w3.org/Bugs/Public/show_bug.cgi?id=26457
-  [Pref="media.eme.enabled", Throws, NewObject]
+  [Pref="media.eme.enabled", NewObject]
   Promise<void> setMediaKeys(MediaKeys? mediaKeys);
 
   [Pref="media.eme.enabled"]
   attribute EventHandler onencrypted;
 
   [Pref="media.eme.enabled"]
   readonly attribute MediaWaitingFor waitingFor;
 };
--- a/dom/webidl/MediaKeySession.webidl
+++ b/dom/webidl/MediaKeySession.webidl
@@ -18,27 +18,27 @@ interface MediaKeySession : EventTarget 
   // session properties
   readonly attribute DOMString keySystem;
   readonly attribute DOMString sessionId;
 
   readonly attribute unrestricted double expiration;
 
   readonly attribute Promise<void> closed;
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> generateRequest(DOMString initDataType, (ArrayBufferView or ArrayBuffer) initData);
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<boolean> load(DOMString sessionId);
 
   // session operations
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> update((ArrayBufferView or ArrayBuffer) response);
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> close();
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> remove();
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<sequence<ArrayBuffer>> getUsableKeyIds();
 };
--- a/dom/webidl/MediaKeySystemAccess.webidl
+++ b/dom/webidl/MediaKeySystemAccess.webidl
@@ -24,11 +24,11 @@ dictionary MediaKeySystemOptions {
   DOMString            videoCapability = "";
   MediaKeysRequirement uniqueidentifier = "optional";
   MediaKeysRequirement stateful = "optional";
 };
 
 [Pref="media.eme.enabled"]
 interface MediaKeySystemAccess {
   readonly    attribute DOMString keySystem;
-  [NewObject, Throws]
+  [NewObject]
   Promise<MediaKeys> createMediaKeys();
 };
--- a/dom/webidl/MediaKeys.webidl
+++ b/dom/webidl/MediaKeys.webidl
@@ -15,11 +15,11 @@ enum SessionType { "temporary", "persist
 
 [Pref="media.eme.enabled"]
 interface MediaKeys {
   readonly attribute DOMString keySystem;
 
   [NewObject, Throws]
   MediaKeySession createSession(optional SessionType sessionType = "temporary");
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> setServerCertificate((ArrayBufferView or ArrayBuffer) serverCertificate);
 };
--- a/dom/webidl/MozIcc.webidl
+++ b/dom/webidl/MozIcc.webidl
@@ -346,11 +346,11 @@ interface MozIcc : EventTarget
    *
    * @param service
    *        Identifies the service type.
    *
    * @return a Promise
    *         If succeeds, the promise is resolved with boolean indicating the
    *         availability of the service. Otherwise, rejected with a DOMError.
    */
-  [NewObject, Throws]
+  [NewObject]
   Promise<boolean> getServiceState(IccService service);
 };
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -127,17 +127,17 @@ interface NavigatorBattery {
     readonly attribute BatteryManager? battery;
 };
 Navigator implements NavigatorBattery;
 
 // https://wiki.mozilla.org/WebAPI/DataStore
 [NoInterfaceObject,
  Exposed=(Window,Worker)]
 interface NavigatorDataStore {
-    [Throws, NewObject, Func="Navigator::HasDataStoreSupport"]
+    [NewObject, Func="Navigator::HasDataStoreSupport"]
     Promise<sequence<DataStore>> getDataStores(DOMString name,
                                                optional DOMString? owner = null);
 };
 Navigator implements NavigatorDataStore;
 
 // http://www.w3.org/TR/vibration/#vibration-interface
 partial interface Navigator {
     // We don't support sequences in unions yet
@@ -168,17 +168,17 @@ dictionary MobileIdOptions {
 };
 
 [NoInterfaceObject]
 interface NavigatorMobileId {
     // Ideally we would use [CheckPermissions] here, but the "mobileid"
     // permission is set to PROMPT_ACTION and [CheckPermissions] only checks
     // for ALLOW_ACTION.
     // XXXbz what is this promise resolved with?
-    [Throws, NewObject, Func="Navigator::HasMobileIdSupport"]
+    [NewObject, Func="Navigator::HasMobileIdSupport"]
     Promise<any> getMobileIdAssertion(optional MobileIdOptions options);
 };
 Navigator implements NavigatorMobileId;
 #endif // MOZ_B2G
 
 // nsIDOMNavigator
 partial interface Navigator {
   [Throws]
@@ -400,14 +400,14 @@ partial interface Navigator {
 
 partial interface Navigator {
   [Pref="dom.tv.enabled", CheckPermissions="tv", Func="Navigator::HasTVSupport"]
   readonly attribute TVManager? tv;
 };
 
 #ifdef MOZ_EME
 partial interface Navigator {
-  [Pref="media.eme.enabled", Throws, NewObject]
+  [Pref="media.eme.enabled", NewObject]
   Promise<MediaKeySystemAccess>
   requestMediaKeySystemAccess(DOMString keySystem,
                               optional sequence<MediaKeySystemOptions> supportedConfigurations);
 };
 #endif
--- a/dom/webidl/Promise.webidl
+++ b/dom/webidl/Promise.webidl
@@ -22,28 +22,28 @@ callback AnyCallback = any (any value);
 interface _Promise {
   // TODO bug 875289 - static Promise fulfill(any value);
 
   // Disable the static methods when the interface object is supposed to be
   // disabled, just in case some code decides to walk over to .constructor from
   // the proto of a promise object or someone screws up and manages to create a
   // Promise object in this scope without having resolved the interface object
   // first.
-  [NewObject, Throws]
+  [NewObject]
   static Promise<any> resolve(optional any value);
-  [NewObject, Throws]
+  [NewObject]
   static Promise<void> reject(optional any value);
 
   // The [TreatNonCallableAsNull] annotation is required since then() should do
   // nothing instead of throwing errors when non-callable arguments are passed.
-  [NewObject, Throws]
+  [NewObject]
   Promise<any> then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null,
                     [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<any> catch([TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null);
 
-  [NewObject, Throws]
+  [NewObject]
   static Promise<any> all(sequence<any> iterable);
 
-  [NewObject, Throws]
+  [NewObject]
   static Promise<any> race(sequence<any> iterable);
 };
--- a/dom/webidl/TelephonyCall.webidl
+++ b/dom/webidl/TelephonyCall.webidl
@@ -28,23 +28,23 @@ interface TelephonyCall : EventTarget {
 
   // Indicate whether the call can be added into TelephonyCallGroup.
   readonly attribute boolean mergeable;
 
   readonly attribute DOMError? error;
 
   readonly attribute TelephonyCallGroup? group;
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> answer();
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> hangUp();
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> hold();
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> resume();
 
   attribute EventHandler onstatechange;
   attribute EventHandler ondialing;
   attribute EventHandler onalerting;
   attribute EventHandler onconnecting;
   attribute EventHandler onconnected;
   attribute EventHandler ondisconnecting;
--- a/dom/webidl/TelephonyCallGroup.webidl
+++ b/dom/webidl/TelephonyCallGroup.webidl
@@ -12,17 +12,17 @@ interface TelephonyCallGroup : EventTarg
   void add(TelephonyCall call);
 
   [Throws]
   void add(TelephonyCall call, TelephonyCall secondCall);
 
   [Throws]
   void remove(TelephonyCall call);
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> hangUp();
 
   [Throws]
   void hold();
 
   [Throws]
   void resume();
 
--- a/dom/webidl/TextEncoder.webidl
+++ b/dom/webidl/TextEncoder.webidl
@@ -10,11 +10,11 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 [Constructor(optional DOMString utfLabel = "utf-8"),
  Exposed=(Window,Worker,System)]
 interface TextEncoder {
   [Constant]
   readonly attribute DOMString encoding;
-  [Throws, NewObject]
+  [NewObject]
   Uint8Array encode(optional USVString input = "");
 };
--- a/dom/webidl/USSDSession.webidl
+++ b/dom/webidl/USSDSession.webidl
@@ -4,14 +4,14 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 [Pref="dom.telephony.enabled",
  CheckPermissions="telephony",
  AvailableIn="CertifiedApps",
  Constructor(unsigned long serviceId)]
 interface USSDSession {
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> send(DOMString ussd);
 
-  [NewObject, Throws]
+  [NewObject]
   Promise<void> cancel();
 };
--- a/gfx/thebes/gfxFT2FontBase.cpp
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -15,16 +15,18 @@ using namespace mozilla::gfx;
 gfxFT2FontBase::gfxFT2FontBase(cairo_scaled_font_t *aScaledFont,
                                gfxFontEntry *aFontEntry,
                                const gfxFontStyle *aFontStyle)
     : gfxFont(aFontEntry, aFontStyle, kAntialiasDefault, aScaledFont),
       mSpaceGlyph(0),
       mHasMetrics(false)
 {
     cairo_scaled_font_reference(mScaledFont);
+    gfxFT2LockedFace face(this);
+    mFUnitsConvFactor = face.XScale();
 }
 
 gfxFT2FontBase::~gfxFT2FontBase()
 {
     cairo_scaled_font_destroy(mScaledFont);
 }
 
 uint32_t
@@ -112,17 +114,16 @@ gfxFT2FontBase::GetHorizontalMetrics()
     if (mHasMetrics)
         return mMetrics;
 
     if (MOZ_UNLIKELY(GetStyle()->size <= 0.0)) {
         new(&mMetrics) gfxFont::Metrics(); // zero initialize
         mSpaceGlyph = 0;
     } else {
         gfxFT2LockedFace face(this);
-        mFUnitsConvFactor = face.XScale();
         face.GetMetrics(&mMetrics, &mSpaceGlyph);
     }
 
     SanitizeMetrics(&mMetrics, false);
 
 #if 0
     //    printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size);
     //    printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font)));
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -3156,17 +3156,17 @@ gfxFont::InitMetricsFromSfntTables(Metri
 
     if (mFUnitsConvFactor == 0.0) {
         // If the conversion factor from FUnits is not yet set,
         // get the unitsPerEm from the 'head' table via the font entry
         uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm();
         if (unitsPerEm == gfxFontEntry::kInvalidUPEM) {
             return false;
         }
-        mFUnitsConvFactor = mAdjustedSize / unitsPerEm;
+        mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm;
     }
 
     // 'hhea' table is required to get vertical extents
     gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
     if (!hheaTable) {
         return false; // no 'hhea' table -> not an sfnt
     }
     const MetricsHeader* hhea =
@@ -3449,17 +3449,17 @@ gfxFont::CreateVerticalMetrics()
         const MetricsHeader* vhea =
             reinterpret_cast<const MetricsHeader*>
                 (hb_blob_get_data(vheaTable, &len));
         if (len >= sizeof(MetricsHeader)) {
             SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax);
             // Redistribute space between ascent/descent because we want a
             // centered vertical baseline by default.
             gfxFloat halfExtent = 0.5 * gfxFloat(mFUnitsConvFactor) *
-                (int16_t(vhea->ascender) - int16_t(vhea->descender));
+                (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender)));
             metrics->maxAscent = halfExtent;
             metrics->maxDescent = halfExtent;
             SET_SIGNED(externalLeading, vhea->lineGap);
         }
     }
 
     // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
     // font of some kind (Type1, bitmap, vector, ...), so fall back to using
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -337,16 +337,17 @@ nsICODecoder::WriteInternal(const char* 
     aCount -= toCopy;
     aBuffer += toCopy;
 
     mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
                      PNGSIGNATURESIZE);
     if (mIsPNG) {
       mContainedDecoder = new nsPNGDecoder(mImage);
       mContainedDecoder->SetSizeDecode(IsSizeDecode());
+      mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
       mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                            mColormap, mColormapSize,
                                            Move(mRefForContainedDecoder));
       if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
         return;
       }
     }
   }
@@ -415,16 +416,17 @@ nsICODecoder::WriteInternal(const char* 
 
     // Init the bitmap decoder which will do most of the work for us
     // It will do everything except the AND mask which isn't present in bitmaps
     // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
     nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
     mContainedDecoder = bmpDecoder;
     bmpDecoder->SetUseAlphaData(true);
     mContainedDecoder->SetSizeDecode(IsSizeDecode());
+    mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
     mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                          mColormap, mColormapSize,
                                          Move(mRefForContainedDecoder));
 
     // The ICO format when containing a BMP does not include the 14 byte
     // bitmap file header. To use the code of the BMP decoder we need to
     // generate this header ourselves and feed it to the BMP decoder.
     int8_t bfhBuffer[BMPFILEHEADERSIZE];
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -23,16 +23,17 @@ namespace image {
 Decoder::Decoder(RasterImage &aImage)
   : mImage(aImage)
   , mProgress(NoProgress)
   , mImageData(nullptr)
   , mColormap(nullptr)
   , mChunkCount(0)
   , mDecodeFlags(0)
   , mBytesDecoded(0)
+  , mSendPartialInvalidations(false)
   , mDecodeDone(false)
   , mDataError(false)
   , mFrameCount(0)
   , mFailCode(NS_OK)
   , mNeedsNewFrame(false)
   , mNeedsToFlushData(false)
   , mInitialized(false)
   , mSizeDecode(false)
@@ -362,23 +363,23 @@ Decoder::InternalAddFrame(uint32_t aFram
   }
 
   RawAccessFrameRef ref = frame->RawAccessRef();
   if (!ref) {
     frame->Abort();
     return RawAccessFrameRef();
   }
 
-  bool succeeded =
+  InsertOutcome outcome =
     SurfaceCache::Insert(frame, ImageKey(&mImage),
                          RasterSurfaceKey(imageSize.ToIntSize(),
                                           aDecodeFlags,
                                           aFrameNum),
                          Lifetime::Persistent);
-  if (!succeeded) {
+  if (outcome != InsertOutcome::SUCCESS) {
     ref->Abort();
     return RawAccessFrameRef();
   }
 
   nsIntRect refreshArea;
 
   if (aFrameNum == 1) {
     MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
@@ -482,28 +483,37 @@ Decoder::PostFrameStop(Opacity aFrameOpa
   MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
 
   // Update our state
   mInFrame = false;
 
   mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
 
   mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
+
+  // If we're not sending partial invalidations, then we send an invalidation
+  // here when the first frame is complete.
+  if (!mSendPartialInvalidations && !mIsAnimated) {
+    mInvalidRect.UnionRect(mInvalidRect, mCurrentFrame->GetRect());
+  }
 }
 
 void
 Decoder::PostInvalidation(nsIntRect& aRect)
 {
   // We should be mid-frame
   NS_ABORT_IF_FALSE(mInFrame, "Can't invalidate when not mid-frame!");
   NS_ABORT_IF_FALSE(mCurrentFrame, "Can't invalidate when not mid-frame!");
 
-  // Account for the new region
-  mInvalidRect.UnionRect(mInvalidRect, aRect);
-  mCurrentFrame->ImageUpdated(aRect);
+  // Record this invalidation, unless we're not sending partial invalidations
+  // or we're past the first frame.
+  if (mSendPartialInvalidations && !mIsAnimated) {
+    mInvalidRect.UnionRect(mInvalidRect, aRect);
+    mCurrentFrame->ImageUpdated(aRect);
+  }
 }
 
 void
 Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
 {
   NS_ABORT_IF_FALSE(!IsSizeDecode(), "Can't be done with decoding with size decode!");
   NS_ABORT_IF_FALSE(!mInFrame, "Can't be done decoding if we're mid-frame!");
   NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!");
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -110,16 +110,34 @@ public:
   // must be enabled by SetSizeDecode() _before_calling Init().
   bool IsSizeDecode() { return mSizeDecode; }
   void SetSizeDecode(bool aSizeDecode)
   {
     NS_ABORT_IF_FALSE(!mInitialized, "Can't set size decode after Init()!");
     mSizeDecode = aSizeDecode;
   }
 
+  /**
+   * Set whether should send partial invalidations.
+   *
+   * If @aSend is true, we'll send partial invalidations when decoding the first
+   * frame of the image, so image notifications observers will be able to
+   * gradually draw in the image as it downloads.
+   *
+   * If @aSend is false (the default), we'll only send an invalidation when we
+   * complete the first frame.
+   *
+   * This must be called before Init() is called.
+   */
+  void SetSendPartialInvalidations(bool aSend)
+  {
+    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
+    mSendPartialInvalidations = aSend;
+  }
+
   size_t BytesDecoded() const { return mBytesDecoded; }
 
   // The amount of time we've spent inside Write() so far for this decoder.
   TimeDuration DecodeTime() const { return mDecodeTime; }
 
   // The number of times Write() has been called so far for this decoder.
   uint32_t ChunkCount() const { return mChunkCount; }
 
@@ -306,16 +324,17 @@ protected:
   uint32_t mColormapSize;
 
   // Telemetry data for this decoder.
   TimeDuration mDecodeTime;
   uint32_t mChunkCount;
 
   uint32_t mDecodeFlags;
   size_t mBytesDecoded;
+  bool mSendPartialInvalidations;
   bool mDecodeDone;
   bool mDataError;
 
 private:
   uint32_t mFrameCount; // Number of frames, including anything in-progress
 
   nsresult mFailCode;
 
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -1493,16 +1493,17 @@ RasterImage::InitDecoder(bool aDoSizeDec
     // We already have the size; tell the decoder so it can preallocate a
     // frame.  By default, we create an ARGB frame with no offset. If decoders
     // need a different type, they need to ask for it themselves.
     mDecoder->SetSize(mSize, mOrientation);
     mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
                            SurfaceFormat::B8G8R8A8);
     mDecoder->AllocateFrame();
   }
+  mDecoder->SetSendPartialInvalidations(!mHasBeenDecoded);
   mDecoder->Init();
   CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
 
   if (!aDoSizeDecode) {
     Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Subtract(mDecodeCount);
     mDecodeCount++;
     Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(mDecodeCount);
 
@@ -2395,26 +2396,16 @@ RasterImage::FinishedSomeDecoding(Shutdo
       }
 
       // If there were any final changes, grab them.
       invalidRect.Union(decoder->TakeInvalidRect());
       progress |= decoder->TakeProgress();
     }
   }
 
-  if (GetCurrentFrameIndex() > 0) {
-    // Don't send invalidations for animated frames after the first; let
-    // RequestRefresh take care of that.
-    invalidRect = nsIntRect();
-  }
-  if (mHasBeenDecoded && !invalidRect.IsEmpty()) {
-    // Don't send partial invalidations if we've been decoded before.
-    invalidRect = mDecoded ? GetFirstFrameRect()
-                           : nsIntRect();
-  }
   if (!invalidRect.IsEmpty() && wasDefaultFlags) {
     // Update our image container since we're invalidating.
     UpdateImageContainer();
   }
 
   if (mNotifying) {
     // Accumulate the progress changes. We don't permit recursive notifications
     // because they cause subtle concurrency bugs, so we'll delay sending out
--- a/image/src/SurfaceCache.cpp
+++ b/image/src/SurfaceCache.cpp
@@ -299,29 +299,32 @@ private:
     UnregisterWeakMemoryReporter(this);
   }
 
 public:
   void InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
 
   Mutex& GetMutex() { return mMutex; }
 
-  bool Insert(imgFrame*         aSurface,
-              const Cost        aCost,
-              const ImageKey    aImageKey,
-              const SurfaceKey& aSurfaceKey,
-              Lifetime          aLifetime)
+  InsertOutcome Insert(imgFrame*         aSurface,
+                       const Cost        aCost,
+                       const ImageKey    aImageKey,
+                       const SurfaceKey& aSurfaceKey,
+                       Lifetime          aLifetime)
   {
-    MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey),
-               "Inserting a duplicate surface into the SurfaceCache");
+    // If this is a duplicate surface, refuse to replace the original.
+    if (MOZ_UNLIKELY(Lookup(aImageKey, aSurfaceKey))) {
+      return InsertOutcome::FAILURE_ALREADY_PRESENT;
+    }
 
     // If this is bigger than we can hold after discarding everything we can,
     // refuse to cache it.
-    if (!CanHoldAfterDiscarding(aCost))
-      return false;
+    if (MOZ_UNLIKELY(!CanHoldAfterDiscarding(aCost))) {
+      return InsertOutcome::FAILURE;
+    }
 
     // Remove elements in order of cost until we can fit this in the cache. Note
     // that locked surfaces aren't in mCosts, so we never remove them here.
     while (aCost > mAvailableCost) {
       MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and it still won't fit");
       Remove(mCosts.LastElement().GetSurface());
     }
 
@@ -336,26 +339,26 @@ public:
     nsRefPtr<CachedSurface> surface =
       new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey, aLifetime);
 
     // We require that locking succeed if the image is locked and the surface is
     // persistent; the caller may need to know this to handle errors correctly.
     if (cache->IsLocked() && aLifetime == Lifetime::Persistent) {
       surface->SetLocked(true);
       if (!surface->IsLocked()) {
-        return false;
+        return InsertOutcome::FAILURE;
       }
     }
 
     // Insert.
     MOZ_ASSERT(aCost <= mAvailableCost, "Inserting despite too large a cost");
     cache->Insert(aSurfaceKey, surface);
     StartTracking(surface);
 
-    return true;
+    return InsertOutcome::SUCCESS;
   }
 
   void Remove(CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
     ImageKey imageKey = aSurface->GetImageKey();
 
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
@@ -790,24 +793,24 @@ SurfaceCache::Lookup(const ImageKey    a
   if (!sInstance) {
     return DrawableFrameRef();
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
   return sInstance->Lookup(aImageKey, aSurfaceKey);
 }
 
-/* static */ bool
+/* static */ InsertOutcome
 SurfaceCache::Insert(imgFrame*         aSurface,
                      const ImageKey    aImageKey,
                      const SurfaceKey& aSurfaceKey,
                      Lifetime          aLifetime)
 {
   if (!sInstance) {
-    return false;
+    return InsertOutcome::FAILURE;
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
   Cost cost = ComputeCost(aSurfaceKey.Size());
   return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
 }
 
 /* static */ bool
--- a/image/src/SurfaceCache.h
+++ b/image/src/SurfaceCache.h
@@ -109,16 +109,22 @@ VectorSurfaceKey(const gfx::IntSize& aSi
   return SurfaceKey(aSize, aSVGContext, aAnimationTime, 0);
 }
 
 MOZ_BEGIN_ENUM_CLASS(Lifetime, uint8_t)
   Transient,
   Persistent
 MOZ_END_ENUM_CLASS(Lifetime)
 
+MOZ_BEGIN_ENUM_CLASS(InsertOutcome, uint8_t)
+  SUCCESS,                 // Success (but see Insert documentation).
+  FAILURE,                 // Couldn't insert (e.g., for capacity reasons).
+  FAILURE_ALREADY_PRESENT  // A surface with the same key is already present.
+MOZ_END_ENUM_CLASS(InsertOutcome)
+
 /**
  * SurfaceCache is an imagelib-global service that allows caching of temporary
  * surfaces. Surfaces normally expire from the cache automatically if they go
  * too long without being accessed.
  *
  * SurfaceCache does not hold surfaces directly; instead, it holds imgFrame
  * objects, which hold surfaces but also layer on additional features specific
  * to imagelib's needs like animation, padding support, and transparent support
@@ -168,53 +174,57 @@ struct SurfaceCache
    *
    * @return a DrawableFrameRef to the imgFrame wrapping the requested surface,
    *         or an empty DrawableFrameRef if not found.
    */
   static DrawableFrameRef Lookup(const ImageKey    aImageKey,
                                  const SurfaceKey& aSurfaceKey);
 
   /**
-   * Insert a surface into the cache. It is an error to call this function
-   * without first calling Lookup to verify that the surface is not already in
-   * the cache.
+   * Insert a surface into the cache. If a surface with the same ImageKey and
+   * SurfaceKey is already in the cache, Insert returns FAILURE_ALREADY_PRESENT.
    *
    * Each surface in the cache has a lifetime, either Transient or Persistent.
    * Transient surfaces can expire from the cache at any time. Persistent
    * surfaces can ordinarily also expire from the cache at any time, but if the
    * image they're associated with is locked, then these surfaces will never
    * expire. This means that surfaces which cannot be rematerialized should be
    * inserted with a persistent lifetime *after* the image is locked with
    * LockImage(); if you use the other order, the surfaces might expire before
    * LockImage() gets called.
    *
    * If a surface cannot be rematerialized, it may be important to know whether
-   * it was inserted into the cache successfully. Insert() returns false if it
+   * it was inserted into the cache successfully. Insert() returns FAILURE if it
    * failed to insert the surface, which could happen because of capacity
    * reasons, or because it was already freed by the OS. If you aren't inserting
    * a surface with persistent lifetime, or if the surface isn't associated with
-   * a locked image, the return value is useless: the surface might expire
-   * immediately after being inserted, even though Insert() returned true. Thus,
-   * most callers do not need to check the return value.
+   * a locked image, checking for SUCCESS or FAILURE is useless: the surface
+   * might expire immediately after being inserted, even though Insert()
+   * returned SUCCESS. Thus, many callers do not need to check the result of
+   * Insert() at all.
    *
    * @param aTarget      The new surface (wrapped in an imgFrame) to insert into
    *                     the cache.
    * @param aImageKey    Key data identifying which image the surface belongs to.
    * @param aSurfaceKey  Key data which uniquely identifies the requested surface.
    * @param aLifetime    Whether this is a transient surface that can always be
    *                     allowed to expire, or a persistent surface that
    *                     shouldn't expire if the image is locked.
-   * @return false if the surface could not be inserted. Only check this if
-   *         inserting a persistent surface associated with a locked image (see
-   *         above for more information).
+   * @return SUCCESS if the surface was inserted successfully. (But see above
+   *           for more information about when you should check this.)
+   *         FAILURE if the surface could not be inserted, e.g. for capacity
+   *           reasons. (But see above for more information about when you
+   *           should check this.)
+   *         FAILURE_ALREADY_PRESENT if a surface with the same ImageKey and
+   *           SurfaceKey already exists in the cache.
    */
-  static bool Insert(imgFrame*         aSurface,
-                     const ImageKey    aImageKey,
-                     const SurfaceKey& aSurfaceKey,
-                     Lifetime          aLifetime);
+  static InsertOutcome Insert(imgFrame*         aSurface,
+                              const ImageKey    aImageKey,
+                              const SurfaceKey& aSurfaceKey,
+                              Lifetime          aLifetime);
 
   /**
    * Checks if a surface of a given size could possibly be stored in the cache.
    * If CanHold() returns false, Insert() will always fail to insert the
    * surface, but the inverse is not true: Insert() may take more information
    * into account than just image size when deciding whether to cache the
    * surface, so Insert() may still fail even if CanHold() returns true.
    *
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -2908,17 +2908,17 @@ class FunctionCompiler
     template<typename T>
     MDefinition *constructSimd(MDefinition *x, MDefinition *y, MDefinition *z, MDefinition *w,
                                MIRType type)
     {
         if (inDeadCode())
             return nullptr;
 
         MOZ_ASSERT(IsSimdType(type));
-        T *ins = T::New(alloc(), type, x, y, z, w);
+        T *ins = T::NewAsmJS(alloc(), type, x, y, z, w);
         curBlock_->add(ins);
         return ins;
     }
 
     /***************************************************************** Calls */
 
     // The IonMonkey backend maintains a single stack offset (from the stack
     // pointer to the base of the frame) by adding the total amount of spill
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -988,59 +988,27 @@ static const JSClass FinalizeCounterClas
     nullptr, /* getProperty */
     nullptr, /* setProperty */
     nullptr, /* enumerate */
     nullptr, /* resolve */
     nullptr, /* convert */
     finalize_counter_finalize
 };
 
-static const JSClass NurseryFinalizeCounterClass = {
-    "NurseryFinalizeCounter", JSCLASS_IS_ANONYMOUS | JSCLASS_FINALIZE_FROM_NURSERY,
-    nullptr, /* addProperty */
-    nullptr, /* delProperty */
-    nullptr, /* getProperty */
-    nullptr, /* setProperty */
-    nullptr, /* enumerate */
-    nullptr, /* resolve */
-    nullptr, /* convert */
-    finalize_counter_finalize
-};
-
 static bool
 MakeFinalizeObserver(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    bool isNursery = false;
-    if (args.length() > 0) {
-        if (!args[0].isString()) {
-            JS_ReportError(cx, "first argument must be unset or a string.");
-            return false;
-        }
-        bool isTenured;
-        if (!JS_StringEqualsAscii(cx, args[0].toString(), "nursery", &isNursery))
-            return false;
-        if (!JS_StringEqualsAscii(cx, args[0].toString(), "tenured", &isTenured))
-            return false;
-        if (!isNursery && !isTenured) {
-            JS_ReportError(cx, "first argument must be either 'nursery' or 'tenured'.");
-            return false;
-        }
-        MOZ_ASSERT(isNursery != isTenured);
-    }
-
     RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
     if (!scope)
         return false;
 
-    const JSClass *clasp = isNursery ? &NurseryFinalizeCounterClass : &FinalizeCounterClass;
-    JSObject *obj = JS_NewObjectWithGivenProto(cx, clasp, JS::NullPtr(), scope);
+    JSObject *obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, JS::NullPtr(), scope);
     if (!obj)
         return false;
-    MOZ_ASSERT(isNursery == IsInsideNursery(obj));
 
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
 FinalizeCount(JSContext *cx, unsigned argc, jsval *vp)
 {
@@ -2287,21 +2255,19 @@ static const JSFunctionSpecWithHelp Test
     JS_FN_HELP("settleFakePromise", SettleFakePromise, 1, 0,
 "settleFakePromise(promise)",
 "  'Settle' a 'promise' created by makeFakePromise(). This doesn't have any\n"
 "  observable effects outside of firing any onPromiseSettled hooks set on\n"
 "  Debugger instances that are observing the given promise's global as a\n"
 "  debuggee."),
 
     JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0,
-"makeFinalizeObserver(['nursery'|'tenured'])",
+"makeFinalizeObserver()",
 "  Get a special object whose finalization increases the counter returned\n"
-"  by the finalizeCount function. Pass an optional string of 'nursery' or\n"
-"  'tenured' (default of 'tenured') to the function to select a target heap\n"
-"  for the allocation."),
+"  by the finalizeCount function."),
 
     JS_FN_HELP("finalizeCount", FinalizeCount, 0, 0,
 "finalizeCount()",
 "  Return the current value of the finalization counter that is incremented\n"
 "  each time an object returned by the makeFinalizeObserver is finalized."),
 
     JS_FN_HELP("gcPreserveCode", GCPreserveCode, 0, 0,
 "gcPreserveCode()",
deleted file mode 100644
--- a/js/src/jit-test/tests/gc/nursery-finalize.js
+++ /dev/null
@@ -1,16 +0,0 @@
-assertEq(0, finalizeCount());
-makeFinalizeObserver('tenured');
-minorgc();
-assertEq(0, finalizeCount());
-makeFinalizeObserver('nursery');
-minorgc();
-assertEq(1, finalizeCount());
-gc();
-assertEq(2, finalizeCount());
-
-var N = 10000;
-for (var i = 0; i < N; ++i)
-    makeFinalizeObserver('nursery');
-minorgc();
-assertEq(N + 2, finalizeCount());
-
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -9110,17 +9110,17 @@ GetTemplateObjectForClassHook(JSContext 
         Rooted<TypeDescr *> descr(cx, &args.callee().as<TypeDescr>());
         JSObject *obj = TypedObject::createZeroed(cx, descr, 1, gc::TenuredHeap);
         if (!obj)
             return false;
         templateObject.set(obj);
         return true;
     }
 
-    if (hook == SimdTypeDescr::call) {
+    if (hook == SimdTypeDescr::call && JitSupportsSimd()) {
         Rooted<SimdTypeDescr *> descr(cx, &args.callee().as<SimdTypeDescr>());
         JSObject *obj = TypedObject::createZeroed(cx, descr, 0, gc::TenuredHeap);
         if (!obj)
             return false;
         templateObject.set(obj);
         return true;
     }
 
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4188,16 +4188,46 @@ CodeGenerator::visitNewTypedObject(LNewT
                                    (ArgList(), ImmGCPtr(templateObject), Imm32(initialHeap)),
                                    StoreRegisterTo(object));
 
     masm.createGCObject(object, temp, templateObject, initialHeap, ool->entry());
 
     masm.bind(ool->rejoin());
 }
 
+void
+CodeGenerator::visitSimdBox(LSimdBox *lir)
+{
+    FloatRegister in = ToFloatRegister(lir->input());
+    Register object = ToRegister(lir->output());
+    Register temp = ToRegister(lir->temp());
+    InlineTypedObject *templateObject = lir->mir()->templateObject();
+    gc::InitialHeap initialHeap = lir->mir()->initialHeap();
+    MIRType type = lir->mir()->input()->type();
+
+    // :TODO: We cannot spill SIMD registers (Bug 1112164) from safepoints, thus
+    // we cannot use the same oolCallVM as visitNewTypedObject for allocating
+    // SIMD Typed Objects if we are at the end of the nursery. (Bug 1119303)
+    Label bail;
+    masm.createGCObject(object, temp, templateObject, initialHeap, &bail);
+    bailoutFrom(&bail, lir->snapshot());
+
+    Address objectData(object, InlineTypedObject::offsetOfDataStart());
+    switch (type) {
+      case MIRType_Int32x4:
+        masm.storeUnalignedInt32x4(in, objectData);
+        break;
+      case MIRType_Float32x4:
+        masm.storeUnalignedFloat32x4(in, objectData);
+        break;
+      default:
+        MOZ_CRASH("Unknown SIMD kind when generating code for SimdBox.");
+    }
+}
+
 typedef js::DeclEnvObject *(*NewDeclEnvObjectFn)(JSContext *, HandleFunction, gc::InitialHeap);
 static const VMFunction NewDeclEnvObjectInfo =
     FunctionInfo<NewDeclEnvObjectFn>(DeclEnvObject::createTemplateObject);
 
 void
 CodeGenerator::visitNewDeclEnvObject(LNewDeclEnvObject *lir)
 {
     Register objReg = ToRegister(lir->output());
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -150,16 +150,17 @@ class CodeGenerator : public CodeGenerat
     void visitNewArray(LNewArray *lir);
     void visitOutOfLineNewArray(OutOfLineNewArray *ool);
     void visitNewArrayCopyOnWrite(LNewArrayCopyOnWrite *lir);
     void visitNewArrayDynamicLength(LNewArrayDynamicLength *lir);
     void visitNewObjectVMCall(LNewObject *lir);
     void visitNewObject(LNewObject *lir);
     void visitOutOfLineNewObject(OutOfLineNewObject *ool);
     void visitNewTypedObject(LNewTypedObject *lir);
+    void visitSimdBox(LSimdBox *lir);
     void visitNewDeclEnvObject(LNewDeclEnvObject *lir);
     void visitNewCallObject(LNewCallObject *lir);
     void visitNewSingletonCallObject(LNewSingletonCallObject *lir);
     void visitNewStringObject(LNewStringObject *lir);
     void visitNewDerivedTypedObject(LNewDerivedTypedObject *lir);
     void visitInitElem(LInitElem *lir);
     void visitInitElemGetterSetter(LInitElemGetterSetter *lir);
     void visitMutateProto(LMutateProto *lir);
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -844,17 +844,17 @@ IonScript::New(JSContext *cx, types::Rec
     size_t paddedCacheEntriesSize = AlignBytes(cacheEntries * sizeof(uint32_t), DataAlignment);
     size_t paddedRuntimeSize = AlignBytes(runtimeSize, DataAlignment);
     size_t paddedSafepointSize = AlignBytes(safepointsSize, DataAlignment);
     size_t paddedBackedgeSize = AlignBytes(backedgeEntries * sizeof(PatchableBackedge), DataAlignment);
     size_t bytes = paddedSnapshotsSize +
                    paddedRecoversSize +
                    paddedBailoutSize +
                    paddedConstantsSize +
-                   paddedSafepointIndicesSize+
+                   paddedSafepointIndicesSize +
                    paddedOsiIndicesSize +
                    paddedCacheEntriesSize +
                    paddedRuntimeSize +
                    paddedSafepointSize +
                    paddedBackedgeSize;
     IonScript *script = cx->zone()->pod_malloc_with_extra<IonScript, uint8_t>(bytes);
     if (!script)
         return nullptr;
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -780,16 +780,17 @@ class IonBuilder
     InliningStatus inlineTypedArrayLength(CallInfo &callInfo);
 
     // TypedObject intrinsics and natives.
     InliningStatus inlineObjectIsTypeDescr(CallInfo &callInfo);
     InliningStatus inlineSetTypedObjectOffset(CallInfo &callInfo);
     bool elementAccessIsTypedObjectArrayOfScalarType(MDefinition* obj, MDefinition* id,
                                                      ScalarTypeDescr::Type *arrayType);
     InliningStatus inlineConstructTypedObject(CallInfo &callInfo, TypeDescr *target);
+    InliningStatus inlineConstructSimdObject(CallInfo &callInfo, SimdTypeDescr *target);
 
     // Utility intrinsics.
     InliningStatus inlineIsCallable(CallInfo &callInfo);
     InliningStatus inlineIsObject(CallInfo &callInfo);
     InliningStatus inlineToObject(CallInfo &callInfo);
     InliningStatus inlineToInteger(CallInfo &callInfo);
     InliningStatus inlineToString(CallInfo &callInfo);
     InliningStatus inlineDump(CallInfo &callInfo);
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -130,16 +130,38 @@ class LMoveGroup : public LInstructionHe
     size_t numMoves() const {
         return moves_.length();
     }
     const LMove &getMove(size_t i) const {
         return moves_[i];
     }
 };
 
+
+// Constructs a SIMD object (value type) based on the MIRType of its input.
+class LSimdBox : public LInstructionHelper<1, 1, 1>
+{
+  public:
+    LIR_HEADER(SimdBox)
+
+    explicit LSimdBox(const LAllocation &simd, const LDefinition &temp)
+    {
+        setOperand(0, simd);
+        setTemp(0, temp);
+    }
+
+    const LDefinition *temp() {
+        return getTemp(0);
+    }
+
+    MSimdBox *mir() const {
+        return mir_->toSimdBox();
+    }
+};
+
 // Constructs a SIMD value with 4 equal components (e.g. int32x4, float32x4).
 class LSimdSplatX4 : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(SimdSplatX4)
     explicit LSimdSplatX4(const LAllocation &v)
     {
         setOperand(0, v);
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -12,16 +12,17 @@
     _(Nop)                          \
     _(Mop)                          \
     _(OsiPoint)                     \
     _(MoveGroup)                    \
     _(Integer)                      \
     _(Pointer)                      \
     _(Double)                       \
     _(Float32)                      \
+    _(SimdBox)                      \
     _(SimdSplatX4)                  \
     _(Int32x4)                      \
     _(Float32x4)                    \
     _(SimdExtractElementI)          \
     _(SimdExtractElementF)          \
     _(SimdInsertElementI)           \
     _(SimdInsertElementF)           \
     _(SimdSignMaskX4)               \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3724,16 +3724,27 @@ LIRGenerator::visitRecompileCheck(MRecom
 void
 LIRGenerator::visitMemoryBarrier(MMemoryBarrier *ins)
 {
     LMemoryBarrier *lir = new(alloc()) LMemoryBarrier(ins->type());
     add(lir, ins);
 }
 
 void
+LIRGenerator::visitSimdBox(MSimdBox *ins)
+{
+    MOZ_ASSERT(IsSimdType(ins->input()->type()));
+    LUse in = useRegister(ins->input());
+    LSimdBox *lir = new(alloc()) LSimdBox(in, temp());
+    // :TODO: Cannot spill SIMD registers (Bug 1112164)
+    assignSnapshot(lir, Bailout_Inevitable);
+    define(lir, ins);
+}
+
+void
 LIRGenerator::visitSimdConstant(MSimdConstant *ins)
 {
     MOZ_ASSERT(IsSimdType(ins->type()));
 
     if (ins->type() == MIRType_Int32x4)
         define(new(alloc()) LInt32x4(), ins);
     else if (ins->type() == MIRType_Float32x4)
         define(new(alloc()) LFloat32x4(), ins);
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -260,16 +260,17 @@ class LIRGenerator : public LIRGenerator
     void visitAsmJSVoidReturn(MAsmJSVoidReturn *ins);
     void visitAsmJSPassStackArg(MAsmJSPassStackArg *ins);
     void visitAsmJSCall(MAsmJSCall *ins);
     void visitSetDOMProperty(MSetDOMProperty *ins);
     void visitGetDOMProperty(MGetDOMProperty *ins);
     void visitGetDOMMember(MGetDOMMember *ins);
     void visitRecompileCheck(MRecompileCheck *ins);
     void visitMemoryBarrier(MMemoryBarrier *ins);
+    void visitSimdBox(MSimdBox *ins);
     void visitSimdExtractElement(MSimdExtractElement *ins);
     void visitSimdInsertElement(MSimdInsertElement *ins);
     void visitSimdSignMask(MSimdSignMask *ins);
     void visitSimdSwizzle(MSimdSwizzle *ins);
     void visitSimdShuffle(MSimdShuffle *ins);
     void visitSimdUnaryArith(MSimdUnaryArith *ins);
     void visitSimdBinaryComp(MSimdBinaryComp *ins);
     void visitSimdBinaryBitwise(MSimdBinaryBitwise *ins);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -290,16 +290,19 @@ IonBuilder::InliningStatus
 IonBuilder::inlineNonFunctionCall(CallInfo &callInfo, JSObject *target)
 {
     // Inline a call to a non-function object, invoking the object's call or
     // construct hook.
 
     if (callInfo.constructing() && target->constructHook() == TypedObject::construct)
         return inlineConstructTypedObject(callInfo, &target->as<TypeDescr>());
 
+    if (!callInfo.constructing() && target->callHook() == SimdTypeDescr::call)
+        return inlineConstructSimdObject(callInfo, &target->as<SimdTypeDescr>());
+
     return InliningStatus_NotInlined;
 }
 
 types::TemporaryTypeSet *
 IonBuilder::getInlineReturnTypeSet()
 {
     return bytecodeTypes(pc);
 }
@@ -2507,10 +2510,61 @@ IonBuilder::inlineConstructTypedObject(C
     MNewTypedObject *ins = MNewTypedObject::New(alloc(), constraints(), templateObject,
                                                 templateObject->type()->initialHeap(constraints()));
     current->add(ins);
     current->push(ins);
 
     return InliningStatus_Inlined;
 }
 
+IonBuilder::InliningStatus
+IonBuilder::inlineConstructSimdObject(CallInfo &callInfo, SimdTypeDescr *descr)
+{
+    if (callInfo.argc() == 1)
+        return InliningStatus_NotInlined;
+
+    // Generic constructor of SIMD valuesX4.
+    MIRType simdType;
+    switch (descr->type()) {
+      case SimdTypeDescr::TYPE_INT32:
+        simdType = MIRType_Int32x4;
+        break;
+      case SimdTypeDescr::TYPE_FLOAT32:
+        simdType = MIRType_Float32x4;
+        break;
+      default:
+        MOZ_CRASH("Unknown SIMD kind when generating MSimdBox instruction.");
+        return InliningStatus_NotInlined;
+    }
+
+    // We do not inline SIMD constructors if the number of arguments does not
+    // match the number of lanes.
+    if (SimdTypeToLength(simdType) != callInfo.argc())
+        return InliningStatus_NotInlined;
+
+    // Take the templateObject out of Baseline ICs, such that we can box
+    // SIMD value type in the same kind of objects.
+    MOZ_ASSERT(size_t(descr->size(descr->type())) < InlineTypedObject::MaximumSize);
+    JSObject *templateObject = inspector->getTemplateObjectForClassHook(pc, descr->getClass());
+    if (!templateObject)
+        return InliningStatus_NotInlined;
+
+    // The previous assertion ensures this will never fail if we were able to
+    // allocate a templateObject in Baseline.
+    InlineTypedObject *inlineTypedObject = &templateObject->as<InlineTypedObject>();
+    MOZ_ASSERT(&inlineTypedObject->typeDescr() == descr);
+
+    MSimdValueX4 *values = MSimdValueX4::New(alloc(), simdType,
+                                             callInfo.getArg(0), callInfo.getArg(1),
+                                             callInfo.getArg(2), callInfo.getArg(3));
+    current->add(values);
+
+    MSimdBox *obj = MSimdBox::New(alloc(), constraints(), values, inlineTypedObject,
+                                  inlineTypedObject->type()->initialHeap(constraints()));
+    current->add(obj);
+    current->push(obj);
+
+    callInfo.setImplicitlyUsedUnchecked();
+    return InliningStatus_Inlined;
+}
+
 } // namespace jit
 } // namespace js
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1314,43 +1314,50 @@ class MConstant : public MNullaryInstruc
     bool canProduceFloat32() const;
 
     ALLOW_CLONE(MConstant)
 };
 
 // Generic constructor of SIMD valuesX4.
 class MSimdValueX4
   : public MQuaternaryInstruction,
-    public NoTypePolicy::Data
+    public Mix4Policy<SimdScalarPolicy<0>, SimdScalarPolicy<1>,
+                      SimdScalarPolicy<2>, SimdScalarPolicy<3> >::Data
 {
   protected:
     MSimdValueX4(MIRType type, MDefinition *x, MDefinition *y, MDefinition *z, MDefinition *w)
       : MQuaternaryInstruction(x, y, z, w)
     {
         MOZ_ASSERT(IsSimdType(type));
         MOZ_ASSERT(SimdTypeToLength(type) == 4);
-        mozilla::DebugOnly<MIRType> scalarType = SimdTypeToScalarType(type);
-        MOZ_ASSERT(scalarType == x->type());
-        MOZ_ASSERT(scalarType == y->type());
-        MOZ_ASSERT(scalarType == z->type());
-        MOZ_ASSERT(scalarType == w->type());
 
         setMovable();
         setResultType(type);
     }
 
   public:
     INSTRUCTION_HEADER(SimdValueX4)
 
     static MSimdValueX4 *New(TempAllocator &alloc, MIRType type, MDefinition *x,
                              MDefinition *y, MDefinition *z, MDefinition *w)
     {
         return new(alloc) MSimdValueX4(type, x, y, z, w);
     }
 
+    static MSimdValueX4 *NewAsmJS(TempAllocator &alloc, MIRType type, MDefinition *x,
+                                  MDefinition *y, MDefinition *z, MDefinition *w)
+    {
+        mozilla::DebugOnly<MIRType> scalarType = SimdTypeToScalarType(type);
+        MOZ_ASSERT(scalarType == x->type());
+        MOZ_ASSERT(scalarType == y->type());
+        MOZ_ASSERT(scalarType == z->type());
+        MOZ_ASSERT(scalarType == w->type());
+        return MSimdValueX4::New(alloc, type, x, y, z, w);
+    }
+
     bool canConsumeFloat32(MUse *use) const {
         return SimdTypeToScalarType(type()) == MIRType_Float32;
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
@@ -2900,16 +2907,66 @@ class MTypedObjectDescr
     bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
+// Generic way for constructing a SIMD object in IonMonkey, this instruction
+// takes as argument a SIMD instruction and returns a new SIMD object which
+// corresponds to the MIRType of its operand.
+class MSimdBox
+  : public MUnaryInstruction,
+    public NoTypePolicy::Data
+{
+  protected:
+    AlwaysTenured<InlineTypedObject *> templateObject_;
+    gc::InitialHeap initialHeap_;
+
+    MSimdBox(types::CompilerConstraintList *constraints,
+             MDefinition *op,
+             InlineTypedObject *templateObject,
+             gc::InitialHeap initialHeap)
+      : MUnaryInstruction(op),
+        templateObject_(templateObject),
+        initialHeap_(initialHeap)
+    {
+        MOZ_ASSERT(IsSimdType(op->type()));
+        setMovable();
+        setResultType(MIRType_Object);
+        setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject));
+    }
+
+  public:
+    INSTRUCTION_HEADER(SimdBox)
+
+    static MSimdBox *New(TempAllocator &alloc,
+                         types::CompilerConstraintList *constraints,
+                         MDefinition *op,
+                         InlineTypedObject *templateObject,
+                         gc::InitialHeap initialHeap)
+    {
+        return new(alloc) MSimdBox(constraints, op, templateObject, initialHeap);
+    }
+
+    InlineTypedObject *templateObject() const {
+        return templateObject_;
+    }
+
+    gc::InitialHeap initialHeap() const {
+        return initialHeap_;
+    }
+
+    AliasSet getAliasSet() const {
+        return AliasSet::None();
+    }
+};
+
 // Creates a new derived type object. At runtime, this is just a call
 // to `BinaryBlock::createDerived()`. That is, the MIR itself does not
 // compile to particularly optimized code. However, using a distinct
 // MIR for creating derived type objects allows the compiler to
 // optimize ephemeral typed objects as would be created for a
 // reference like `a.b.c` -- here, the `a.b` will create an ephemeral
 // derived type object that aliases the memory of `a` itself. The
 // specific nature of `a.b` is revealed by using
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -7,16 +7,17 @@
 #ifndef jit_MOpcodes_h
 #define jit_MOpcodes_h
 
 namespace js {
 namespace jit {
 
 #define MIR_OPCODE_LIST(_)                                                  \
     _(Constant)                                                             \
+    _(SimdBox)                                                              \
     _(SimdValueX4)                                                          \
     _(SimdSplatX4)                                                          \
     _(SimdConstant)                                                         \
     _(SimdConvert)                                                          \
     _(SimdReinterpretCast)                                                  \
     _(SimdExtractElement)                                                   \
     _(SimdInsertElement)                                                    \
     _(SimdSignMask)                                                         \
--- a/js/src/jit/TypePolicy.cpp
+++ b/js/src/jit/TypePolicy.cpp
@@ -517,16 +517,46 @@ NoFloatPolicyAfter<FirstOp>::adjustInput
     return true;
 }
 
 template bool NoFloatPolicyAfter<1>::adjustInputs(TempAllocator &alloc, MInstruction *def);
 template bool NoFloatPolicyAfter<2>::adjustInputs(TempAllocator &alloc, MInstruction *def);
 
 template <unsigned Op>
 bool
+SimdScalarPolicy<Op>::staticAdjustInputs(TempAllocator &alloc, MInstruction *ins)
+{
+    MOZ_ASSERT(IsSimdType(ins->type()));
+    MIRType scalarType = SimdTypeToScalarType(ins->type());
+
+    MDefinition *in = ins->getOperand(Op);
+    if (in->type() == scalarType)
+        return true;
+
+    MInstruction *replace;
+    if (scalarType == MIRType_Int32) {
+        replace = MTruncateToInt32::New(alloc, in);
+    } else {
+        MOZ_ASSERT(scalarType == MIRType_Float32);
+        replace = MToFloat32::New(alloc, in);
+    }
+
+    ins->block()->insertBefore(ins, replace);
+    ins->replaceOperand(Op, replace);
+
+    return replace->typePolicy()->adjustInputs(alloc, replace);
+}
+
+template bool SimdScalarPolicy<0>::staticAdjustInputs(TempAllocator &alloc, MInstruction *def);
+template bool SimdScalarPolicy<1>::staticAdjustInputs(TempAllocator &alloc, MInstruction *def);
+template bool SimdScalarPolicy<2>::staticAdjustInputs(TempAllocator &alloc, MInstruction *def);
+template bool SimdScalarPolicy<3>::staticAdjustInputs(TempAllocator &alloc, MInstruction *def);
+
+template <unsigned Op>
+bool
 BoxPolicy<Op>::staticAdjustInputs(TempAllocator &alloc, MInstruction *ins)
 {
     MDefinition *in = ins->getOperand(Op);
     if (in->type() == MIRType_Value)
         return true;
 
     ins->replaceOperand(Op, BoxAt(alloc, ins, in));
     return true;
@@ -992,16 +1022,17 @@ FilterTypeSetPolicy::adjustInputs(TempAl
     _(Mix3Policy<ObjectPolicy<0>, BoxPolicy<1>, ObjectPolicy<2> >)      \
     _(Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, BoxPolicy<2> >)         \
     _(Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2> >)         \
     _(Mix3Policy<ObjectPolicy<0>, ObjectPolicy<1>, IntPolicy<2> >)      \
     _(Mix3Policy<StringPolicy<0>, IntPolicy<1>, IntPolicy<2>>)          \
     _(Mix3Policy<StringPolicy<0>, ObjectPolicy<1>, StringPolicy<2> >)   \
     _(Mix3Policy<StringPolicy<0>, StringPolicy<1>, StringPolicy<2> >)   \
     _(Mix4Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2>, IntPolicy<3>>) \
+    _(Mix4Policy<SimdScalarPolicy<0>, SimdScalarPolicy<1>, SimdScalarPolicy<2>, SimdScalarPolicy<3> >) \
     _(MixPolicy<BoxPolicy<0>, ObjectPolicy<1> >)                        \
     _(MixPolicy<ConvertToStringPolicy<0>, ConvertToStringPolicy<1> >)   \
     _(MixPolicy<ConvertToStringPolicy<0>, ObjectPolicy<1> >)            \
     _(MixPolicy<DoublePolicy<0>, DoublePolicy<1> >)                     \
     _(MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >)                        \
     _(MixPolicy<ObjectPolicy<0>, ConvertToStringPolicy<1> >)            \
     _(MixPolicy<ObjectPolicy<0>, IntPolicy<1> >)                        \
     _(MixPolicy<ObjectPolicy<0>, NoFloatPolicy<1> >)                    \
--- a/js/src/jit/TypePolicy.h
+++ b/js/src/jit/TypePolicy.h
@@ -299,16 +299,29 @@ class ObjectPolicy MOZ_FINAL : public Ty
         return staticAdjustInputs(alloc, ins);
     }
 };
 
 // Single-object input. If the input is a Value, it is unboxed. If it is
 // a primitive, we use ValueToNonNullObject.
 typedef ObjectPolicy<0> SingleObjectPolicy;
 
+// Convert an operand to have a type identical to the scalar type of the
+// returned type of the instruction.
+template <unsigned Op>
+class SimdScalarPolicy MOZ_FINAL : public TypePolicy
+{
+  public:
+    EMPTY_DATA_;
+    static bool staticAdjustInputs(TempAllocator &alloc, MInstruction *def);
+    virtual bool adjustInputs(TempAllocator &alloc, MInstruction *def) MOZ_OVERRIDE {
+        return staticAdjustInputs(alloc, def);
+    }
+};
+
 template <unsigned Op>
 class BoxPolicy MOZ_FINAL : public TypePolicy
 {
   public:
     EMPTY_DATA_;
     static bool staticAdjustInputs(TempAllocator &alloc, MInstruction *ins);
     virtual bool adjustInputs(TempAllocator &alloc, MInstruction *ins) MOZ_OVERRIDE {
         return staticAdjustInputs(alloc, ins);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4517,16 +4517,33 @@ static const JSFunctionSpecWithHelp fuzz
 "  and that it knows nothing about. The reverse applies as well: the\n"
 "  original hook, that we reinstate after the call to |fun| completes,\n"
 "  might be asked for the source code of compilations that |fun|\n"
 "  performed, and which, presumably, only |hook| knows how to find.\n"),
 
     JS_FS_HELP_END
 };
 
+static const JSFunctionSpecWithHelp console_functions[] = {
+    JS_FN_HELP("log", Print, 0, 0,
+"log([exp ...])",
+"  Evaluate and print expressions to stdout.\n"
+"  This function is an alias of the print() function."),
+    JS_FS_HELP_END
+};
+
+bool
+DefineConsole(JSContext *cx, HandleObject global)
+{
+    RootedObject obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
+    return obj &&
+           JS_DefineFunctionsWithHelp(cx, obj, console_functions) &&
+           JS_DefineProperty(cx, global, "console", obj, 0);
+}
+
 #ifdef MOZ_PROFILING
 # define PROFILING_FUNCTION_COUNT 5
 # ifdef MOZ_CALLGRIND
 #  define CALLGRIND_FUNCTION_COUNT 3
 # else
 #  define CALLGRIND_FUNCTION_COUNT 0
 # endif
 # ifdef MOZ_VTUNE
@@ -5214,16 +5231,18 @@ NewGlobalObject(JSContext *cx, JS::Compa
         if (!js::DefineTestingFunctions(cx, glob, fuzzingSafe))
             return nullptr;
 
         if (!fuzzingSafe) {
             if (!JS_DefineFunctionsWithHelp(cx, glob, fuzzing_unsafe_functions))
                 return nullptr;
             if (!js::DefineOS(cx, glob))
                 return nullptr;
+            if (!DefineConsole(cx, glob))
+                return nullptr;
         }
 
         /* Initialize FakeDOMObject. */
         static const js::DOMCallbacks DOMcallbacks = {
             InstanceClassHasProtoAtDepth
         };
         SetDOMCallbacks(cx->runtime(), &DOMcallbacks);
 
--- a/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
@@ -1,555 +1,204 @@
 /* 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 <stdint.h>
-#include <stdio.h>
+#include <string.h>
+#include <vector>
 
 #include "ClearKeyDecryptionManager.h"
-#include "ClearKeyUtils.h"
-#include "ClearKeyStorage.h"
-#include "ClearKeyPersistence.h"
-#include "gmp-task-utils.h"
-
+#include "gmp-decryption.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
 
-using namespace mozilla;
-using namespace std;
-
-class ClearKeyDecryptor
+class ClearKeyDecryptor : public RefCounted
 {
 public:
-  ClearKeyDecryptor(GMPDecryptorCallback* aCallback, const Key& aKey);
-  ~ClearKeyDecryptor();
-
-  void InitKey();
+  MOZ_IMPLICIT ClearKeyDecryptor();
 
-  void QueueDecrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
+  void InitKey(const Key& aKey);
+  bool HasKey() const { return !!mKey.size(); }
 
-  uint32_t AddRef();
-  uint32_t Release();
+  GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+                 GMPEncryptedBufferMetadata* aMetadata);
 
   const Key& DecryptionKey() const { return mKey; }
 
 private:
-  struct DecryptTask : public GMPTask
-  {
-    DecryptTask(ClearKeyDecryptor* aTarget, GMPBuffer* aBuffer,
-                GMPEncryptedBufferMetadata* aMetadata)
-      : mTarget(aTarget), mBuffer(aBuffer), mMetadata(aMetadata) { }
-
-    virtual void Run() MOZ_OVERRIDE
-    {
-      mTarget->Decrypt(mBuffer, mMetadata);
-    }
-
-    virtual void Destroy() MOZ_OVERRIDE {
-      delete this;
-    }
-
-    virtual ~DecryptTask() { }
-
-    ClearKeyDecryptor* mTarget;
-    GMPBuffer* mBuffer;
-    GMPEncryptedBufferMetadata* mMetadata;
-  };
-
-  struct DestroyTask : public GMPTask
-  {
-    explicit DestroyTask(ClearKeyDecryptor* aTarget) : mTarget(aTarget) { }
-
-    virtual void Run() MOZ_OVERRIDE {
-      delete mTarget;
-    }
-
-    virtual void Destroy() MOZ_OVERRIDE {
-      delete this;
-    }
-
-    virtual ~DestroyTask() { }
-
-    ClearKeyDecryptor* mTarget;
-  };
-
-  void Decrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
-
-  uint32_t mRefCnt;
-
-  GMPDecryptorCallback* mCallback;
-  GMPThread* mThread;
+  ~ClearKeyDecryptor();
 
   Key mKey;
 };
 
+
+/* static */ ClearKeyDecryptionManager* ClearKeyDecryptionManager::sInstance = nullptr;
+
+/* static */ ClearKeyDecryptionManager*
+ClearKeyDecryptionManager::Get()
+{
+  if (!sInstance) {
+    sInstance = new ClearKeyDecryptionManager();
+  }
+  return sInstance;
+}
+
 ClearKeyDecryptionManager::ClearKeyDecryptionManager()
 {
-  CK_LOGD("ClearKeyDecryptionManager ctor");
-
-  // The ClearKeyDecryptionManager maintains a self reference which is
-  // removed when the host is finished with the interface and calls
-  // DecryptingComplete(). We make ClearKeyDecryptionManager refcounted so
-  // that the tasks to that we dispatch to call functions on it won't end up
-  // derefing a null reference after DecryptingComplete() is called.
-  AddRef();
+  CK_LOGD("ClearKeyDecryptionManager::ClearKeyDecryptionManager");
 }
 
 ClearKeyDecryptionManager::~ClearKeyDecryptionManager()
 {
-  CK_LOGD("ClearKeyDecryptionManager dtor");
-  MOZ_ASSERT(mRefCount == 1);
+  CK_LOGD("ClearKeyDecryptionManager::~ClearKeyDecryptionManager");
+
+  sInstance = nullptr;
+
+  for (auto it = mDecryptors.begin(); it != mDecryptors.end(); it++) {
+    it->second->Release();
+  }
+  mDecryptors.clear();
 }
 
-void
-ClearKeyDecryptionManager::Init(GMPDecryptorCallback* aCallback)
+bool
+ClearKeyDecryptionManager::HasSeenKeyId(const KeyId& aKeyId) const
 {
-  CK_LOGD("ClearKeyDecryptionManager::Init");
-  mCallback = aCallback;
-  mCallback->SetCapabilities(GMP_EME_CAP_DECRYPT_AUDIO |
-                             GMP_EME_CAP_DECRYPT_VIDEO);
-  ClearKeyPersistence::EnsureInitialized();
+  CK_LOGD("ClearKeyDecryptionManager::HasSeenKeyId");
+  return mDecryptors.find(aKeyId) != mDecryptors.end();
 }
 
-void
-ClearKeyDecryptionManager::CreateSession(uint32_t aCreateSessionToken,
-                                         uint32_t aPromiseId,
-                                         const char* aInitDataType,
-                                         uint32_t aInitDataTypeSize,
-                                         const uint8_t* aInitData,
-                                         uint32_t aInitDataSize,
-                                         GMPSessionType aSessionType)
+bool
+ClearKeyDecryptionManager::IsExpectingKeyForKeyId(const KeyId& aKeyId) const
 {
-  CK_LOGD("ClearKeyDecryptionManager::CreateSession type:%s", aInitDataType);
-
-  // initDataType must be "cenc".
-  if (strcmp("cenc", aInitDataType)) {
-    mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
-                             nullptr /* message */, 0 /* messageLen */);
-    return;
-  }
-
-  if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
-                                                        aCreateSessionToken,
-                                                        aPromiseId,
-                                                        aInitData,
-                                                        aInitDataSize,
-                                                        aSessionType)) {
-    return;
-  }
-
-  string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
-  MOZ_ASSERT(mSessions.find(sessionId) == mSessions.end());
-
-  ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
-  session->Init(aCreateSessionToken, aPromiseId, aInitData, aInitDataSize);
-  mSessions[sessionId] = session;
-
-  const vector<KeyId>& sessionKeys = session->GetKeyIds();
-  vector<KeyId> neededKeys;
-  for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) {
-    if (!Contains(mDecryptors, *it)) {
-      // Need to request this key ID from the client.
-      neededKeys.push_back(*it);
-    } else {
-      // We already have a key for this key ID. Mark as usable.
-      mCallback->KeyIdUsable(sessionId.c_str(), sessionId.length(),
-                             &(*it)[0], it->size());
-    }
-  }
-
-  if (neededKeys.empty()) {
-    CK_LOGD("No keys needed from client.");
-    return;
-  }
-
-  // Send a request for needed key data.
-  string request;
-  ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
-  mCallback->SessionMessage(&sessionId[0], sessionId.length(),
-                            (uint8_t*)&request[0], request.length(),
-                            "" /* destination url */, 0);
+  CK_LOGD("ClearKeyDecryptionManager::IsExpectingKeyForKeyId");
+  const auto& decryptor = mDecryptors.find(aKeyId);
+  return decryptor != mDecryptors.end() && !decryptor->second->HasKey();
 }
 
-void
-ClearKeyDecryptionManager::LoadSession(uint32_t aPromiseId,
-                                       const char* aSessionId,
-                                       uint32_t aSessionIdLength)
+bool
+ClearKeyDecryptionManager::HasKeyForKeyId(const KeyId& aKeyId) const
 {
-  CK_LOGD("ClearKeyDecryptionManager::LoadSession");
-
-  if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
-    mCallback->ResolveLoadSessionPromise(aPromiseId, false);
-    return;
-  }
+  CK_LOGD("ClearKeyDecryptionManager::HasKeyForKeyId");
+  const auto& decryptor = mDecryptors.find(aKeyId);
+  return decryptor != mDecryptors.end() && decryptor->second->HasKey();
+}
 
-  if (ClearKeyPersistence::DeferLoadSessionIfNotReady(this,
-                                                      aPromiseId,
-                                                      aSessionId,
-                                                      aSessionIdLength)) {
-    return;
-  }
-
-  string sid(aSessionId, aSessionId + aSessionIdLength);
-  if (!ClearKeyPersistence::IsPersistentSessionId(sid)) {
-    mCallback->ResolveLoadSessionPromise(aPromiseId, false);
-    return;
-  }
-
-  // Callsback PersistentSessionDataLoaded with results...
-  ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId);
+const Key&
+ClearKeyDecryptionManager::GetDecryptionKey(const KeyId& aKeyId)
+{
+  MOZ_ASSERT(HasKeyForKeyId(aKeyId));
+  return mDecryptors[aKeyId]->DecryptionKey();
 }
 
 void
-ClearKeyDecryptionManager::PersistentSessionDataLoaded(GMPErr aStatus,
-                                                       uint32_t aPromiseId,
-                                                       const string& aSessionId,
-                                                       const uint8_t* aKeyData,
-                                                       uint32_t aKeyDataSize)
-{
-  if (GMP_FAILED(aStatus) ||
-      Contains(mSessions, aSessionId) ||
-      (aKeyDataSize % (2 * CLEARKEY_KEY_LEN)) != 0) {
-    mCallback->ResolveLoadSessionPromise(aPromiseId, false);
-    return;
-  }
-
-  ClearKeySession* session = new ClearKeySession(aSessionId,
-                                                 mCallback,
-                                                 kGMPPersistentSession);
-  mSessions[aSessionId] = session;
-
-  uint32_t numKeys = aKeyDataSize / (2 * CLEARKEY_KEY_LEN);
-  for (uint32_t i = 0; i < numKeys; i ++) {
-    const uint8_t* base = aKeyData + 2 * CLEARKEY_KEY_LEN * i;
-
-    KeyId keyId(base, base + CLEARKEY_KEY_LEN);
-    MOZ_ASSERT(keyId.size() == CLEARKEY_KEY_LEN);
-
-    Key key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
-    MOZ_ASSERT(key.size() == CLEARKEY_KEY_LEN);
-
-    session->AddKeyId(keyId);
-
-    if (!Contains(mDecryptors, keyId)) {
-      mDecryptors[keyId] = new ClearKeyDecryptor(mCallback, key);
-    }
-    mDecryptors[keyId]->AddRef();
-    mCallback->KeyIdUsable(aSessionId.c_str(), aSessionId.size(),
-                           &keyId[0], keyId.size());
-  }
-
-  mCallback->ResolveLoadSessionPromise(aPromiseId, true);
-}
-
-void
-ClearKeyDecryptionManager::UpdateSession(uint32_t aPromiseId,
-                                         const char* aSessionId,
-                                         uint32_t aSessionIdLength,
-                                         const uint8_t* aResponse,
-                                         uint32_t aResponseSize)
+ClearKeyDecryptionManager::InitKey(KeyId aKeyId, Key aKey)
 {
-  CK_LOGD("ClearKeyDecryptionManager::UpdateSession");
-  string sessionId(aSessionId, aSessionId + aSessionIdLength);
-
-  auto itr = mSessions.find(sessionId);
-  if (itr == mSessions.end() || !(itr->second)) {
-    CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
-    mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
-    return;
-  }
-  ClearKeySession* session = itr->second;
-
-  // Parse the response for any (key ID, key) pairs.
-  vector<KeyIdPair> keyPairs;
-  if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
-    CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
-    mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError, nullptr, 0);
-    return;
-  }
-
-  for (auto it = keyPairs.begin(); it != keyPairs.end(); it++) {
-    KeyId& keyId = it->mKeyId;
-
-    if (!Contains(mDecryptors, keyId)) {
-      mDecryptors[keyId] = new ClearKeyDecryptor(mCallback, it->mKey);
-      mCallback->KeyIdUsable(aSessionId, aSessionIdLength,
-                             &keyId[0], keyId.size());
-    }
-
-    mDecryptors[keyId]->AddRef();
-  }
-
-  if (session->Type() != kGMPPersistentSession) {
-    mCallback->ResolvePromise(aPromiseId);
-    return;
-  }
-
-  // Store the keys on disk. We store a record whose name is the sessionId,
-  // and simply append each keyId followed by its key.
-  vector<uint8_t> keydata;
-  Serialize(session, keydata);
-  GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
-  static const char* message = "Couldn't store cenc key init data";
-  GMPTask* reject = WrapTask(mCallback,
-                             &GMPDecryptorCallback::RejectPromise,
-                             aPromiseId,
-                             kGMPInvalidStateError,
-                             message,
-                             strlen(message));
-  StoreData(sessionId, keydata, resolve, reject);
-}
-
-void
-ClearKeyDecryptionManager::Serialize(const ClearKeySession* aSession,
-                                     std::vector<uint8_t>& aOutKeyData)
-{
-  const std::vector<KeyId>& keyIds = aSession->GetKeyIds();
-  for (size_t i = 0; i < keyIds.size(); i++) {
-    const KeyId& keyId = keyIds[i];
-    if (!Contains(mDecryptors, keyId)) {
-      continue;
-    }
-    MOZ_ASSERT(keyId.size() == CLEARKEY_KEY_LEN);
-    aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end());
-    const Key& key = mDecryptors[keyId]->DecryptionKey();
-    MOZ_ASSERT(key.size() == CLEARKEY_KEY_LEN);
-    aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end());
+  CK_LOGD("ClearKeyDecryptionManager::InitKey");
+  if (IsExpectingKeyForKeyId(aKeyId)) {
+    mDecryptors[aKeyId]->InitKey(aKey);
   }
 }
 
 void
-ClearKeyDecryptionManager::CloseSession(uint32_t aPromiseId,
-                                        const char* aSessionId,
-                                        uint32_t aSessionIdLength)
+ClearKeyDecryptionManager::ExpectKeyId(KeyId aKeyId)
 {
-  CK_LOGD("ClearKeyDecryptionManager::CloseSession");
-
-  string sessionId(aSessionId, aSessionId + aSessionIdLength);
-  auto itr = mSessions.find(sessionId);
-  if (itr == mSessions.end()) {
-    CK_LOGW("ClearKey CDM couldn't close non-existent session.");
-    mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
-    return;
+  CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId");
+  if (!HasSeenKeyId(aKeyId)) {
+    mDecryptors[aKeyId] = new ClearKeyDecryptor();
   }
-
-  ClearKeySession* session = itr->second;
-  MOZ_ASSERT(session);
-
-  ClearInMemorySessionData(session);
-  mCallback->ResolvePromise(aPromiseId);
-  mCallback->SessionClosed(aSessionId, aSessionIdLength);
-}
-
-void
-ClearKeyDecryptionManager::ClearInMemorySessionData(ClearKeySession* aSession)
-{
-  MOZ_ASSERT(aSession);
-
-  const vector<KeyId>& keyIds = aSession->GetKeyIds();
-  for (auto it = keyIds.begin(); it != keyIds.end(); it++) {
-    MOZ_ASSERT(Contains(mDecryptors, *it));
-    if (!mDecryptors[*it]->Release()) {
-      mDecryptors.erase(*it);
-      mCallback->KeyIdNotUsable(aSession->Id().c_str(), aSession->Id().size(),
-                                &(*it)[0], it->size());
-    }
-  }
-
-  mSessions.erase(aSession->Id());
-  delete aSession;
+  mDecryptors[aKeyId]->AddRef();
 }
 
 void
-ClearKeyDecryptionManager::RemoveSession(uint32_t aPromiseId,
-                                         const char* aSessionId,
-                                         uint32_t aSessionIdLength)
+ClearKeyDecryptionManager::ReleaseKeyId(KeyId aKeyId)
 {
-  string sessionId(aSessionId, aSessionId + aSessionIdLength);
-  auto itr = mSessions.find(sessionId);
-  if (itr == mSessions.end()) {
-    CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
-    mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
-    return;
+  CK_LOGD("ClearKeyDecryptionManager::ReleaseKeyId");
+  ClearKeyDecryptor* decryptor = mDecryptors[aKeyId];
+  if (!decryptor->Release()) {
+    mDecryptors.erase(aKeyId);
   }
-
-  ClearKeySession* session = itr->second;
-  MOZ_ASSERT(session);
-  string sid = session->Id();
-  bool isPersistent = session->Type() == kGMPPersistentSession;
-  ClearInMemorySessionData(session);
-
-  if (!isPersistent) {
-    mCallback->ResolvePromise(aPromiseId);
-    return;
-  }
-
-  ClearKeyPersistence::PersistentSessionRemoved(sid);
-
-  // Overwrite the record storing the sessionId's key data with a zero
-  // length record to delete it.
-  vector<uint8_t> emptyKeydata;
-  GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
-  static const char* message = "Could not remove session";
-  GMPTask* reject = WrapTask(mCallback,
-                             &GMPDecryptorCallback::RejectPromise,
-                             aPromiseId,
-                             kGMPInvalidAccessError,
-                             message,
-                             strlen(message));
-  StoreData(sessionId, emptyKeydata, resolve, reject);
 }
 
-void
-ClearKeyDecryptionManager::SetServerCertificate(uint32_t aPromiseId,
-                                                const uint8_t* aServerCert,
-                                                uint32_t aServerCertSize)
-{
-  // ClearKey CDM doesn't support this method by spec.
-  CK_LOGD("ClearKeyDecryptionManager::SetServerCertificate");
-  mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
-                           nullptr /* message */, 0 /* messageLen */);
-}
-
-void
-ClearKeyDecryptionManager::Decrypt(GMPBuffer* aBuffer,
+GMPErr
+ClearKeyDecryptionManager::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
                                    GMPEncryptedBufferMetadata* aMetadata)
 {
   CK_LOGD("ClearKeyDecryptionManager::Decrypt");
   KeyId keyId(aMetadata->KeyId(), aMetadata->KeyId() + aMetadata->KeyIdSize());
 
-  if (!Contains(mDecryptors, keyId)) {
-    CK_LOGD("ClearKeyDecryptionManager::Decrypt GMPNoKeyErr");
-    mCallback->Decrypted(aBuffer, GMPNoKeyErr);
-    return;
+  if (!HasKeyForKeyId(keyId)) {
+    return GMPNoKeyErr;
   }
 
-  mDecryptors[keyId]->QueueDecrypt(aBuffer, aMetadata);
+  return mDecryptors[keyId]->Decrypt(aBuffer, aBufferSize, aMetadata);
+}
+
+ClearKeyDecryptor::ClearKeyDecryptor()
+{
+  CK_LOGD("ClearKeyDecryptor ctor");
+}
+
+ClearKeyDecryptor::~ClearKeyDecryptor()
+{
+  CK_LOGD("ClearKeyDecryptor dtor; key ID = %08x...", *(uint32_t*)&mKey[0]);
 }
 
 void
-ClearKeyDecryptionManager::DecryptingComplete()
+ClearKeyDecryptor::InitKey(const Key& aKey)
 {
-  CK_LOGD("ClearKeyDecryptionManager::DecryptingComplete");
-
-  for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
-    delete it->second;
-  }
-  mSessions.clear();
-
-  for (auto it = mDecryptors.begin(); it != mDecryptors.end(); it++) {
-    delete it->second;
-  }
-  mDecryptors.clear();
-
-  Release();
+  mKey = aKey;
 }
 
-void
-ClearKeyDecryptor::QueueDecrypt(GMPBuffer* aBuffer,
-                                GMPEncryptedBufferMetadata* aMetadata)
-{
-  CK_LOGD("ClearKeyDecryptor::QueueDecrypt");
-  mThread->Post(new DecryptTask(this, aBuffer, aMetadata));
-}
-
-void
-ClearKeyDecryptor::Decrypt(GMPBuffer* aBuffer,
+GMPErr
+ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
                            GMPEncryptedBufferMetadata* aMetadata)
 {
-  if (!mThread) {
-    mCallback->Decrypted(aBuffer, GMPGenericErr);
-  }
-
+  CK_LOGD("ClearKeyDecryptor::Decrypt");
   // If the sample is split up into multiple encrypted subsamples, we need to
   // stitch them into one continuous buffer for decryption.
-  vector<uint8_t> tmp(aBuffer->Size());
+  std::vector<uint8_t> tmp(aBufferSize);
 
   if (aMetadata->NumSubsamples()) {
     // Take all encrypted parts of subsamples and stitch them into one
     // continuous encrypted buffer.
-    unsigned char* data = aBuffer->Data();
+    unsigned char* data = aBuffer;
     unsigned char* iter = &tmp[0];
     for (size_t i = 0; i < aMetadata->NumSubsamples(); i++) {
       data += aMetadata->ClearBytes()[i];
       uint32_t cipherBytes = aMetadata->CipherBytes()[i];
 
       memcpy(iter, data, cipherBytes);
 
       data += cipherBytes;
       iter += cipherBytes;
     }
 
     tmp.resize((size_t)(iter - &tmp[0]));
   } else {
-    memcpy(&tmp[0], aBuffer->Data(), aBuffer->Size());
+    memcpy(&tmp[0], aBuffer, aBufferSize);
   }
 
   MOZ_ASSERT(aMetadata->IVSize() == 8 || aMetadata->IVSize() == 16);
-  vector<uint8_t> iv(aMetadata->IV(), aMetadata->IV() + aMetadata->IVSize());
+  std::vector<uint8_t> iv(aMetadata->IV(), aMetadata->IV() + aMetadata->IVSize());
   iv.insert(iv.end(), CLEARKEY_KEY_LEN - aMetadata->IVSize(), 0);
 
   ClearKeyUtils::DecryptAES(mKey, tmp, iv);
 
   if (aMetadata->NumSubsamples()) {
     // Take the decrypted buffer, split up into subsamples, and insert those
     // subsamples back into their original position in the original buffer.
-    unsigned char* data = aBuffer->Data();
+    unsigned char* data = aBuffer;
     unsigned char* iter = &tmp[0];
     for (size_t i = 0; i < aMetadata->NumSubsamples(); i++) {
       data += aMetadata->ClearBytes()[i];
       uint32_t cipherBytes = aMetadata->CipherBytes()[i];
 
       memcpy(data, iter, cipherBytes);
 
       data += cipherBytes;
       iter += cipherBytes;
     }
   } else {
-    memcpy(aBuffer->Data(), &tmp[0], aBuffer->Size());
+    memcpy(aBuffer, &tmp[0], aBufferSize);
   }
 
-  mCallback->Decrypted(aBuffer, GMPNoErr);
-}
-
-ClearKeyDecryptor::ClearKeyDecryptor(GMPDecryptorCallback* aCallback,
-                                     const Key& aKey)
-  : mRefCnt(0)
-  , mCallback(aCallback)
-  , mThread(nullptr)
-  , mKey(aKey)
-{
-  if (GetPlatform()->createthread(&mThread) != GMPNoErr) {
-    CK_LOGD("failed to create thread in clearkey cdm");
-    mThread = nullptr;
-    return;
-  }
-}
-
-ClearKeyDecryptor::~ClearKeyDecryptor()
-{
-  CK_LOGD("ClearKeyDecryptor dtor; key ID = %08x...", *(uint32_t*)&mKey[0]);
+  return GMPNoErr;
 }
-
-uint32_t
-ClearKeyDecryptor::AddRef()
-{
-  return ++mRefCnt;
-}
-
-uint32_t
-ClearKeyDecryptor::Release()
-{
-  uint32_t newCount = --mRefCnt;
-  if (!newCount) {
-    if (mThread) {
-      // Shutdown mThread. We cache a pointer to mThread, as the DestroyTask
-      // may run and delete |this| before Post() returns.
-      GMPThread* thread = mThread;
-      thread->Post(new DestroyTask(this));
-      thread->Join();
-    } else {
-      delete this;
-    }
-  }
-
-  return newCount;
-}
--- a/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.h
+++ b/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.h
@@ -1,80 +1,47 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef __ClearKeyDecryptor_h__
-#define __ClearKeyDecryptor_h__
+#ifndef __ClearKeyDecryptionManager_h__
+#define __ClearKeyDecryptionManager_h__
 
 #include <map>
-#include <string>
-#include <vector>
 
-#include "ClearKeySession.h"
 #include "ClearKeyUtils.h"
-#include "gmp-api/gmp-decryption.h"
-#include "ScopedNSSTypes.h"
 #include "RefCounted.h"
 
 class ClearKeyDecryptor;
-class ClearKeyDecryptionManager MOZ_FINAL : public GMPDecryptor
-                                          , public RefCounted
-{
-public:
-  ClearKeyDecryptionManager();
-
-  virtual void Init(GMPDecryptorCallback* aCallback) MOZ_OVERRIDE;
 
-  virtual void CreateSession(uint32_t aCreateSessionToken,
-                             uint32_t aPromiseId,
-                             const char* aInitDataType,
-                             uint32_t aInitDataTypeSize,
-                             const uint8_t* aInitData,
-                             uint32_t aInitDataSize,
-                             GMPSessionType aSessionType) MOZ_OVERRIDE;
+class ClearKeyDecryptionManager : public RefCounted
+{
+private:
+  ClearKeyDecryptionManager();
+  ~ClearKeyDecryptionManager();
 
-  virtual void LoadSession(uint32_t aPromiseId,
-                           const char* aSessionId,
-                           uint32_t aSessionIdLength) MOZ_OVERRIDE;
+  static ClearKeyDecryptionManager* sInstance;
+
+public:
+  static ClearKeyDecryptionManager* Get();
 
-  virtual void UpdateSession(uint32_t aPromiseId,
-                             const char* aSessionId,
-                             uint32_t aSessionIdLength,
-                             const uint8_t* aResponse,
-                             uint32_t aResponseSize) MOZ_OVERRIDE;
+  bool HasSeenKeyId(const KeyId& aKeyId) const;
+  bool HasKeyForKeyId(const KeyId& aKeyId) const;
 
-  virtual void CloseSession(uint32_t aPromiseId,
-                            const char* aSessionId,
-                            uint32_t aSessionIdLength) MOZ_OVERRIDE;
-
-  virtual void RemoveSession(uint32_t aPromiseId,
-                             const char* aSessionId,
-                             uint32_t aSessionIdLength) MOZ_OVERRIDE;
+  const Key& GetDecryptionKey(const KeyId& aKeyId);
 
-  virtual void SetServerCertificate(uint32_t aPromiseId,
-                                    const uint8_t* aServerCert,
-                                    uint32_t aServerCertSize) MOZ_OVERRIDE;
-
-  virtual void Decrypt(GMPBuffer* aBuffer,
-                       GMPEncryptedBufferMetadata* aMetadata) MOZ_OVERRIDE;
+  // Create a decryptor for the given KeyId if one does not already exist.
+  void InitKey(KeyId aKeyId, Key aKey);
+  void ExpectKeyId(KeyId aKeyId);
+  void ReleaseKeyId(KeyId aKeyId);
 
-  virtual void DecryptingComplete() MOZ_OVERRIDE;
+  GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
+                 GMPEncryptedBufferMetadata* aMetadata);
 
-  void PersistentSessionDataLoaded(GMPErr aStatus,
-                                   uint32_t aPromiseId,
-                                   const std::string& aSessionId,
-                                   const uint8_t* aKeyData,
-                                   uint32_t aKeyDataSize);
+  void Shutdown();
 
 private:
-  ~ClearKeyDecryptionManager();
-
-  void ClearInMemorySessionData(ClearKeySession* aSession);
-  void Serialize(const ClearKeySession* aSession, std::vector<uint8_t>& aOutKeyData);
-
-  GMPDecryptorCallback* mCallback;
+  bool IsExpectingKeyForKeyId(const KeyId& aKeyId) const;
 
   std::map<KeyId, ClearKeyDecryptor*> mDecryptors;
-  std::map<std::string, ClearKeySession*> mSessions;
 };
 
-#endif // __ClearKeyDecryptor_h__
+#endif // __ClearKeyDecryptionManager_h__
--- a/media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ClearKeyPersistence.h"
 #include "ClearKeyUtils.h"
 #include "ClearKeyStorage.h"
-#include "ClearKeyDecryptionManager.h"
+#include "ClearKeySessionManager.h"
 #include "mozilla/RefPtr.h"
 
 #include <stdint.h>
 #include <set>
 #include <vector>
 #include <sstream>
 
 using namespace mozilla;
@@ -92,17 +92,17 @@ ClearKeyPersistence::GetNewSessionId(GMP
   sNextSessionId++;
 
   return sessionId;
 }
 
 
 class CreateSessionTask : public GMPTask {
 public:
-  CreateSessionTask(ClearKeyDecryptionManager* aTarget,
+  CreateSessionTask(ClearKeySessionManager* aTarget,
                     uint32_t aCreateSessionToken,
                     uint32_t aPromiseId,
                     const uint8_t* aInitData,
                     uint32_t aInitDataSize,
                     GMPSessionType aSessionType)
     : mTarget(aTarget)
     , mCreateSessionToken(aCreateSessionToken)
     , mPromiseId(aPromiseId)
@@ -120,26 +120,26 @@ public:
                            &mInitData.front(),
                            mInitData.size(),
                            mSessionType);
   }
   virtual void Destroy() MOZ_OVERRIDE {
     delete this;
   }
 private:
-  RefPtr<ClearKeyDecryptionManager> mTarget;
+  RefPtr<ClearKeySessionManager> mTarget;
   uint32_t mCreateSessionToken;
   uint32_t mPromiseId;
   vector<uint8_t> mInitData;
   GMPSessionType mSessionType;
 };
 
 
 /* static */ bool
-ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
+ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
                                                   uint32_t aCreateSessionToken,
                                                   uint32_t aPromiseId,
                                                   const uint8_t* aInitData,
                                                   uint32_t aInitDataSize,
                                                   GMPSessionType aSessionType)
 {
   if (sPersistentKeyState >= LOADED)  {
     return false;
@@ -151,17 +151,17 @@ ClearKeyPersistence::DeferCreateSessionI
                                      aInitDataSize,
                                      aSessionType);
   sTasksBlockedOnSessionIdLoad.push_back(t);
   return true;
 }
 
 class LoadSessionTask : public GMPTask {
 public:
-  LoadSessionTask(ClearKeyDecryptionManager* aTarget,
+  LoadSessionTask(ClearKeySessionManager* aTarget,
                   uint32_t aPromiseId,
                   const char* aSessionId,
                   uint32_t aSessionIdLength)
     : mTarget(aTarget)
     , mPromiseId(aPromiseId)
     , mSessionId(aSessionId, aSessionId + aSessionIdLength)
   {
   }
@@ -169,23 +169,23 @@ public:
     mTarget->LoadSession(mPromiseId,
                          mSessionId.c_str(),
                          mSessionId.size());
   }
   virtual void Destroy() MOZ_OVERRIDE {
     delete this;
   }
 private:
-  RefPtr<ClearKeyDecryptionManager> mTarget;
+  RefPtr<ClearKeySessionManager> mTarget;
   uint32_t mPromiseId;
   string mSessionId;
 };
 
 /* static */ bool
-ClearKeyPersistence::DeferLoadSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
+ClearKeyPersistence::DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
                                                 uint32_t aPromiseId,
                                                 const char* aSessionId,
                                                 uint32_t aSessionIdLength)
 {
   if (sPersistentKeyState >= LOADED)  {
     return false;
   }
   GMPTask* t = new LoadSessionTask(aInstance,
@@ -199,39 +199,39 @@ ClearKeyPersistence::DeferLoadSessionIfN
 /* static */ bool
 ClearKeyPersistence::IsPersistentSessionId(const string& aSessionId)
 {
   return Contains(sPersistentSessionIds, atoi(aSessionId.c_str()));
 }
 
 class LoadSessionFromKeysTask : public ReadContinuation {
 public:
-  LoadSessionFromKeysTask(ClearKeyDecryptionManager* aTarget,
+  LoadSessionFromKeysTask(ClearKeySessionManager* aTarget,
                           const string& aSessionId,
                           uint32_t aPromiseId)
     : mTarget(aTarget)
     , mSessionId(aSessionId)
     , mPromiseId(aPromiseId)
   {
   }
 
   virtual void ReadComplete(GMPErr aStatus,
                             const uint8_t* aData,
                             uint32_t aLength) MOZ_OVERRIDE
   {
     mTarget->PersistentSessionDataLoaded(aStatus, mPromiseId, mSessionId, aData, aLength);
   }
 private:
-  RefPtr<ClearKeyDecryptionManager> mTarget;
+  RefPtr<ClearKeySessionManager> mTarget;
   string mSessionId;
   uint32_t mPromiseId;
 };
 
 /* static */ void
-ClearKeyPersistence::LoadSessionData(ClearKeyDecryptionManager* aInstance,
+ClearKeyPersistence::LoadSessionData(ClearKeySessionManager* aInstance,
                                      const string& aSid,
                                      uint32_t aPromiseId)
 {
   LoadSessionFromKeysTask* loadTask =
     new LoadSessionFromKeysTask(aInstance, aSid, aPromiseId);
   ReadData(aSid, loadTask);
 }
 
--- a/media/gmp-clearkey/0.1/ClearKeyPersistence.h
+++ b/media/gmp-clearkey/0.1/ClearKeyPersistence.h
@@ -3,38 +3,38 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __ClearKeyPersistence_h__
 #define __ClearKeyPersistence_h__
 
 #include <string>
 #include "gmp-decryption.h"
 
-class ClearKeyDecryptionManager;
+class ClearKeySessionManager;
 
 class ClearKeyPersistence {
 public:
   static void EnsureInitialized();
 
   static std::string GetNewSessionId(GMPSessionType aSessionType);
 
-  static bool DeferCreateSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
+  static bool DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
                                            uint32_t aCreateSessionToken,
                                            uint32_t aPromiseId,
                                            const uint8_t* aInitData,
                                            uint32_t aInitDataSize,
                                            GMPSessionType aSessionType);
 
-  static bool DeferLoadSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
+  static bool DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
                                          uint32_t aPromiseId,
                                          const char* aSessionId,
                                          uint32_t aSessionIdLength);
 
   static bool IsPersistentSessionId(const std::string& aSid);
 
-  static void LoadSessionData(ClearKeyDecryptionManager* aInstance,
+  static void LoadSessionData(ClearKeySessionManager* aInstance,
                               const std::string& aSid,
                               uint32_t aPromiseId);
 
   static void PersistentSessionRemoved(const std::string& aSid);
 };
 
 #endif // __ClearKeyPersistence_h__
--- a/media/gmp-clearkey/0.1/ClearKeySession.h
+++ b/media/gmp-clearkey/0.1/ClearKeySession.h
@@ -27,17 +27,17 @@ public:
   void Init(uint32_t aCreateSessionToken,
             uint32_t aPromiseId,
             const uint8_t* aInitData, uint32_t aInitDataSize);
 
   GMPSessionType Type() const;
 
   void AddKeyId(const KeyId& aKeyId);
 
-  const std::string Id() const { return mSessionId; }
+  const std::string& Id() const { return mSessionId; }
 
 private:
   const std::string mSessionId;
   std::vector<KeyId> mKeyIds;
 
   GMPDecryptorCallback* mCallback;
   const GMPSessionType mSessionType;
 };
copy from media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
copy to media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
--- a/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -1,129 +1,65 @@
 /* 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 <stdint.h>
 #include <stdio.h>
 
 #include "ClearKeyDecryptionManager.h"
+#include "ClearKeySessionManager.h"
 #include "ClearKeyUtils.h"
 #include "ClearKeyStorage.h"
 #include "ClearKeyPersistence.h"
 #include "gmp-task-utils.h"
 
 #include "mozilla/Assertions.h"
 
 using namespace mozilla;
 using namespace std;
 
-class ClearKeyDecryptor
+ClearKeySessionManager::ClearKeySessionManager()
+  : mDecryptionManager(ClearKeyDecryptionManager::Get())
 {
-public:
-  ClearKeyDecryptor(GMPDecryptorCallback* aCallback, const Key& aKey);
-  ~ClearKeyDecryptor();
-
-  void InitKey();
-
-  void QueueDecrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
-
-  uint32_t AddRef();
-  uint32_t Release();
-
-  const Key& DecryptionKey() const { return mKey; }
-
-private:
-  struct DecryptTask : public GMPTask
-  {
-    DecryptTask(ClearKeyDecryptor* aTarget, GMPBuffer* aBuffer,
-                GMPEncryptedBufferMetadata* aMetadata)
-      : mTarget(aTarget), mBuffer(aBuffer), mMetadata(aMetadata) { }
-
-    virtual void Run() MOZ_OVERRIDE
-    {
-      mTarget->Decrypt(mBuffer, mMetadata);
-    }
-
-    virtual void Destroy() MOZ_OVERRIDE {
-      delete this;
-    }
-
-    virtual ~DecryptTask() { }
-
-    ClearKeyDecryptor* mTarget;
-    GMPBuffer* mBuffer;
-    GMPEncryptedBufferMetadata* mMetadata;
-  };
+  CK_LOGD("ClearKeySessionManager ctor");
+  AddRef();
 
-  struct DestroyTask : public GMPTask
-  {
-    explicit DestroyTask(ClearKeyDecryptor* aTarget) : mTarget(aTarget) { }
-
-    virtual void Run() MOZ_OVERRIDE {
-      delete mTarget;
-    }
-
-    virtual void Destroy() MOZ_OVERRIDE {
-      delete this;
-    }
-
-    virtual ~DestroyTask() { }
-
-    ClearKeyDecryptor* mTarget;
-  };
-
-  void Decrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
-
-  uint32_t mRefCnt;
-
-  GMPDecryptorCallback* mCallback;
-  GMPThread* mThread;
-
-  Key mKey;
-};
-
-ClearKeyDecryptionManager::ClearKeyDecryptionManager()
-{
-  CK_LOGD("ClearKeyDecryptionManager ctor");
-
-  // The ClearKeyDecryptionManager maintains a self reference which is
-  // removed when the host is finished with the interface and calls
-  // DecryptingComplete(). We make ClearKeyDecryptionManager refcounted so
-  // that the tasks to that we dispatch to call functions on it won't end up
-  // derefing a null reference after DecryptingComplete() is called.
-  AddRef();
+  if (GetPlatform()->createthread(&mThread) != GMPNoErr) {
+    CK_LOGD("failed to create thread in clearkey cdm");
+    mThread = nullptr;
+  }
 }
 
-ClearKeyDecryptionManager::~ClearKeyDecryptionManager()
+ClearKeySessionManager::~ClearKeySessionManager()
 {
-  CK_LOGD("ClearKeyDecryptionManager dtor");
-  MOZ_ASSERT(mRefCount == 1);
+  CK_LOGD("ClearKeySessionManager dtor");
+   MOZ_ASSERT(!mRefCount);
 }
 
 void
-ClearKeyDecryptionManager::Init(GMPDecryptorCallback* aCallback)
+ClearKeySessionManager::Init(GMPDecryptorCallback* aCallback)
 {
-  CK_LOGD("ClearKeyDecryptionManager::Init");
+  CK_LOGD("ClearKeySessionManager::Init");
   mCallback = aCallback;
   mCallback->SetCapabilities(GMP_EME_CAP_DECRYPT_AUDIO |
                              GMP_EME_CAP_DECRYPT_VIDEO);
   ClearKeyPersistence::EnsureInitialized();
 }
 
 void
-ClearKeyDecryptionManager::CreateSession(uint32_t aCreateSessionToken,
-                                         uint32_t aPromiseId,
-                                         const char* aInitDataType,
-                                         uint32_t aInitDataTypeSize,
-                                         const uint8_t* aInitData,
-                                         uint32_t aInitDataSize,
-                                         GMPSessionType aSessionType)
+ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
+                                      uint32_t aPromiseId,
+                                      const char* aInitDataType,
+                                      uint32_t aInitDataTypeSize,
+                                      const uint8_t* aInitData,
+                                      uint32_t aInitDataSize,
+                                      GMPSessionType aSessionType)
 {
-  CK_LOGD("ClearKeyDecryptionManager::CreateSession type:%s", aInitDataType);
+  CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType);
 
   // initDataType must be "cenc".
   if (strcmp("cenc", aInitDataType)) {
     mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
                              nullptr /* message */, 0 /* messageLen */);
     return;
   }
 
@@ -141,45 +77,43 @@ ClearKeyDecryptionManager::CreateSession
 
   ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
   session->Init(aCreateSessionToken, aPromiseId, aInitData, aInitDataSize);
   mSessions[sessionId] = session;
 
   const vector<KeyId>& sessionKeys = session->GetKeyIds();
   vector<KeyId> neededKeys;
   for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) {
-    if (!Contains(mDecryptors, *it)) {
-      // Need to request this key ID from the client.
-      neededKeys.push_back(*it);
-    } else {
-      // We already have a key for this key ID. Mark as usable.
-      mCallback->KeyIdUsable(sessionId.c_str(), sessionId.length(),
-                             &(*it)[0], it->size());
-    }
+    // Need to request this key ID from the client. We always send a key
+    // request, whether or not another session has sent a request with the same
+    // key ID. Otherwise a script can end up waiting for another script to
+    // respond to the request (which may not necessarily happen).
+    neededKeys.push_back(*it);
+    mDecryptionManager->ExpectKeyId(*it);
   }
 
   if (neededKeys.empty()) {
     CK_LOGD("No keys needed from client.");
     return;
   }
 
   // Send a request for needed key data.
   string request;
   ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
   mCallback->SessionMessage(&sessionId[0], sessionId.length(),
                             (uint8_t*)&request[0], request.length(),
                             "" /* destination url */, 0);
 }
 
 void
-ClearKeyDecryptionManager::LoadSession(uint32_t aPromiseId,
-                                       const char* aSessionId,
-                                       uint32_t aSessionIdLength)
+ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
+                                    const char* aSessionId,
+                                    uint32_t aSessionIdLength)
 {
-  CK_LOGD("ClearKeyDecryptionManager::LoadSession");
+  CK_LOGD("ClearKeySessionManager::LoadSession");
 
   if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
     mCallback->ResolveLoadSessionPromise(aPromiseId, false);
     return;
   }
 
   if (ClearKeyPersistence::DeferLoadSessionIfNotReady(this,
                                                       aPromiseId,
@@ -194,22 +128,23 @@ ClearKeyDecryptionManager::LoadSession(u
     return;
   }
 
   // Callsback PersistentSessionDataLoaded with results...
   ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId);
 }
 
 void
-ClearKeyDecryptionManager::PersistentSessionDataLoaded(GMPErr aStatus,
-                                                       uint32_t aPromiseId,
-                                                       const string& aSessionId,
-                                                       const uint8_t* aKeyData,
-                                                       uint32_t aKeyDataSize)
+ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus,
+                                                    uint32_t aPromiseId,
+                                                    const string& aSessionId,
+                                                    const uint8_t* aKeyData,
+                                                    uint32_t aKeyDataSize)
 {
+  CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
   if (GMP_FAILED(aStatus) ||
       Contains(mSessions, aSessionId) ||
       (aKeyDataSize % (2 * CLEARKEY_KEY_LEN)) != 0) {
     mCallback->ResolveLoadSessionPromise(aPromiseId, false);
     return;
   }
 
   ClearKeySession* session = new ClearKeySession(aSessionId,
@@ -224,35 +159,34 @@ ClearKeyDecryptionManager::PersistentSes
     KeyId keyId(base, base + CLEARKEY_KEY_LEN);
     MOZ_ASSERT(keyId.size() == CLEARKEY_KEY_LEN);
 
     Key key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
     MOZ_ASSERT(key.size() == CLEARKEY_KEY_LEN);
 
     session->AddKeyId(keyId);
 
-    if (!Contains(mDecryptors, keyId)) {
-      mDecryptors[keyId] = new ClearKeyDecryptor(mCallback, key);
-    }
-    mDecryptors[keyId]->AddRef();
-    mCallback->KeyIdUsable(aSessionId.c_str(), aSessionId.size(),
+    mDecryptionManager->ExpectKeyId(keyId);
+    mDecryptionManager->InitKey(keyId, key);
+    mKeyIds.insert(key);
+    mCallback->KeyIdUsable(&aSessionId[0], aSessionId.size(),
                            &keyId[0], keyId.size());
   }
 
   mCallback->ResolveLoadSessionPromise(aPromiseId, true);
 }
 
 void
-ClearKeyDecryptionManager::UpdateSession(uint32_t aPromiseId,
-                                         const char* aSessionId,
-                                         uint32_t aSessionIdLength,
-                                         const uint8_t* aResponse,
-                                         uint32_t aResponseSize)
+ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
+                                      const char* aSessionId,
+                                      uint32_t aSessionIdLength,
+                                      const uint8_t* aResponse,
+                                      uint32_t aResponseSize)
 {
-  CK_LOGD("ClearKeyDecryptionManager::UpdateSession");
+  CK_LOGD("ClearKeySessionManager::UpdateSession");
   string sessionId(aSessionId, aSessionId + aSessionIdLength);
 
   auto itr = mSessions.find(sessionId);
   if (itr == mSessions.end() || !(itr->second)) {
     CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
     mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
     return;
   }
@@ -262,25 +196,20 @@ ClearKeyDecryptionManager::UpdateSession
   vector<KeyIdPair> keyPairs;
   if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
     CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
     mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError, nullptr, 0);
     return;
   }
 
   for (auto it = keyPairs.begin(); it != keyPairs.end(); it++) {
-    KeyId& keyId = it->mKeyId;
-
-    if (!Contains(mDecryptors, keyId)) {
-      mDecryptors[keyId] = new ClearKeyDecryptor(mCallback, it->mKey);
-      mCallback->KeyIdUsable(aSessionId, aSessionIdLength,
-                             &keyId[0], keyId.size());
-    }
-
-    mDecryptors[keyId]->AddRef();
+    mDecryptionManager->InitKey(it->mKeyId, it->mKey);
+    mKeyIds.insert(it->mKeyId);
+    mCallback->KeyIdUsable(aSessionId, aSessionIdLength,
+                           &it->mKeyId[0], it->mKeyId.size());
   }
 
   if (session->Type() != kGMPPersistentSession) {
     mCallback->ResolvePromise(aPromiseId);
     return;
   }
 
   // Store the keys on disk. We store a record whose name is the sessionId,
@@ -294,39 +223,39 @@ ClearKeyDecryptionManager::UpdateSession
                              aPromiseId,
                              kGMPInvalidStateError,
                              message,
                              strlen(message));
   StoreData(sessionId, keydata, resolve, reject);
 }
 
 void
-ClearKeyDecryptionManager::Serialize(const ClearKeySession* aSession,
-                                     std::vector<uint8_t>& aOutKeyData)
+ClearKeySessionManager::Serialize(const ClearKeySession* aSession,
+                                  std::vector<uint8_t>& aOutKeyData)
 {
   const std::vector<KeyId>& keyIds = aSession->GetKeyIds();
   for (size_t i = 0; i < keyIds.size(); i++) {
     const KeyId& keyId = keyIds[i];
-    if (!Contains(mDecryptors, keyId)) {
+    if (!mDecryptionManager->HasKeyForKeyId(keyId)) {
       continue;
     }
     MOZ_ASSERT(keyId.size() == CLEARKEY_KEY_LEN);
     aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end());
-    const Key& key = mDecryptors[keyId]->DecryptionKey();
+    const Key& key = mDecryptionManager->GetDecryptionKey(keyId);
     MOZ_ASSERT(key.size() == CLEARKEY_KEY_LEN);
     aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end());
   }
 }
 
 void
-ClearKeyDecryptionManager::CloseSession(uint32_t aPromiseId,
-                                        const char* aSessionId,
-                                        uint32_t aSessionIdLength)
+ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
+                                     const char* aSessionId,
+                                     uint32_t aSessionIdLength)
 {
-  CK_LOGD("ClearKeyDecryptionManager::CloseSession");
+  CK_LOGD("ClearKeySessionManager::CloseSession");
 
   string sessionId(aSessionId, aSessionId + aSessionIdLength);
   auto itr = mSessions.find(sessionId);
   if (itr == mSessions.end()) {
     CK_LOGW("ClearKey CDM couldn't close non-existent session.");
     mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
     return;
   }
@@ -335,39 +264,40 @@ ClearKeyDecryptionManager::CloseSession(
   MOZ_ASSERT(session);
 
   ClearInMemorySessionData(session);
   mCallback->ResolvePromise(aPromiseId);
   mCallback->SessionClosed(aSessionId, aSessionIdLength);
 }
 
 void
-ClearKeyDecryptionManager::ClearInMemorySessionData(ClearKeySession* aSession)
+ClearKeySessionManager::ClearInMemorySessionData(ClearKeySession* aSession)
 {
   MOZ_ASSERT(aSession);
 
   const vector<KeyId>& keyIds = aSession->GetKeyIds();
   for (auto it = keyIds.begin(); it != keyIds.end(); it++) {
-    MOZ_ASSERT(Contains(mDecryptors, *it));
-    if (!mDecryptors[*it]->Release()) {
-      mDecryptors.erase(*it);
-      mCallback->KeyIdNotUsable(aSession->Id().c_str(), aSession->Id().size(),
-                                &(*it)[0], it->size());
-    }
+    MOZ_ASSERT(mDecryptionManager->HasKeyForKeyId(*it));
+    mDecryptionManager->ReleaseKeyId(*it);
+
+    const string& sessionId = aSession->Id();
+    mCallback->KeyIdNotUsable(&sessionId[0], sessionId.size(),
+                              &(*it)[0], it->size());
   }
 
   mSessions.erase(aSession->Id());
   delete aSession;
 }
 
 void
-ClearKeyDecryptionManager::RemoveSession(uint32_t aPromiseId,
-                                         const char* aSessionId,
-                                         uint32_t aSessionIdLength)
+ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
+                                      const char* aSessionId,
+                                      uint32_t aSessionIdLength)
 {
+  CK_LOGD("ClearKeySessionManager::RemoveSession");
   string sessionId(aSessionId, aSessionId + aSessionIdLength);
   auto itr = mSessions.find(sessionId);
   if (itr == mSessions.end()) {
     CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
     mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
     return;
   }
 
@@ -394,162 +324,70 @@ ClearKeyDecryptionManager::RemoveSession
                              aPromiseId,
                              kGMPInvalidAccessError,
                              message,
                              strlen(message));
   StoreData(sessionId, emptyKeydata, resolve, reject);
 }
 
 void
-ClearKeyDecryptionManager::SetServerCertificate(uint32_t aPromiseId,
-                                                const uint8_t* aServerCert,
-                                                uint32_t aServerCertSize)
+ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
+                                             const uint8_t* aServerCert,
+                                             uint32_t aServerCertSize)
 {
   // ClearKey CDM doesn't support this method by spec.
-  CK_LOGD("ClearKeyDecryptionManager::SetServerCertificate");
+  CK_LOGD("ClearKeySessionManager::SetServerCertificate");
   mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
                            nullptr /* message */, 0 /* messageLen */);
 }
 
 void
-ClearKeyDecryptionManager::Decrypt(GMPBuffer* aBuffer,
-                                   GMPEncryptedBufferMetadata* aMetadata)
+ClearKeySessionManager::Decrypt(GMPBuffer* aBuffer,
+                                GMPEncryptedBufferMetadata* aMetadata)
 {
-  CK_LOGD("ClearKeyDecryptionManager::Decrypt");
-  KeyId keyId(aMetadata->KeyId(), aMetadata->KeyId() + aMetadata->KeyIdSize());
+  CK_LOGD("ClearKeySessionManager::Decrypt");
 
-  if (!Contains(mDecryptors, keyId)) {
-    CK_LOGD("ClearKeyDecryptionManager::Decrypt GMPNoKeyErr");
-    mCallback->Decrypted(aBuffer, GMPNoKeyErr);
+  if (!mThread) {
+    CK_LOGW("No decrypt thread");
+    mCallback->Decrypted(aBuffer, GMPGenericErr);
     return;
   }
 
-  mDecryptors[keyId]->QueueDecrypt(aBuffer, aMetadata);
+  mThread->Post(WrapTask(this,
+                         &ClearKeySessionManager::DoDecrypt,
+                         aBuffer, aMetadata));
 }
 
 void
-ClearKeyDecryptionManager::DecryptingComplete()
+ClearKeySessionManager::DoDecrypt(GMPBuffer* aBuffer,
+                                  GMPEncryptedBufferMetadata* aMetadata)
 {
-  CK_LOGD("ClearKeyDecryptionManager::DecryptingComplete");
+  CK_LOGD("ClearKeySessionManager::DoDecrypt");
+
+  GMPErr rv = mDecryptionManager->Decrypt(aBuffer->Data(), aBuffer->Size(),
+                                              aMetadata);
+  CK_LOGD("DeDecrypt finished with code %x\n", rv);
+  mCallback->Decrypted(aBuffer, rv);
+}
+
+void
+ClearKeySessionManager::Shutdown()
+{
+  CK_LOGD("ClearKeySessionManager::Shutdown");
 
   for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
     delete it->second;
   }
   mSessions.clear();
-
-  for (auto it = mDecryptors.begin(); it != mDecryptors.end(); it++) {
-    delete it->second;
-  }
-  mDecryptors.clear();
-
-  Release();
-}
-
-void
-ClearKeyDecryptor::QueueDecrypt(GMPBuffer* aBuffer,
-                                GMPEncryptedBufferMetadata* aMetadata)
-{
-  CK_LOGD("ClearKeyDecryptor::QueueDecrypt");
-  mThread->Post(new DecryptTask(this, aBuffer, aMetadata));
 }
 
 void
-ClearKeyDecryptor::Decrypt(GMPBuffer* aBuffer,
-                           GMPEncryptedBufferMetadata* aMetadata)
+ClearKeySessionManager::DecryptingComplete()
 {
-  if (!mThread) {
-    mCallback->Decrypted(aBuffer, GMPGenericErr);
-  }
-
-  // If the sample is split up into multiple encrypted subsamples, we need to
-  // stitch them into one continuous buffer for decryption.
-  vector<uint8_t> tmp(aBuffer->Size());
-
-  if (aMetadata->NumSubsamples()) {
-    // Take all encrypted parts of subsamples and stitch them into one
-    // continuous encrypted buffer.
-    unsigned char* data = aBuffer->Data();
-    unsigned char* iter = &tmp[0];
-    for (size_t i = 0; i < aMetadata->NumSubsamples(); i++) {
-      data += aMetadata->ClearBytes()[i];
-      uint32_t cipherBytes = aMetadata->CipherBytes()[i];
-
-      memcpy(iter, data, cipherBytes);
-
-      data += cipherBytes;
-      iter += cipherBytes;
-    }
-
-    tmp.resize((size_t)(iter - &tmp[0]));
-  } else {
-    memcpy(&tmp[0], aBuffer->Data(), aBuffer->Size());
-  }
-
-  MOZ_ASSERT(aMetadata->IVSize() == 8 || aMetadata->IVSize() == 16);
-  vector<uint8_t> iv(aMetadata->IV(), aMetadata->IV() + aMetadata->IVSize());
-  iv.insert(iv.end(), CLEARKEY_KEY_LEN - aMetadata->IVSize(), 0);
-
-  ClearKeyUtils::DecryptAES(mKey, tmp, iv);
-
-  if (aMetadata->NumSubsamples()) {
-    // Take the decrypted buffer, split up into subsamples, and insert those
-    // subsamples back into their original position in the original buffer.
-    unsigned char* data = aBuffer->Data();
-    unsigned char* iter = &tmp[0];
-    for (size_t i = 0; i < aMetadata->NumSubsamples(); i++) {
-      data += aMetadata->ClearBytes()[i];
-      uint32_t cipherBytes = aMetadata->CipherBytes()[i];
-
-      memcpy(data, iter, cipherBytes);
+  CK_LOGD("ClearKeySessionManager::DecryptingComplete");
 
-      data += cipherBytes;
-      iter += cipherBytes;
-    }
-  } else {
-    memcpy(aBuffer->Data(), &tmp[0], aBuffer->Size());
-  }
-
-  mCallback->Decrypted(aBuffer, GMPNoErr);
-}
-
-ClearKeyDecryptor::ClearKeyDecryptor(GMPDecryptorCallback* aCallback,
-                                     const Key& aKey)
-  : mRefCnt(0)
-  , mCallback(aCallback)
-  , mThread(nullptr)
-  , mKey(aKey)
-{
-  if (GetPlatform()->createthread(&mThread) != GMPNoErr) {
-    CK_LOGD("failed to create thread in clearkey cdm");
-    mThread = nullptr;
-    return;
-  }
-}
+  GMPThread* thread = mThread;
+  thread->Join();
 
-ClearKeyDecryptor::~ClearKeyDecryptor()
-{
-  CK_LOGD("ClearKeyDecryptor dtor; key ID = %08x...", *(uint32_t*)&mKey[0]);
-}
-
-uint32_t
-ClearKeyDecryptor::AddRef()
-{
-  return ++mRefCnt;
+  Shutdown();
+  mDecryptionManager = nullptr;
+  Release();
 }
-
-uint32_t
-ClearKeyDecryptor::Release()
-{
-  uint32_t newCount = --mRefCnt;
-  if (!newCount) {
-    if (mThread) {
-      // Shutdown mThread. We cache a pointer to mThread, as the DestroyTask
-      // may run and delete |this| before Post() returns.
-      GMPThread* thread = mThread;
-      thread->Post(new DestroyTask(this));
-      thread->Join();
-    } else {
-      delete this;
-    }
-  }
-
-  return newCount;
-}
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.h
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __ClearKeyDecryptor_h__
+#define __ClearKeyDecryptor_h__
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeySession.h"
+#include "ClearKeyUtils.h"
+#include "gmp-api/gmp-decryption.h"
+#include "mozilla/RefPtr.h"
+#include "ScopedNSSTypes.h"
+#include "RefCounted.h"
+
+class ClearKeySessionManager MOZ_FINAL : public GMPDecryptor
+                                       , public RefCounted
+{
+public:
+  ClearKeySessionManager();
+
+  virtual void Init(GMPDecryptorCallback* aCallback) MOZ_OVERRIDE;
+
+  virtual void CreateSession(uint32_t aCreateSessionToken,
+                             uint32_t aPromiseId,
+                             const char* aInitDataType,
+                             uint32_t aInitDataTypeSize,
+                             const uint8_t* aInitData,
+                             uint32_t aInitDataSize,
+                             GMPSessionType aSessionType) MOZ_OVERRIDE;
+
+  virtual void LoadSession(uint32_t aPromiseId,
+                           const char* aSessionId,
+                           uint32_t aSessionIdLength) MOZ_OVERRIDE;
+
+  virtual void UpdateSession(uint32_t aPromiseId,
+                             const char* aSessionId,
+                             uint32_t aSessionIdLength,
+                             const uint8_t* aResponse,
+                             uint32_t aResponseSize) MOZ_OVERRIDE;
+
+  virtual void CloseSession(uint32_t aPromiseId,
+                            const char* aSessionId,
+                            uint32_t aSessionIdLength) MOZ_OVERRIDE;
+
+  virtual void RemoveSession(uint32_t aPromiseId,
+                             const char* aSessionId,
+                             uint32_t aSessionIdLength) MOZ_OVERRIDE;
+
+  virtual void SetServerCertificate(uint32_t aPromiseId,
+                                    const uint8_t* aServerCert,
+                                    uint32_t aServerCertSize) MOZ_OVERRIDE;
+
+  virtual void Decrypt(GMPBuffer* aBuffer,
+                       GMPEncryptedBufferMetadata* aMetadata) MOZ_OVERRIDE;
+
+  virtual void DecryptingComplete() MOZ_OVERRIDE;
+
+  void PersistentSessionDataLoaded(GMPErr aStatus,
+                                   uint32_t aPromiseId,
+                                   const std::string& aSessionId,
+                                   const uint8_t* aKeyData,
+                                   uint32_t aKeyDataSize);
+
+private:
+  ~ClearKeySessionManager();
+
+  void DoDecrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
+  void Shutdown();
+
+  void ClearInMemorySessionData(ClearKeySession* aSession);
+  void Serialize(const ClearKeySession* aSession, std::vector<uint8_t>& aOutKeyData);
+
+  mozilla::RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
+
+  GMPDecryptorCallback* mCallback;
+  GMPThread* mThread;
+
+  std::set<KeyId> mKeyIds;
+  std::map<std::string, ClearKeySession*> mSessions;
+};
+
+#endif // __ClearKeyDecryptor_h__
--- a/media/gmp-clearkey/0.1/RefCounted.h
+++ b/media/gmp-clearkey/0.1/RefCounted.h
@@ -7,22 +7,22 @@
 
 // Note: Not thread safe!
 class RefCounted {
 public:
   void AddRef() {
     ++mRefCount;
   }
 
-  void Release() {
-    if (mRefCount == 1) {
+  uint32_t Release() {
+    uint32_t newCount = --mRefCount;
+    if (!newCount) {
       delete this;
-    } else {
-      --mRefCount;
     }
+    return newCount;
   }
 
 protected:
   RefCounted()
     : mRefCount(0)
   {
   }
   virtual ~RefCounted()
--- a/media/gmp-clearkey/0.1/gmp-clearkey.cpp
+++ b/media/gmp-clearkey/0.1/gmp-clearkey.cpp
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <stdio.h>
 #include <string.h>
 
-#include "ClearKeyDecryptionManager.h"
+#include "ClearKeySessionManager.h"
 
 #include "gmp-api/gmp-decryption.h"
 #include "gmp-api/gmp-platform.h"
 #include "mozilla/Attributes.h"
 
 static GMPPlatformAPI* sPlatform = nullptr;
 GMPPlatformAPI*
 GetPlatform()
@@ -29,17 +29,17 @@ GMPInit(GMPPlatformAPI* aPlatformAPI)
 
 MOZ_EXPORT GMPErr
 GMPGetAPI(const char* aApiName, void* aHostAPI, void** aPluginAPI)
 {
   if (strcmp(aApiName, GMP_API_DECRYPTOR)) {
     return GMPNotImplementedErr;
   }
 
-  *aPluginAPI = new ClearKeyDecryptionManager();
+  *aPluginAPI = new ClearKeySessionManager();
 
   return GMPNoErr;
 }
 
 MOZ_EXPORT GMPErr
 GMPShutdown(void)
 {
   return GMPNoErr;
--- a/media/gmp-clearkey/0.1/moz.build
+++ b/media/gmp-clearkey/0.1/moz.build
@@ -7,16 +7,17 @@
 SharedLibrary('clearkey')
 
 FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
 
 UNIFIED_SOURCES += [
     'ClearKeyDecryptionManager.cpp',
     'ClearKeyPersistence.cpp',
     'ClearKeySession.cpp',
+    'ClearKeySessionManager.cpp',
     'ClearKeyStorage.cpp',
     'ClearKeyUtils.cpp',
     'gmp-clearkey.cpp',
     'openaes/oaes_lib.c',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/media/gmp',
--- a/netwerk/base/src/nsBaseChannel.cpp
+++ b/netwerk/base/src/nsBaseChannel.cpp
@@ -284,20 +284,25 @@ void
 nsBaseChannel::ClassifyURI()
 {
   // For channels created in the child process, delegate to the parent to
   // classify URIs.
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
     return;
   }
 
+  nsresult rv;
+
   if (mLoadFlags & LOAD_CLASSIFY_URI) {
     nsRefPtr<nsChannelClassifier> classifier = new nsChannelClassifier();
     if (classifier) {
-      classifier->Start(this);
+      rv = classifier->Start(this);
+      if (NS_FAILED(rv)) {
+        Cancel(rv);
+      }
     } else {
       Cancel(NS_ERROR_OUT_OF_MEMORY);
     }
   }
 }
 
 //-----------------------------------------------------------------------------
 // nsBaseChannel::nsISupports
--- a/netwerk/base/src/nsChannelClassifier.cpp
+++ b/netwerk/base/src/nsChannelClassifier.cpp
@@ -44,18 +44,17 @@ static PRLogModuleInfo *gChannelClassifi
 #endif
 #undef LOG
 #define LOG(args)     PR_LOG(gChannelClassifierLog, PR_LOG_DEBUG, args)
 
 NS_IMPL_ISUPPORTS(nsChannelClassifier,
                   nsIURIClassifierCallback)
 
 nsChannelClassifier::nsChannelClassifier()
-  : mIsAllowListed(false),
-    mSuspendedChannel(false)
+  : mIsAllowListed(false)
 {
 #if defined(PR_LOGGING)
     if (!gChannelClassifierLog)
         gChannelClassifierLog = PR_NewLogModule("nsChannelClassifier");
 #endif
 }
 
 nsresult
@@ -205,83 +204,71 @@ nsChannelClassifier::NotifyTrackingProte
     doc->SetHasTrackingContentLoaded(true);
     securityUI->GetState(&state);
     state |= nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT;
     eventSink->OnSecurityChange(nullptr, state);
 
     return NS_OK;
 }
 
-void
+nsresult
 nsChannelClassifier::Start(nsIChannel *aChannel)
 {
-  mChannel = aChannel;
-  nsresult rv = StartInternal(aChannel);
-  if (NS_FAILED(rv)) {
-    // If we aren't getting a callback for any reason, assume a good verdict and
-    // make sure we resume the channel if necessary.
-    OnClassifyComplete(NS_OK);
-  }
-}
-
-nsresult
-nsChannelClassifier::StartInternal(nsIChannel *aChannel)
-{
     // Should only be called in the parent process.
     MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
     // Don't bother to run the classifier on a load that has already failed.
     // (this might happen after a redirect)
     nsresult status;
     aChannel->GetStatus(&status);
     if (NS_FAILED(status))
-        return status;
+        return NS_OK;
 
     // Don't bother to run the classifier on a cached load that was
-    // previously classified as good.
+    // previously classified.
     if (HasBeenClassified(aChannel)) {
-        return NS_ERROR_UNEXPECTED;
+        return NS_OK;
     }
 
     nsCOMPtr<nsIURI> uri;
     nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Don't bother checking certain types of URIs.
     bool hasFlags;
     rv = NS_URIChainHasFlags(uri,
                              nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
                              &hasFlags);
     NS_ENSURE_SUCCESS(rv, rv);
-    if (hasFlags) return NS_ERROR_UNEXPECTED;
+    if (hasFlags) return NS_OK;
 
     rv = NS_URIChainHasFlags(uri,
                              nsIProtocolHandler::URI_IS_LOCAL_FILE,
                              &hasFlags);
     NS_ENSURE_SUCCESS(rv, rv);
-    if (hasFlags) return NS_ERROR_UNEXPECTED;
+    if (hasFlags) return NS_OK;
 
     rv = NS_URIChainHasFlags(uri,
                              nsIProtocolHandler::URI_IS_UI_RESOURCE,
                              &hasFlags);
     NS_ENSURE_SUCCESS(rv, rv);
-    if (hasFlags) return NS_ERROR_UNEXPECTED;
+    if (hasFlags) return NS_OK;
 
     rv = NS_URIChainHasFlags(uri,
                              nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
                              &hasFlags);
     NS_ENSURE_SUCCESS(rv, rv);
-    if (hasFlags) return NS_ERROR_UNEXPECTED;
+    if (hasFlags) return NS_OK;
 
     nsCOMPtr<nsIURIClassifier> uriClassifier =
         do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
     if (rv == NS_ERROR_FACTORY_NOT_REGISTERED ||
         rv == NS_ERROR_NOT_AVAILABLE) {
         // no URI classifier, ignore this failure.
-        return NS_ERROR_NOT_AVAILABLE;
+        return NS_OK;
     }
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIScriptSecurityManager> securityManager =
         do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIPrincipal> principal;
@@ -290,38 +277,34 @@ nsChannelClassifier::StartInternal(nsICh
     NS_ENSURE_SUCCESS(rv, rv);
 
     bool expectCallback;
     bool trackingProtectionEnabled = false;
     (void)ShouldEnableTrackingProtection(aChannel, &trackingProtectionEnabled);
 
     rv = uriClassifier->Classify(principal, trackingProtectionEnabled, this,
                                  &expectCallback);
-    if (NS_FAILED(rv)) {
-        return rv;
-    }
+    if (NS_FAILED(rv)) return rv;
 
     if (expectCallback) {
         // Suspend the channel, it will be resumed when we get the classifier
         // callback.
         rv = aChannel->Suspend();
         if (NS_FAILED(rv)) {
             // Some channels (including nsJSChannel) fail on Suspend.  This
             // shouldn't be fatal, but will prevent malware from being
             // blocked on these channels.
-            LOG(("nsChannelClassifier[%p]: Couldn't suspend channel", this));
-            return rv;
+            return NS_OK;
         }
 
-        mSuspendedChannel = true;
+        mSuspendedChannel = aChannel;
+#ifdef DEBUG
         LOG(("nsChannelClassifier[%p]: suspended channel %p",
-             this, mChannel.get()));
-    } else {
-        LOG(("nsChannelClassifier[%p]: not expecting callback", this));
-        return NS_ERROR_FAILURE;
+             this, mSuspendedChannel.get()));
+#endif
     }
 
     return NS_OK;
 }
 
 // Note in the cache entry that this URL was classified, so that future
 // cached loads don't need to be checked.
 void
@@ -330,17 +313,18 @@ nsChannelClassifier::MarkEntryClassified
     // Should only be called in the parent process.
     MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
     // Don't cache tracking classifications because we support allowlisting.
     if (status == NS_ERROR_TRACKING_URI || mIsAllowListed) {
         return;
     }
 
-    nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel);
+    nsCOMPtr<nsICachingChannel> cachingChannel =
+        do_QueryInterface(mSuspendedChannel);
     if (!cachingChannel) {
         return;
     }
 
     nsCOMPtr<nsISupports> cacheToken;
     cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
     if (!cacheToken) {
         return;
@@ -455,47 +439,41 @@ nsChannelClassifier::SetBlockedTrackingC
 }
 
 NS_IMETHODIMP
 nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode)
 {
     // Should only be called in the parent process.
     MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
-    LOG(("nsChannelClassifier[%p]:OnClassifyComplete", this));
     if (mSuspendedChannel) {
         MarkEntryClassified(aErrorCode);
 
         if (NS_FAILED(aErrorCode)) {
 #ifdef DEBUG
             nsCOMPtr<nsIURI> uri;
-            mChannel->GetURI(getter_AddRefs(uri));
+            mSuspendedChannel->GetURI(getter_AddRefs(uri));
             nsCString spec;
             uri->GetSpec(spec);
             LOG(("nsChannelClassifier[%p]: cancelling channel %p for %s "
-                 "with error code: %x", this, mChannel.get(),
+                 "with error code: %x", this, mSuspendedChannel.get(),
                  spec.get(), aErrorCode));
 #endif
 
             // Channel will be cancelled (page element blocked) due to tracking.
             // Do update the security state of the document and fire a security
             // change event.
             if (aErrorCode == NS_ERROR_TRACKING_URI) {
-              SetBlockedTrackingContent(mChannel);
+              SetBlockedTrackingContent(mSuspendedChannel);
             }
 
-            mChannel->Cancel(aErrorCode);
-        }
+            mSuspendedChannel->Cancel(aErrorCode);
+          }
+#ifdef DEBUG
         LOG(("nsChannelClassifier[%p]: resuming channel %p from "
-             "OnClassifyComplete", this, mChannel.get()));
-        mChannel->Resume();
+             "OnClassifyComplete", this, mSuspendedChannel.get()));
+#endif
+        mSuspendedChannel->Resume();
+        mSuspendedChannel = nullptr;
     }
-    nsresult rv;
-    nsCOMPtr<nsIHttpChannelInternal> channel = do_QueryInterface(mChannel, &rv);
-    // Even if we have cancelled the channel, we need to call
-    // ContinueBeginConnect so that we abort appropriately.
-    if (NS_SUCCEEDED(rv)) {
-        channel->ContinueBeginConnect();
-    }
-    mChannel = nullptr;
 
     return NS_OK;
 }
--- a/netwerk/base/src/nsChannelClassifier.h
+++ b/netwerk/base/src/nsChannelClassifier.h
@@ -15,37 +15,26 @@ class nsIChannel;
 class nsChannelClassifier MOZ_FINAL : public nsIURIClassifierCallback
 {
 public:
     nsChannelClassifier();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIURICLASSIFIERCALLBACK
 
-    // Calls nsIURIClassifier.Classify with the principal of the given channel,
-    // and cancels the channel on a bad verdict.  If aChannel is
-    // nsIHttpChannelInternal, nsChannelClassifier must call
-    // ContinueBeginConnect once Start has successfully returned.
-    void Start(nsIChannel *aChannel);
+    nsresult Start(nsIChannel *aChannel);
 
 private:
-    // True if the channel is on the allow list.
+    nsCOMPtr<nsIChannel> mSuspendedChannel;
+    // Set true if the channel is on the allow list.
     bool mIsAllowListed;
-    // True if the channel has been suspended.
-    bool mSuspendedChannel;
-    nsCOMPtr<nsIChannel> mChannel;
 
     ~nsChannelClassifier() {}
-    // Caches good classifications for the channel principal.
     void MarkEntryClassified(nsresult status);
     bool HasBeenClassified(nsIChannel *aChannel);
-    // Helper function so that we ensure we call ContinueBeginConnect once
-    // Start is called. Returns NS_OK if and only if we will get a callback
-    // from the classifier service.
-    nsresult StartInternal(nsIChannel *aChannel);
     // Whether or not tracking protection should be enabled on this channel.
     nsresult ShouldEnableTrackingProtection(nsIChannel *aChannel, bool *result);
 
 public:
     // If we are blocking tracking content, update the corresponding flag in
     // the respective docshell and call nsISecurityEventSink::onSecurityChange.
     static nsresult SetBlockedTrackingContent(nsIChannel *channel);
     static nsresult NotifyTrackingProtectionDisabled(nsIChannel *aChannel);
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -1405,25 +1405,16 @@ HttpBaseChannel::RedirectTo(nsIURI *newU
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsIHttpChannelInternal
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
-HttpBaseChannel::ContinueBeginConnect()
-{
-  MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default,
-             "The parent overrides this");
-  MOZ_ASSERT(false, "This method must be overridden");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
 HttpBaseChannel::GetTopWindowURI(nsIURI **aTopWindowURI)
 {
   nsresult rv = NS_OK;
   nsCOMPtr<mozIThirdPartyUtil> util;
   // Only compute the top window URI once. In e10s, this must be computed in the
   // child. The parent gets the top window URI through HttpChannelOpenArgs.
   if (!mTopWindowURI) {
     util = do_GetService(THIRDPARTYUTIL_CONTRACTID);
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -183,17 +183,16 @@ public:
   NS_IMETHOD TakeAllSecurityMessages(nsCOMArray<nsISecurityConsoleMessage> &aMessages) MOZ_OVERRIDE;
   NS_IMETHOD GetResponseTimeoutEnabled(bool *aEnable) MOZ_OVERRIDE;
   NS_IMETHOD SetResponseTimeoutEnabled(bool aEnable) MOZ_OVERRIDE;
   NS_IMETHOD AddRedirect(nsIPrincipal *aRedirect) MOZ_OVERRIDE;
   NS_IMETHOD ForcePending(bool aForcePending) MOZ_OVERRIDE;
   NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) MOZ_OVERRIDE;
   NS_IMETHOD ForceNoIntercept() MOZ_OVERRIDE;
   NS_IMETHOD GetTopWindowURI(nsIURI **aTopWindowURI) MOZ_OVERRIDE;
-  NS_IMETHOD ContinueBeginConnect();
 
   inline void CleanRedirectCacheChainIfNecessary()
   {
       mRedirectedCachekeys = nullptr;
   }
   NS_IMETHOD HTTPUpgrade(const nsACString & aProtocolName,
                          nsIHttpUpgradeListener *aListener) MOZ_OVERRIDE;
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -17,17 +17,16 @@
 #include "nsICacheEntry.h"
 #include "nsICryptoHash.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIStringBundle.h"
 #include "nsIStreamListenerTee.h"
 #include "nsISeekableStream.h"
 #include "nsILoadGroupChild.h"
 #include "nsIProtocolProxyService2.h"
-#include "nsIURIClassifier.h"
 #include "nsMimeTypes.h"
 #include "nsNetUtil.h"
 #include "prprf.h"
 #include "prnetdb.h"
 #include "nsEscape.h"
 #include "nsStreamUtils.h"
 #include "nsIOService.h"
 #include "nsDNSPrefetch.h"
@@ -35,17 +34,16 @@
 #include "nsIRedirectResultListener.h"
 #include "mozilla/TimeStamp.h"
 #include "nsError.h"
 #include "nsPrintfCString.h"
 #include "nsAlgorithm.h"
 #include "GeckoProfiler.h"
 #include "nsIConsoleService.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/Preferences.h"
 #include "mozilla/VisualEventTracer.h"
 #include "nsISSLSocketControl.h"
 #include "sslt.h"
 #include "nsContentUtils.h"
 #include "nsIClassOfService.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
@@ -237,17 +235,16 @@ nsHttpChannel::nsHttpChannel()
     , mCacheEntryIsReadOnly(false)
     , mCacheEntryIsWriteOnly(false)
     , mCacheEntriesToWaitFor(0)
     , mHasQueryString(0)
     , mConcurentCacheAccess(0)
     , mIsPartialRequest(0)
     , mHasAutoRedirectVetoNotifier(0)
     , mPushedStream(nullptr)
-    , mLocalBlocklist(false)
     , mDidReval(false)
 {
     LOG(("Creating nsHttpChannel [this=%p]\n", this));
     mChannelCreationTime = PR_Now();
     mChannelCreationTimestamp = TimeStamp::Now();
 }
 
 nsHttpChannel::~nsHttpChannel()
@@ -324,17 +321,17 @@ nsHttpChannel::Connect()
     if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
         return NS_ERROR_UNKNOWN_HOST;
 
     // Finalize ConnectionInfo flags before SpeculativeConnect
     mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
     mConnectionInfo->SetPrivate(mPrivateBrowsing);
     mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
 
-    // Consider opening a TCP connection right away.
+    // Consider opening a TCP connection right away
     SpeculativeConnect();
 
     // Don't allow resuming when cache must be used
     if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
         LOG(("Resuming from cache is not supported yet"));
         return NS_ERROR_DOCUMENT_NOT_CACHED;
     }
 
@@ -431,21 +428,21 @@ nsHttpChannel::ContinueConnect()
 }
 
 void
 nsHttpChannel::SpeculativeConnect()
 {
     // Before we take the latency hit of dealing with the cache, try and
     // get the TCP (and SSL) handshakes going so they can overlap.
 
-    // don't speculate if we are on a local blocklist, on uses of the offline
-    // application cache, if we are offline, when doing http upgrade (i.e.
-    // websockets bootstrap), or if we can't do keep-alive (because then we
-    // couldn't reuse the speculative connection anyhow).
-    if (mLocalBlocklist || mApplicationCache || gIOService->IsOffline() ||
+    // don't speculate on uses of the offline application cache,
+    // if we are offline, when doing http upgrade (i.e. websockets bootstrap),
+    // or if we can't do keep-alive (because then we couldn't reuse
+    // the speculative connection anyhow).
+    if (mApplicationCache || gIOService->IsOffline() ||
         mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE))
         return;
 
     // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
     // LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network,
     // so skip preconnects for them.
     if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
                       LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE))
@@ -4871,31 +4868,16 @@ nsHttpChannel::BeginConnect()
     // notify "http-on-modify-request" observers
     CallOnModifyRequestObservers();
 
     // Check to see if we should redirect this channel elsewhere by
     // nsIHttpChannel.redirectTo API request
     if (mAPIRedirectToURI) {
         return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
     }
-    // Check to see if this principal exists on local blocklists.
-    if (mLoadFlags & LOAD_CLASSIFY_URI) {
-        nsCOMPtr<nsIURIClassifier> classifier = do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID);
-        if (classifier) {
-            nsCOMPtr<nsIPrincipal> principal = GetPrincipal(false);
-            bool tp = Preferences::GetBool("privacy.trackingprotection.enabled",
-                                           false);
-            nsresult response = NS_OK;
-            classifier->ClassifyLocal(principal, tp, &response);
-            if (NS_FAILED(response)) {
-                LOG(("nsHttpChannel::Found principal on local blocklist [this=%p]", this));
-                mLocalBlocklist = true;
-            }
-        }
-    }
 
     // If mTimingEnabled flag is not set after OnModifyRequest() then
     // clear the already recorded AsyncOpen value for consistency.
     if (!mTimingEnabled)
         mAsyncOpenTime = TimeStamp();
 
     // when proxying only use the pipeline bit if ProxyPipelining() allows it.
     if (!mConnectionInfo->UsingConnect() && mConnectionInfo->UsingHttpProxy()) {
@@ -4905,17 +4887,17 @@ nsHttpChannel::BeginConnect()
     }
 
     // if this somehow fails we can go on without it
     gHttpHandler->AddConnectionHeader(&mRequestHead.Headers(), mCaps);
 
     if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags))
         mCaps |= NS_HTTP_REFRESH_DNS;
 
-    if (!mLocalBlocklist && !mConnectionInfo->UsingHttpProxy() &&
+    if (!mConnectionInfo->UsingHttpProxy() &&
         !(mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE))) {
         // Start a DNS lookup very early in case the real open is queued the DNS can
         // happen in parallel. Do not do so in the presence of an HTTP proxy as
         // all lookups other than for the proxy itself are done by the proxy.
         // Also we don't do a lookup if the LOAD_NO_NETWORK_IO or
         // LOAD_ONLY_FROM_CACHE flags are set.
         //
         // We keep the DNS prefetch object around so that we can retrieve
@@ -4949,27 +4931,37 @@ nsHttpChannel::BeginConnect()
     // Force-Reload should reset the persistent connection pool for this host
     if (mLoadFlags & LOAD_FRESH_CONNECTION) {
         // just the initial document resets the whole pool
         if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
             gHttpHandler->ConnMgr()->DoShiftReloadConnectionCleanup(mConnectionInfo);
         }
         mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
     }
-    if (mCanceled || !mLocalBlocklist) {
-        return ContinueBeginConnect();
-    }
-    MOZ_ASSERT(!mCanceled && mLocalBlocklist);
-    // nsChannelClassifier must call ContinueBeginConnect after optionally
-    // cancelling the channel once we have a remote verdict. We call a concrete
-    // class instead of an nsI* that might be overridden.
-    nsRefPtr<nsChannelClassifier> classifier = new nsChannelClassifier();
-    LOG(("nsHttpChannel::Starting nsChannelClassifier %p [this=%p]",
-         classifier.get(), this));
-    classifier->Start(this);
+
+    // We may have been cancelled already, either by on-modify-request
+    // listeners or by load group observers; in that case, we should
+    // not send the request to the server
+    if (mCanceled)
+        rv = mStatus;
+    else
+        rv = Connect();
+    if (NS_FAILED(rv)) {
+        LOG(("Calling AsyncAbort [rv=%x mCanceled=%i]\n", rv, mCanceled));
+        CloseCacheEntry(true);
+        AsyncAbort(rv);
+    } else if (mLoadFlags & LOAD_CLASSIFY_URI) {
+        nsRefPtr<nsChannelClassifier> classifier = new nsChannelClassifier();
+        rv = classifier->Start(this);
+        if (NS_FAILED(rv)) {
+            Cancel(rv);
+            return rv;
+        }
+    }
+
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIHttpChannelInternal
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
@@ -4997,40 +4989,16 @@ nsHttpChannel::SetPriority(int32_t value
         return NS_OK;
     mPriority = newValue;
     if (mTransaction)
         gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// nsHttpChannel::nsIHttpChannelInternal
-//-----------------------------------------------------------------------------
-NS_IMETHODIMP
-nsHttpChannel::ContinueBeginConnect()
-{
-    LOG(("nsHttpChannel::ContinueBeginConnect [this=%p]", this));
-    nsresult rv;
-    // We may have been cancelled already, either by on-modify-request
-    // listeners or load group observers or nsChannelClassifier; in that case,
-    // we should not send the request to the server
-    if (mCanceled) {
-        rv = mStatus;
-    } else {
-        rv = Connect();
-    }
-    if (NS_FAILED(rv)) {
-        LOG(("Calling AsyncAbort [rv=%x mCanceled=%i]\n", rv, mCanceled));
-        CloseCacheEntry(true);
-        AsyncAbort(rv);
-    }
-    return rv;
-}
-
-//-----------------------------------------------------------------------------
 // HttpChannel::nsIClassOfService
 //-----------------------------------------------------------------------------
 NS_IMETHODIMP
 nsHttpChannel::SetClassFlags(uint32_t inFlags)
 {
     mClassOfService = inFlags;
     return NS_OK;
 }
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -114,17 +114,16 @@ public:
     NS_IMETHOD Cancel(nsresult status) MOZ_OVERRIDE;
     NS_IMETHOD Suspend() MOZ_OVERRIDE;
     NS_IMETHOD Resume() MOZ_OVERRIDE;
     // nsIChannel
     NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo) MOZ_OVERRIDE;
     NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *aContext) MOZ_OVERRIDE;
     // nsIHttpChannelInternal
     NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey) MOZ_OVERRIDE;
-    NS_IMETHOD ContinueBeginConnect();
     // nsISupportsPriority
     NS_IMETHOD SetPriority(int32_t value) MOZ_OVERRIDE;
     // nsIClassOfService
     NS_IMETHOD SetClassFlags(uint32_t inFlags) MOZ_OVERRIDE;
     NS_IMETHOD AddClassFlags(uint32_t inFlags) MOZ_OVERRIDE;
     NS_IMETHOD ClearClassFlags(uint32_t inFlags) MOZ_OVERRIDE;
 
     // nsIResumableChannel
@@ -461,19 +460,16 @@ private:
     uint32_t                          mHasAutoRedirectVetoNotifier : 1;
 
     nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
 
     // Needed for accurate DNS timing
     nsRefPtr<nsDNSPrefetch>           mDNSPrefetch;
 
     Http2PushedStream                 *mPushedStream;
-    // True if the channel's principal was found on a phishing, malware, or
-    // tracking (if tracking protection is enabled) blocklist
-    bool                              mLocalBlocklist;
 
     nsresult WaitForRedirectCallback();
     void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
     void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
 
     nsCString mUsername;
 
 protected:
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -219,15 +219,9 @@ interface nsIHttpChannelInternal : nsISu
      * interception and proceed immediately to the network/cache.
      */
     void forceNoIntercept();
 
     /**
      * The URI of the top-level window that's associated with this channel.
      */
     readonly attribute nsIURI topWindowURI;
-
-    /**
-     * Used only by nsChannelClassifier to resume connecting or abort the
-     * channel after a remote classification verdict.
-     */
-    void continueBeginConnect();
 };
--- a/parser/html/nsHtml5DocumentBuilder.h
+++ b/parser/html/nsHtml5DocumentBuilder.h
@@ -97,18 +97,18 @@ public:
   void SetDocumentMode(nsHtml5DocumentMode m);
 
   void SetNodeInfoManager(nsNodeInfoManager* aManager)
   {
     mNodeInfoManager = aManager;
   }
 
   // nsContentSink methods
-  virtual void UpdateChildCounts();
-  virtual nsresult FlushTags();
+  virtual void UpdateChildCounts() MOZ_OVERRIDE;
+  virtual nsresult FlushTags() MOZ_OVERRIDE;
 
 protected:
 
   explicit nsHtml5DocumentBuilder(bool aRunsToCompletion);
   virtual ~nsHtml5DocumentBuilder();
 
 protected:
   nsAutoTArray<nsCOMPtr<nsIContent>, 32> mOwnedElements;
--- a/parser/html/nsHtml5Module.cpp
+++ b/parser/html/nsHtml5Module.cpp
@@ -87,17 +87,17 @@ nsHtml5Module::Initialize(nsIParser* aPa
 
 class nsHtml5ParserThreadTerminator MOZ_FINAL : public nsIObserver
 {
   public:
     NS_DECL_ISUPPORTS
     explicit nsHtml5ParserThreadTerminator(nsIThread* aThread)
       : mThread(aThread)
     {}
-    NS_IMETHODIMP Observe(nsISupports *, const char *topic, const char16_t *)
+    NS_IMETHODIMP Observe(nsISupports *, const char *topic, const char16_t *) MOZ_OVERRIDE
     {
       NS_ASSERTION(!strcmp(topic, "xpcom-shutdown-threads"), 
                    "Unexpected topic");
       if (mThread) {
         mThread->Shutdown();
         mThread = nullptr;
       }
       return NS_OK;
--- a/parser/html/nsHtml5Parser.cpp
+++ b/parser/html/nsHtml5Parser.cpp
@@ -183,17 +183,17 @@ nsHtml5Parser::Parse(nsIURI* aURL,
                   "Can't call this Parse() variant on script-created parser");
   GetStreamParser()->SetObserver(aObserver);
   GetStreamParser()->SetViewSourceTitle(aURL); // In case we're viewing source
   mExecutor->SetStreamParser(GetStreamParser());
   mExecutor->SetParser(this);
   return NS_OK;
 }
 
-NS_IMETHODIMP
+nsresult
 nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
                      void* aKey,
                      const nsACString& aContentType,
                      bool aLastCall,
                      nsDTDMode aMode) // ignored
 {
   nsresult rv;
   if (NS_FAILED(rv = mExecutor->IsBroken())) {
@@ -531,24 +531,16 @@ nsHtml5Parser::CancelParsingEvents()
 
 void
 nsHtml5Parser::Reset()
 {
   NS_NOTREACHED("Don't call this!");
 }
 
 bool
-nsHtml5Parser::CanInterrupt()
-{
-  // nsContentSink needs this to let nsContentSink::DidProcessATokenImpl
-  // interrupt.
-  return true;
-}
-
-bool
 nsHtml5Parser::IsInsertionPointDefined()
 {
   return !mExecutor->IsFlushing() &&
     (!GetStreamParser() || mParserInsertedScriptsBeingEvaluated);
 }
 
 void
 nsHtml5Parser::BeginEvaluatingParserInsertedScript()
--- a/parser/html/nsHtml5Parser.h
+++ b/parser/html/nsHtml5Parser.h
@@ -37,189 +37,184 @@ class nsHtml5Parser MOZ_FINAL : public n
     NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHtml5Parser, nsIParser)
 
     nsHtml5Parser();
 
     /* Start nsIParser */
     /**
      * No-op for backwards compat.
      */
-    NS_IMETHOD_(void) SetContentSink(nsIContentSink* aSink);
+    NS_IMETHOD_(void) SetContentSink(nsIContentSink* aSink) MOZ_OVERRIDE;
 
     /**
      * Returns the tree op executor for backwards compat.
      */
-    NS_IMETHOD_(nsIContentSink*) GetContentSink();
+    NS_IMETHOD_(nsIContentSink*) GetContentSink() MOZ_OVERRIDE;
 
     /**
      * Always returns "view" for backwards compat.
      */
-    NS_IMETHOD_(void) GetCommand(nsCString& aCommand);
+    NS_IMETHOD_(void) GetCommand(nsCString& aCommand) MOZ_OVERRIDE;
 
     /**
      * No-op for backwards compat.
      */
-    NS_IMETHOD_(void) SetCommand(const char* aCommand);
+    NS_IMETHOD_(void) SetCommand(const char* aCommand) MOZ_OVERRIDE;
 
     /**
      * No-op for backwards compat.
      */
-    NS_IMETHOD_(void) SetCommand(eParserCommands aParserCommand);
+    NS_IMETHOD_(void) SetCommand(eParserCommands aParserCommand) MOZ_OVERRIDE;
 
     /**
      *  Call this method once you've created a parser, and want to instruct it
      *  about what charset to load
      *
      *  @param   aCharset the charset of a document
      *  @param   aCharsetSource the source of the charset
      */
-    NS_IMETHOD_(void) SetDocumentCharset(const nsACString& aCharset, int32_t aSource);
+    NS_IMETHOD_(void) SetDocumentCharset(const nsACString& aCharset, int32_t aSource) MOZ_OVERRIDE;
 
     /**
      * Don't call. For interface compat only.
      */
-    NS_IMETHOD_(void) GetDocumentCharset(nsACString& aCharset, int32_t& aSource)
+    NS_IMETHOD_(void) GetDocumentCharset(nsACString& aCharset, int32_t& aSource) MOZ_OVERRIDE
     {
       NS_NOTREACHED("No one should call this.");
     }
 
     /**
      * Get the channel associated with this parser
      * @param aChannel out param that will contain the result
      * @return NS_OK if successful or NS_NOT_AVAILABLE if not
      */
-    NS_IMETHOD GetChannel(nsIChannel** aChannel);
+    NS_IMETHOD GetChannel(nsIChannel** aChannel) MOZ_OVERRIDE;
 
     /**
      * Return |this| for backwards compat.
      */
-    NS_IMETHOD GetDTD(nsIDTD** aDTD);
+    NS_IMETHOD GetDTD(nsIDTD** aDTD) MOZ_OVERRIDE;
 
     /**
      * Get the stream parser for this parser
      */
-    virtual nsIStreamListener* GetStreamListener();
+    virtual nsIStreamListener* GetStreamListener() MOZ_OVERRIDE;
 
     /**
      * Don't call. For interface compat only.
      */
-    NS_IMETHOD ContinueInterruptedParsing();
+    NS_IMETHOD ContinueInterruptedParsing() MOZ_OVERRIDE;
 
     /**
      * Blocks the parser.
      */
-    NS_IMETHOD_(void) BlockParser();
+    NS_IMETHOD_(void) BlockParser() MOZ_OVERRIDE;
 
     /**
      * Unblocks the parser.
      */
-    NS_IMETHOD_(void) UnblockParser();
+    NS_IMETHOD_(void) UnblockParser() MOZ_OVERRIDE;
 
     /**
      * Asynchronously continues parsing.
      */
-    NS_IMETHOD_(void) ContinueInterruptedParsingAsync();
+    NS_IMETHOD_(void) ContinueInterruptedParsingAsync() MOZ_OVERRIDE;
 
     /**
      * Query whether the parser is enabled (i.e. not blocked) or not.
      */
-    NS_IMETHOD_(bool) IsParserEnabled();
+    NS_IMETHOD_(bool) IsParserEnabled() MOZ_OVERRIDE;
 
     /**
      * Query whether the parser thinks it's done with parsing.
      */
-    NS_IMETHOD_(bool) IsComplete();
+    NS_IMETHOD_(bool) IsComplete() MOZ_OVERRIDE;
 
     /**
      * Set up request observer.
      *
      * @param   aURL used for View Source title
      * @param   aListener a listener to forward notifications to
      * @param   aKey the root context key (used for document.write)
      * @param   aMode ignored (for interface compat only)
      */
     NS_IMETHOD Parse(nsIURI* aURL,
                      nsIRequestObserver* aListener = nullptr,
                      void* aKey = 0,
-                     nsDTDMode aMode = eDTDMode_autodetect);
+                     nsDTDMode aMode = eDTDMode_autodetect) MOZ_OVERRIDE;
 
     /**
      * document.write and document.close
      *
      * @param   aSourceBuffer the argument of document.write (empty for .close())
      * @param   aKey a key unique to the script element that caused this call
      * @param   aContentType "text/html" for HTML mode, else text/plain mode
      * @param   aLastCall true if .close() false if .write()
      * @param   aMode ignored (for interface compat only)
      */
-    NS_IMETHOD Parse(const nsAString& aSourceBuffer,
-                     void* aKey,
-                     const nsACString& aContentType,
-                     bool aLastCall,
-                     nsDTDMode aMode = eDTDMode_autodetect);
+    nsresult Parse(const nsAString& aSourceBuffer,
+                   void* aKey,
+                   const nsACString& aContentType,
+                   bool aLastCall,
+                   nsDTDMode aMode = eDTDMode_autodetect);
 
     /**
      * Stops the parser prematurely
      */
-    NS_IMETHOD Terminate();
+    NS_IMETHOD Terminate() MOZ_OVERRIDE;
 
     /**
      * Don't call. For interface backwards compat only.
      */
     NS_IMETHOD ParseFragment(const nsAString& aSourceBuffer,
-                             nsTArray<nsString>& aTagStack);
+                             nsTArray<nsString>& aTagStack) MOZ_OVERRIDE;
 
     /**
      * Don't call. For interface compat only.
      */
-    NS_IMETHOD BuildModel();
+    NS_IMETHOD BuildModel() MOZ_OVERRIDE;
 
     /**
      * Don't call. For interface compat only.
      */
-    NS_IMETHODIMP CancelParsingEvents();
+    NS_IMETHODIMP CancelParsingEvents() MOZ_OVERRIDE;
 
     /**
      * Don't call. For interface compat only.
      */
-    virtual void Reset();
-    
-    /**
-     * True in fragment mode and during synchronous document.write
-     */
-    virtual bool CanInterrupt();
+    virtual void Reset() MOZ_OVERRIDE;
 
     /**
      * True if the insertion point (per HTML5) is defined.
      */
-    virtual bool IsInsertionPointDefined();
+    virtual bool IsInsertionPointDefined() MOZ_OVERRIDE;
 
     /**
      * Call immediately before starting to evaluate a parser-inserted script.
      */
-    virtual void BeginEvaluatingParserInsertedScript();
+    virtual void BeginEvaluatingParserInsertedScript() MOZ_OVERRIDE;
 
     /**
      * Call immediately after having evaluated a parser-inserted script.
      */
-    virtual void EndEvaluatingParserInsertedScript();
+    virtual void EndEvaluatingParserInsertedScript() MOZ_OVERRIDE;
 
     /**
      * Marks the HTML5 parser as not a script-created parser: Prepares the 
      * parser to be able to read a stream.
      *
      * @param aCommand the parser command (Yeah, this is bad API design. Let's
      * make this better when retiring nsIParser)
      */
-    virtual void MarkAsNotScriptCreated(const char* aCommand);
+    virtual void MarkAsNotScriptCreated(const char* aCommand) MOZ_OVERRIDE;
 
     /**
      * True if this is a script-created HTML5 parser.
      */
-    virtual bool IsScriptCreated();
+    virtual bool IsScriptCreated() MOZ_OVERRIDE;
 
     /* End nsIParser  */
 
     // Not from an external interface
     // Non-inherited methods
 
   public:
 
--- a/parser/html/nsHtml5StreamParser.h
+++ b/parser/html/nsHtml5StreamParser.h
@@ -132,17 +132,17 @@ class nsHtml5StreamParser : public nsICh
     nsresult OnStopRequest(nsIRequest* aRequest,
                            nsISupports* aContext,
                            nsresult status);
 
     // nsICharsetDetectionObserver
     /**
      * Chardet calls this to report the detection result
      */
-    NS_IMETHOD Notify(const char* aCharset, nsDetectionConfident aConf);
+    NS_IMETHOD Notify(const char* aCharset, nsDetectionConfident aConf) MOZ_OVERRIDE;
 
     // EncodingDeclarationHandler
     // http://hg.mozilla.org/projects/htmlparser/file/tip/src/nu/validator/htmlparser/common/EncodingDeclarationHandler.java
     /**
      * Tree builder uses this to report a late <meta charset>
      */
     bool internalEncodingDeclaration(nsString* aEncoding);
 
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -104,86 +104,80 @@ class nsHtml5TreeOpExecutor MOZ_FINAL : 
 
   public:
 
     // nsIContentSink
 
     /**
      * Unimplemented. For interface compat only.
      */
-    NS_IMETHOD WillParse();
+    NS_IMETHOD WillParse() MOZ_OVERRIDE;
 
     /**
      * 
      */
-    NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode);
+    NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) MOZ_OVERRIDE;
 
     /**
      * Emits EOF.
      */
-    NS_IMETHOD DidBuildModel(bool aTerminated);
+    NS_IMETHOD DidBuildModel(bool aTerminated) MOZ_OVERRIDE;
 
     /**
      * Forwards to nsContentSink
      */
-    NS_IMETHOD WillInterrupt();
+    NS_IMETHOD WillInterrupt() MOZ_OVERRIDE;
 
     /**
      * Unimplemented. For interface compat only.
      */
-    NS_IMETHOD WillResume();
+    NS_IMETHOD WillResume() MOZ_OVERRIDE;
 
     /**
      * Sets the parser.
      */
-    NS_IMETHOD SetParser(nsParserBase* aParser);
+    NS_IMETHOD SetParser(nsParserBase* aParser) MOZ_OVERRIDE;
 
     /**
      * No-op for backwards compat.
      */
-    virtual void FlushPendingNotifications(mozFlushType aType);
+    virtual void FlushPendingNotifications(mozFlushType aType) MOZ_OVERRIDE;
 
     /**
      * Don't call. For interface compat only.
      */
-    NS_IMETHOD SetDocumentCharset(nsACString& aCharset) {
+    NS_IMETHOD SetDocumentCharset(nsACString& aCharset) MOZ_OVERRIDE {
     	NS_NOTREACHED("No one should call this.");
     	return NS_ERROR_NOT_IMPLEMENTED;
     }
 
     /**
      * Returns the document.
      */
-    virtual nsISupports *GetTarget();
+    virtual nsISupports *GetTarget() MOZ_OVERRIDE;
   
-    virtual void ContinueInterruptedParsingAsync();
- 
-    // XXX Does anyone need this?
-    nsIDocShell* GetDocShell()
-    {
-      return mDocShell;
-    }
+    virtual void ContinueInterruptedParsingAsync() MOZ_OVERRIDE;
 
-    bool IsScriptExecuting()
+    bool IsScriptExecuting() MOZ_OVERRIDE
     {
       return IsScriptExecutingImpl();
     }
 
     // Not from interface
 
     void SetStreamParser(nsHtml5StreamParser* aStreamParser)
     {
       mStreamParser = aStreamParser;
     }
     
     void InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine);
 
     bool IsScriptEnabled();
 
-    virtual nsresult MarkAsBroken(nsresult aReason);
+    virtual nsresult MarkAsBroken(nsresult aReason) MOZ_OVERRIDE;
 
     void StartLayout();
     
     void FlushSpeculativeLoads();
                   
     void RunFlushLoop();
 
     nsresult FlushDocumentWrite();
@@ -225,17 +219,17 @@ class nsHtml5TreeOpExecutor MOZ_FINAL : 
 #endif
     
     void RunScript(nsIContent* aScriptElement);
     
     /**
      * Flush the operations from the tree operations from the argument
      * queue unconditionally. (This is for the main thread case.)
      */
-    virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue);
+    virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue) MOZ_OVERRIDE;
     
     nsHtml5TreeOpStage* GetStage()
     {
       return &mStage;
     }
     
     void StartReadingFromStage()
     {
--- a/parser/htmlparser/nsIDTD.h
+++ b/parser/htmlparser/nsIDTD.h
@@ -122,17 +122,17 @@ public:
      * DTD actually is using, as it may differ from aParserContext.mDTDMode.
      */
     NS_IMETHOD_(nsDTDMode) GetMode() const = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIDTD, NS_IDTD_IID)
 
 #define NS_DECL_NSIDTD \
-    NS_IMETHOD WillBuildModel(  const CParserContext& aParserContext, nsITokenizer* aTokenizer, nsIContentSink* aSink);\
-    NS_IMETHOD DidBuildModel(nsresult anErrorCode);\
-    NS_IMETHOD BuildModel(nsITokenizer* aTokenizer, nsIContentSink* aSink);\
-    NS_IMETHOD_(bool) CanContain(int32_t aParent,int32_t aChild) const;\
-    NS_IMETHOD_(bool) IsContainer(int32_t aTag) const;\
-    NS_IMETHOD_(void)  Terminate();\
-    NS_IMETHOD_(int32_t) GetType();\
-    NS_IMETHOD_(nsDTDMode) GetMode() const;
+    NS_IMETHOD WillBuildModel(  const CParserContext& aParserContext, nsITokenizer* aTokenizer, nsIContentSink* aSink) MOZ_OVERRIDE;\
+    NS_IMETHOD DidBuildModel(nsresult anErrorCode) MOZ_OVERRIDE;\
+    NS_IMETHOD BuildModel(nsITokenizer* aTokenizer, nsIContentSink* aSink) MOZ_OVERRIDE;\
+    NS_IMETHOD_(bool) CanContain(int32_t aParent,int32_t aChild) const MOZ_OVERRIDE;\
+    NS_IMETHOD_(bool) IsContainer(int32_t aTag) const MOZ_OVERRIDE;\
+    NS_IMETHOD_(void)  Terminate() MOZ_OVERRIDE;\
+    NS_IMETHOD_(int32_t) GetType() MOZ_OVERRIDE;\
+    NS_IMETHOD_(nsDTDMode) GetMode() const MOZ_OVERRIDE;
 #endif /* nsIDTD_h___ */
--- a/parser/htmlparser/nsITokenizer.h
+++ b/parser/htmlparser/nsITokenizer.h
@@ -32,13 +32,13 @@ public:
 
   NS_IMETHOD                     WillTokenize(bool aIsFinalChunk)=0;
   NS_IMETHOD                     ConsumeToken(nsScanner& aScanner,bool& aFlushTokens)=0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsITokenizer, NS_ITOKENIZER_IID)
 
 #define NS_DECL_NSITOKENIZER \
-  NS_IMETHOD                     WillTokenize(bool aIsFinalChunk);\
-  NS_IMETHOD                     ConsumeToken(nsScanner& aScanner,bool& aFlushTokens);\
+  NS_IMETHOD                     WillTokenize(bool aIsFinalChunk) MOZ_OVERRIDE;\
+  NS_IMETHOD                     ConsumeToken(nsScanner& aScanner,bool& aFlushTokens) MOZ_OVERRIDE;\
 
 
 #endif
--- a/parser/htmlparser/nsParser.h
+++ b/parser/htmlparser/nsParser.h
@@ -93,103 +93,103 @@ class nsParser MOZ_FINAL : public nsIPar
     nsParser();
 
     /**
      * Select given content sink into parser for parser output
      * @update	gess5/11/98
      * @param   aSink is the new sink to be used by parser
      * @return  old sink, or nullptr
      */
-    NS_IMETHOD_(void) SetContentSink(nsIContentSink* aSink);
+    NS_IMETHOD_(void) SetContentSink(nsIContentSink* aSink) MOZ_OVERRIDE;
 
     /**
      * retrive the sink set into the parser 
      * @update	gess5/11/98
      * @param   aSink is the new sink to be used by parser
      * @return  old sink, or nullptr
      */
-    NS_IMETHOD_(nsIContentSink*) GetContentSink(void);
+    NS_IMETHOD_(nsIContentSink*) GetContentSink(void) MOZ_OVERRIDE;
     
     /**
      *  Call this method once you've created a parser, and want to instruct it
      *  about the command which caused the parser to be constructed. For example,
      *  this allows us to select a DTD which can do, say, view-source.
      *  
      *  @update  gess 3/25/98
      *  @param   aCommand -- ptrs to string that contains command
      *  @return	 nada
      */
-    NS_IMETHOD_(void) GetCommand(nsCString& aCommand);
-    NS_IMETHOD_(void) SetCommand(const char* aCommand);
-    NS_IMETHOD_(void) SetCommand(eParserCommands aParserCommand);
+    NS_IMETHOD_(void) GetCommand(nsCString& aCommand) MOZ_OVERRIDE;
+    NS_IMETHOD_(void) SetCommand(const char* aCommand) MOZ_OVERRIDE;
+    NS_IMETHOD_(void) SetCommand(eParserCommands aParserCommand) MOZ_OVERRIDE;
 
     /**
      *  Call this method once you've created a parser, and want to instruct it
      *  about what charset to load
      *  
      *  @update  ftang 4/23/99
      *  @param   aCharset- the charset of a document
      *  @param   aCharsetSource- the source of the charset
      *  @return	 nada
      */
-    NS_IMETHOD_(void) SetDocumentCharset(const nsACString& aCharset, int32_t aSource);
+    NS_IMETHOD_(void) SetDocumentCharset(const nsACString& aCharset, int32_t aSource) MOZ_OVERRIDE;
 
-    NS_IMETHOD_(void) GetDocumentCharset(nsACString& aCharset, int32_t& aSource)
+    NS_IMETHOD_(void) GetDocumentCharset(nsACString& aCharset, int32_t& aSource) MOZ_OVERRIDE
     {
          aCharset = mCharset;
          aSource = mCharsetSource;
     }
 
     /**
      * Cause parser to parse input from given URL 
      * @update	gess5/11/98
      * @param   aURL is a descriptor for source document
      * @param   aListener is a listener to forward notifications to
      * @return  TRUE if all went well -- FALSE otherwise
      */
     NS_IMETHOD Parse(nsIURI* aURL,
                      nsIRequestObserver* aListener = nullptr,
                      void* aKey = 0,
-                     nsDTDMode aMode = eDTDMode_autodetect);
+                     nsDTDMode aMode = eDTDMode_autodetect) MOZ_OVERRIDE;
 
     /**
      * This method needs documentation
      */
     NS_IMETHOD ParseFragment(const nsAString& aSourceBuffer,
-                             nsTArray<nsString>& aTagStack);
+                             nsTArray<nsString>& aTagStack) MOZ_OVERRIDE;
                              
     /**
      * This method gets called when the tokens have been consumed, and it's time
      * to build the model via the content sink.
      * @update	gess5/11/98
      * @return  YES if model building went well -- NO otherwise.
      */
-    NS_IMETHOD BuildModel(void);
+    NS_IMETHOD BuildModel(void) MOZ_OVERRIDE;
 
-    NS_IMETHOD        ContinueInterruptedParsing();
-    NS_IMETHOD_(void) BlockParser();
-    NS_IMETHOD_(void) UnblockParser();
-    NS_IMETHOD_(void) ContinueInterruptedParsingAsync();
-    NS_IMETHOD        Terminate(void);
+    NS_IMETHOD        ContinueInterruptedParsing() MOZ_OVERRIDE;
+    NS_IMETHOD_(void) BlockParser() MOZ_OVERRIDE;
+    NS_IMETHOD_(void) UnblockParser() MOZ_OVERRIDE;
+    NS_IMETHOD_(void) ContinueInterruptedParsingAsync() MOZ_OVERRIDE;
+    NS_IMETHOD        Terminate(void) MOZ_OVERRIDE;
 
     /**
      * Call this to query whether the parser is enabled or not.
      *
      *  @update  vidur 4/12/99
      *  @return  current state
      */
-    NS_IMETHOD_(bool) IsParserEnabled();
+    NS_IMETHOD_(bool) IsParserEnabled() MOZ_OVERRIDE;
 
     /**
      * Call this to query whether the parser thinks it's done with parsing.
      *
      *  @update  rickg 5/12/01
      *  @return  complete state
      */
-    NS_IMETHOD_(bool) IsComplete();
+    NS_IMETHOD_(bool) IsComplete() MOZ_OVERRIDE;
 
     /**
      *  This rather arcane method (hack) is used as a signal between the
      *  DTD and the parser. It allows the DTD to tell the parser that content
      *  that comes through (parser::parser(string)) but not consumed should
      *  propagate into the next string based parse call.
      *  
      *  @update  gess 9/1/98
@@ -222,64 +222,64 @@ class nsParser MOZ_FINAL : public nsIPar
     CParserContext*   PeekContext() {return mParserContext;}
 
     /** 
      * Get the channel associated with this parser
      * @update harishd,gagan 07/17/01
      * @param aChannel out param that will contain the result
      * @return NS_OK if successful
      */
-    NS_IMETHOD GetChannel(nsIChannel** aChannel);
+    NS_IMETHOD GetChannel(nsIChannel** aChannel) MOZ_OVERRIDE;
 
     /** 
      * Get the DTD associated with this parser
      * @update vidur 9/29/99
      * @param aDTD out param that will contain the result
      * @return NS_OK if successful, NS_ERROR_FAILURE for runtime error
      */
-    NS_IMETHOD GetDTD(nsIDTD** aDTD);
+    NS_IMETHOD GetDTD(nsIDTD** aDTD) MOZ_OVERRIDE;
   
     /**
      * Get the nsIStreamListener for this parser
      */
-    virtual nsIStreamListener* GetStreamListener();
+    virtual nsIStreamListener* GetStreamListener() MOZ_OVERRIDE;
 
     void SetSinkCharset(nsACString& aCharset);
 
     /**
      *  Removes continue parsing events
      *  @update  kmcclusk 5/18/98
      */
 
-    NS_IMETHODIMP CancelParsingEvents();
+    NS_IMETHODIMP CancelParsingEvents() MOZ_OVERRIDE;
 
     /**
      * Return true.
      */
-    virtual bool IsInsertionPointDefined();
+    virtual bool IsInsertionPointDefined() MOZ_OVERRIDE;
 
     /**
      * No-op.
      */
-    virtual void BeginEvaluatingParserInsertedScript();
+    virtual void BeginEvaluatingParserInsertedScript() MOZ_OVERRIDE;
 
     /**
      * No-op.
      */
-    virtual void EndEvaluatingParserInsertedScript();
+    virtual void EndEvaluatingParserInsertedScript() MOZ_OVERRIDE;
 
     /**
      * No-op.
      */
-    virtual void MarkAsNotScriptCreated(const char* aCommand);
+    virtual void MarkAsNotScriptCreated(const char* aCommand) MOZ_OVERRIDE;
 
     /**
      * Always false.
      */
-    virtual bool IsScriptCreated();
+    virtual bool IsScriptCreated() MOZ_OVERRIDE;
 
     /**  
      *  Set to parser state to indicate whether parsing tokens can be interrupted
      *  @param aCanInterrupt true if parser can be interrupted, false if it can not be interrupted.
      *  @update  kmcclusk 5/18/98
      */
     void SetCanInterrupt(bool aCanInterrupt);
 
@@ -294,17 +294,17 @@ class nsParser MOZ_FINAL : public nsIPar
     nsresult PostContinueEvent();
 
     /**
      *  Fired when the continue parse event is triggered.
      *  @update  kmcclusk 5/18/98
      */
     void HandleParserContinueEvent(class nsParserContinueEvent *);
 
-    virtual void Reset() {
+    virtual void Reset() MOZ_OVERRIDE {
       Cleanup();
       Initialize();
     }
 
     bool IsScriptExecuting() {
       return mSink && mSink->IsScriptExecuting();
     }
 
--- a/parser/htmlparser/nsParserService.h
+++ b/parser/htmlparser/nsParserService.h
@@ -16,27 +16,27 @@ extern "C" int MOZ_XMLTranslateEntity(co
 class nsParserService : public nsIParserService {
   virtual ~nsParserService();
 
 public:
   nsParserService();
 
   NS_DECL_ISUPPORTS
 
-  int32_t HTMLAtomTagToId(nsIAtom* aAtom) const;
+  int32_t HTMLAtomTagToId(nsIAtom* aAtom) const MOZ_OVERRIDE;
 
-  int32_t HTMLCaseSensitiveAtomTagToId(nsIAtom* aAtom) const;
+  int32_t HTMLCaseSensitiveAtomTagToId(nsIAtom* aAtom) const MOZ_OVERRIDE;
 
-  int32_t HTMLStringTagToId(const nsAString& aTag) const;
+  int32_t HTMLStringTagToId(const nsAString& aTag) const MOZ_OVERRIDE;
 
-  const char16_t *HTMLIdToStringTag(int32_t aId) const;
+  const char16_t *HTMLIdToStringTag(int32_t aId) const MOZ_OVERRIDE;
   
-  nsIAtom *HTMLIdToAtomTag(int32_t aId) const;
+  nsIAtom *HTMLIdToAtomTag(int32_t aId) const MOZ_OVERRIDE;
 
   NS_IMETHOD HTMLConvertEntityToUnicode(const nsAString& aEntity, 
-                                        int32_t* aUnicode) const;
+                                        int32_t* aUnicode) const MOZ_OVERRIDE;
   NS_IMETHOD HTMLConvertUnicodeToEntity(int32_t aUnicode,
-                                        nsCString& aEntity) const;
-  NS_IMETHOD IsContainer(int32_t aId, bool& aIsContainer) const;
-  NS_IMETHOD IsBlock(int32_t aId, bool& aIsBlock) const;
+                                        nsCString& aEntity) const MOZ_OVERRIDE;
+  NS_IMETHOD IsContainer(int32_t aId, bool& aIsContainer) const MOZ_OVERRIDE;
+  NS_IMETHOD IsBlock(int32_t aId, bool& aIsBlock) const MOZ_OVERRIDE;
 };
 
 #endif
--- a/parser/xml/nsSAXXMLReader.h
+++ b/parser/xml/nsSAXXMLReader.h
@@ -36,45 +36,45 @@ public:
   NS_DECL_NSIEXTENDEDEXPATSINK
   NS_DECL_NSISAXXMLREADER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
 
   nsSAXXMLReader();
 
   //nsIContentSink
-  NS_IMETHOD WillParse()
+  NS_IMETHOD WillParse() MOZ_OVERRIDE
   {
     return NS_OK;
   }
 
-  NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode);
-  NS_IMETHOD DidBuildModel(bool aTerminated);
-  NS_IMETHOD SetParser(nsParserBase* aParser);
+  NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) MOZ_OVERRIDE;
+  NS_IMETHOD DidBuildModel(bool aTerminated) MOZ_OVERRIDE;
+  NS_IMETHOD SetParser(nsParserBase* aParser) MOZ_OVERRIDE;
   
-  NS_IMETHOD WillInterrupt()
+  NS_IMETHOD WillInterrupt() MOZ_OVERRIDE
   {
     return NS_OK;
   }
 
-  NS_IMETHOD WillResume()
+  NS_IMETHOD WillResume() MOZ_OVERRIDE
   {
     return NS_OK;
   }
   
-  virtual void FlushPendingNotifications(mozFlushType aType)
+  virtual void FlushPendingNotifications(mozFlushType aType) MOZ_OVERRIDE
   {
   }
   
-  NS_IMETHOD SetDocumentCharset(nsACString& aCharset)
+  NS_IMETHOD SetDocumentCharset(nsACString& aCharset) MOZ_OVERRIDE
   {
     return NS_OK;
   }
   
-  virtual nsISupports *GetTarget()
+  virtual nsISupports *GetTarget() MOZ_OVERRIDE
   {
     return nullptr;
   }
 
 private:
   ~nsSAXXMLReader() {}
 
   nsCOMPtr<nsISAXContentHandler> mContentHandler;
new file mode 100644
--- /dev/null
+++ b/python/redo/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: redo
+Version: 1.4
+Summary: Utilities to retry Python callables.
+Home-page: https://github.com/bhearsum/redo
+Author: Ben Hearsum
+Author-email: ben@hearsum.ca
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
new file mode 100644
--- /dev/null
+++ b/python/redo/README
@@ -0,0 +1,4 @@
+Redo - Utilities to retry Python callables
+******************************************
+
+Redo provides various means to add seamless retriability to any Python callable. Redo includes a plain function (redo.retry), a decorator (redo.retriable), and a context manager (redo.retrying) to enable you to integrate it in the best possible way for your project. As a bonus, a standalone interface is also included ("retry"). For details and sample invocations have a look at the docstrings in redo/__init__.py.
new file mode 100644
--- /dev/null
+++ b/python/redo/redo.egg-info/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: redo
+Version: 1.4
+Summary: Utilities to retry Python callables.
+Home-page: https://github.com/bhearsum/redo
+Author: Ben Hearsum
+Author-email: ben@hearsum.ca
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
new file mode 100644
--- /dev/null
+++ b/python/redo/redo.egg-info/SOURCES.txt
@@ -0,0 +1,9 @@
+README
+setup.py
+redo/__init__.py
+redo/cmd.py
+redo.egg-info/PKG-INFO
+redo.egg-info/SOURCES.txt
+redo.egg-info/dependency_links.txt
+redo.egg-info/entry_points.txt
+redo.egg-info/top_level.txt
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/python/redo/redo.egg-info/dependency_links.txt
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/python/redo/redo.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+retry = redo.cmd:main
+
new file mode 100644
--- /dev/null
+++ b/python/redo/redo.egg-info/top_level.txt
@@ -0,0 +1,1 @@
+redo
new file mode 100644
--- /dev/null
+++ b/python/redo/redo/__init__.py
@@ -0,0 +1,218 @@
+# ***** BEGIN LICENSE BLOCK *****
+# 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/.
+# ***** END LICENSE BLOCK *****
+
+import time
+from functools import wraps
+from contextlib import contextmanager
+import logging
+import random
+log = logging.getLogger(__name__)
+
+
+def retrier(attempts=5, sleeptime=10, max_sleeptime=300, sleepscale=1.5, jitter=1):
+    """
+    A generator function that sleeps between retries, handles exponential
+    backoff and jitter. The action you are retrying is meant to run after
+    retrier yields.
+
+    At each iteration, we sleep for sleeptime + random.randint(-jitter, jitter).
+    Afterwards sleeptime is multiplied by sleepscale for the next iteration.
+
+    Args:
+        attempts (int): maximum number of times to try; defaults to 5
+        sleeptime (float): how many seconds to sleep between tries; defaults to
+                           60s (one minute)
+        max_sleeptime (float): the longest we'll sleep, in seconds; defaults to
+                               300s (five minutes)
+        sleepscale (float): how much to multiply the sleep time by each
+                            iteration; defaults to 1.5
+        jitter (int): random jitter to introduce to sleep time each iteration.
+                      the amount is chosen at random between [-jitter, +jitter]
+                      defaults to 1
+
+    Yields:
+        None, a maximum of `attempts` number of times
+
+    Example:
+        >>> n = 0
+        >>> for _ in retrier(sleeptime=0, jitter=0):
+        ...     if n == 3:
+        ...         # We did the thing!
+        ...         break
+        ...     n += 1
+        >>> n
+        3
+
+        >>> n = 0
+        >>> for _ in retrier(sleeptime=0, jitter=0):
+        ...     if n == 6:
+        ...         # We did the thing!
+        ...         break
+        ...     n += 1
+        ... else:
+        ...     print "max tries hit"
+        max tries hit
+    """
+    for _ in range(attempts):
+        log.debug("attempt %i/%i", _ + 1, attempts)
+        yield
+        if jitter:
+            sleeptime += random.randint(-jitter, jitter)
+            sleeptime = max(sleeptime, 0)
+
+        if _ == attempts - 1:
+            # Don't need to sleep the last time
+            break
+        log.debug("sleeping for %.2fs (attempt %i/%i)", sleeptime, _ + 1, attempts)
+        time.sleep(sleeptime)
+        sleeptime *= sleepscale
+        if sleeptime > max_sleeptime:
+            sleeptime = max_sleeptime
+
+
+def retry(action, attempts=5, sleeptime=60, max_sleeptime=5 * 60,
+          sleepscale=1.5, jitter=1, retry_exceptions=(Exception,),
+          cleanup=None, args=(), kwargs={}):
+    """
+    Calls an action function until it succeeds, or we give up.
+
+    Args:
+        action (callable): the function to retry
+        attempts (int): maximum number of times to try; defaults to 5
+        sleeptime (float): how many seconds to sleep between tries; defaults to
+                           60s (one minute)
+        max_sleeptime (float): the longest we'll sleep, in seconds; defaults to
+                               300s (five minutes)
+        sleepscale (float): how much to multiply the sleep time by each
+                            iteration; defaults to 1.5
+        jitter (int): random jitter to introduce to sleep time each iteration.
+                      the amount is chosen at random between [-jitter, +jitter]
+                      defaults to 1
+        retry_exceptions (tuple): tuple of exceptions to be caught. If other
+                                  exceptions are raised by action(), then these
+                                  are immediately re-raised to the caller.
+        cleanup (callable): optional; called if one of `retry_exceptions` is
+                            caught. No arguments are passed to the cleanup
+                            function; if your cleanup requires arguments,
+                            consider using functools.partial or a lambda
+                            function.
+        args (tuple): positional arguments to call `action` with
+        hwargs (dict): keyword arguments to call `action` with
+
+    Returns:
+        Whatever action(*args, **kwargs) returns
+
+    Raises:
+        Whatever action(*args, **kwargs) raises. `retry_exceptions` are caught
+        up until the last attempt, in which case they are re-raised.
+
+    Example:
+        >>> count = 0
+        >>> def foo():
+        ...     global count
+        ...     count += 1
+        ...     print count
+        ...     if count < 3:
+        ...         raise ValueError("count is too small!")
+        ...     return "success!"
+        >>> retry(foo, sleeptime=0, jitter=0)
+        1
+        2
+        3
+        'success!'
+    """
+    assert callable(action)
+    assert not cleanup or callable(cleanup)
+    if max_sleeptime < sleeptime:
+        log.debug("max_sleeptime %d less than sleeptime %d" % (
+            max_sleeptime, sleeptime))
+
+    n = 1
+    for _ in retrier(attempts=attempts, sleeptime=sleeptime,
+                     max_sleeptime=max_sleeptime, sleepscale=sleepscale,
+                     jitter=jitter):
+        try:
+            log.info("retry: Calling %s with args: %s, kwargs: %s, "
+                     "attempt #%d" % (action, str(args), str(kwargs), n))
+            return action(*args, **kwargs)
+        except retry_exceptions:
+            log.debug("retry: Caught exception: ", exc_info=True)
+            if cleanup:
+                cleanup()
+            if n == attempts:
+                log.info("retry: Giving up on %s" % action)
+                raise
+            continue
+        finally:
+            n += 1
+
+
+def retriable(*retry_args, **retry_kwargs):
+    """
+    A decorator factory for retry(). Wrap your function in @retriable(...) to
+    give it retry powers!
+
+    Arguments:
+        Same as for `retry`, with the exception of `action`, `args`, and `kwargs`,
+        which are left to the normal function definition.
+
+    Returns:
+        A function decorator
+
+    Example:
+        >>> count = 0
+        >>> @retriable(sleeptime=0, jitter=0)
+        ... def foo():
+        ...     global count
+        ...     count += 1
+        ...     print count
+        ...     if count < 3:
+        ...         raise ValueError("count too small")
+        ...     return "success!"
+        >>> foo()
+        1
+        2
+        3
+        'success!'
+    """
+    def _retriable_factory(func):
+        @wraps(func)
+        def _retriable_wrapper(*args, **kwargs):
+            return retry(func, args=args, kwargs=kwargs, *retry_args,
+                         **retry_kwargs)
+        return _retriable_wrapper
+    return _retriable_factory
+
+
+@contextmanager
+def retrying(func, *retry_args, **retry_kwargs):
+    """
+    A context manager for wrapping functions with retry functionality.
+
+    Arguments:
+        func (callable): the function to wrap
+        other arguments as per `retry`
+
+    Returns:
+        A context manager that returns retriable(func) on __enter__
+
+    Example:
+        >>> count = 0
+        >>> def foo():
+        ...     global count
+        ...     count += 1
+        ...     print count
+        ...     if count < 3:
+        ...         raise ValueError("count too small")
+        ...     return "success!"
+        >>> with retrying(foo, sleeptime=0, jitter=0) as f:
+        ...     f()
+        1
+        2
+        3
+        'success!'
+    """
+    yield retriable(*retry_args, **retry_kwargs)(func)
new file mode 100644
--- /dev/null
+++ b/python/redo/redo/cmd.py
@@ -0,0 +1,53 @@
+# ***** BEGIN LICENSE BLOCK *****
+# 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/.
+# ***** END LICENSE BLOCK *****
+import logging
+from subprocess import check_call, CalledProcessError
+import sys
+
+from redo import retrying
+
+log = logging.getLogger(__name__)
+
+
+def main():
+    from argparse import ArgumentParser
+
+    parser = ArgumentParser()
+    parser.add_argument(
+        "-a", "--attempts", type=int, default=5,
+        help="How many times to retry.")
+    parser.add_argument(
+        "-s", "--sleeptime", type=int, default=60,
+        help="How long to sleep between attempts. Sleeptime doubles after each attempt.")
+    parser.add_argument(
+        "-m", "--max-sleeptime", type=int, default=5*60,
+        help="Maximum length of time to sleep between attempts (limits backoff length).")
+    parser.add_argument("-v", "--verbose", action="store_true", default=False)
+    parser.add_argument("cmd", nargs="+", help="Command to run. Eg: wget http://blah")
+
+    args = parser.parse_args()
+
+    if args.verbose:
+        logging.basicConfig(level=logging.INFO)
+        logging.getLogger("retry").setLevel(logging.INFO)
+    else:
+        logging.basicConfig(level=logging.ERROR)
+        logging.getLogger("retry").setLevel(logging.ERROR)
+
+    try:
+        with retrying(check_call, attempts=args.attempts, sleeptime=args.sleeptime,
+                      max_sleeptime=args.max_sleeptime,
+                      retry_exceptions=(CalledProcessError,)) as r_check_call:
+            r_check_call(args.cmd)
+    except KeyboardInterrupt:
+        sys.exit(-1)
+    except Exception as e:
+        log.error("Unable to run command after %d attempts" % args.attempts, exc_info=True)
+        rc = getattr(e, "returncode", -2)
+        sys.exit(rc)
+
+if __name__ == "__main__":
+    main()
new file mode 100644
--- /dev/null
+++ b/python/redo/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
new file mode 100644
--- /dev/null
+++ b/python/redo/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+setup(
+    name="redo",
+    version="1.4",
+    description="Utilities to retry Python callables.",
+    author="Ben Hearsum",
+    author_email="ben@hearsum.ca",
+    packages=["redo"],
+    entry_points={
+        "console_scripts": ["retry = redo.cmd:main"],
+    },
+    url="https://github.com/bhearsum/redo",
+)
--- a/services/datareporting/DataReportingService.js
+++ b/services/datareporting/DataReportingService.js
@@ -66,16 +66,20 @@ this.DataReportingService = function () 
                .getService(Ci.nsIObserverService);
 
   this._clientID = null;
   this._loadClientIdTask = null;
   this._saveClientIdTask = null;
 
   this._stateDir = null;
   this._stateFilePath = null;
+
+  // Used for testing only, when true results in getSessionRecorder() returning
+  // undefined. Controlled via simulate* methods.
+  this._simulateNoSessionRecorder = false;
 }
 
 DataReportingService.prototype = Object.freeze({
   classID: Components.ID("{41f6ae36-a79f-4613-9ac3-915e70f83789}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
@@ -392,17 +396,27 @@ DataReportingService.prototype = Object.
   }),
 
   /**
    * Returns the SessionRecorder instance associated with the data reporting service.
    * Returns an actual object only if FHR is enabled and after initialization,
    * else returns undefined.
    */
   getSessionRecorder: function() {
-    return this.sessionRecorder;
+    return this._simulateNoSessionRecorder ? undefined : this.sessionRecorder;
+  },
+
+  // These two simulate* methods below are only used for testings and control
+  // whether getSessionRecorder() behaves normally or forced to return undefined
+  simulateNoSessionRecorder() {
+    this._simulateNoSessionRecorder = true;
+  },
+
+  simulateRestoreSessionRecorder() {
+    this._simulateNoSessionRecorder = false;
   },
 
   /*
    * Simulate a restart of the service. This is for testing only.
    */
   _reset: Task.async(function* () {
     yield this._loadClientIdTask;
     yield this._saveClientIdTask;
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -368,17 +368,17 @@ Tester.prototype = {
 
       if (this.currentTest.passCount === 0 &&
           this.currentTest.failCount === 0 &&
           this.currentTest.todoCount === 0) {
         this.currentTest.addResult(new testResult(false, "This test contains no passes, no fails and no todos. Maybe it threw a silent exception? Make sure you use waitForExplicitFinish() if you need it.", "", false));
       }
 
       if (testScope.__expected == 'fail' && testScope.__num_failed <= 0) {
-        this.currentTest.addResult(new testResult(false, "We expected at least one assertion to fail because this test file was marked as fail-if in the manifest", "", false));
+        this.currentTest.addResult(new testResult(false, "We expected at least one assertion to fail because this test file was marked as fail-if in the manifest!", "", true));
       }
 
       this.Promise.Debugging.flushUncaughtErrors();
 
       let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
       if (winUtils.isTestControllingRefreshes) {
         this.currentTest.addResult(new testResult(false, "test left refresh driver under test control", "", false));
--- a/testing/mochitest/tests/SimpleTest/SimpleTest.js
+++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js
@@ -966,18 +966,18 @@ SimpleTest.finish = function() {
         } else {
             dump(err + '\n');
         }
     }
 
     if (SimpleTest.expected == 'fail' && SimpleTest.num_failed <= 0) {
         msg = 'We expected at least one failure';
         var test = {'result': false, 'name': 'fail-if condition in manifest', 'diag': msg};
-        var successInfo = {status:"PASS", expected:"PASS", message:"TEST-PASS"};
-        var failureInfo = {status:"FAIL", expected:"FAIL", message:"TEST-KNOWN-FAIL"};
+        var successInfo = {status:"FAIL", expected:"FAIL", message:"TEST-KNOWN-FAIL"};
+        var failureInfo = {status:"PASS", expected:"FAIL", message:"TEST-UNEXPECTED-PASS"};
 
         SimpleTest._logResult(test, successInfo, failureInfo);
         SimpleTest._tests.push(test);
     }
 
     SimpleTest.testsLength = SimpleTest._tests.length;
 
     SimpleTest._alreadyFinished = true;
--- a/testing/mozbase/mozinstall/mozinstall/mozinstall.py
+++ b/testing/mozbase/mozinstall/mozinstall/mozinstall.py
@@ -119,19 +119,19 @@ def install(src, dest):
             install_dir = mozfile.extract(src, dest)[0]
         elif src.lower().endswith('.dmg'):
             install_dir = _install_dmg(src, dest)
         elif src.lower().endswith('.exe'):
             install_dir = _install_exe(src, dest)
 
         return install_dir
 
-    except Exception:
+    except Exception, ex:
         cls, exc, trbk = sys.exc_info()
-        error = InstallError('Failed to install "%s"' % src)
+        error = InstallError('Failed to install "%s (%s)"' % src, str(ex))
         raise InstallError, error, trbk
 
     finally:
         # trbk won't get GC'ed due to circular reference
         # http://docs.python.org/library/sys.html#sys.exc_info
         del trbk
 
 
@@ -208,19 +208,19 @@ def uninstall(install_folder):
                 # folder has been removed or until we run into a timeout.
                 end_time = time.time() + TIMEOUT_UNINSTALL
                 while os.path.exists(uninstall_folder):
                     time.sleep(1)
 
                     if time.time() > end_time:
                         raise Exception('Failure removing uninstall folder.')
 
-            except Exception:
+            except Exception, ex:
                 cls, exc, trbk = sys.exc_info()
-                error = UninstallError('Failed to uninstall %s' % install_folder)
+                error = UninstallError('Failed to uninstall %s (%s)' % install_folder, str(ex))
                 raise UninstallError, error, trbk
 
             finally:
                 # trbk won't get GC'ed due to circular reference
                 # http://docs.python.org/library/sys.html#sys.exc_info
                 del trbk
 
     # Ensure that we remove any trace of the installation. Even the uninstaller
--- a/testing/web-platform/meta/gamepad/idlharness.html.ini
+++ b/testing/web-platform/meta/gamepad/idlharness.html.ini
@@ -1,8 +1,5 @@
 [idlharness.html]
   type: testharness
-  [Gamepad interface: attribute timestamp]
-    expected: FAIL
-
   [GamepadEvent interface: existence and properties of interface object]
     expected: FAIL
 
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js
@@ -43,24 +43,25 @@ const NUMBER_OF_THREADS_TO_LAUNCH = 30;
 let gNumberOfThreadsLaunched = 0;
 
 const PREF_BRANCH = "toolkit.telemetry.";
 const PREF_ENABLED = PREF_BRANCH + "enabled";
 const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
 const PREF_FHR_SERVICE_ENABLED = "datareporting.healthreport.service.enabled";
 
 const HAS_DATAREPORTINGSERVICE = "@mozilla.org/datareporting/service;1" in Cc;
+const SESSION_RECORDER_EXPECTED = HAS_DATAREPORTINGSERVICE &&
+                                  Preferences.get(PREF_FHR_SERVICE_ENABLED, true);
 
 const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
 
 let gHttpServer = new HttpServer();
 let gServerStarted = false;
 let gRequestIterator = null;
 let gDataReportingClientID = null;
-let gOrigFhrServiceEnabled = true;
 
 XPCOMUtils.defineLazyGetter(this, "gDatareportingService",
   () => Cc["@mozilla.org/datareporting/service;1"]
           .getService(Ci.nsISupports)
           .wrappedJSObject);
 
 function sendPing () {
   TelemetryPing.gatherStartup();
@@ -220,17 +221,17 @@ function checkPayload(request, reason, s
   do_check_true(payload.simpleMeasurements.uptime >= 0);
   do_check_true(payload.simpleMeasurements.startupInterrupted === 1);
   do_check_eq(payload.simpleMeasurements.shutdownDuration, SHUTDOWN_TIME);
   do_check_eq(payload.simpleMeasurements.savedPings, 1);
   do_check_true("maximalNumberOfConcurrentThreads" in payload.simpleMeasurements);
   do_check_true(payload.simpleMeasurements.maximalNumberOfConcurrentThreads >= gNumberOfThreadsLaunched);
 
   let activeTicks = payload.simpleMeasurements.activeTicks;
-  do_check_true(HAS_DATAREPORTINGSERVICE ? activeTicks >= 0 : activeTicks == -1);
+  do_check_true(SESSION_RECORDER_EXPECTED ? activeTicks >= 0 : activeTicks == -1);
 
   do_check_eq(payload.simpleMeasurements.failedProfileLockCount,
               FAILED_PROFILE_LOCK_ATTEMPTS);
   let profileDirectory = Services.dirsvc.get("ProfD", Ci.nsIFile);
   let failedProfileLocksFile = profileDirectory.clone();
   failedProfileLocksFile.append("Telemetry.FailedProfileLocks.txt");
   do_check_true(!failedProfileLocksFile.exists());
 
@@ -434,37 +435,26 @@ function run_test() {
   try {
     let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfoDebug);
     gfxInfo.spoofVendorID("0xabcd");
     gfxInfo.spoofDeviceID("0x1234");
   } catch (x) {
     // If we can't test gfxInfo, that's fine, we'll note it later.
   }
 
-  // make sure getSessionRecorder() can be called before the DRS init.
-  // It's not a requirement that it returns undefined, but that's how it behaves
-  // now - so just let this test fail if this behavior changes.
-  if (HAS_DATAREPORTINGSERVICE) {
-    do_check_true(gDatareportingService.getSessionRecorder() === undefined);
-  }
-
   // Addon manager needs a profile directory
   do_get_profile();
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
   Services.prefs.setBoolPref(PREF_ENABLED, true);
   Services.prefs.setBoolPref(PREF_FHR_UPLOAD_ENABLED, true);
 
   // Send the needed startup notifications to the datareporting service
   // to ensure that it has been initialized.
   if (HAS_DATAREPORTINGSERVICE) {
-    // Initially disable FHR to verify that activeTicks ends up as -1
-    gOrigFhrServiceEnabled = Services.prefs.getBoolPref(PREF_FHR_SERVICE_ENABLED, true);
-    Services.prefs.setBoolPref(PREF_FHR_SERVICE_ENABLED, false);
-
     gDatareportingService.observe(null, "app-startup", null);
     gDatareportingService.observe(null, "profile-after-change", null);
   }
 
   // Make it look like we've previously failed to lock a profile a couple times.
   write_fake_failedprofilelocks_file();
 
   // Make it look like we've shutdown before.
@@ -508,27 +498,27 @@ function actualTest() {
   registerFakePluginHost();
 
   run_next_test();
 }
 
 add_task(function* asyncSetup() {
   yield TelemetryPing.setup();
 
-  // When FHR is disabled or no DRS, the payload's activeTicks should be -1.
-  do_check_true(TelemetryPing.getPayload().simpleMeasurements.activeTicks == -1);
+  if (HAS_DATAREPORTINGSERVICE) {
+    // force getSessionRecorder()==undefined to check the payload's activeTicks
+    gDatareportingService.simulateNoSessionRecorder();
+  }
+
+  // When no DRS or no DRS.getSessionRecorder(), activeTicks should be -1.
+  do_check_eq(TelemetryPing.getPayload().simpleMeasurements.activeTicks, -1);
 
   if (HAS_DATAREPORTINGSERVICE) {
-    // after we got the no-FHR activeTicks, re-enable FHR and re-init the DRS.
-    // Note: this relies on the fact that the data reporting service reinitializes
-    // itself when calling its 'observe' method, without checking if it's already
-    // initialized. If this DRS behavior changes, this test would need to be adapted.
-    Services.prefs.setBoolPref(PREF_FHR_SERVICE_ENABLED, gOrigFhrServiceEnabled);
-    gDatareportingService.observe(null, "app-startup", null);
-    gDatareportingService.observe(null, "profile-after-change", null);
+    // Restore normal behavior for getSessionRecorder()
+    gDatareportingService.simulateRestoreSessionRecorder();
 
     gDataReportingClientID = yield gDatareportingService.getClientID();
 
     // We should have cached the client id now. Lets confirm that by
     // checking the client id before the async ping setup is finished.
     let promisePingSetup = TelemetryPing.reset();
     do_check_eq(TelemetryPing.clientID, gDataReportingClientID);
     yield promisePingSetup;