Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 29 Oct 2015 17:12:28 -0700
changeset 304398 b41b92c09fcf94d077a54297aea1dc675b161a9d
parent 304344 6d1e12f5725b56453e368b960e873261e0d48b5a (current diff)
parent 304397 3adbe4cbbfdd2d7516baafa3edcfc7a624856a8c (diff)
child 304400 899a340b00b5bc27269b72b2fa34818a81383505
child 304416 963e002aaa31eb7bb3789ccf9df1a9a7e9a01279
child 304496 f7a19fa7fad23aecdf371b7b4c6522b5219de30d
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone45.0a1
first release with
nightly linux32
b41b92c09fcf / 45.0a1 / 20151030030236 / files
nightly linux64
b41b92c09fcf / 45.0a1 / 20151030030236 / files
nightly mac
b41b92c09fcf / 45.0a1 / 20151030030236 / files
nightly win32
b41b92c09fcf / 45.0a1 / 20151030030236 / files
nightly win64
b41b92c09fcf / 45.0a1 / 20151030030236 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge
netwerk/test/unit/test_cache2-28-concurrent_read_resumable_entry_size_zero.js
netwerk/test/unit/test_cache2-29-concurrent_read_non-resumable_entry_size_zero.js
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Merge day clobber
\ No newline at end of file
+Merge day clobber
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1518,18 +1518,23 @@ pref("identity.fxaccounts.migrateToDevEd
 #ifdef MOZ_WIDGET_GTK
 pref("ui.key.menuAccessKeyFocuses", true);
 #endif
 
 // Encrypted media extensions.
 pref("media.eme.enabled", true);
 pref("media.eme.apiVisible", true);
 
-// If decoding-via-gmp is turned on for <video>, default to using
-// Adobe's GMP for decoding.
+// Decode using Gecko Media Plugins in <video>, if a system decoder is not
+// availble and the preferred GMP is available.
+pref("media.gmp.decoder.enabled", true);
+
+// If decoding-via-GMP is turned on for <video>, use Adobe's GMP for decoding,
+// if it's available. Note: We won't fallback to another GMP if Adobe's is not
+// installed.
 pref("media.gmp.decoder.aac", 2);
 pref("media.gmp.decoder.h264", 2);
 
 // Whether we should run a test-pattern through EME GMPs before assuming they'll
 // decode H.264.
 pref("media.gmp.trial-create.enabled", true);
 
 #ifdef MOZ_ADOBE_EME
--- a/build/autoconf/toolchain.m4
+++ b/build/autoconf/toolchain.m4
@@ -219,17 +219,43 @@ if test "$GNU_CXX"; then
                             ac_cv_cxx0x_clang_workaround="no")])
 
         if test "ac_cv_cxx0x_clang_workaround" = "no"; then
             AC_MSG_ERROR([Your toolchain does not support C++0x/C++11 mode properly. Please upgrade your toolchain])
         fi
     elif test "$ac_cv_cxx0x_headers_bug" = "yes"; then
         AC_MSG_ERROR([Your toolchain does not support C++0x/C++11 mode properly. Please upgrade your toolchain])
     fi
+
+    AC_CACHE_CHECK([whether 64-bits std::atomic requires -latomic],
+        ac_cv_needs_atomic,
+        AC_TRY_LINK(
+            [#include <cstdint>
+             #include <atomic>],
+            [ std::atomic<uint64_t> foo; foo = 1; ],
+            ac_cv_needs_atomic=no,
+            _SAVE_LIBS="$LIBS"
+            LIBS="$LIBS -latomic"
+            AC_TRY_LINK(
+                [#include <cstdint>
+                 #include <atomic>],
+                [ std::atomic<uint64_t> foo; foo = 1; ],
+                ac_cv_needs_atomic=yes,
+                ac_cv_needs_atomic="do not know; assuming no")
+            LIBS="$_SAVE_LIBS"
+        )
+    )
+    if test "$ac_cv_needs_atomic" = yes; then
+      MOZ_NEEDS_LIBATOMIC=1
+    else
+      MOZ_NEEDS_LIBATOMIC=
+    fi
+    AC_SUBST(MOZ_NEEDS_LIBATOMIC)
 fi
+
 if test -n "$CROSS_COMPILE"; then
     dnl When cross compile, we have no variable telling us what the host compiler is. Figure it out.
     cat > conftest.C <<EOF
 #if defined(__clang__)
 COMPILER CLANG __clang_major__.__clang_minor__.__clang_patchlevel__
 #elif defined(__GNUC__)
 COMPILER GCC __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
 #endif
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -512,17 +512,16 @@ EventStateManager::PreHandleEvent(nsPres
         mozilla::services::GetObserverService();
       if (obs) {
         obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
         UpdateUserActivityTimer();
       }
     }
     ++gMouseOrKeyboardEventCounter;
 
-
     nsCOMPtr<nsINode> node = do_QueryInterface(aTargetContent);
     if (node &&
         (aEvent->mMessage == eKeyUp || aEvent->mMessage == eMouseUp ||
          aEvent->mMessage == eWheel || aEvent->mMessage == eTouchEnd ||
          aEvent->mMessage == ePointerUp)) {
       nsIDocument* doc = node->OwnerDoc();
       while (doc && !doc->UserHasInteracted()) {
         doc->SetUserHasInteracted(true);
@@ -594,16 +593,17 @@ EventStateManager::PreHandleEvent(nsPres
     switch (mouseEvent->button) {
       case WidgetMouseEvent::eLeftButton:
         if (Prefs::ClickHoldContextMenu()) {
           KillClickHoldTimer();
         }
         StopTrackingDragGesture();
         sNormalLMouseEventInProcess = false;
         // then fall through...
+        MOZ_FALLTHROUGH;
       case WidgetMouseEvent::eRightButton:
       case WidgetMouseEvent::eMiddleButton:
         SetClickCount(aPresContext, mouseEvent, aStatus);
         break;
     }
     break;
   }
   case eMouseEnterIntoWidget:
@@ -647,16 +647,17 @@ EventStateManager::PreHandleEvent(nsPres
         // We should synthetize corresponding pointer events
         GeneratePointerEnterExit(ePointerLeave, mouseEvent);
       }
       GenerateMouseEnterExit(mouseEvent);
       //This is a window level mouse exit event and should stop here
       aEvent->mMessage = eVoidEvent;
       break;
     }
+    MOZ_FALLTHROUGH;
   case eMouseMove:
   case ePointerDown:
   case ePointerMove: {
     // on the Mac, GenerateDragGesture() may not return until the drag
     // has completed and so |aTargetFrame| may have been deleted (moving
     // a bookmark, for example).  If this is the case, however, we know
     // that ClearFrameRefs() has been called and it cleared out
     // |mCurrentTarget|. As a result, we should pass |mCurrentTarget|
@@ -707,16 +708,17 @@ EventStateManager::PreHandleEvent(nsPres
 
         if (HandleAccessKey(aPresContext, accessCharCodes,
                             keyEvent->mFlags.mIsTrusted, modifierMask)) {
           *aStatus = nsEventStatus_eConsumeNoDefault;
         }
       }
     }
     // then fall through...
+    MOZ_FALLTHROUGH;
   case eBeforeKeyDown:
   case eKeyDown:
   case eAfterKeyDown:
   case eBeforeKeyUp:
   case eKeyUp:
   case eAfterKeyUp:
     {
       nsIContent* content = GetFocusedContent();
@@ -1194,17 +1196,17 @@ EventStateManager::IsRemoteTarget(nsICon
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(target);
   if (browserFrame && browserFrame->GetReallyIsBrowserOrApp()) {
     return !!TabParent::GetFrom(target);
   }
 
   return false;
 }
 
-bool
+static bool
 CrossProcessSafeEvent(const WidgetEvent& aEvent)
 {
   switch (aEvent.mClass) {
   case eKeyboardEventClass:
   case eWheelEventClass:
     return true;
   case eMouseEventClass:
     switch (aEvent.mMessage) {
@@ -1230,17 +1232,17 @@ CrossProcessSafeEvent(const WidgetEvent&
     }
   case eDragEventClass:
     switch (aEvent.mMessage) {
     case eDragOver:
     case eDragExit:
     case eDrop:
       return true;
     default:
-      break;
+      return false;
     }
   default:
     return false;
   }
 }
 
 bool
 EventStateManager::HandleCrossProcessEvent(WidgetEvent* aEvent,
@@ -1366,32 +1368,30 @@ EventStateManager::CreateClickHoldTimer(
     int32_t clickHoldDelay =
       Preferences::GetInt("ui.click_hold_context_menus.delay", 500);
     mClickHoldTimer->InitWithFuncCallback(sClickHoldCallback, this,
                                           clickHoldDelay,
                                           nsITimer::TYPE_ONE_SHOT);
   }
 } // CreateClickHoldTimer
 
-
 //
 // KillClickHoldTimer
 //
 // Stop the timer that would show the context menu dead in its tracks
 //
 void
 EventStateManager::KillClickHoldTimer()
 {
   if (mClickHoldTimer) {
     mClickHoldTimer->Cancel();
     mClickHoldTimer = nullptr;
   }
 }
 
-
 //
 // sClickHoldCallback
 //
 // This fires after the mouse has been down for a certain length of time.
 //
 void
 EventStateManager::sClickHoldCallback(nsITimer* aTimer, void* aESM)
 {
@@ -1399,17 +1399,16 @@ EventStateManager::sClickHoldCallback(ns
   if (self) {
     self->FireContextClick();
   }
 
   // NOTE: |aTimer| and |self->mAutoHideTimer| are invalid after calling ClosePopup();
 
 } // sAutoHideCallback
 
-
 //
 // FireContextClick
 //
 // If we're this far, our timer has fired, which means the mouse has been down
 // for a certain period of time and has not moved enough to generate a dragGesture.
 // We can be certain the user wants a context-click at this stage, so generate
 // a dom event and fire it in.
 //
@@ -1524,17 +1523,16 @@ EventStateManager::FireContextClick()
   if (status == nsEventStatus_eConsumeNoDefault) {
     StopTrackingDragGesture();
   }
 
   KillClickHoldTimer();
 
 } // FireContextClick
 
-
 //
 // BeginTrackingDragGesture
 //
 // Record that the mouse has gone down and that we should move to TRACKING state
 // of d&d gesture tracker.
 //
 // We also use this to track click-hold context menus. When the mouse goes down,
 // fire off a short timer. If the timer goes off and we have yet to fire the
@@ -3043,18 +3041,19 @@ EventStateManager::PostHandleEvent(nsPre
       }
       SetActiveManager(this, activeContent);
     }
     break;
   case ePointerCancel: {
     if(WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
       GenerateMouseEnterExit(mouseEvent);
     }
-    // This break was commented specially
-    // break;
+    // After firing the pointercancel event, a user agent must also fire a
+    // pointerout event followed by a pointerleave event.
+    MOZ_FALLTHROUGH;
   }
   case ePointerUp: {
     WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
     // After UP/Cancel Touch pointers become invalid so we can remove relevant helper from Table
     // Mouse/Pen pointers are valid all the time (not only between down/up)
     if (pointerEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
       mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
       GenerateMouseEnterExit(pointerEvent);
@@ -4198,18 +4197,18 @@ EventStateManager::GenerateMouseEnterExi
         // case, so make the current and previous refPoints the same.
         aMouseEvent->lastRefPoint = aMouseEvent->refPoint;
       } else {
         aMouseEvent->lastRefPoint = sLastRefPoint;
       }
 
       // Update the last known refPoint with the current refPoint.
       sLastRefPoint = aMouseEvent->refPoint;
-
     }
+    MOZ_FALLTHROUGH;
   case ePointerMove:
   case ePointerDown:
     {
       // Get the target content target (mousemove target == mouseover target)
       nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
       if (!targetElement) {
         // We're always over the document root, even if we're only
         // over dead space in a page (whose frame is not associated with
@@ -5857,9 +5856,8 @@ AutoHandlingUserInputStatePusher::~AutoH
     nsFocusManager* fm = nsFocusManager::GetFocusManager();
     NS_ENSURE_TRUE_VOID(fm);
     nsCOMPtr<nsIDocument> handlingDocument =
       fm->SetMouseButtonHandlingDocument(mMouseButtonEventHandlingDocument);
   }
 }
 
 } // namespace mozilla
-
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -190,16 +190,17 @@
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/dom/telephony/PTelephonyChild.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
 #include "mozilla/dom/voicemail/VoicemailIPCService.h"
 #include "mozilla/net/NeckoMessageUtils.h"
 #include "mozilla/widget/PuppetBidiKeyboard.h"
 #include "mozilla/RemoteSpellCheckEngineChild.h"
 #include "GMPServiceChild.h"
+#include "GMPDecoderModule.h"
 #include "gfxPlatform.h"
 #include "nscore.h" // for NS_FREE_PERMANENT_DATA
 
 using namespace mozilla;
 using namespace mozilla::docshell;
 using namespace mozilla::dom::bluetooth;
 using namespace mozilla::dom::cellbroadcast;
 using namespace mozilla::dom::devicestorage;
@@ -1458,16 +1459,23 @@ ContentChild::RecvNotifyPresentationRece
       do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   NS_WARN_IF(!service);
 
   NS_WARN_IF(NS_FAILED(service->UntrackSessionInfo(aSessionId)));
 
   return true;
 }
 
+bool
+ContentChild::RecvNotifyGMPsChanged()
+{
+  GMPDecoderModule::UpdateUsableCodecs();
+  return true;
+}
+
 PCrashReporterChild*
 ContentChild::AllocPCrashReporterChild(const mozilla::dom::NativeThreadId& id,
                                        const uint32_t& processType)
 {
 #ifdef MOZ_CRASHREPORTER
     return new CrashReporterChild();
 #else
     return nullptr;
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -282,16 +282,18 @@ public:
     virtual bool DeallocPFMRadioChild(PFMRadioChild* aActor) override;
 
     virtual PPresentationChild* AllocPPresentationChild() override;
     virtual bool DeallocPPresentationChild(PPresentationChild* aActor) override;
     virtual bool RecvNotifyPresentationReceiverLaunched(PBrowserChild* aIframe,
                                                         const nsString& aSessionId) override;
     virtual bool RecvNotifyPresentationReceiverCleanUp(const nsString& aSessionId) override;
 
+    virtual bool RecvNotifyGMPsChanged() override;
+
     virtual PSpeechSynthesisChild* AllocPSpeechSynthesisChild() override;
     virtual bool DeallocPSpeechSynthesisChild(PSpeechSynthesisChild* aActor) override;
 
     virtual bool RecvRegisterChrome(InfallibleTArray<ChromePackage>&& packages,
                                     InfallibleTArray<SubstitutionMapping>&& resources,
                                     InfallibleTArray<OverrideMapping>&& overrides,
                                     const nsCString& locale,
                                     const bool& reset) override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -688,16 +688,17 @@ static const char* sObserverTopics[] = {
 #ifdef MOZ_ENABLE_PROFILER_SPS
     "profiler-started",
     "profiler-stopped",
     "profiler-paused",
     "profiler-resumed",
     "profiler-subprocess-gather",
     "profiler-subprocess",
 #endif
+    "gmp-changed",
 };
 
 #ifdef MOZ_NUWA_PROCESS
 // Contains the observer topics that can be sent to the Nuwa process after it
 // becomes ready. The ContentParent instance will unregister sObserverTopics
 // if not listed in sNuwaSafeObserverTopics.
 static const char* sNuwaSafeObserverTopics[] = {
     "xpcom-shutdown",
@@ -3292,16 +3293,19 @@ ContentParent::Observe(nsISupports* aSub
         if (pse) {
             if (!mProfile.IsEmpty()) {
                 pse->AddSubProfile(mProfile.get());
                 mProfile.Truncate();
             }
         }
     }
 #endif
+    else if (!strcmp(aTopic, "gmp-changed")) {
+      unused << SendNotifyGMPsChanged();
+    }
     return NS_OK;
 }
 
 PGMPServiceParent*
 ContentParent::AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport,
                                       base::ProcessId aOtherProcess)
 {
     return GMPServiceParent::Create(aTransport, aOtherProcess);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -672,16 +672,21 @@ child:
     async NotifyPresentationReceiverLaunched(PBrowser aIframe, nsString aSessionId);
 
     /**
      * Notify the child that the info about a presentation receiver needs to be
      * cleaned up.
      */
     async NotifyPresentationReceiverCleanUp(nsString aSessionId);
 
+    /**
+     * Notify the child that the Gecko Media Plugins installed changed.
+     */
+    async NotifyGMPsChanged();
+
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1595,17 +1595,18 @@ MediaMemoryTracker::CollectReports(nsIHa
 
   return NS_OK;
 }
 
 MediaDecoderOwner*
 MediaDecoder::GetOwner()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  return mOwner;
+  // mOwner is valid until shutdown.
+  return !mShuttingDown ? mOwner : nullptr;
 }
 
 void
 MediaDecoder::ConstructMediaTracks()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown || mMediaTracksConstructed || !mInfo) {
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1873,16 +1873,25 @@ MediaManager::GetUserMedia(nsPIDOMWindow
     Telemetry::Accumulate(loop ? Telemetry::LOOP_GET_USER_MEDIA_TYPE :
                                  Telemetry::WEBRTC_GET_USER_MEDIA_TYPE,
                           (uint32_t) videoType);
     switch (videoType) {
       case dom::MediaSourceEnum::Camera:
         break;
 
       case dom::MediaSourceEnum::Browser:
+        // If no window id is passed in then default to the caller's window.
+        // Functional defaults are helpful in tests, but also a natural outcome
+        // of the constraints API's limited semantics for requiring input.
+        if (!vc.mBrowserWindow.WasPassed()) {
+          nsPIDOMWindow *outer = aWindow->GetOuterWindow();
+          vc.mBrowserWindow.Construct(outer->WindowID());
+        }
+        // | Fall through
+        // V
       case dom::MediaSourceEnum::Screen:
       case dom::MediaSourceEnum::Application:
       case dom::MediaSourceEnum::Window:
         // Deny screensharing request if support is disabled, or
         // the requesting document is not from a host on the whitelist, or
         // we're on Mac OSX 10.6 and WinXP until proved that they work
         if (!Preferences::GetBool(((videoType == dom::MediaSourceEnum::Browser)?
                                    "media.getusermedia.browser.enabled" :
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -37,16 +37,17 @@
 #include "nsHashKeys.h"
 #include "nsIFile.h"
 #include "nsISimpleEnumerator.h"
 #if defined(MOZ_CRASHREPORTER)
 #include "nsExceptionHandler.h"
 #include "nsPrintfCString.h"
 #endif
 #include "nsIXULRuntime.h"
+#include "GMPDecoderModule.h"
 #include <limits>
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
@@ -695,26 +696,57 @@ GeckoMediaPluginServiceParent::LoadFromE
       AddOnGMPThread(nsDependentSubstring(allpaths, pos, next - pos));
       pos = next + 1;
     }
   }
 
   mScannedPluginOnDisk = true;
 }
 
+class NotifyObserversTask final : public nsRunnable {
+public:
+  explicit NotifyObserversTask(const char* aTopic, nsString aData = EmptyString())
+    : mTopic(aTopic)
+    , mData(aData)
+  {}
+  NS_IMETHOD Run() override {
+    MOZ_ASSERT(NS_IsMainThread());
+    nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+    MOZ_ASSERT(obsService);
+    if (obsService) {
+      obsService->NotifyObservers(nullptr, mTopic, mData.get());
+    }
+    return NS_OK;
+  }
+private:
+  ~NotifyObserversTask() {}
+  const char* mTopic;
+  const nsString mData;
+};
+
 NS_IMETHODIMP
 GeckoMediaPluginServiceParent::PathRunnable::Run()
 {
   if (mOperation == ADD) {
     mService->AddOnGMPThread(mPath);
   } else {
     mService->RemoveOnGMPThread(mPath,
                                 mOperation == REMOVE_AND_DELETE_FROM_DISK,
                                 mDefer);
   }
+#ifndef MOZ_WIDGET_GONK // Bug 1214967: disabled on B2G due to inscrutable test failures.
+  // For e10s, we must fire a notification so that all ContentParents notify
+  // their children to update the codecs that the GMPDecoderModule can use.
+  NS_DispatchToMainThread(new NotifyObserversTask("gmp-changed"), NS_DISPATCH_NORMAL);
+  // For non-e10s, and for decoding in the chrome process, must update GMP
+  // PDM's codecs list directly.
+  NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+    GMPDecoderModule::UpdateUsableCodecs();
+  }));
+#endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceParent::AddPluginDirectory(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_IsMainThread());
   return GMPDispatch(new PathRunnable(this, aDirectory,
@@ -930,37 +962,16 @@ GeckoMediaPluginServiceParent::ClonePlug
   }
 
   MutexAutoLock lock(mMutex);
   mPlugins.AppendElement(gmp);
 
   return gmp.get();
 }
 
-class NotifyObserversTask final : public nsRunnable {
-public:
-  explicit NotifyObserversTask(const char* aTopic, nsString aData = EmptyString())
-    : mTopic(aTopic)
-    , mData(aData)
-  {}
-  NS_IMETHOD Run() override {
-    MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
-    MOZ_ASSERT(obsService);
-    if (obsService) {
-      obsService->NotifyObservers(nullptr, mTopic, mData.get());
-    }
-    return NS_OK;
-  }
-private:
-  ~NotifyObserversTask() {}
-  const char* mTopic;
-  const nsString mData;
-};
-
 void
 GeckoMediaPluginServiceParent::AddOnGMPThread(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
 
   nsCOMPtr<nsIFile> directory;
   nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -245,20 +245,16 @@ PDMFactory::CreatePDMs()
     m = CreateBlankDecoderModule();
     StartupPDM(m);
     // The Blank PDM SupportsMimeType reports true for all codecs; the creation
     // of its decoder is infallible. As such it will be used for all media, we
     // can stop creating more PDM from this point.
     return;
   }
 
-  if (sGMPDecoderEnabled) {
-    m = new GMPDecoderModule();
-    StartupPDM(m);
-  }
 #ifdef MOZ_WIDGET_ANDROID
   if(sAndroidMCDecoderPreferred && sAndroidMCDecoderEnabled) {
     m = new AndroidDecoderModule();
     StartupPDM(m);
   }
 #endif
 #ifdef XP_WIN
   if (sWMFDecoderEnabled) {
@@ -286,16 +282,21 @@ PDMFactory::CreatePDMs()
   if(sAndroidMCDecoderEnabled){
     m = new AndroidDecoderModule();
     StartupPDM(m);
   }
 #endif
 
   m = new AgnosticDecoderModule();
   StartupPDM(m);
+
+  if (sGMPDecoderEnabled) {
+    m = new GMPDecoderModule();
+    StartupPDM(m);
+  }  
 }
 
 bool
 PDMFactory::StartupPDM(PlatformDecoderModule* aPDM)
 {
   if (aPDM && NS_SUCCEEDED(aPDM->Startup())) {
     mCurrentPDMs.AppendElement(aPDM);
     return true;
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -6,18 +6,22 @@
 
 #include "GMPDecoderModule.h"
 #include "GMPAudioDecoder.h"
 #include "GMPVideoDecoder.h"
 #include "MediaDataDecoderProxy.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticMutex.h"
 #include "gmp-audio-decode.h"
 #include "gmp-video-decode.h"
+#ifdef XP_WIN
+#include "WMFDecoderModule.h"
+#endif
 
 namespace mozilla {
 
 GMPDecoderModule::GMPDecoderModule()
 {
 }
 
 GMPDecoderModule::~GMPDecoderModule()
@@ -84,23 +88,95 @@ GMPDecoderModule::DecoderNeedsConversion
   // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format.
   if (aConfig.IsVideo()) {
     return kNeedAVCC;
   } else {
     return kNeedNone;
   }
 }
 
+static bool
+HasGMPFor(const nsACString& aAPI,
+          const nsACString& aCodec,
+          const nsACString& aGMP)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+#ifdef XP_WIN
+  // gmp-clearkey uses WMF for decoding, so if we're using clearkey we must
+  // verify that WMF works before continuing.
+  if (aGMP.EqualsLiteral("org.w3.clearkey")) {
+    RefPtr<WMFDecoderModule> pdm(new WMFDecoderModule());
+    if (aCodec.EqualsLiteral("aac") &&
+        !pdm->SupportsMimeType(NS_LITERAL_CSTRING("audio/mp4a-latm"))) {
+      return false;
+    }
+    if (aCodec.EqualsLiteral("h264") &&
+        !pdm->SupportsMimeType(NS_LITERAL_CSTRING("video/avc"))) {
+      return false;
+    }
+  }
+#endif
+  nsTArray<nsCString> tags;
+  tags.AppendElement(aCodec);
+  tags.AppendElement(aGMP);
+  nsCOMPtr<mozIGeckoMediaPluginService> mps =
+    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  if (NS_WARN_IF(!mps)) {
+    return false;
+  }
+  bool hasPlugin = false;
+  if (NS_FAILED(mps->HasPluginForAPI(aAPI, &tags, &hasPlugin))) {
+    return false;
+  }
+  return hasPlugin;
+}
+
+StaticMutex sGMPCodecsMutex;
+
+struct GMPCodecs {
+  const char* mKeySystem;
+  bool mHasAAC;
+  bool mHasH264;
+};
+
+static GMPCodecs sGMPCodecs[] = {
+  { "org.w3.clearkey", false, false },
+  { "com.adobe.primetime", false, false },
+};
+
+void
+GMPDecoderModule::UpdateUsableCodecs()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  StaticMutexAutoLock lock(sGMPCodecsMutex);
+  for (GMPCodecs& gmp : sGMPCodecs) {
+    gmp.mHasAAC = HasGMPFor(NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
+                            NS_LITERAL_CSTRING("aac"),
+                            nsDependentCString(gmp.mKeySystem));
+    gmp.mHasH264 = HasGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+                             NS_LITERAL_CSTRING("h264"),
+                             nsDependentCString(gmp.mKeySystem));
+  }
+}
+
 static uint32_t sPreferredAacGmp = 0;
 static uint32_t sPreferredH264Gmp = 0;
 
 /* static */
 void
 GMPDecoderModule::Init()
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // GMPService::HasPluginForAPI is main thread only, so to implement
+  // SupportsMimeType() we build a table of the codecs which each whitelisted
+  // GMP has and update it when any GMPs are removed or added at runtime.
+  UpdateUsableCodecs();
+
   Preferences::AddUintVarCache(&sPreferredAacGmp,
                                "media.gmp.decoder.aac", 0);
   Preferences::AddUintVarCache(&sPreferredH264Gmp,
                                "media.gmp.decoder.h264", 0);
 }
 
 /* static */
 const Maybe<nsCString>
@@ -110,56 +186,50 @@ GMPDecoderModule::PreferredGMP(const nsA
   if (aMimeType.EqualsLiteral("audio/mp4a-latm")) {
     switch (sPreferredAacGmp) {
       case 1: rv.emplace(NS_LITERAL_CSTRING("org.w3.clearkey")); break;
       case 2: rv.emplace(NS_LITERAL_CSTRING("com.adobe.primetime")); break;
       default: break;
     }
   }
 
-  if (aMimeType.EqualsLiteral("video/avc")) {
+  if (aMimeType.EqualsLiteral("video/avc") ||
+      aMimeType.EqualsLiteral("video/mp4")) {
     switch (sPreferredH264Gmp) {
       case 1: rv.emplace(NS_LITERAL_CSTRING("org.w3.clearkey")); break;
       case 2: rv.emplace(NS_LITERAL_CSTRING("com.adobe.primetime")); break;
       default: break;
     }
   }
 
   return rv;
 }
 
+/* static */
 bool
 GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                    const Maybe<nsCString>& aGMP)
 {
-  nsTArray<nsCString> tags;
-  nsCString api;
-  if (aMimeType.EqualsLiteral("audio/mp4a-latm")) {
-    tags.AppendElement(NS_LITERAL_CSTRING("aac"));
-    api = NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER);
-  } else if (aMimeType.EqualsLiteral("video/avc") ||
-             aMimeType.EqualsLiteral("video/mp4")) {
-    tags.AppendElement(NS_LITERAL_CSTRING("h264"));
-    api = NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
-  } else {
-    return false;
+  const bool isAAC = aMimeType.EqualsLiteral("audio/mp4a-latm");
+  const bool isH264 = aMimeType.EqualsLiteral("video/avc") ||
+                      aMimeType.EqualsLiteral("video/mp4");
+
+  StaticMutexAutoLock lock(sGMPCodecsMutex);
+  for (GMPCodecs& gmp : sGMPCodecs) {
+    if (isAAC && gmp.mHasAAC &&
+        (aGMP.isNothing() || aGMP.value().EqualsASCII(gmp.mKeySystem))) {
+      return true;
+    }
+    if (isH264 && gmp.mHasH264 &&
+        (aGMP.isNothing() || aGMP.value().EqualsASCII(gmp.mKeySystem))) {
+      return true;
+    }
   }
-  if (aGMP.isSome()) {
-    tags.AppendElement(aGMP.value());
-  }
-  nsCOMPtr<mozIGeckoMediaPluginService> mps =
-    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
-  if (NS_WARN_IF(!mps)) {
-    return false;
-  }
-  bool hasPlugin = false;
-  if (NS_FAILED(mps->HasPluginForAPI(api, &tags, &hasPlugin))) {
-    return false;
-  }
-  return hasPlugin;
+
+  return false;
 }
 
 bool
 GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType)
 {
   return SupportsMimeType(aMimeType, PreferredGMP(aMimeType));
 }
 
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
@@ -33,20 +33,23 @@ public:
                      MediaDataDecoderCallback* aCallback) override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   bool
   SupportsMimeType(const nsACString& aMimeType) override;
 
+  // Main thread only.
   static void Init();
 
   static const Maybe<nsCString> PreferredGMP(const nsACString& aMimeType);
 
   static bool SupportsMimeType(const nsACString& aMimeType,
                                const Maybe<nsCString>& aGMP);
 
+  // Main thread only.
+  static void UpdateUsableCodecs();
 };
 
 } // namespace mozilla
 
 #endif // GMPDecoderModule_h_
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -1401,16 +1401,21 @@ const DEBUG_TEST_LOOP_FOREVER = false;
 //      MediaTestManager.start(token) if it starts a test. The test object is
 //      guaranteed to be playable by our supported decoders; you don't need to
 //      check canPlayType.
 //   3. When your tests finishes, call MediaTestManager.finished(), passing
 //      the token back to the manager. The manager may either start the next run
 //      or end the mochitest if all the tests are done.
 function MediaTestManager() {
 
+  // Return how many seconds elapsed since |begin|.
+  function elapsedTime(begin) {
+    var end = new Date();
+    return (end.getTime() - begin.getTime()) / 1000;
+  }
   // Sets up a MediaTestManager to runs through the 'tests' array, which needs
   // to be one of, or have the same fields as, the g*Test arrays of tests. Uses
   // the user supplied 'startTest' function to initialize the test. This
   // function must accept two arguments, the test entry from the 'tests' array,
   // and a token. Call MediaTestManager.started(token) if you start the test,
   // and MediaTestManager.finished(token) when the test finishes. You don't have
   // to start every test, but if you call started() you *must* call finish()
   // else you'll timeout.
@@ -1445,33 +1450,35 @@ function MediaTestManager() {
   }
 
   // Registers that the test corresponding to 'token' has been started.
   // Don't call more than once per token.
   this.started = function(token, handler) {
     this.tokens.push(token);
     this.numTestsRunning++;
     this.handlers[token] = handler;
-    is(this.numTestsRunning, this.tokens.length, "[started " + token + "] Length of array should match number of running tests");
+    is(this.numTestsRunning, this.tokens.length,
+       "[started " + token + " t=" + elapsedTime(this.startTime) + "] Length of array should match number of running tests");
   }
 
   // Registers that the test corresponding to 'token' has finished. Call when
   // you've finished your test. If all tests are complete this will finish the
   // run, otherwise it may start up the next run. It's ok to call multiple times
   // per token.
   this.finished = function(token) {
     var i = this.tokens.indexOf(token);
     if (i != -1) {
       // Remove the element from the list of running tests.
       this.tokens.splice(i, 1);
     }
 
     info("[finished " + token + "] remaining= " + this.tokens);
     this.numTestsRunning--;
-    is(this.numTestsRunning, this.tokens.length, "[finished " + token + "] Length of array should match number of running tests");
+    is(this.numTestsRunning, this.tokens.length,
+       "[finished " + token + " t=" + elapsedTime(this.startTime) + "] Length of array should match number of running tests");
     if (this.tokens.length < PARALLEL_TESTS) {
       this.nextTest();
     }
   }
 
   // Starts the next batch of tests, or finishes if they're all done.
   // Don't call this directly, call finished(token) when you're done.
   this.nextTest = function() {
@@ -1499,17 +1506,17 @@ function MediaTestManager() {
     {
       this.isShutdown = true;
       if (this.onFinished) {
         this.onFinished();
       }
       var onCleanup = function() {
         var end = new Date();
         SimpleTest.info("Finished at " + end + " (" + (end.getTime() / 1000) + "s)");
-        SimpleTest.info("Running time: " + (end.getTime() - this.startTime.getTime())/1000 + "s");
+        SimpleTest.info("Running time: " + elapsedTime(this.startTime) + "s");
         SimpleTest.finish();
       }.bind(this);
       mediaTestCleanup(onCleanup);
       return;
     }
   }
 }
 
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -385,16 +385,28 @@ function waitUntil(func, time) {
     }, time || 200);
   });
 }
 
 /** Time out while waiting for a promise to get resolved or rejected. */
 var timeout = (promise, time, msg) =>
   Promise.race([promise, wait(time).then(() => Promise.reject(new Error(msg)))]);
 
+/** Use event listener to call passed-in function on fire until it returns true */
+var listenUntil = (target, eventName, onFire) => {
+  return new Promise(resolve => target.addEventListener(eventName,
+                                                        function callback() {
+    var result = onFire();
+    if (result) {
+      target.removeEventListener(eventName, callback, false);
+      resolve(result);
+    }
+  }, false));
+};
+
 /*** Test control flow methods */
 
 /**
  * Generates a callback function fired only under unexpected circumstances
  * while running the tests. The generated function kills off the test as well
  * gracefully.
  *
  * @param {String} [message]
--- a/dom/media/tests/mochitest/mediaStreamPlayback.js
+++ b/dom/media/tests/mochitest/mediaStreamPlayback.js
@@ -1,19 +1,18 @@
 /* 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/. */
 
-const TIMEUPDATE_TIMEOUT_LENGTH = 10000;
 const ENDED_TIMEOUT_LENGTH = 30000;
 
-/* Time we wait for the canplaythrough event to fire
+/* The time we wait depends primarily on the canplaythrough event firing
  * Note: this needs to be at least 30s because the
  *       B2G emulator in VMs is really slow. */
-const CANPLAYTHROUGH_TIMEOUT_LENGTH = 60000;
+const VERIFYPLAYING_TIMEOUT_LENGTH = 60000;
 
 /**
  * This class manages playback of a HTMLMediaElement with a MediaStream.
  * When constructed by a caller, an object instance is created with
  * a media element and a media stream object.
  *
  * @param {HTMLMediaElement} mediaElement the media element for playback
  * @param {MediaStream} mediaStream the media stream used in
@@ -29,49 +28,56 @@ MediaStreamPlayback.prototype = {
   /**
    * Starts media with a media stream, runs it until a canplaythrough and
    * timeupdate event fires, and stops the media.
    *
    * @param {Boolean} isResume specifies if this media element is being resumed
    *                           from a previous run
    */
   playMedia : function(isResume) {
-    return this.startMedia(isResume)
+    this.startMedia(isResume);
+    return this.verifyPlaying()
       .then(() => this.stopMediaElement());
   },
 
   /**
    * Starts the media with the associated stream.
    *
    * @param {Boolean} isResume specifies if the media element playback
    *                           is being resumed from a previous run
    */
   startMedia : function(isResume) {
-    var canPlayThroughFired = false;
 
-    // If we're playing this media element for the first time,
-    // check that the time is zero.
+    // If we're playing media element for the first time, check that time is zero.
     if (!isResume) {
       is(this.mediaElement.currentTime, 0,
          "Before starting the media element, currentTime = 0");
     }
+    this.canPlayThroughFired = listenUntil(this.mediaElement, 'canplaythrough',
+                                           () => true);
 
-    return new Promise((resolve, reject) => {
-      /**
-       * Callback fired when the canplaythrough event is fired. We only
-       * run the logic of this function once, as this event can fire
-       * multiple times while a HTMLMediaStream is playing content from
-       * a real-time MediaStream.
-       */
-      var canPlayThroughCallback = () => {
-        // Disable the canplaythrough event listener to prevent multiple calls
-        canPlayThroughFired = true;
-        this.mediaElement.removeEventListener('canplaythrough',
-                                              canPlayThroughCallback, false);
+    // Hooks up the media stream to the media element and starts playing it
+    this.mediaElement.srcObject = this.mediaStream;
+    this.mediaElement.play();
+  },
 
+  /**
+   * Verifies that media is playing.
+   */
+  verifyPlaying : function() {
+    var lastStreamTime = this.mediaStream.currentTime;
+    var lastElementTime = this.mediaElement.currentTime;
+
+    var mediaTimeProgressed = listenUntil(this.mediaElement, 'timeupdate',
+        () => this.mediaStream.currentTime > lastStreamTime &&
+              this.mediaElement.currentTime > lastElementTime);
+
+    return timeout(Promise.all([this.canPlayThroughFired, mediaTimeProgressed]),
+                   VERIFYPLAYING_TIMEOUT_LENGTH, "verifyPlaying timed out")
+      .then(() => {
         is(this.mediaElement.paused, false,
            "Media element should be playing");
         is(this.mediaElement.duration, Number.POSITIVE_INFINITY,
            "Duration should be infinity");
 
         // When the media element is playing with a real-time stream, we
         // constantly switch between having data to play vs. queuing up data,
         // so we can only check that the ready state is one of those two values
@@ -88,55 +94,17 @@ MediaStreamPlayback.prototype = {
            "MediaElement is not seekable with MediaStream");
         ok(isNaN(this.mediaElement.startOffsetTime),
            "Start offset time shall not be a number");
         is(this.mediaElement.loop, false, "Loop shall be false");
         is(this.mediaElement.preload, "", "Preload should not exist");
         is(this.mediaElement.src, "", "No src should be defined");
         is(this.mediaElement.currentSrc, "",
            "Current src should still be an empty string");
-
-        var timeUpdateCallback = () => {
-          if (this.mediaStream.currentTime > 0 &&
-              this.mediaElement.currentTime > 0) {
-            this.mediaElement.removeEventListener('timeupdate',
-                                                  timeUpdateCallback, false);
-            resolve();
-          }
-        };
-
-        // When timeupdate fires, we validate time has passed and move
-        // onto the success condition
-        this.mediaElement.addEventListener('timeupdate', timeUpdateCallback,
-                                           false);
-
-        // If timeupdate doesn't fire in enough time, we fail the test
-        setTimeout(() => {
-          this.mediaElement.removeEventListener('timeupdate',
-                                                timeUpdateCallback, false);
-          reject(new Error("timeUpdate event never fired"));
-        }, TIMEUPDATE_TIMEOUT_LENGTH);
-      };
-
-      // Adds a listener intended to be fired when playback is available
-      // without further buffering.
-      this.mediaElement.addEventListener('canplaythrough', canPlayThroughCallback,
-                                         false);
-
-      // Hooks up the media stream to the media element and starts playing it
-      this.mediaElement.srcObject = this.mediaStream;
-      this.mediaElement.play();
-
-      // If canplaythrough doesn't fire in enough time, we fail the test
-      setTimeout(() => {
-        this.mediaElement.removeEventListener('canplaythrough',
-                                              canPlayThroughCallback, false);
-        reject(new Error("canplaythrough event never fired"));
-      }, CANPLAYTHROUGH_TIMEOUT_LENGTH);
-    });
+      });
   },
 
   /**
    * Stops the media with the associated stream.
    *
    * Precondition: The media stream and element should both be actively
    *               being played.
    */
@@ -167,17 +135,18 @@ LocalMediaStreamPlayback.prototype = Obj
    * Starts media element with a media stream, runs it until a canplaythrough
    * and timeupdate event fires, and calls stop() on all its tracks.
    *
    * @param {Boolean} isResume specifies if this media element is being resumed
    *                           from a previous run
    */
   playMediaWithMediaStreamTracksStop: {
     value: function(isResume) {
-      return this.startMedia(isResume)
+      this.startMedia(isResume);
+      return this.verifyPlaying()
         .then(() => this.stopTracksForStreamInMediaPlayback())
         .then(() => this.stopMediaElement());
     }
   },
 
   /**
    * Stops the local media stream's tracks while it's currently in playback in
    * a media element.
@@ -212,17 +181,18 @@ LocalMediaStreamPlayback.prototype = Obj
    * Starts media with a media stream, runs it until a canplaythrough and
    * timeupdate event fires, and calls stop() on the stream.
    *
    * @param {Boolean} isResume specifies if this media element is being resumed
    *                           from a previous run
    */
   playMediaWithDeprecatedStreamStop : {
     value: function(isResume) {
-      return this.startMedia(isResume)
+      this.startMedia(isResume);
+      return this.verifyPlaying()
         .then(() => this.deprecatedStopStreamInMediaPlayback())
         .then(() => this.stopMediaElement());
     }
   },
 
   /**
    * DEPRECATED - MediaStream.stop() is going away. Use MediaStreamTrack.stop()!
    *
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -38,16 +38,18 @@ skip-if = (toolkit == 'gonk' || buildapp
 [test_getUserMedia_basicAudio.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicVideo.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicVideo_playAfterLoadedmetadata.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicScreenshare.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # no screenshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
+[test_getUserMedia_basicTabshare.html]
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # no windowshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
 [test_getUserMedia_basicWindowshare.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # no windowshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
 [test_getUserMedia_basicVideoAudio.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure, turned an intermittent (bug 962579) into a permanant orange
 [test_getUserMedia_constraints.html]
 [test_getUserMedia_callbacks.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' || buildapp == 'mulet' # Bug 1063290, intermittent timeout # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
 [test_getUserMedia_gumWithinGum.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_basicTabshare.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    title: "getUserMedia Basic Tabshare Test",
+    bug: "1193075"
+  });
+  /**
+   * Run a test to verify that we can complete a start and stop media playback
+   * cycle for a tabshare LocalMediaStream on a video HTMLMediaElement.
+   *
+   * Additionally, exercise applyConstraints code for tabshare viewport offset.
+   */
+  runTest(function () {
+    const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
+    if (IsMacOSX10_6orOlder() || isWinXP) {
+        ok(true, "Screensharing disabled for OSX10.6 and WinXP");
+        return;
+    }
+    var testVideo = createMediaElement('video', 'testVideo');
+
+    return Promise.resolve()
+      .then(() => getUserMedia({ video: { mediaSource: "browser",
+                                          scrollWithPage: true } }))
+      .then(stream => {
+        var playback = new LocalMediaStreamPlayback(testVideo, stream);
+        return playback.playMediaWithDeprecatedStreamStop(false);
+      })
+      .then(() => getUserMedia({
+        video: {
+          mediaSource: "browser",
+          viewportOffsetX: 0,
+          viewportOffsetY: 0,
+          viewportWidth: 100,
+          viewportHeight: 100
+        }
+      }))
+      .then(stream => {
+        var playback = new LocalMediaStreamPlayback(testVideo, stream);
+        playback.startMedia(false);
+        return playback.verifyPlaying()
+          .then(() => Promise.all([
+            () => testVideo.srcObject.getVideoTracks()[0].applyConstraints({
+              mediaSource: "browser",
+              viewportOffsetX: 10,
+              viewportOffsetY: 50,
+              viewportWidth: 90,
+              viewportHeight: 50
+            }),
+            () => listenUntil(testVideo, "resize", () => true)
+          ]))
+          .then(() => playback.verifyPlaying()) // still playing
+          .then(() => playback.deprecatedStopStreamInMediaPlayback())
+          .then(() => playback.stopMediaElement());
+      });
+  });
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/tests/mochitest/test_getUserMedia_constraints.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_constraints.html
@@ -67,26 +67,27 @@ var tests = [
     error: null },
   { message: "legacy facingMode ignored",
     constraints: { video: { mandatory: { facingMode: 'left' } }, fake: true },
     error: null },
 ];
 
 var mustSupport = [
   'width', 'height', 'frameRate', 'facingMode', 'deviceId',
-// Yet to add:
-//  'aspectRatio', 'frameRate', 'volume', 'sampleRate', 'sampleSize',
-//  'echoCancellation', 'latency', 'groupId'
+  // Yet to add:
+  //  'aspectRatio', 'frameRate', 'volume', 'sampleRate', 'sampleSize',
+  //  'echoCancellation', 'latency', 'groupId'
 
   // http://fluffy.github.io/w3c-screen-share/#screen-based-video-constraints
   // OBE by http://w3c.github.io/mediacapture-screen-share
   'mediaSource',
 
   // Experimental https://bugzilla.mozilla.org/show_bug.cgi?id=1131568#c3
   'browserWindow', 'scrollWithPage',
+  'viewportOffsetX', 'viewportOffsetY', 'viewportWidth', 'viewportHeight'
 ];
 
 /**
  * Starts the test run by running through each constraint
  * test by verifying that the right resolution and rejection is fired.
  */
 
 runTest(function() {
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -26,19 +26,27 @@
 
 namespace mozilla {
 
 using namespace mozilla::gfx;
 
 NS_IMPL_ISUPPORTS(MediaEngineTabVideoSource, nsIDOMEventListener, nsITimerCallback)
 
 MediaEngineTabVideoSource::MediaEngineTabVideoSource()
-: mData(NULL), mDataSize(0), mMonitor("MediaEngineTabVideoSource"), mTabSource(nullptr)
-{
-}
+  : mBufWidthMax(0)
+  , mBufHeightMax(0)
+  , mWindowId(0)
+  , mScrollWithPage(false)
+  , mViewportOffsetX(0)
+  , mViewportOffsetY(0)
+  , mViewportWidth(0)
+  , mViewportHeight(0)
+  , mTimePerFrame(0)
+  , mDataSize(0)
+  , mMonitor("MediaEngineTabVideoSource") {}
 
 nsresult
 MediaEngineTabVideoSource::StartRunnable::Run()
 {
   mVideoSource->Draw();
   mVideoSource->mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   mVideoSource->mTimer->InitWithCallback(mVideoSource, mVideoSource->mTimePerFrame, nsITimer:: TYPE_REPEATING_SLACK);
   if (mVideoSource->mTabSource) {
@@ -118,33 +126,49 @@ MediaEngineTabVideoSource::GetUUID(nsACS
 #define DEFAULT_TABSHARE_VIDEO_MAX_HEIGHT 4096
 #define DEFAULT_TABSHARE_VIDEO_FRAMERATE 30
 
 nsresult
 MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
                                     const MediaEnginePrefs& aPrefs,
                                     const nsString& aDeviceId)
 {
-  // windowId and scrollWithPage are not proper constraints, so just read them.
-  // They have no well-defined behavior in advanced, so ignore them there.
+  // windowId is not a proper constraint, so just read it.
+  // It has no well-defined behavior in advanced, so ignore it there.
 
   mWindowId = aConstraints.mBrowserWindow.WasPassed() ?
               aConstraints.mBrowserWindow.Value() : -1;
+
+  return Restart(aConstraints, aPrefs, aDeviceId);
+}
+
+nsresult
+MediaEngineTabVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+                                   const mozilla::MediaEnginePrefs& aPrefs,
+                                   const nsString& aDeviceId)
+{
+  // scrollWithPage is not proper a constraint, so just read it.
+  // It has no well-defined behavior in advanced, so ignore it there.
+
   mScrollWithPage = aConstraints.mScrollWithPage.WasPassed() ?
-                    aConstraints.mScrollWithPage.Value() : true;
+                    aConstraints.mScrollWithPage.Value() : false;
 
   FlattenedConstraints c(aConstraints);
 
-  mBufWidthMax = c.mWidth.Clamp(c.mWidth.mIdeal.WasPassed() ?
-    c.mWidth.mIdeal.Value() : DEFAULT_TABSHARE_VIDEO_MAX_WIDTH);
-  mBufHeightMax = c.mHeight.Clamp(c.mHeight.mIdeal.WasPassed() ?
-    c.mHeight.mIdeal.Value() : DEFAULT_TABSHARE_VIDEO_MAX_HEIGHT);
-  double frameRate = c.mFrameRate.Clamp(c.mFrameRate.mIdeal.WasPassed() ?
-    c.mFrameRate.mIdeal.Value() : DEFAULT_TABSHARE_VIDEO_FRAMERATE);
+  mBufWidthMax = c.mWidth.Get(DEFAULT_TABSHARE_VIDEO_MAX_WIDTH);
+  mBufHeightMax = c.mHeight.Get(DEFAULT_TABSHARE_VIDEO_MAX_HEIGHT);
+  double frameRate = c.mFrameRate.Get(DEFAULT_TABSHARE_VIDEO_FRAMERATE);
   mTimePerFrame = std::max(10, int(1000.0 / (frameRate > 0? frameRate : 1)));
+
+  if (!mScrollWithPage) {
+    mViewportOffsetX = c.mViewportOffsetX.Get(0);
+    mViewportOffsetY = c.mViewportOffsetY.Get(0);
+    mViewportWidth = c.mViewportWidth.Get(INT32_MAX);
+    mViewportHeight = c.mViewportHeight.Get(INT32_MAX);
+  }
   return NS_OK;
 }
 
 nsresult
 MediaEngineTabVideoSource::Deallocate()
 {
   return NS_OK;
 }
@@ -187,85 +211,90 @@ MediaEngineTabVideoSource::NotifyPull(Me
 void
 MediaEngineTabVideoSource::Draw() {
   nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mWindow);
 
   if (!win) {
     return;
   }
 
-  int32_t innerWidth, innerHeight;
-  win->GetInnerWidth(&innerWidth);
-  win->GetInnerHeight(&innerHeight);
-
-  if (innerWidth == 0 || innerHeight == 0) {
+  if (mScrollWithPage || mViewportWidth == INT32_MAX) {
+    win->GetInnerWidth(&mViewportWidth);
+  }
+  if (mScrollWithPage || mViewportHeight == INT32_MAX) {
+    win->GetInnerHeight(&mViewportHeight);
+  }
+  if (!mViewportWidth || !mViewportHeight) {
     return;
   }
 
-  float pixelRatio;
-  win->GetDevicePixelRatio(&pixelRatio);
-  const int deviceInnerWidth = (int)(pixelRatio * innerWidth);
-  const int deviceInnerHeight = (int)(pixelRatio * innerHeight);
-
   IntSize size;
+  {
+    float pixelRatio;
+    win->GetDevicePixelRatio(&pixelRatio);
+    const int32_t deviceWidth = (int32_t)(pixelRatio * mViewportWidth);
+    const int32_t deviceHeight = (int32_t)(pixelRatio * mViewportHeight);
 
-  if ((deviceInnerWidth <= mBufWidthMax) && (deviceInnerHeight <= mBufHeightMax)) {
-    size = IntSize(deviceInnerWidth, deviceInnerHeight);
-  } else {
+    if ((deviceWidth <= mBufWidthMax) && (deviceHeight <= mBufHeightMax)) {
+      size = IntSize(deviceWidth, deviceHeight);
+    } else {
+      const float scaleWidth = (float)mBufWidthMax / (float)deviceWidth;
+      const float scaleHeight = (float)mBufHeightMax / (float)deviceHeight;
+      const float scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight;
 
-    const float scaleWidth = (float)mBufWidthMax / (float)deviceInnerWidth;
-    const float scaleHeight = (float)mBufHeightMax / (float)deviceInnerHeight;
-    const float scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight;
-
-    size = IntSize((int)(scale * deviceInnerWidth), (int)(scale * deviceInnerHeight));
+      size = IntSize((int)(scale * deviceWidth), (int)(scale * deviceHeight));
+    }
   }
 
   gfxImageFormat format = gfxImageFormat::RGB24;
   uint32_t stride = gfxASurface::FormatStrideForWidth(format, size.width);
 
   if (mDataSize < static_cast<size_t>(stride * size.height)) {
     mDataSize = stride * size.height;
     mData = static_cast<unsigned char*>(malloc(mDataSize));
   }
-
   if (!mData) {
     return;
   }
 
-  RefPtr<nsPresContext> presContext;
-  nsIDocShell* docshell = win->GetDocShell();
-  if (docshell) {
-    docshell->GetPresContext(getter_AddRefs(presContext));
-  }
-  if (!presContext) {
-    return;
+  nsCOMPtr<nsIPresShell> presShell;
+  {
+    RefPtr<nsPresContext> presContext;
+    nsIDocShell* docshell = win->GetDocShell();
+    if (docshell) {
+      docshell->GetPresContext(getter_AddRefs(presContext));
+    }
+    if (!presContext) {
+      return;
+    }
+    presShell = presContext->PresShell();
   }
 
   nscolor bgColor = NS_RGB(255, 255, 255);
-  nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
-  uint32_t renderDocFlags = 0;
-  if (!mScrollWithPage) {
-    renderDocFlags |= nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
-  }
-  nsRect r(0, 0, nsPresContext::CSSPixelsToAppUnits((float)innerWidth),
-           nsPresContext::CSSPixelsToAppUnits((float)innerHeight));
+  uint32_t renderDocFlags = mScrollWithPage? 0 :
+      (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
+       nsIPresShell::RENDER_DOCUMENT_RELATIVE);
+  nsRect r(nsPresContext::CSSPixelsToAppUnits((float)mViewportOffsetX),
+           nsPresContext::CSSPixelsToAppUnits((float)mViewportOffsetY),
+           nsPresContext::CSSPixelsToAppUnits((float)mViewportWidth),
+           nsPresContext::CSSPixelsToAppUnits((float)mViewportHeight));
 
   RefPtr<layers::ImageContainer> container = layers::LayerManager::CreateImageContainer();
   RefPtr<DrawTarget> dt =
     Factory::CreateDrawTargetForData(BackendType::CAIRO,
                                      mData.rwget(),
                                      size,
                                      stride,
                                      SurfaceFormat::B8G8R8X8);
   if (!dt) {
     return;
   }
   RefPtr<gfxContext> context = new gfxContext(dt);
-  context->SetMatrix(context->CurrentMatrix().Scale((((float) size.width)/innerWidth),
-                                                    (((float) size.height)/innerHeight)));
+  context->SetMatrix(context->CurrentMatrix().Scale((((float) size.width)/mViewportWidth),
+                                                    (((float) size.height)/mViewportHeight)));
 
   NS_ENSURE_SUCCESS_VOID(presShell->RenderDocument(r, renderDocFlags, bgColor, context));
 
   RefPtr<SourceSurface> surface = dt->Snapshot();
   if (!surface) {
     return;
   }
 
@@ -287,25 +316,16 @@ MediaEngineTabVideoSource::Stop(mozilla:
   if (!mWindow)
     return NS_OK;
 
   NS_DispatchToMainThread(new StopRunnable(this));
   return NS_OK;
 }
 
 nsresult
-MediaEngineTabVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
-                                   const mozilla::MediaEnginePrefs& aPrefs,
-                                   const nsString& aDeviceId)
-{
-  // TODO
-  return NS_OK;
-}
-
-nsresult
 MediaEngineTabVideoSource::Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t)
 {
   return NS_OK;
 }
 
 bool
 MediaEngineTabVideoSource::IsFake()
 {
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -71,21 +71,25 @@ class MediaEngineTabVideoSource : public
       NS_IMETHOD Run();
       RefPtr<MediaEngineTabVideoSource> mVideoSource;
     };
 
 protected:
     ~MediaEngineTabVideoSource() {}
 
 private:
-    int mBufWidthMax;
-    int mBufHeightMax;
+    int32_t mBufWidthMax;
+    int32_t mBufHeightMax;
     int64_t mWindowId;
     bool mScrollWithPage;
-    int mTimePerFrame;
+    int32_t mViewportOffsetX;
+    int32_t mViewportOffsetY;
+    int32_t mViewportWidth;
+    int32_t mViewportHeight;
+    int32_t mTimePerFrame;
     ScopedFreePtr<unsigned char> mData;
     size_t mDataSize;
     nsCOMPtr<nsIDOMWindow> mWindow;
     RefPtr<layers::CairoImage> mImage;
     nsCOMPtr<nsITimer> mTimer;
     Monitor mMonitor;
     nsCOMPtr<nsITabSource> mTabSource;
   };
--- a/dom/media/webrtc/MediaTrackConstraints.h
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -43,16 +43,19 @@ struct NormalizedConstraintSet
     ValueType mMin, mMax;
     dom::Optional<ValueType> mIdeal;
 
     Range(ValueType aMin, ValueType aMax) : mMin(aMin), mMax(aMax) {}
 
     template<class ConstrainRange>
     void SetFrom(const ConstrainRange& aOther);
     ValueType Clamp(ValueType n) const { return std::max(mMin, std::min(n, mMax)); }
+    ValueType Get(ValueType defaultValue) const {
+      return Clamp(mIdeal.WasPassed() ? mIdeal.Value() : defaultValue);
+    }
     bool Intersects(const Range& aOther) const {
       return mMax >= aOther.mMin && mMin <= aOther.mMax;
     }
     void Intersect(const Range& aOther) {
       MOZ_ASSERT(Intersects(aOther));
       mMin = std::max(mMin, aOther.mMin);
       mMax = std::min(mMax, aOther.mMax);
     }
@@ -67,22 +70,27 @@ struct NormalizedConstraintSet
   {
     DoubleRange(const dom::OwningDoubleOrConstrainDoubleRange& aOther,
                 bool advanced);
   };
 
   // Do you need to add your constraint here? Only if your code uses flattening
   LongRange mWidth, mHeight;
   DoubleRange mFrameRate;
+  LongRange mViewportOffsetX, mViewportOffsetY, mViewportWidth, mViewportHeight;
 
   NormalizedConstraintSet(const dom::MediaTrackConstraintSet& aOther,
                           bool advanced)
   : mWidth(aOther.mWidth, advanced)
   , mHeight(aOther.mHeight, advanced)
-  , mFrameRate(aOther.mFrameRate, advanced) {}
+  , mFrameRate(aOther.mFrameRate, advanced)
+  , mViewportOffsetX(aOther.mViewportOffsetX, advanced)
+  , mViewportOffsetY(aOther.mViewportOffsetY, advanced)
+  , mViewportWidth(aOther.mViewportWidth, advanced)
+  , mViewportHeight(aOther.mViewportHeight, advanced) {}
 };
 
 struct FlattenedConstraints : public NormalizedConstraintSet
 {
   explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther);
 };
 
 // A helper class for MediaEngines
--- a/dom/plugins/ipc/PluginProcessParent.cpp
+++ b/dom/plugins/ipc/PluginProcessParent.cpp
@@ -148,16 +148,19 @@ PluginProcessParent::Launch(mozilla::Uni
             selectedArchitecture = base::PROCESS_ARCH_I386;
         }
         else if (base::PROCESS_ARCH_PPC & pluginLibArchitectures & containerArchitectures) {
             selectedArchitecture = base::PROCESS_ARCH_PPC;
         }
         else if (base::PROCESS_ARCH_ARM & pluginLibArchitectures & containerArchitectures) {
           selectedArchitecture = base::PROCESS_ARCH_ARM;
         }
+        else if (base::PROCESS_ARCH_MIPS & pluginLibArchitectures & containerArchitectures) {
+          selectedArchitecture = base::PROCESS_ARCH_MIPS;
+        }
         else {
             return false;
         }
     }
 
     mLaunchCompleteTask = mozilla::Move(aLaunchCompleteTask);
 
     vector<string> args;
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -45,16 +45,20 @@ dictionary MediaTrackConstraintSet {
     ConstrainLong width;
     ConstrainLong height;
     ConstrainDouble frameRate;
     ConstrainDOMString facingMode;
     DOMString mediaSource = "camera";
     long long browserWindow;
     boolean scrollWithPage;
     ConstrainDOMString deviceId;
+    ConstrainLong viewportOffsetX;
+    ConstrainLong viewportOffsetY;
+    ConstrainLong viewportWidth;
+    ConstrainLong viewportHeight;
 };
 
 dictionary MediaTrackConstraints : MediaTrackConstraintSet {
     sequence<MediaTrackConstraintSet> advanced;
 };
 
 [Exposed=Window]
 interface MediaStreamTrack : EventTarget {
--- a/dom/webidl/MediaTrackSupportedConstraints.webidl
+++ b/dom/webidl/MediaTrackSupportedConstraints.webidl
@@ -24,12 +24,17 @@ dictionary MediaTrackSupportedConstraint
     // Mozilla-specific extensions:
 
     // http://fluffy.github.io/w3c-screen-share/#screen-based-video-constraints
     // OBE by http://w3c.github.io/mediacapture-screen-share
 
     boolean mediaSource = true;
 
     // Experimental https://bugzilla.mozilla.org/show_bug.cgi?id=1131568#c3
+    //              https://bugzilla.mozilla.org/show_bug.cgi?id=1193075
 
     boolean browserWindow = true;
     boolean scrollWithPage = true;
+    boolean viewportOffsetX = true;
+    boolean viewportOffsetY = true;
+    boolean viewportWidth = true;
+    boolean viewportHeight = true;
 };
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -128,17 +128,18 @@ private:
 /// where such module would disable the output, in all but gfxDebug cases,
 /// we will still send a printf.
 
 // The range is due to the values set in Histograms.json
 enum class LogReason : int {
   MustBeMoreThanThis = -1,
   // Start.  Do not insert, always add at end.  If you remove items,
   // make sure the other items retain their values.
-
+  D3D11InvalidCallDeviceRemoved = 0,
+  D3D11InvalidCall = 1,
   // End
   MustBeLessThanThis = 101,
 };
 
 struct BasicLogger
 {
   // For efficiency, this method exists and copies the logic of the
   // OutputMessage below.  If making any changes here, also make it
--- a/gfx/layers/client/TextureClientSharedSurface.cpp
+++ b/gfx/layers/client/TextureClientSharedSurface.cpp
@@ -22,20 +22,20 @@ using namespace mozilla::gl;
 
 namespace mozilla {
 namespace layers {
 
 SharedSurfaceTextureClient::SharedSurfaceTextureClient(ISurfaceAllocator* aAllocator,
                                                        TextureFlags aFlags,
                                                        UniquePtr<gl::SharedSurface> surf,
                                                        gl::SurfaceFactory* factory)
-  : TextureClient(aAllocator, aFlags | TextureFlags::RECYCLE)
+  : TextureClient(aAllocator,
+                  aFlags | TextureFlags::RECYCLE | surf->GetTextureFlags())
   , mSurf(Move(surf))
 {
-  AddFlags(mSurf->GetTextureFlags());
 }
 
 SharedSurfaceTextureClient::~SharedSurfaceTextureClient()
 {
   // Free the ShSurf implicitly.
 }
 
 gfx::IntSize
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -475,16 +475,20 @@ CompositorD3D11::CreateRenderTarget(cons
 
   if (aRect.width * aRect.height == 0) {
     return nullptr;
   }
 
   CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, aRect.width, aRect.height, 1, 1,
                              D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET);
 
+  if (mDevice != gfxWindowsPlatform::GetPlatform()->GetD3D11Device()) {
+    gfxCriticalError() << "Out of sync D3D11 devices in CreateRenderTarget";
+  }
+
   RefPtr<ID3D11Texture2D> texture;
   HRESULT hr = mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture));
   if (Failed(hr) || !texture) {
     gfxCriticalNote << "Failed in CreateRenderTarget " << hexa(hr);
     return nullptr;
   }
 
   RefPtr<CompositingRenderTargetD3D11> rt = new CompositingRenderTargetD3D11(texture, aRect.TopLeft());
@@ -508,16 +512,20 @@ CompositorD3D11::CreateRenderTargetFromS
   if (aRect.width * aRect.height == 0) {
     return nullptr;
   }
 
   CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM,
                              aRect.width, aRect.height, 1, 1,
                              D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET);
 
+  if (mDevice != gfxWindowsPlatform::GetPlatform()->GetD3D11Device()) {
+    gfxCriticalError() << "Out of sync D3D11 devices in CreateRenderTargetFromSource";
+  }
+
   RefPtr<ID3D11Texture2D> texture;
   HRESULT hr = mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture));
   NS_ASSERTION(texture, "Could not create texture");
   if (Failed(hr) || !texture) {
     gfxCriticalNote << "Failed in CreateRenderTargetFromSource " << hexa(hr);
     return nullptr;
   }
 
@@ -1010,30 +1018,33 @@ CompositorD3D11::DrawQuad(const gfx::Rec
 
 void
 CompositorD3D11::BeginFrame(const nsIntRegion& aInvalidRegion,
                             const Rect* aClipRectIn,
                             const Rect& aRenderBounds,
                             Rect* aClipRectOut,
                             Rect* aRenderBoundsOut)
 {
+  if (mDevice != gfxWindowsPlatform::GetPlatform()->GetD3D11Device()) {
+    gfxCriticalError() << "Out of sync D3D11 devices in BeginFrame";
+  }
+
   // Don't composite if we are minimised. Other than for the sake of efficency,
   // this is important because resizing our buffers when mimised will fail and
   // cause a crash when we're restored.
   NS_ASSERTION(mHwnd, "Couldn't find an HWND when initialising?");
   if (::IsIconic(mHwnd) || mDevice->GetDeviceRemovedReason() != S_OK) {
     *aRenderBoundsOut = Rect();
     return;
   }
 
   IntSize oldSize = mSize;
-  UpdateRenderTarget();
 
   // Failed to create a render target or the view.
-  if (!mDefaultRT || !mDefaultRT->mRTView ||
+  if (!UpdateRenderTarget() || !mDefaultRT || !mDefaultRT->mRTView ||
       mSize.width <= 0 || mSize.height <= 0) {
     *aRenderBoundsOut = Rect();
     return;
   }
 
   mContext->IASetInputLayout(mAttachments->mInputLayout);
 
   ID3D11Buffer* buffer = mAttachments->mVertexBuffer;
@@ -1135,17 +1146,20 @@ CompositorD3D11::EndFrame()
         rects[i].bottom = r->YMost();
         rects[i].right = r->XMost();
         i++;
       }
 
       params.pDirtyRects = params.DirtyRectsCount ? rects.data() : nullptr;
       chain->Present1(presentInterval, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0, &params);
     } else {
-      mSwapChain->Present(presentInterval, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0);
+      hr = mSwapChain->Present(presentInterval, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0);
+      if (Failed(hr)) {
+        gfxCriticalNote << "D3D11 swap chain preset failed " << hexa(hr);
+      }
     }
     mDisableSequenceForNextFrame = false;
     if (mTarget) {
       PaintToTarget();
     }
   }
 
   mCurrentRT = nullptr;
@@ -1219,57 +1233,66 @@ CompositorD3D11::VerifyBufferSize()
     }
     MOZ_ASSERT(mDefaultRT->hasOneRef());
     mDefaultRT = nullptr;
   }
 
   hr = mSwapChain->ResizeBuffers(1, mSize.width, mSize.height,
                                  DXGI_FORMAT_B8G8R8A8_UNORM,
                                  0);
+  if (Failed(hr)) {
+    gfxCriticalNote << "D3D11 swap resize buffers failed " << hexa(hr) << " on " << mSize;
+  }
 
   return Succeeded(hr);
 }
 
-void
+bool
 CompositorD3D11::UpdateRenderTarget()
 {
+  if (mDevice != gfxWindowsPlatform::GetPlatform()->GetD3D11Device()) {
+    gfxCriticalError() << "Out of sync D3D11 devices in UpdateRenderTarget";
+  }
+
   EnsureSize();
   if (!VerifyBufferSize()) {
     gfxCriticalNote << "Failed VerifyBufferSize in UpdateRenderTarget " << mSize;
-    return;
+    return false;
   }
 
   if (mDefaultRT) {
-    return;
+    return true;
   }
 
   if (mSize.width <= 0 || mSize.height <= 0) {
     gfxCriticalNote << "Invalid size in UpdateRenderTarget " << mSize;
-    return;
+    return false;
   }
 
   HRESULT hr;
 
   RefPtr<ID3D11Texture2D> backBuf;
 
   hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)backBuf.StartAssignment());
   if (hr == DXGI_ERROR_INVALID_CALL) {
     // This happens on some GPUs/drivers when there's a TDR.
     if (mDevice->GetDeviceRemovedReason() != S_OK) {
       gfxCriticalError() << "GetBuffer returned invalid call!";
-      return;
+      return false;
     }
   }
   if (Failed(hr)) {
     gfxCriticalNote << "Failed in UpdateRenderTarget " << hexa(hr);
-    return;
+    return false;
   }
 
   mDefaultRT = new CompositingRenderTargetD3D11(backBuf, IntPoint(0, 0));
   mDefaultRT->SetSize(mSize);
+
+  return true;
 }
 
 bool
 DeviceAttachmentsD3D11::InitSyncObject()
 {
   // Sync object is not supported on WARP.
   if (gfxWindowsPlatform::GetPlatform()->IsWARP()) {
     return true;
@@ -1277,16 +1300,20 @@ DeviceAttachmentsD3D11::InitSyncObject()
 
   // It's okay to do this on Windows 8. But for now we'll just bail
   // whenever we're using WARP.
   CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, 1, 1, 1, 1,
                              D3D11_BIND_SHADER_RESOURCE |
                              D3D11_BIND_RENDER_TARGET);
   desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
 
+  if (mDevice != gfxWindowsPlatform::GetPlatform()->GetD3D11Device()) {
+    gfxCriticalError() << "Out of sync D3D11 devices in InitSyncObject";
+  }
+
   RefPtr<ID3D11Texture2D> texture;
   HRESULT hr = mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture));
   if (Failed(hr, "create sync texture")) {
     return false;
   }
 
   hr = texture->QueryInterface((IDXGIResource**)getter_AddRefs(mSyncTexture));
   if (Failed(hr, "QI sync texture")) {
@@ -1405,16 +1432,20 @@ CompositorD3D11::PaintToTarget()
   CD3D11_TEXTURE2D_DESC softDesc(bbDesc.Format, bbDesc.Width, bbDesc.Height);
   softDesc.MipLevels = 1;
   softDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
   softDesc.Usage = D3D11_USAGE_STAGING;
   softDesc.BindFlags = 0;
 
   RefPtr<ID3D11Texture2D> readTexture;
 
+  if (mDevice != gfxWindowsPlatform::GetPlatform()->GetD3D11Device()) {
+    gfxCriticalError() << "Out of sync D3D11 devices in PaintToTarget";
+  }
+
   hr = mDevice->CreateTexture2D(&softDesc, nullptr, getter_AddRefs(readTexture));
   if (Failed(hr)) {
     gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false)) << "Failed in PaintToTarget 2";
     return;
   }
   mContext->CopyResource(readTexture, backBuf);
 
   D3D11_MAPPED_SUBRESOURCE map;
@@ -1452,32 +1483,42 @@ CompositorD3D11::HandleError(HRESULT hr,
   if (SUCCEEDED(hr)) {
     return;
   }
 
   if (aSeverity == Critical) {
     MOZ_CRASH("Unrecoverable D3D11 error");
   }
 
+  if (mDevice != gfxWindowsPlatform::GetPlatform()->GetD3D11Device()) {
+    gfxCriticalError() << "Out of sync D3D11 devices in HandleError";
+  }
+
+  HRESULT hrOnReset = S_OK;
   bool deviceRemoved = hr == DXGI_ERROR_DEVICE_REMOVED;
 
   if (deviceRemoved && mDevice) {
-    hr = mDevice->GetDeviceRemovedReason();
+    hrOnReset = mDevice->GetDeviceRemovedReason();
+  } else if (hr == DXGI_ERROR_INVALID_CALL && mDevice) {
+    hrOnReset = mDevice->GetDeviceRemovedReason();
+    if (hrOnReset != S_OK) {
+      deviceRemoved = true;
+    }
   }
 
   // Device reset may not be an error on our side, but can mess things up so
   // it's useful to see it in the reports.
   gfxCriticalError(CriticalLog::DefaultOptions(!deviceRemoved))
     << (deviceRemoved ? "[CompositorD3D11] device removed with error code: "
                       : "[CompositorD3D11] error code: ")
-    << hexa(hr);
+    << hexa(hr) << ", " << hexa(hrOnReset);
 
-  // Always crash if we are making invalid calls
+  // Crash if we are making invalid calls outside of device removal
   if (hr == DXGI_ERROR_INVALID_CALL) {
-    MOZ_CRASH("Invalid D3D11 api call");
+    gfxCrash(deviceRemoved ? LogReason::D3D11InvalidCallDeviceRemoved : LogReason::D3D11InvalidCall) << "Invalid D3D11 api call";
   }
 
   if (aSeverity == Recoverable) {
     NS_WARNING("Encountered a recoverable D3D11 error");
   }
 }
 
 bool
--- a/gfx/layers/d3d11/CompositorD3D11.h
+++ b/gfx/layers/d3d11/CompositorD3D11.h
@@ -162,17 +162,17 @@ private:
 
   // Same as Failed(), except the severity is critical (with no abort) and
   // a string prefix must be provided.
   bool Failed(HRESULT hr, const char* aContext);
 
   // ensure mSize is up to date with respect to mWidget
   void EnsureSize();
   bool VerifyBufferSize();
-  void UpdateRenderTarget();
+  bool UpdateRenderTarget();
   bool UpdateConstantBuffers();
   void SetSamplerForFilter(gfx::Filter aFilter);
   void SetPSForEffect(Effect *aEffect, MaskType aMaskType, gfx::SurfaceFormat aFormat);
   void PaintToTarget();
 
   virtual gfx::IntSize GetWidgetSize() const override { return mSize; }
 
   RefPtr<ID3D11DeviceContext> mContext;
--- a/ipc/chromium/src/base/process_util.h
+++ b/ipc/chromium/src/base/process_util.h
@@ -66,30 +66,33 @@ struct kinfo_proc;
 
 namespace base {
 
 // These can be used in a 32-bit bitmask.
 enum ProcessArchitecture {
   PROCESS_ARCH_I386 = 0x1,
   PROCESS_ARCH_X86_64 = 0x2,
   PROCESS_ARCH_PPC = 0x4,
-  PROCESS_ARCH_ARM = 0x8
+  PROCESS_ARCH_ARM = 0x8,
+  PROCESS_ARCH_MIPS = 0x10
 };
 
 inline ProcessArchitecture GetCurrentProcessArchitecture()
 {
   base::ProcessArchitecture currentArchitecture;
 #if defined(ARCH_CPU_X86)
   currentArchitecture = base::PROCESS_ARCH_I386;
 #elif defined(ARCH_CPU_X86_64)
   currentArchitecture = base::PROCESS_ARCH_X86_64;
 #elif defined(ARCH_CPU_PPC)
   currentArchitecture = base::PROCESS_ARCH_PPC;
 #elif defined(ARCH_CPU_ARMEL)
   currentArchitecture = base::PROCESS_ARCH_ARM;
+#elif defined(ARCH_CPU_MIPS)
+  currentArchitecture = base::PROCESS_ARCH_MIPS;
 #endif
   return currentArchitecture;
 }
 
 // A minimalistic but hopefully cross-platform set of exit codes.
 // Do not change the enumeration values or you will break third-party
 // installers.
 enum {
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -37,22 +37,21 @@ using mozilla::Maybe;
 using mozilla::PodAssign;
 using mozilla::PodCopy;
 using mozilla::PodZero;
 using mozilla::UniquePtr;
 
 struct KeywordInfo {
     const char* chars;         // C string with keyword text
     TokenKind   tokentype;
-    JSVersion   version;
 };
 
 static const KeywordInfo keywords[] = {
-#define KEYWORD_INFO(keyword, name, type, version) \
-    {js_##keyword##_str, type, version},
+#define KEYWORD_INFO(keyword, name, type) \
+    {js_##keyword##_str, type},
     FOR_EACH_JAVASCRIPT_KEYWORD(KEYWORD_INFO)
 #undef KEYWORD_INFO
 };
 
 // Returns a KeywordInfo for the specified characters, or nullptr if the string
 // is not a keyword.
 template <typename CharT>
 static const KeywordInfo*
@@ -992,34 +991,31 @@ TokenStream::checkForKeyword(const Keywo
         || kw->tokentype == TOK_EXTENDS
         || kw->tokentype == TOK_SUPER
 #endif
         )
     {
         return reportError(JSMSG_RESERVED_ID, kw->chars);
     }
 
-    if (kw->tokentype != TOK_STRICT_RESERVED) {
-        if (kw->version <= versionNumber()) {
-            // Treat 'let' as an identifier and contextually a keyword in
-            // sloppy mode. It is always a keyword in strict mode.
-            if (kw->tokentype == TOK_LET && !strictMode())
-                return true;
+    if (kw->tokentype == TOK_STRICT_RESERVED)
+        return reportStrictModeError(JSMSG_RESERVED_ID, kw->chars);
 
-            // Working keyword.
-            if (ttp) {
-                *ttp = kw->tokentype;
-                return true;
-            }
-            return reportError(JSMSG_RESERVED_ID, kw->chars);
-        }
+    // Treat 'let' as an identifier and contextually a keyword in sloppy mode.
+    // It is always a keyword in strict mode.
+    if (kw->tokentype == TOK_LET && !strictMode())
+        return true;
+
+    // Working keyword.
+    if (ttp) {
+        *ttp = kw->tokentype;
+        return true;
     }
 
-    // Strict reserved word.
-    return reportStrictModeError(JSMSG_RESERVED_ID, kw->chars);
+    return reportError(JSMSG_RESERVED_ID, kw->chars);
 }
 
 bool
 TokenStream::checkForKeyword(JSAtom* atom, TokenKind* ttp)
 {
     const KeywordInfo* kw = FindKeyword(atom);
     if (!kw)
         return true;
--- a/js/src/jit-test/tests/asm.js/sta-transition.js
+++ b/js/src/jit-test/tests/asm.js/sta-transition.js
@@ -1,17 +1,17 @@
 // Transitional test cases, useful while Odin accepts both
 // "Int32Array" and "SharedInt32Array" to construct a view onto shared
 // memory but the former only when an atomic operation is referenced,
 // as per spec.  Eventually it will stop accepting "SharedInt32Array",
 // because that name is going away.
 //
 // These should not run with --no-asmjs.
 
-if (!isAsmJSCompilationAvailable())
+if (!this.SharedArrayBuffer || !isAsmJSCompilationAvailable())
     quit(0);
 
 //////////////////////////////////////////////////////////////////////
 //
 // Int8Array can be used on SharedArrayBuffer, if atomics are present
 
 function m1(stdlib, ffi, heap) {
     "use asm";
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -487,18 +487,18 @@ class MacroAssembler : public MacroAssem
     void call(JitCode* c) PER_SHARED_ARCH;
 
     inline void call(const CallSiteDesc& desc, const Register reg);
     inline void call(const CallSiteDesc& desc, Label* label);
 
     // Push the return address and make a call. On platforms where this function
     // is not defined, push the link register (pushReturnAddress) at the entry
     // point of the callee.
-    void callAndPushReturnAddress(Register reg) DEFINED_ON(mips32, x86_shared);
-    void callAndPushReturnAddress(Label* label) DEFINED_ON(mips32, x86_shared);
+    void callAndPushReturnAddress(Register reg) DEFINED_ON(mips_shared, x86_shared);
+    void callAndPushReturnAddress(Label* label) DEFINED_ON(mips_shared, x86_shared);
 
     void pushReturnAddress() DEFINED_ON(arm, arm64);
 
   public:
     // ===============================================================
     // ABI function calls.
 
     // Setup a call to C/C++ code, given the assumption that the framePushed
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -106,33 +106,60 @@ CodeGeneratorMIPSShared::visitTestIAndBr
     MBasicBlock* ifFalse = test->ifFalse();
 
     emitBranch(ToRegister(opd), Imm32(0), Assembler::NonZero, ifTrue, ifFalse);
 }
 
 void
 CodeGeneratorMIPSShared::visitCompare(LCompare* comp)
 {
-    Assembler::Condition cond = JSOpToCondition(comp->mir()->compareType(), comp->jsop());
+    MCompare* mir = comp->mir();
+    Assembler::Condition cond = JSOpToCondition(mir->compareType(), comp->jsop());
     const LAllocation* left = comp->getOperand(0);
     const LAllocation* right = comp->getOperand(1);
     const LDefinition* def = comp->getDef(0);
 
+#ifdef JS_CODEGEN_MIPS64
+    if (mir->compareType() == MCompare::Compare_Object) {
+        if (right->isGeneralReg())
+            masm.cmpPtrSet(cond, ToRegister(left), ToRegister(right), ToRegister(def));
+        else
+            masm.cmpPtrSet(cond, ToRegister(left), ToAddress(right), ToRegister(def));
+        return;
+    }
+#endif
+
     if (right->isConstant())
         masm.cmp32Set(cond, ToRegister(left), Imm32(ToInt32(right)), ToRegister(def));
     else if (right->isGeneralReg())
         masm.cmp32Set(cond, ToRegister(left), ToRegister(right), ToRegister(def));
     else
         masm.cmp32Set(cond, ToRegister(left), ToAddress(right), ToRegister(def));
 }
 
 void
 CodeGeneratorMIPSShared::visitCompareAndBranch(LCompareAndBranch* comp)
 {
-    Assembler::Condition cond = JSOpToCondition(comp->cmpMir()->compareType(), comp->jsop());
+    MCompare* mir = comp->cmpMir();
+    Assembler::Condition cond = JSOpToCondition(mir->compareType(), comp->jsop());
+
+#ifdef JS_CODEGEN_MIPS64
+    if (mir->compareType() == MCompare::Compare_Object) {
+        if (comp->right()->isGeneralReg()) {
+            emitBranch(ToRegister(comp->left()), ToRegister(comp->right()), cond,
+                       comp->ifTrue(), comp->ifFalse());
+        } else {
+            masm.loadPtr(ToAddress(comp->right()), ScratchRegister);
+            emitBranch(ToRegister(comp->left()), ScratchRegister, cond,
+                       comp->ifTrue(), comp->ifFalse());
+        }
+        return;
+    }
+#endif
+
     if (comp->right()->isConstant()) {
         emitBranch(ToRegister(comp->left()), Imm32(ToInt32(comp->right())), cond,
                    comp->ifTrue(), comp->ifFalse());
     } else if (comp->right()->isGeneralReg()) {
         emitBranch(ToRegister(comp->left()), ToRegister(comp->right()), cond,
                    comp->ifTrue(), comp->ifFalse());
     } else {
         masm.load32(ToAddress(comp->right()), ScratchRegister);
@@ -194,26 +221,16 @@ void
 CodeGeneratorMIPSShared::bailout(LSnapshot* snapshot)
 {
     Label label;
     masm.jump(&label);
     bailoutFrom(&label, snapshot);
 }
 
 void
-CodeGeneratorMIPSShared::visitOutOfLineBailout(OutOfLineBailout* ool)
-{
-    // Push snapshotOffset and make sure stack is aligned.
-    masm.subPtr(Imm32(2 * sizeof(void*)), StackPointer);
-    masm.storePtr(ImmWord(ool->snapshot()->snapshotOffset()), Address(StackPointer, 0));
-
-    masm.jump(&deoptLabel_);
-}
-
-void
 CodeGeneratorMIPSShared::visitMinMaxD(LMinMaxD* ins)
 {
     FloatRegister first = ToFloatRegister(ins->first());
     FloatRegister second = ToFloatRegister(ins->second());
     FloatRegister output = ToFloatRegister(ins->output());
 
     MOZ_ASSERT(first == output);
 
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.h
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.h
@@ -170,17 +170,17 @@ class CodeGeneratorMIPSShared : public C
     virtual void visitCeil(LCeil* lir);
     virtual void visitCeilF(LCeilF* lir);
     virtual void visitRound(LRound* lir);
     virtual void visitRoundF(LRoundF* lir);
     virtual void visitTruncateDToInt32(LTruncateDToInt32* ins);
     virtual void visitTruncateFToInt32(LTruncateFToInt32* ins);
 
     // Out of line visitors.
-    void visitOutOfLineBailout(OutOfLineBailout* ool);
+    virtual void visitOutOfLineBailout(OutOfLineBailout* ool) = 0;
   protected:
     virtual ValueOperand ToOutValue(LInstruction* ins) = 0;
     void memoryBarrier(MemoryBarrierBits barrier);
 
   public:
     CodeGeneratorMIPSShared(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm);
 
     void visitValue(LValue* value);
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp
@@ -893,16 +893,34 @@ void
 MacroAssembler::call(JitCode* c)
 {
     BufferOffset bo = m_buffer.nextOffset();
     addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE);
     ma_liPatchable(ScratchRegister, ImmPtr(c->raw()));
     callJitNoProfiler(ScratchRegister);
 }
 
+void
+MacroAssembler::callAndPushReturnAddress(Register callee)
+{
+    // Push return address during jalr delay slot.
+    subPtr(Imm32(sizeof(intptr_t)), StackPointer);
+    as_jalr(callee);
+    storePtr(ra, Address(StackPointer, 0));
+}
+
+void
+MacroAssembler::callAndPushReturnAddress(Label* label)
+{
+    // Push return address during bal delay slot.
+    subPtr(Imm32(sizeof(intptr_t)), StackPointer);
+    ma_bal(label, DontFillDelaySlot);
+    storePtr(ra, Address(StackPointer, 0));
+}
+
 // ===============================================================
 // Jit Frames.
 
 uint32_t
 MacroAssembler::pushFakeReturnAddress(Register scratch)
 {
     CodeLabel cl;
 
--- a/js/src/jit/mips32/CodeGenerator-mips32.cpp
+++ b/js/src/jit/mips32/CodeGenerator-mips32.cpp
@@ -42,16 +42,26 @@ class js::jit::OutOfLineTableSwitch : pu
     }
 
     CodeLabel* jumpLabel() {
         return &jumpLabel_;
     }
 };
 
 void
+CodeGeneratorMIPS::visitOutOfLineBailout(OutOfLineBailout* ool)
+{
+    // Push snapshotOffset and make sure stack is aligned.
+    masm.subPtr(Imm32(2 * sizeof(void*)), StackPointer);
+    masm.storePtr(ImmWord(ool->snapshot()->snapshotOffset()), Address(StackPointer, 0));
+
+    masm.jump(&deoptLabel_);
+}
+
+void
 CodeGeneratorMIPS::visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool)
 {
     MTableSwitch* mir = ool->mir();
 
     masm.haltingAlign(sizeof(void*));
     masm.bind(ool->jumpLabel()->src());
     masm.addCodeLabel(*ool->jumpLabel());
 
--- a/js/src/jit/mips32/CodeGenerator-mips32.h
+++ b/js/src/jit/mips32/CodeGenerator-mips32.h
@@ -35,16 +35,17 @@ class CodeGeneratorMIPS : public CodeGen
 
   public:
     virtual void visitCompareB(LCompareB* lir);
     virtual void visitCompareBAndBranch(LCompareBAndBranch* lir);
     virtual void visitCompareBitwise(LCompareBitwise* lir);
     virtual void visitCompareBitwiseAndBranch(LCompareBitwiseAndBranch* lir);
 
     // Out of line visitors.
+    void visitOutOfLineBailout(OutOfLineBailout* ool);
     void visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool);
   protected:
     ValueOperand ToValue(LInstruction* ins, size_t pos);
     ValueOperand ToOutValue(LInstruction* ins);
     ValueOperand ToTempValue(LInstruction* ins, size_t pos);
 
     // Functions for LTestVAndBranch.
     Register splitTagForTest(const ValueOperand& value);
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -2558,37 +2558,16 @@ void
 MacroAssembler::reserveStack(uint32_t amount)
 {
     if (amount)
         subPtr(Imm32(amount), StackPointer);
     adjustFrame(amount);
 }
 
 // ===============================================================
-// Simple call functions.
-
-void
-MacroAssembler::callAndPushReturnAddress(Register callee)
-{
-    // Push return address during jalr delay slot.
-    subPtr(Imm32(sizeof(intptr_t)), StackPointer);
-    as_jalr(callee);
-    storePtr(ra, Address(StackPointer, 0));
-}
-
-void
-MacroAssembler::callAndPushReturnAddress(Label* label)
-{
-    // Push return address during bal delay slot.
-    subPtr(Imm32(sizeof(intptr_t)), StackPointer);
-    ma_bal(label, DontFillDelaySlot);
-    storePtr(ra, Address(StackPointer, 0));
-}
-
-// ===============================================================
 // ABI function calls.
 
 void
 MacroAssembler::setupUnalignedABICall(Register scratch)
 {
     setupABICall();
     dynamicAlignment_ = true;
 
--- a/js/src/jit/mips64/Bailouts-mips64.cpp
+++ b/js/src/jit/mips64/Bailouts-mips64.cpp
@@ -9,17 +9,17 @@
 #include "jscntxt.h"
 #include "jscompartment.h"
 
 using namespace js;
 using namespace js::jit;
 
 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
                                    BailoutStack* bailout)
-  : machine_(bailout->machine())
+  : machine_(bailout->machineState())
 {
     uint8_t* sp = bailout->parentStackPointer();
     framePointer_ = sp + bailout->frameSize();
     topFrameSize_ = framePointer_ - sp;
 
     JSScript* script = ScriptFromCalleeToken(((JitFrameLayout*) framePointer_)->calleeToken());
     topIonScript_ = script->ionScript();
 
--- a/js/src/jit/mips64/Bailouts-mips64.h
+++ b/js/src/jit/mips64/Bailouts-mips64.h
@@ -10,68 +10,35 @@
 #include "jit/Bailouts.h"
 #include "jit/JitCompartment.h"
 
 namespace js {
 namespace jit {
 
 class BailoutStack
 {
-    uintptr_t frameClassId_;
-    // This is pushed in the bailout handler. Both entry points into the
-    // handler inserts their own value int lr, which is then placed onto the
-    // stack along with frameClassId_ above. This should be migrated to ip.
-  public:
-    union {
-        uintptr_t frameSize_;
-        uintptr_t tableOffset_;
-    };
-
-  protected:
     RegisterDump::FPUArray fpregs_;
     RegisterDump::GPRArray regs_;
-
+    uintptr_t frameSize_;
     uintptr_t snapshotOffset_;
-    uintptr_t padding_;
 
   public:
-    FrameSizeClass frameClass() const {
-        return FrameSizeClass::FromClass(frameClassId_);
+    MachineState machineState() {
+        return MachineState::FromBailout(regs_, fpregs_);
     }
-    uintptr_t tableOffset() const {
-        MOZ_ASSERT(frameClass() != FrameSizeClass::None());
-        return tableOffset_;
+    uint32_t snapshotOffset() const {
+        return snapshotOffset_;
     }
     uint32_t frameSize() const {
-        if (frameClass() == FrameSizeClass::None())
-            return frameSize_;
-        return frameClass().frameSize();
-    }
-    MachineState machine() {
-        return MachineState::FromBailout(regs_, fpregs_);
+        return frameSize_;
     }
-    SnapshotOffset snapshotOffset() const {
-        MOZ_ASSERT(frameClass() == FrameSizeClass::None());
-        return snapshotOffset_;
-    }
-    uint8_t* parentStackPointer() const {
-        if (frameClass() == FrameSizeClass::None())
-            return (uint8_t*)this + sizeof(BailoutStack);
-        return (uint8_t*)this + offsetof(BailoutStack, snapshotOffset_);
-    }
-    static size_t offsetOfFrameClass() {
-        return offsetof(BailoutStack, frameClassId_);
+    uint8_t* parentStackPointer() {
+        return (uint8_t*)this + sizeof(BailoutStack);
     }
     static size_t offsetOfFrameSize() {
         return offsetof(BailoutStack, frameSize_);
     }
-    static size_t offsetOfFpRegs() {
-        return offsetof(BailoutStack, fpregs_);
-    }
-    static size_t offsetOfRegs() {
-        return offsetof(BailoutStack, regs_);
-    }
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_mips64_Bailouts_mips64_h */
new file mode 100644
--- /dev/null
+++ b/js/src/jit/mips64/CodeGenerator-mips64.cpp
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/mips64/CodeGenerator-mips64.h"
+
+#include "mozilla/MathAlgorithms.h"
+
+#include "jit/CodeGenerator.h"
+#include "jit/JitCompartment.h"
+#include "jit/JitFrames.h"
+#include "jit/MIR.h"
+#include "jit/MIRGraph.h"
+#include "js/Conversions.h"
+#include "vm/Shape.h"
+#include "vm/TraceLogging.h"
+
+#include "jit/MacroAssembler-inl.h"
+#include "jit/shared/CodeGenerator-shared-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+class js::jit::OutOfLineTableSwitch : public OutOfLineCodeBase<CodeGeneratorMIPS64>
+{
+    MTableSwitch* mir_;
+    CodeLabel jumpLabel_;
+
+    void accept(CodeGeneratorMIPS64* codegen) {
+        codegen->visitOutOfLineTableSwitch(this);
+    }
+
+  public:
+    OutOfLineTableSwitch(MTableSwitch* mir)
+      : mir_(mir)
+    {}
+
+    MTableSwitch* mir() const {
+        return mir_;
+    }
+
+    CodeLabel* jumpLabel() {
+        return &jumpLabel_;
+    }
+};
+
+void
+CodeGeneratorMIPS64::visitOutOfLineBailout(OutOfLineBailout* ool)
+{
+    masm.push(ImmWord(ool->snapshot()->snapshotOffset()));
+
+    masm.jump(&deoptLabel_);
+}
+
+void
+CodeGeneratorMIPS64::visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool)
+{
+    MTableSwitch* mir = ool->mir();
+
+    masm.haltingAlign(sizeof(void*));
+    masm.bind(ool->jumpLabel()->src());
+    masm.addCodeLabel(*ool->jumpLabel());
+
+    for (size_t i = 0; i < mir->numCases(); i++) {
+        LBlock* caseblock = skipTrivialBlocks(mir->getCase(i))->lir();
+        Label* caseheader = caseblock->label();
+        uint32_t caseoffset = caseheader->offset();
+
+        // The entries of the jump table need to be absolute addresses and thus
+        // must be patched after codegen is finished. Each table entry uses 8
+        // instructions (4 for load address, 2 for branch, and 2 padding).
+        CodeLabel cl;
+        masm.ma_li(ScratchRegister, cl.dest());
+        masm.branch(ScratchRegister);
+        masm.as_nop();
+        masm.as_nop();
+        cl.src()->bind(caseoffset);
+        masm.addCodeLabel(cl);
+    }
+}
+
+void
+CodeGeneratorMIPS64::emitTableSwitchDispatch(MTableSwitch* mir, Register index,
+                                             Register address)
+{
+    Label* defaultcase = skipTrivialBlocks(mir->getDefault())->lir()->label();
+
+    // Lower value with low value
+    if (mir->low() != 0)
+        masm.subPtr(Imm32(mir->low()), index);
+
+    // Jump to default case if input is out of range
+    int32_t cases = mir->numCases();
+    masm.branch32(Assembler::AboveOrEqual, index, Imm32(cases), defaultcase);
+
+    // To fill in the CodeLabels for the case entries, we need to first
+    // generate the case entries (we don't yet know their offsets in the
+    // instruction stream).
+    OutOfLineTableSwitch* ool = new(alloc()) OutOfLineTableSwitch(mir);
+    addOutOfLineCode(ool, mir);
+
+    // Compute the position where a pointer to the right case stands.
+    masm.ma_li(address, ool->jumpLabel()->dest());
+    // index = size of table entry * index.
+    // See CodeGeneratorMIPS64::visitOutOfLineTableSwitch
+    masm.lshiftPtr(Imm32(5), index);
+    masm.addPtr(index, address);
+
+    masm.branch(address);
+}
+
+FrameSizeClass
+FrameSizeClass::FromDepth(uint32_t frameDepth)
+{
+    return FrameSizeClass::None();
+}
+
+FrameSizeClass
+FrameSizeClass::ClassLimit()
+{
+    return FrameSizeClass(0);
+}
+
+uint32_t
+FrameSizeClass::frameSize() const
+{
+    MOZ_CRASH("MIPS64 does not use frame size classes");
+}
+
+ValueOperand
+CodeGeneratorMIPS64::ToValue(LInstruction* ins, size_t pos)
+{
+    return ValueOperand(ToRegister(ins->getOperand(pos)));
+}
+
+ValueOperand
+CodeGeneratorMIPS64::ToOutValue(LInstruction* ins)
+{
+    return ValueOperand(ToRegister(ins->getDef(0)));
+}
+
+ValueOperand
+CodeGeneratorMIPS64::ToTempValue(LInstruction* ins, size_t pos)
+{
+    return ValueOperand(ToRegister(ins->getTemp(pos)));
+}
+
+void
+CodeGeneratorMIPS64::visitBox(LBox* box)
+{
+    const LAllocation* in = box->getOperand(0);
+    const LDefinition* result = box->getDef(0);
+
+    if (IsFloatingPointType(box->type())) {
+        FloatRegister reg = ToFloatRegister(in);
+        if (box->type() == MIRType_Float32) {
+            masm.convertFloat32ToDouble(reg, ScratchDoubleReg);
+            reg = ScratchDoubleReg;
+        }
+        masm.moveFromDouble(reg, ToRegister(result));
+    } else {
+        masm.boxValue(ValueTypeFromMIRType(box->type()), ToRegister(in), ToRegister(result));
+    }
+}
+
+void
+CodeGeneratorMIPS64::visitUnbox(LUnbox* unbox)
+{
+    MUnbox* mir = unbox->mir();
+
+    if (mir->fallible()) {
+        const ValueOperand value = ToValue(unbox, LUnbox::Input);
+        masm.splitTag(value, SecondScratchReg);
+        bailoutCmp32(Assembler::NotEqual, SecondScratchReg, Imm32(MIRTypeToTag(mir->type())),
+                     unbox->snapshot());
+    }
+
+    Operand input = ToOperand(unbox->getOperand(LUnbox::Input));
+    Register result = ToRegister(unbox->output());
+    switch (mir->type()) {
+      case MIRType_Int32:
+        masm.unboxInt32(input, result);
+        break;
+      case MIRType_Boolean:
+        masm.unboxBoolean(input, result);
+        break;
+      case MIRType_Object:
+        masm.unboxObject(input, result);
+        break;
+      case MIRType_String:
+        masm.unboxString(input, result);
+        break;
+      case MIRType_Symbol:
+        masm.unboxSymbol(input, result);
+        break;
+      default:
+        MOZ_CRASH("Given MIRType cannot be unboxed.");
+    }
+}
+
+Register
+CodeGeneratorMIPS64::splitTagForTest(const ValueOperand& value)
+{
+    MOZ_ASSERT(value.valueReg() != SecondScratchReg);
+    masm.splitTag(value.valueReg(), SecondScratchReg);
+    return SecondScratchReg;
+}
+
+void
+CodeGeneratorMIPS64::visitCompareB(LCompareB* lir)
+{
+    MCompare* mir = lir->mir();
+
+    const ValueOperand lhs = ToValue(lir, LCompareB::Lhs);
+    const LAllocation* rhs = lir->rhs();
+    const Register output = ToRegister(lir->output());
+
+    MOZ_ASSERT(mir->jsop() == JSOP_STRICTEQ || mir->jsop() == JSOP_STRICTNE);
+    Assembler::Condition cond = JSOpToCondition(mir->compareType(), mir->jsop());
+
+    // Load boxed boolean in ScratchRegister.
+    if (rhs->isConstant())
+        masm.moveValue(*rhs->toConstant(), ScratchRegister);
+    else
+        masm.boxValue(JSVAL_TYPE_BOOLEAN, ToRegister(rhs), ScratchRegister);
+
+    // Perform the comparison.
+    masm.cmpPtrSet(cond, lhs.valueReg(), ScratchRegister, output);
+}
+
+void
+CodeGeneratorMIPS64::visitCompareBAndBranch(LCompareBAndBranch* lir)
+{
+    MCompare* mir = lir->cmpMir();
+    const ValueOperand lhs = ToValue(lir, LCompareBAndBranch::Lhs);
+    const LAllocation* rhs = lir->rhs();
+
+    MOZ_ASSERT(mir->jsop() == JSOP_STRICTEQ || mir->jsop() == JSOP_STRICTNE);
+
+    // Load boxed boolean in ScratchRegister.
+    if (rhs->isConstant())
+        masm.moveValue(*rhs->toConstant(), ScratchRegister);
+    else
+        masm.boxValue(JSVAL_TYPE_BOOLEAN, ToRegister(rhs), ScratchRegister);
+
+    // Perform the comparison.
+    Assembler::Condition cond = JSOpToCondition(mir->compareType(), mir->jsop());
+    emitBranch(lhs.valueReg(), ScratchRegister, cond, lir->ifTrue(), lir->ifFalse());
+}
+
+void
+CodeGeneratorMIPS64::visitCompareBitwise(LCompareBitwise* lir)
+{
+    MCompare* mir = lir->mir();
+    Assembler::Condition cond = JSOpToCondition(mir->compareType(), mir->jsop());
+    const ValueOperand lhs = ToValue(lir, LCompareBitwise::LhsInput);
+    const ValueOperand rhs = ToValue(lir, LCompareBitwise::RhsInput);
+    const Register output = ToRegister(lir->output());
+
+    MOZ_ASSERT(IsEqualityOp(mir->jsop()));
+
+    masm.cmpPtrSet(cond, lhs.valueReg(), rhs.valueReg(), output);
+}
+
+void
+CodeGeneratorMIPS64::visitCompareBitwiseAndBranch(LCompareBitwiseAndBranch* lir)
+{
+    MCompare* mir = lir->cmpMir();
+    Assembler::Condition cond = JSOpToCondition(mir->compareType(), mir->jsop());
+    const ValueOperand lhs = ToValue(lir, LCompareBitwiseAndBranch::LhsInput);
+    const ValueOperand rhs = ToValue(lir, LCompareBitwiseAndBranch::RhsInput);
+
+    MOZ_ASSERT(mir->jsop() == JSOP_EQ || mir->jsop() == JSOP_STRICTEQ ||
+               mir->jsop() == JSOP_NE || mir->jsop() == JSOP_STRICTNE);
+
+    emitBranch(lhs.valueReg(), rhs.valueReg(), cond, lir->ifTrue(), lir->ifFalse());
+}
+
+void
+CodeGeneratorMIPS64::setReturnDoubleRegs(LiveRegisterSet* regs)
+{
+    MOZ_ASSERT(ReturnFloat32Reg.reg_ == FloatRegisters::f0);
+    MOZ_ASSERT(ReturnDoubleReg.reg_ == FloatRegisters::f0);
+    FloatRegister f1 = { FloatRegisters::f1, FloatRegisters::Single };
+    regs->add(ReturnFloat32Reg);
+    regs->add(f1);
+    regs->add(ReturnDoubleReg);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit/mips64/CodeGenerator-mips64.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef jit_mips64_CodeGenerator_mips64_h
+#define jit_mips64_CodeGenerator_mips64_h
+
+#include "jit/mips-shared/CodeGenerator-mips-shared.h"
+
+namespace js {
+namespace jit {
+
+class CodeGeneratorMIPS64 : public CodeGeneratorMIPSShared
+{
+  protected:
+    void testNullEmitBranch(Assembler::Condition cond, const ValueOperand& value,
+                            MBasicBlock* ifTrue, MBasicBlock* ifFalse)
+    {
+        MOZ_ASSERT(value.valueReg() != SecondScratchReg);
+        masm.splitTag(value.valueReg(), SecondScratchReg);
+        emitBranch(SecondScratchReg, ImmTag(JSVAL_TAG_NULL), cond, ifTrue, ifFalse);
+    }
+    void testUndefinedEmitBranch(Assembler::Condition cond, const ValueOperand& value,
+                                 MBasicBlock* ifTrue, MBasicBlock* ifFalse)
+    {
+        MOZ_ASSERT(value.valueReg() != SecondScratchReg);
+        masm.splitTag(value.valueReg(), SecondScratchReg);
+        emitBranch(SecondScratchReg, ImmTag(JSVAL_TAG_UNDEFINED), cond, ifTrue, ifFalse);
+    }
+    void testObjectEmitBranch(Assembler::Condition cond, const ValueOperand& value,
+                              MBasicBlock* ifTrue, MBasicBlock* ifFalse)
+    {
+        MOZ_ASSERT(value.valueReg() != SecondScratchReg);
+        masm.splitTag(value.valueReg(), SecondScratchReg);
+        emitBranch(SecondScratchReg, ImmTag(JSVAL_TAG_OBJECT), cond, ifTrue, ifFalse);
+    }
+
+    void emitTableSwitchDispatch(MTableSwitch* mir, Register index, Register base);
+
+  public:
+    virtual void visitCompareB(LCompareB* lir);
+    virtual void visitCompareBAndBranch(LCompareBAndBranch* lir);
+    virtual void visitCompareBitwise(LCompareBitwise* lir);
+    virtual void visitCompareBitwiseAndBranch(LCompareBitwiseAndBranch* lir);
+
+    // Out of line visitors.
+    void visitOutOfLineBailout(OutOfLineBailout* ool);
+    void visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool);
+  protected:
+    ValueOperand ToValue(LInstruction* ins, size_t pos);
+    ValueOperand ToOutValue(LInstruction* ins);
+    ValueOperand ToTempValue(LInstruction* ins, size_t pos);
+
+    // Functions for LTestVAndBranch.
+    Register splitTagForTest(const ValueOperand& value);
+
+  public:
+    CodeGeneratorMIPS64(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm)
+      : CodeGeneratorMIPSShared(gen, graph, masm)
+    { }
+
+  public:
+    void visitBox(LBox* box);
+    void visitUnbox(LUnbox* unbox);
+
+    void setReturnDoubleRegs(LiveRegisterSet* regs);
+};
+
+typedef CodeGeneratorMIPS64 CodeGeneratorSpecific;
+
+} // namespace jit
+} // namespace js
+
+#endif /* jit_mips64_CodeGenerator_mips64_h */
--- a/js/src/jit/mips64/Trampoline-mips64.cpp
+++ b/js/src/jit/mips64/Trampoline-mips64.cpp
@@ -568,54 +568,54 @@ JitRuntime::generateArgumentsRectifier(J
  * Frame size is stored in $ra (look at
  * CodeGeneratorMIPS64::generateOutOfLineCode()) and thunk code should save it
  * on stack. Other difference is that members snapshotOffset_ and padding_ are
  * pushed to the stack by CodeGeneratorMIPS64::visitOutOfLineBailout(). Field
  * frameClassId_ is forced to be NO_FRAME_SIZE_CLASS_ID
  * (See: JitRuntime::generateBailoutHandler).
  */
 static void
-PushBailoutFrame(MacroAssembler& masm, uint32_t frameClass, Register spArg)
+PushBailoutFrame(MacroAssembler& masm, Register spArg)
 {
-    // Make sure that alignment is proper.
-    masm.checkStackAlignment();
+    // Push the frameSize_ stored in ra
+    // See: CodeGeneratorMIPS64::generateOutOfLineCode()
+    masm.push(ra);
 
     // Push registers such that we can access them from [base + code].
     masm.PushRegsInMask(AllRegs);
 
-    // Push the frameSize_ or tableOffset_ stored in ra
-    // See: CodeGeneratorMIPS64::generateOutOfLineCode()
-    masm.push(ra);
-
-    // Push frame class to stack
-    masm.push(ImmWord(frameClass));
-
     // Put pointer to BailoutStack as first argument to the Bailout()
     masm.movePtr(StackPointer, spArg);
 }
 
 static void
 GenerateBailoutThunk(JSContext* cx, MacroAssembler& masm, uint32_t frameClass)
 {
-    PushBailoutFrame(masm, frameClass, a0);
+    PushBailoutFrame(masm, a0);
 
     // Put pointer to BailoutInfo
     static const uint32_t sizeOfBailoutInfo = sizeof(uintptr_t) * 2;
     masm.subPtr(Imm32(sizeOfBailoutInfo), StackPointer);
-    masm.storePtr(ImmPtr(nullptr), Address(StackPointer, 0));
     masm.movePtr(StackPointer, a1);
 
     masm.setupAlignedABICall();
     masm.passABIArg(a0);
     masm.passABIArg(a1);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, Bailout));
 
     // Get BailoutInfo pointer
     masm.loadPtr(Address(StackPointer, 0), a2);
 
+    // Stack is:
+    //     [frame]
+    //     snapshotOffset
+    //     frameSize
+    //     [bailoutFrame]
+    //     [bailoutInfo]
+    //
     // Remove both the bailout frame and the topmost Ion frame's stack.
     // Load frameSize from stack
     masm.loadPtr(Address(StackPointer,
                          sizeOfBailoutInfo + BailoutStack::offsetOfFrameSize()), a1);
     // Remove complete BailoutStack class and data after it
     masm.addPtr(Imm32(sizeof(BailoutStack) + sizeOfBailoutInfo), StackPointer);
     // Remove frame size srom stack
     masm.addPtr(a1, StackPointer);
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -776,17 +776,18 @@ class AsmJSHeapAccess
     {
         mozilla::PodZero(this);  // zero padding for Valgrind
         insnOffset_ = insnOffset;
         offsetWithinWholeSimdVector_ = offsetWithinWholeSimdVector;
         throwOnOOB_ = oob == Throw;
         cmpDelta_ = cmp == NoLengthCheck ? 0 : insnOffset - cmp;
         MOZ_ASSERT(offsetWithinWholeSimdVector_ == offsetWithinWholeSimdVector);
     }
-#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS32)
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
+      defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     explicit AsmJSHeapAccess(uint32_t insnOffset)
     {
         mozilla::PodZero(this);  // zero padding for Valgrind
         insnOffset_ = insnOffset;
     }
 #endif
 
     uint32_t insnOffset() const { return insnOffset_; }
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -1592,18 +1592,18 @@ CodeGeneratorShared::jumpToBlock(MBasicB
         masm.bind(&rejoin);
 
         masm.propagateOOM(patchableBackedges_.append(PatchableBackedgeInfo(backedge, mir->lir()->label(), oolEntry)));
     } else {
         masm.jump(mir->lir()->label());
     }
 }
 
-// This function is not used for MIPS. MIPS has branchToBlock.
-#ifndef JS_CODEGEN_MIPS32
+// This function is not used for MIPS/MIPS64. MIPS has branchToBlock.
+#if !defined(JS_CODEGEN_MIPS32) && !defined(JS_CODEGEN_MIPS64)
 void
 CodeGeneratorShared::jumpToBlock(MBasicBlock* mir, Assembler::Condition cond)
 {
     // Skip past trivial blocks.
     mir = skipTrivialBlocks(mir);
 
     if (Label* oolEntry = labelForBackedgeWithImplicitCheck(mir)) {
         // Note: the backedge is initially a jump to the next instruction.
--- a/js/src/jit/shared/CodeGenerator-shared.h
+++ b/js/src/jit/shared/CodeGenerator-shared.h
@@ -466,17 +466,17 @@ class CodeGeneratorShared : public LElem
 
     // Generate a jump to the start of the specified block, adding information
     // if this is a loop backedge. Use this in place of jumping directly to
     // mir->lir()->label(), or use getJumpLabelForBranch() if a label to use
     // directly is needed.
     void jumpToBlock(MBasicBlock* mir);
 
 // This function is not used for MIPS. MIPS has branchToBlock.
-#ifndef JS_CODEGEN_MIPS32
+#if !defined(JS_CODEGEN_MIPS32) && !defined(JS_CODEGEN_MIPS64)
     void jumpToBlock(MBasicBlock* mir, Assembler::Condition cond);
 #endif
 
   private:
     void generateInvalidateEpilogue();
 
   public:
     CodeGeneratorShared(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm);
--- a/js/src/jit/shared/Lowering-shared-inl.h
+++ b/js/src/jit/shared/Lowering-shared-inl.h
@@ -185,17 +185,17 @@ LIRGeneratorShared::defineSinCos(LInstru
     uint32_t vreg = getVirtualRegister();
     lir->setDef(0, LDefinition(vreg, LDefinition::DOUBLE, LFloatReg(ReturnDoubleReg)));
 #if defined(JS_CODEGEN_ARM)
     lir->setDef(1, LDefinition(vreg + VREG_INCREMENT, LDefinition::DOUBLE,
                 LFloatReg(FloatRegister(FloatRegisters::d1, FloatRegister::Double))));
 #elif defined(JS_CODEGEN_ARM64)
     lir->setDef(1, LDefinition(vreg + VREG_INCREMENT, LDefinition::DOUBLE,
                 LFloatReg(FloatRegister(FloatRegisters::d1, FloatRegisters::Double))));
-#elif defined(JS_CODEGEN_MIPS32)
+#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     lir->setDef(1, LDefinition(vreg + VREG_INCREMENT, LDefinition::DOUBLE, LFloatReg(f2)));
 #elif defined(JS_CODEGEN_NONE)
     MOZ_CRASH();
 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
     lir->setDef(1, LDefinition(vreg + VREG_INCREMENT, LDefinition::DOUBLE, LFloatReg(xmm1)));
 #else
 #error "Unsupported architecture for SinCos"
 #endif
--- a/js/src/jskwgen.cpp
+++ b/js/src/jskwgen.cpp
@@ -9,17 +9,17 @@
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "vm/Keywords.h"
 
 static const char * const keyword_list[] = {
-#define KEYWORD_STRING(keyword, name, type, version) #keyword,
+#define KEYWORD_STRING(keyword, name, type) #keyword,
     FOR_EACH_JAVASCRIPT_KEYWORD(KEYWORD_STRING)
 #undef KEYWORD_STRING
 };
 
 struct gen_opt {
     FILE* output;                       /* output file for generated source */
     unsigned use_if_threshold;          /* max number of choices to generate
                                            "if" selector instead of "switch" */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -510,16 +510,17 @@ elif CONFIG['JS_CODEGEN_MIPS32'] or CONF
             ]
     elif CONFIG['JS_CODEGEN_MIPS64']:
         UNIFIED_SOURCES += [
             'jit/mips64/Architecture-mips64.cpp',
             'jit/mips64/Assembler-mips64.cpp',
             'jit/mips64/Bailouts-mips64.cpp',
             'jit/mips64/BaselineCompiler-mips64.cpp',
             'jit/mips64/BaselineIC-mips64.cpp',
+            'jit/mips64/CodeGenerator-mips64.cpp',
             'jit/mips64/Lowering-mips64.cpp',
             'jit/mips64/SharedIC-mips64.cpp',
             'jit/mips64/Trampoline-mips64.cpp',
         ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     SOURCES += [
         'jit/ExecutableAllocatorWin.cpp',
--- a/js/src/vm/Keywords.h
+++ b/js/src/vm/Keywords.h
@@ -5,63 +5,63 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* A higher-order macro for enumerating keyword tokens. */
 
 #ifndef vm_Keywords_h
 #define vm_Keywords_h
 
 #define FOR_EACH_JAVASCRIPT_KEYWORD(macro) \
-    macro(false, false_, TOK_FALSE, JSVERSION_DEFAULT) \
-    macro(true, true_, TOK_TRUE, JSVERSION_DEFAULT) \
-    macro(null, null, TOK_NULL, JSVERSION_DEFAULT) \
+    macro(false, false_, TOK_FALSE) \
+    macro(true, true_, TOK_TRUE) \
+    macro(null, null, TOK_NULL) \
     /* Keywords. */ \
-    macro(break, break_, TOK_BREAK, JSVERSION_DEFAULT) \
-    macro(case, case_, TOK_CASE, JSVERSION_DEFAULT) \
-    macro(catch, catch_, TOK_CATCH, JSVERSION_DEFAULT) \
-    macro(const, const_, TOK_CONST, JSVERSION_DEFAULT) \
-    macro(continue, continue_, TOK_CONTINUE, JSVERSION_DEFAULT) \
-    macro(debugger, debugger, TOK_DEBUGGER, JSVERSION_DEFAULT) \
-    macro(default, default_, TOK_DEFAULT, JSVERSION_DEFAULT) \
-    macro(delete, delete_, TOK_DELETE, JSVERSION_DEFAULT) \
-    macro(do, do_, TOK_DO, JSVERSION_DEFAULT) \
-    macro(else, else_, TOK_ELSE, JSVERSION_DEFAULT) \
-    macro(finally, finally_, TOK_FINALLY, JSVERSION_DEFAULT) \
-    macro(for, for_, TOK_FOR, JSVERSION_DEFAULT) \
-    macro(function, function, TOK_FUNCTION, JSVERSION_DEFAULT) \
-    macro(if, if_, TOK_IF, JSVERSION_DEFAULT) \
-    macro(in, in, TOK_IN, JSVERSION_DEFAULT) \
-    macro(instanceof, instanceof, TOK_INSTANCEOF, JSVERSION_DEFAULT) \
-    macro(new, new_, TOK_NEW, JSVERSION_DEFAULT) \
-    macro(return, return_, TOK_RETURN, JSVERSION_DEFAULT) \
-    macro(switch, switch_, TOK_SWITCH, JSVERSION_DEFAULT) \
-    macro(this, this_, TOK_THIS, JSVERSION_DEFAULT) \
-    macro(throw, throw_, TOK_THROW, JSVERSION_DEFAULT) \
-    macro(try, try_, TOK_TRY, JSVERSION_DEFAULT) \
-    macro(typeof, typeof, TOK_TYPEOF, JSVERSION_DEFAULT) \
-    macro(var, var, TOK_VAR, JSVERSION_DEFAULT) \
-    macro(void, void_, TOK_VOID, JSVERSION_DEFAULT) \
-    macro(while, while_, TOK_WHILE, JSVERSION_DEFAULT) \
-    macro(with, with, TOK_WITH, JSVERSION_DEFAULT) \
-    macro(import, import, TOK_IMPORT, JSVERSION_DEFAULT) \
-    macro(export, export, TOK_EXPORT, JSVERSION_DEFAULT) \
-    macro(class, class_, TOK_CLASS, JSVERSION_DEFAULT) \
-    macro(extends, extends, TOK_EXTENDS, JSVERSION_DEFAULT) \
-    macro(super, super, TOK_SUPER, JSVERSION_DEFAULT) \
+    macro(break, break_, TOK_BREAK) \
+    macro(case, case_, TOK_CASE) \
+    macro(catch, catch_, TOK_CATCH) \
+    macro(const, const_, TOK_CONST) \
+    macro(continue, continue_, TOK_CONTINUE) \
+    macro(debugger, debugger, TOK_DEBUGGER) \
+    macro(default, default_, TOK_DEFAULT) \
+    macro(delete, delete_, TOK_DELETE) \
+    macro(do, do_, TOK_DO) \
+    macro(else, else_, TOK_ELSE) \
+    macro(finally, finally_, TOK_FINALLY) \
+    macro(for, for_, TOK_FOR) \
+    macro(function, function, TOK_FUNCTION) \
+    macro(if, if_, TOK_IF) \
+    macro(in, in, TOK_IN) \
+    macro(instanceof, instanceof, TOK_INSTANCEOF) \
+    macro(new, new_, TOK_NEW) \
+    macro(return, return_, TOK_RETURN) \
+    macro(switch, switch_, TOK_SWITCH) \
+    macro(this, this_, TOK_THIS) \
+    macro(throw, throw_, TOK_THROW) \
+    macro(try, try_, TOK_TRY) \
+    macro(typeof, typeof, TOK_TYPEOF) \
+    macro(var, var, TOK_VAR) \
+    macro(void, void_, TOK_VOID) \
+    macro(while, while_, TOK_WHILE) \
+    macro(with, with, TOK_WITH) \
+    macro(import, import, TOK_IMPORT) \
+    macro(export, export, TOK_EXPORT) \
+    macro(class, class_, TOK_CLASS) \
+    macro(extends, extends, TOK_EXTENDS) \
+    macro(super, super, TOK_SUPER) \
     /* Reserved keywords. */ \
-    macro(enum, enum_, TOK_RESERVED, JSVERSION_DEFAULT) \
+    macro(enum, enum_, TOK_RESERVED) \
     /* Future reserved keywords, but only in strict mode. */ \
-    macro(implements, implements, TOK_STRICT_RESERVED, JSVERSION_DEFAULT) \
-    macro(interface, interface, TOK_STRICT_RESERVED, JSVERSION_DEFAULT) \
-    macro(package, package, TOK_STRICT_RESERVED, JSVERSION_DEFAULT) \
-    macro(private, private_, TOK_STRICT_RESERVED, JSVERSION_DEFAULT) \
-    macro(protected, protected_, TOK_STRICT_RESERVED, JSVERSION_DEFAULT) \
-    macro(public, public_, TOK_STRICT_RESERVED, JSVERSION_DEFAULT) \
-    macro(static, static_, TOK_STRICT_RESERVED, JSVERSION_DEFAULT) \
+    macro(implements, implements, TOK_STRICT_RESERVED) \
+    macro(interface, interface, TOK_STRICT_RESERVED) \
+    macro(package, package, TOK_STRICT_RESERVED) \
+    macro(private, private_, TOK_STRICT_RESERVED) \
+    macro(protected, protected_, TOK_STRICT_RESERVED) \
+    macro(public, public_, TOK_STRICT_RESERVED) \
+    macro(static, static_, TOK_STRICT_RESERVED) \
     /* \
      * Yield is a token inside function*.  Outside of a function*, it is a \
      * future reserved keyword in strict mode, but a keyword in JS1.7 even \
      * when strict.  Punt logic to parser. \
      */ \
-    macro(yield, yield, TOK_YIELD, JSVERSION_DEFAULT) \
-    macro(let, let, TOK_LET, JSVERSION_DEFAULT)
+    macro(yield, yield, TOK_YIELD) \
+    macro(let, let, TOK_LET)
 
 #endif /* vm_Keywords_h */
--- a/js/src/vm/Printer.cpp
+++ b/js/src/vm/Printer.cpp
@@ -380,23 +380,25 @@ QuoteString(ExclusiveContext* cx, JSStri
         return nullptr;
     char* bytes = QuoteString(&sprinter, str, quote);
     if (!bytes)
         return nullptr;
     return NewStringCopyZ<CanGC>(cx, bytes);
 }
 
 Fprinter::Fprinter(FILE* fp)
-  : file_(nullptr)
+  : file_(nullptr),
+    init_(false)
 {
     init(fp);
 }
 
 Fprinter::Fprinter()
-  : file_(nullptr)
+  : file_(nullptr),
+    init_(false)
 { }
 
 Fprinter::~Fprinter()
 {
     MOZ_ASSERT_IF(init_, !file_);
 }
 
 bool
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -60,19 +60,16 @@ public:
     mPresContext = nullptr;
   }
 
   nsPresContext* PresContext() const {
     MOZ_ASSERT(mPresContext);
     return mPresContext;
   }
 
-  nsCSSFrameConstructor* FrameConstructor() const
-    { return PresContext()->FrameConstructor(); }
-
   // Should be called when a frame is going to be destroyed and
   // WillDestroyFrameTree hasn't been called yet.
   void NotifyDestroyingFrame(nsIFrame* aFrame);
 
   // Forwarded nsIDocumentObserver method, to handle restyling (and
   // passing the notification to the frame).
   nsresult ContentStateChanged(nsIContent*   aContent,
                                EventStates aStateMask);
@@ -133,16 +130,19 @@ public:
    */
   nsresult ReparentStyleContext(nsIFrame* aFrame);
 
   void ClearSelectors() {
     mPendingRestyles.ClearSelectors();
   }
 
 private:
+  nsCSSFrameConstructor* FrameConstructor() const
+    { return PresContext()->FrameConstructor(); }
+
   // Used when restyling an element with a frame.
   void ComputeAndProcessStyleChange(nsIFrame*              aFrame,
                                     nsChangeHint           aMinChange,
                                     RestyleTracker&        aRestyleTracker,
                                     nsRestyleHint          aRestyleHint,
                                     const RestyleHintData& aRestyleHintData);
   // Used when restyling a display:contents element.
   void ComputeAndProcessStyleChange(nsStyleContext*        aNewContext,
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -5083,44 +5083,36 @@ nsDisplayTransform::GetResultingTransfor
     // Correct the translation components for zoom:
     float pixelsPerCSSPx = frame->PresContext()->AppUnitsPerCSSPixel() /
                              aAppUnitsPerPixel;
     svgTransform._31 *= pixelsPerCSSPx;
     svgTransform._32 *= pixelsPerCSSPx;
     result = Matrix4x4::From2D(svgTransform);
   }
 
-  if (aProperties.mChildPerspective > 0.0) {
-    Matrix4x4 perspective;
-    perspective._34 =
-      -1.0 / NSAppUnitsToFloatPixels(aProperties.mChildPerspective, aAppUnitsPerPixel);
-    /* At the point when perspective is applied, we have been translated to the transform origin.
-     * The translation to the perspective origin is the difference between these values.
-     */
-    perspective.ChangeBasis(aProperties.GetToPerspectiveOrigin() - aProperties.mToTransformOrigin);
-    result = result * perspective;
-  }
-
   /* Account for the transform-origin property by translating the
    * coordinate space to the new origin.
    */
   Point3D newOrigin =
     Point3D(NSAppUnitsToFloatPixels(aOrigin.x, aAppUnitsPerPixel),
             NSAppUnitsToFloatPixels(aOrigin.y, aAppUnitsPerPixel),
             0.0f);
   Point3D roundedOrigin(hasSVGTransforms ? newOrigin.x : NS_round(newOrigin.x),
                         hasSVGTransforms ? newOrigin.y : NS_round(newOrigin.y),
                         0);
 
+  bool hasPerspective = aProperties.mChildPerspective > 0.0;
+
   if (!hasSVGTransforms || !hasTransformFromSVGParent) {
     // This is a simplification of the following |else| block, the
     // simplification being possible because we don't need to apply
     // mToTransformOrigin between two transforms.
     Point3D offsets = roundedOrigin + aProperties.mToTransformOrigin;
-    if (aOffsetByOrigin) {
+    if (aOffsetByOrigin &&
+        !hasPerspective) {
       // We can fold the final translation by roundedOrigin into the first matrix
       // basis change translation. This is more stable against variation due to
       // insufficient floating point precision than reversing the translation
       // afterwards.
       result.PreTranslate(-aProperties.mToTransformOrigin);
       result.PostTranslate(offsets);
     } else {
       result.ChangeBasis(offsets);
@@ -5144,24 +5136,38 @@ nsDisplayTransform::GetResultingTransfor
     transformFromSVGParent._31 *= pixelsPerCSSPx;
     transformFromSVGParent._32 *= pixelsPerCSSPx;
     result = result * Matrix4x4::From2D(transformFromSVGParent);
 
     // Similar to the code in the |if| block above, but since we've accounted
     // for mToTransformOrigin so we don't include that. We also need to reapply
     // refBoxOffset.
     Point3D offsets = roundedOrigin + refBoxOffset;
-    if (aOffsetByOrigin) {
+    if (aOffsetByOrigin &&
+        !hasPerspective) {
       result.PreTranslate(-refBoxOffset);
       result.PostTranslate(offsets);
     } else {
       result.ChangeBasis(offsets);
     }
   }
 
+  if (hasPerspective) {
+    Matrix4x4 perspective;
+    perspective._34 =
+      -1.0 / NSAppUnitsToFloatPixels(aProperties.mChildPerspective, aAppUnitsPerPixel);
+
+    perspective.ChangeBasis(aProperties.GetToPerspectiveOrigin() + roundedOrigin);
+    result = result * perspective;
+
+    if (aOffsetByOrigin) {
+      result.PreTranslate(roundedOrigin);
+    }
+  }
+
   if (aDoPreserves3D && frame && frame->Combines3DTransformWithAncestors()) {
     // Include the transform set on our parent
     NS_ASSERTION(frame->GetParent() &&
                  frame->GetParent()->IsTransformed() &&
                  frame->GetParent()->Extend3DContext(),
                  "Preserve3D mismatch!");
     FrameTransformProperties props(frame->GetParent(),
                                    aAppUnitsPerPixel,
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -425,17 +425,17 @@ public:
   typedef mozilla::gfx::Matrix Matrix;
   typedef mozilla::gfx::Matrix4x4 Matrix4x4;
   typedef mozilla::Sides Sides;
   typedef mozilla::LogicalSides LogicalSides;
 
   NS_DECL_QUERYFRAME_TARGET(nsIFrame)
 
   nsPresContext* PresContext() const {
-    return StyleContext()->RuleNode()->PresContext();
+    return StyleContext()->PresContext();
   }
 
   /**
    * Called to initialize the frame. This is called immediately after creating
    * the frame.
    *
    * If the frame is a continuing frame, then aPrevInFlow indicates the previous
    * frame (the frame that was split).
copy from layout/reftests/bugs/mozilla-banner.gif
copy to layout/reftests/svg/filters/mozilla-banner.gif
--- a/layout/reftests/svg/filters/reftest.list
+++ b/layout/reftests/svg/filters/reftest.list
@@ -42,16 +42,17 @@ skip-if(d2d) == feFlood-2.svg feFlood-2-
 fuzzy(1,6400) == feGaussianBlur-1.svg feGaussianBlur-1-ref.svg
 == feGaussianBlur-2.svg feGaussianBlur-2-ref.svg
 # != feGaussianBlur-3.svg feGaussianBlur-3-ref.svg
 == feGaussianBlur-4.svg feGaussianBlur-4-ref.svg
 == feGaussianBlur-5.svg feGaussianBlur-5-ref.svg
 == feGaussianBlur-6.svg feGaussianBlur-6-ref.svg
 skip-if(d2d) pref(layers.acceleration.disabled,true) == feGaussianBlur-cap-large-directional-radius-on-software.html feGaussianBlur-cap-large-directional-radius-on-software-ref.html
 
+!= feImage-1.svg about:blank # (Make sure our image renders at all)
 == feImage-1.svg feImage-1-ref.svg
 == feImage-scale-to-primitive-subregion.html feImage-scale-to-primitive-subregion-ref.html
 
 == feMerge-1.svg feMerge-1-ref.svg
 == feMerge-2.svg feMerge-2-ref.svg
 
 == feMorphology-1.svg feMorphology-1-ref.svg
 == feMorphology-2.svg feMorphology-2-ref.svg
--- a/layout/style/nsStyleContext.h
+++ b/layout/style/nsStyleContext.h
@@ -260,32 +260,16 @@ public:
 
   // Does this style context have any children that return true for
   // UsesGrandancestorStyle()?
   bool HasChildThatUsesGrandancestorStyle() const;
 
   // Tell this style context to cache aStruct as the struct for aSID
   void SetStyle(nsStyleStructID aSID, void* aStruct);
 
-  // Setters for inherit structs only, since rulenode only sets those eagerly.
-  #define STYLE_STRUCT_INHERITED(name_, checkdata_cb_)                      \
-    void SetStyle##name_ (nsStyle##name_ * aStruct) {                       \
-      void *& slot =                                                        \
-        mCachedInheritedData.mStyleStructs[eStyleStruct_##name_];           \
-      NS_ASSERTION(!slot ||                                                 \
-                   (mBits &                                                 \
-                    nsCachedStyleData::GetBitForSID(eStyleStruct_##name_)), \
-                   "Going to leak styledata");                              \
-      slot = aStruct;                                                       \
-    }
-#define STYLE_STRUCT_RESET(name_, checkdata_cb_) /* nothing */
-  #include "nsStyleStructList.h"
-  #undef STYLE_STRUCT_RESET
-  #undef STYLE_STRUCT_INHERITED
-
   /**
    * Returns whether this style context has cached style data for a
    * given style struct and it does NOT own that struct.  This can
    * happen because it was inherited from the parent style context, or
    * because it was stored conditionally on the rule node.
    */
   bool HasCachedDependentStyleData(nsStyleStructID aSID) {
     return mBits & nsCachedStyleData::GetBitForSID(aSID);
--- a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
@@ -2185,17 +2185,17 @@ status_t MPEG4Extractor::parseSegmentInd
             !mDataSource->getUInt32(offset + 8, &d3)) { // flags
             return ERROR_MALFORMED;
         }
 
         if (d1 & 0x80000000) {
             ALOGW("sub-sidx boxes not supported yet");
         }
         bool sap = d3 & 0x80000000;
-        bool saptype = d3 >> 28;
+        uint32_t saptype = (d3 >> 28) & 0x3;
         if (!sap || saptype > 2) {
             ALOGW("not a stream access point, or unsupported type");
         }
         total_duration += d2;
         offset += 12;
         ALOGV(" item %d, %08x %08x %08x", i, d1, d2, d3);
         SidxEntry se;
         se.mSize = d1 & 0x7fffffff;
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -119,8 +119,11 @@ DISABLE_STL_WRAPPING = True
 # TODO: Remove these suppressions after bug 993267 is fixed.
 
 if CONFIG['GNU_CXX']:
     SOURCES['/mfbt/Compression.cpp'].flags += ['-Wno-unused-function']
 
 if CONFIG['_MSC_VER']:
     # Error 4804 is "'>' : unsafe use of type 'bool' in operation"
     SOURCES['/mfbt/Compression.cpp'].flags += ['-wd4804']
+
+if CONFIG['MOZ_NEEDS_LIBATOMIC']:
+    OS_LIBS += ['atomic']
--- a/netwerk/base/nsICachingChannel.idl
+++ b/netwerk/base/nsICachingChannel.idl
@@ -12,17 +12,17 @@ interface nsIFile;
  * to affect its behavior with respect to how it uses the cache service.
  *
  * This interface provides:
  *   1) Support for "stream as file" semantics (for JAR and plugins).
  *   2) Support for "pinning" cached data in the cache (for printing and save-as).
  *   3) Support for uniquely identifying cached data in cases when the URL
  *      is insufficient (e.g., HTTP form submission).
  */
-[scriptable, uuid(436b939d-e391-48e5-ba64-ab0e496e3400)]
+[scriptable, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)]
 interface nsICachingChannel : nsICacheInfoChannel
 {
     /**
      * Set/get the cache token... uniquely identifies the data in the cache.
      * Holding a reference to this token prevents the cached data from being
      * removed.
      * 
      * A cache token retrieved from a particular instance of nsICachingChannel
@@ -48,16 +48,21 @@ interface nsICachingChannel : nsICacheIn
     /**
      * Instructs the channel to only store the metadata of the entry, and not
      * the content. When reading an existing entry, this automatically sets
      * LOAD_ONLY_IF_MODIFIED flag.
      * Must be called before asyncOpen().
      */
     attribute boolean cacheOnlyMetadata;
 
+    /**
+     * Tells the channel to use the pinning storage.
+     */
+    attribute boolean pin;
+
     /**************************************************************************
      * Caching channel specific load flags:
      */
 
     /**
      * This load flag inhibits fetching from the net.  An error of
      * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's
      * onStopRequest if network IO is necessary to complete the request.
--- a/netwerk/base/nsIPackagedAppUtils.idl
+++ b/netwerk/base/nsIPackagedAppUtils.idl
@@ -19,17 +19,17 @@ interface nsIVerificationCallback;
  * store the hash values of each resource. When a resource is ready, Necko
  * will calculate its hash value (including the header like Content-Location: xxx),
  * and calls checkIntegrity(...) to verify the integrity.
  *
  * For more detail:
  *   https://wiki.mozilla.org/FirefoxOS/New_security_model/Packaging
  */
 
-[scriptable, uuid(edf91fee-ef4a-4479-9136-27eb3b7a6312)]
+[scriptable, uuid(2963609c-370b-4a76-9858-6f05121d0473)]
 interface nsIPackagedAppUtils : nsISupports
 {
   /**
    * @aHeader is the package's header including
    *   - "manifest-signature: xxxxxx" (base64 encoding)
    * @aManifest is the manifest of the package
    *   - the multipart header is included
    *   - manifest must be the first resource of the package
@@ -50,16 +50,22 @@ interface nsIPackagedAppUtils : nsISuppo
                       in ACString aHashValue,
                       in nsIVerificationCallback aVerifier);
 
   /**
    * The package identifier for signed package. Only available after the
    * manifest is verified.
    */
   readonly attribute ACString packageIdentifier;
+
+  /**
+   * The moz-package-location in the manifest of this signed package.
+   * Only available after the manifest is verified.
+   */
+  readonly attribute ACString packageOrigin;
 };
 
 /**
   * The callback passed to verifyManifest and checkIntegrity
   */
 [scriptable, uuid(e1912028-93e5-4378-aa3f-a58702937169)]
 interface nsIVerificationCallback : nsISupports
 {
--- a/netwerk/cache2/AppCacheStorage.cpp
+++ b/netwerk/cache2/AppCacheStorage.cpp
@@ -20,17 +20,17 @@
 
 namespace mozilla {
 namespace net {
 
 NS_IMPL_ISUPPORTS_INHERITED0(AppCacheStorage, CacheStorage)
 
 AppCacheStorage::AppCacheStorage(nsILoadContextInfo* aInfo,
                                  nsIApplicationCache* aAppCache)
-: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */, false /* skip size check */)
+: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */, false /* skip size check */, false /* pin */)
 , mAppCache(aAppCache)
 {
   MOZ_COUNT_CTOR(AppCacheStorage);
 }
 
 AppCacheStorage::~AppCacheStorage()
 {
   ProxyReleaseMainThread(mAppCache);
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -82,35 +82,55 @@ CacheEntry::Callback::Callback(CacheEntr
 , mCallback(aCallback)
 , mTargetThread(do_GetCurrentThread())
 , mReadOnly(aReadOnly)
 , mRevalidating(false)
 , mCheckOnAnyThread(aCheckOnAnyThread)
 , mRecheckAfterWrite(false)
 , mNotWanted(false)
 , mSecret(aSecret)
+, mDoomWhenFoundPinned(false)
+, mDoomWhenFoundNonPinned(false)
 {
   MOZ_COUNT_CTOR(CacheEntry::Callback);
 
   // The counter may go from zero to non-null only under the service lock
   // but here we expect it to be already positive.
   MOZ_ASSERT(mEntry->HandlesCount());
   mEntry->AddHandleRef();
 }
 
+CacheEntry::Callback::Callback(CacheEntry* aEntry, bool aDoomWhenFoundInPinStatus)
+: mEntry(aEntry)
+, mReadOnly(false)
+, mRevalidating(false)
+, mCheckOnAnyThread(true)
+, mRecheckAfterWrite(false)
+, mNotWanted(false)
+, mSecret(false)
+, mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus == true)
+, mDoomWhenFoundNonPinned(aDoomWhenFoundInPinStatus == false)
+{
+  MOZ_COUNT_CTOR(CacheEntry::Callback);
+  MOZ_ASSERT(mEntry->HandlesCount());
+  mEntry->AddHandleRef();
+}
+
 CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
 : mEntry(aThat.mEntry)
 , mCallback(aThat.mCallback)
 , mTargetThread(aThat.mTargetThread)
 , mReadOnly(aThat.mReadOnly)
 , mRevalidating(aThat.mRevalidating)
 , mCheckOnAnyThread(aThat.mCheckOnAnyThread)
 , mRecheckAfterWrite(aThat.mRecheckAfterWrite)
 , mNotWanted(aThat.mNotWanted)
 , mSecret(aThat.mSecret)
+, mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned)
+, mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned)
 {
   MOZ_COUNT_CTOR(CacheEntry::Callback);
 
   // The counter may go from zero to non-null only under the service lock
   // but here we expect it to be already positive.
   MOZ_ASSERT(mEntry->HandlesCount());
   mEntry->AddHandleRef();
 }
@@ -131,16 +151,30 @@ void CacheEntry::Callback::ExchangeEntry
   // The counter may go from zero to non-null only under the service lock
   // but here we expect it to be already positive.
   MOZ_ASSERT(aEntry->HandlesCount());
   aEntry->AddHandleRef();
   mEntry->ReleaseHandleRef();
   mEntry = aEntry;
 }
 
+bool CacheEntry::Callback::DeferDoom(bool *aDoom) const
+{
+  MOZ_ASSERT(mEntry->mPinningKnown);
+
+  if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) || MOZ_UNLIKELY(mDoomWhenFoundPinned)) {
+    *aDoom = (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && MOZ_LIKELY(!mEntry->mPinned)) ||
+             (MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
+
+    return true;
+  }
+
+  return false;
+}
+
 nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
 {
   if (!mCheckOnAnyThread) {
     // Check we are on the target
     return mTargetThread->IsOnCurrentThread(aOnCheckThread);
   }
 
   // We can invoke check anywhere
@@ -159,30 +193,33 @@ NS_IMPL_ISUPPORTS(CacheEntry,
                   nsICacheEntry,
                   nsIRunnable,
                   CacheFileListener)
 
 CacheEntry::CacheEntry(const nsACString& aStorageID,
                        nsIURI* aURI,
                        const nsACString& aEnhanceID,
                        bool aUseDisk,
-                       bool aSkipSizeCheck)
+                       bool aSkipSizeCheck,
+                       bool aPin)
 : mFrecency(0)
 , mSortingExpirationTime(uint32_t(-1))
 , mLock("CacheEntry")
 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
 , mURI(aURI)
 , mEnhanceID(aEnhanceID)
 , mStorageID(aStorageID)
 , mUseDisk(aUseDisk)
 , mSkipSizeCheck(aSkipSizeCheck)
-, mIsDoomed(false)
 , mSecurityInfoLoaded(false)
 , mPreventCallbacks(false)
 , mHasData(false)
+, mPinned(aPin)
+, mPinningKnown(false)
+, mIsDoomed(false)
 , mState(NOTLOADED)
 , mRegistration(NEVERREGISTERED)
 , mWriter(nullptr)
 , mPredictedDataSize(0)
 , mUseCount(0)
 , mReleaseThread(NS_GetCurrentThread())
 {
   MOZ_COUNT_CTOR(CacheEntry);
@@ -345,42 +382,45 @@ bool CacheEntry::Load(bool aTruncate, bo
   //    If there is or could be, doom that file.
   if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
     // Check the index right now to know we have or have not the entry
     // as soon as possible.
     CacheIndex::EntryStatus status;
     if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
       switch (status) {
       case CacheIndex::DOES_NOT_EXIST:
-        LOG(("  entry doesn't exist according information from the index, truncating"));
+        // Doesn't apply to memory-only entries, Load() is called only once for them
+        // and never again for their session lifetime.
         if (!aTruncate && mUseDisk) {
+          LOG(("  entry doesn't exist according information from the index, truncating"));
           reportMiss = true;
+          aTruncate = true;
         }
-        aTruncate = true;
         break;
       case CacheIndex::EXISTS:
       case CacheIndex::DO_NOT_KNOW:
         if (!mUseDisk) {
-          LOG(("  entry open as memory-only, but there is (status=%d) a file, dooming it", status));
+          LOG(("  entry open as memory-only, but there is a file, status=%d, dooming it", status));
           CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
         }
         break;
       }
     }
   }
 
   mFile = new CacheFile();
 
   BackgroundOp(Ops::REGISTER);
 
   bool directLoad = aTruncate || !mUseDisk;
   if (directLoad) {
     // mLoadStart will be used to calculate telemetry of life-time of this entry.
     // Low resulution is then enough.
     mLoadStart = TimeStamp::NowLoRes();
+    mPinningKnown = true;
   } else {
     mLoadStart = TimeStamp::Now();
   }
 
   {
     mozilla::MutexAutoUnlock unlock(mLock);
 
     if (reportMiss) {
@@ -390,16 +430,17 @@ bool CacheEntry::Load(bool aTruncate, bo
 
     LOG(("  performing load, file=%p", mFile.get()));
     if (NS_SUCCEEDED(rv)) {
       rv = mFile->Init(fileKey,
                        aTruncate,
                        !mUseDisk,
                        mSkipSizeCheck,
                        aPriority,
+                       mPinned,
                        directLoad ? nullptr : this);
     }
 
     if (NS_FAILED(rv)) {
       mFileStatus = rv;
       AsyncDoom(nullptr);
       return false;
     }
@@ -427,40 +468,46 @@ NS_IMETHODIMP CacheEntry::OnFileReady(ns
         CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
     } else {
       CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
         CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart);
     }
   }
 
   // OnFileReady, that is the only code that can transit from LOADING
-  // to any follow-on state, can only be invoked ones on an entry,
-  // thus no need to lock.  Until this moment there is no consumer that
-  // could manipulate the entry state.
+  // to any follow-on state and can only be invoked ones on an entry.
+  // Until this moment there is no consumer that could manipulate
+  // the entry state.
+
   mozilla::MutexAutoLock lock(mLock);
 
   MOZ_ASSERT(mState == LOADING);
 
   mState = (aIsNew || NS_FAILED(aResult))
     ? EMPTY
     : READY;
 
   mFileStatus = aResult;
 
+  mPinned = mFile->IsPinned();;
+  mPinningKnown = true;
+  LOG(("  pinning=%d", mPinned));
+
   if (mState == READY) {
     mHasData = true;
 
     uint32_t frecency;
     mFile->GetFrecency(&frecency);
     // mFrecency is held in a double to increase computance precision.
     // It is ok to persist frecency only as a uint32 with some math involved.
     mFrecency = INT2FRECENCY(frecency);
   }
 
   InvokeCallbacks();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
 {
   if (mDoomCallback) {
     RefPtr<DoomCallbackRunnable> event =
       new DoomCallbackRunnable(this, aResult);
@@ -478,23 +525,31 @@ already_AddRefed<CacheEntryHandle> Cache
   mLock.AssertCurrentThreadOwns();
 
   // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
   mPreventCallbacks = true;
 
   RefPtr<CacheEntryHandle> handle;
   RefPtr<CacheEntry> newEntry;
   {
+    if (mPinned) {
+      MOZ_ASSERT(mUseDisk);
+      // We want to pin even no-store entries (the case we recreate a disk entry as
+      // a memory-only entry.)
+      aMemoryOnly = false;
+    }
+
     mozilla::MutexAutoUnlock unlock(mLock);
 
     // The following call dooms this entry (calls DoomAlreadyRemoved on us)
     nsresult rv = CacheStorageService::Self()->AddStorageEntry(
       GetStorageID(), GetURI(), GetEnhanceID(),
       mUseDisk && !aMemoryOnly,
       mSkipSizeCheck,
+      mPinned,
       true, // always create
       true, // truncate existing (this one)
       getter_AddRefs(handle));
 
     if (NS_SUCCEEDED(rv)) {
       newEntry = handle->Entry();
       LOG(("  exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
       newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
@@ -572,28 +627,42 @@ void CacheEntry::InvokeCallbacks()
 
   LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
 }
 
 bool CacheEntry::InvokeCallbacks(bool aReadOnly)
 {
   mLock.AssertCurrentThreadOwns();
 
+  RefPtr<CacheEntryHandle> recreatedHandle;
+
   uint32_t i = 0;
   while (i < mCallbacks.Length()) {
     if (mPreventCallbacks) {
       LOG(("  callbacks prevented!"));
       return false;
     }
 
     if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
       LOG(("  entry is being written/revalidated"));
       return false;
     }
 
+    bool recreate;
+    if (mCallbacks[i].DeferDoom(&recreate)) {
+      mCallbacks.RemoveElementAt(i);
+      if (!recreate) {
+        continue;
+      }
+
+      LOG(("  defer doom marker callback hit positive, recreating"));
+      recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
+      break;
+    }
+
     if (mCallbacks[i].mReadOnly != aReadOnly) {
       // Callback is not r/w or r/o, go to another one in line
       ++i;
       continue;
     }
 
     bool onCheckThread;
     nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
@@ -620,16 +689,22 @@ bool CacheEntry::InvokeCallbacks(bool aR
       // readers or potential writers would be unnecessarily kept from being
       // invoked.
       size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
       mCallbacks.InsertElementAt(pos, callback);
       ++i;
     }
   }
 
+  if (recreatedHandle) {
+    // Must be released outside of the lock, enters InvokeCallback on the new entry
+    mozilla::MutexAutoUnlock unlock(mLock);
+    recreatedHandle = nullptr;
+  }
+
   return true;
 }
 
 bool CacheEntry::InvokeCallback(Callback & aCallback)
 {
   LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
     this, StateString(mState), aCallback.mCallback.get()));
 
@@ -986,16 +1061,23 @@ NS_IMETHODIMP CacheEntry::GetExpirationT
 
   return mFile->GetExpirationTime(aExpirationTime);
 }
 
 NS_IMETHODIMP CacheEntry::GetIsForcedValid(bool *aIsForcedValid)
 {
   NS_ENSURE_ARG(aIsForcedValid);
 
+  MOZ_ASSERT(mState > LOADING);
+
+  if (mPinned) {
+    *aIsForcedValid = true;
+    return NS_OK;
+  }
+
   nsAutoCString key;
 
   nsresult rv = HashingKeyWithStorage(key);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   *aIsForcedValid = CacheStorageService::Self()->IsForcedValidEntry(key);
@@ -1437,16 +1519,38 @@ void CacheEntry::SetRegistered(bool aReg
     mRegistration = REGISTERED;
   }
   else {
     MOZ_ASSERT(mRegistration == REGISTERED);
     mRegistration = DEREGISTERED;
   }
 }
 
+bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned)
+{
+  LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  if (mPinningKnown) {
+    LOG(("  pinned=%d, caller=%d", mPinned, aPinned));
+    // Bypass when the pin status of this entry doesn't match the pin status
+    // caller wants to remove
+    return mPinned != aPinned;
+  }
+
+  LOG(("  pinning unknown, caller=%d", aPinned));
+  // Oterwise, remember to doom after the status is determined for any
+  // callback opening the entry after this point...
+  Callback c(this, aPinned);
+  RememberCallback(c);
+  // ...and always bypass
+  return true;
+}
+
 bool CacheEntry::Purge(uint32_t aWhat)
 {
   LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
 
   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
 
   switch (aWhat) {
   case PURGE_DATA_ONLY_DISK_BACKED:
@@ -1518,16 +1622,20 @@ void CacheEntry::PurgeAndDoom()
 void CacheEntry::DoomAlreadyRemoved()
 {
   LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
 
   mozilla::MutexAutoLock lock(mLock);
 
   mIsDoomed = true;
 
+  // Pretend pinning is know.  This entry is now doomed for good, so don't
+  // bother with defering doom because of unknown pinning state any more.
+  mPinningKnown = true;
+
   // This schedules dooming of the file, dooming is ensured to happen
   // sooner than demand to open the same file made after this point
   // so that we don't get this file for any newer opened entry(s).
   DoomFile();
 
   // Must force post here since may be indirectly called from
   // InvokeCallbacks of this entry and we don't want reentrancy here.
   BackgroundOp(Ops::CALLBACKS, true);
--- a/netwerk/cache2/CacheEntry.h
+++ b/netwerk/cache2/CacheEntry.h
@@ -50,17 +50,17 @@ class CacheEntry final : public nsICache
                        , public CacheFileListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICACHEENTRY
   NS_DECL_NSIRUNNABLE
 
   CacheEntry(const nsACString& aStorageID, nsIURI* aURI, const nsACString& aEnhanceID,
-             bool aUseDisk, bool aSkipSizeCheck);
+             bool aUseDisk, bool aSkipSizeCheck, bool aPin);
 
   void AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags);
 
   CacheEntryHandle* NewHandle();
 
 public:
   uint32_t GetMetadataMemoryConsumption();
   nsCString const &GetStorageID() const { return mStorageID; }
@@ -87,16 +87,17 @@ public:
   TimeStamp const& LoadStart() const { return mLoadStart; }
 
   enum EPurge {
     PURGE_DATA_ONLY_DISK_BACKED,
     PURGE_WHOLE_ONLY_DISK_BACKED,
     PURGE_WHOLE,
   };
 
+  bool DeferOrBypassRemovalOnPinStatus(bool aPinned);
   bool Purge(uint32_t aWhat);
   void PurgeAndDoom();
   void DoomAlreadyRemoved();
 
   nsresult HashingKeyWithStorage(nsACString &aResult) const;
   nsresult HashingKey(nsACString &aResult) const;
 
   static nsresult HashingKey(nsCSubstring const& aStorageID,
@@ -131,36 +132,50 @@ private:
   // for writing it the first time gets released.  We must then invoke
   // waiting callbacks to not break the chain.
   class Callback
   {
   public:
     Callback(CacheEntry* aEntry,
              nsICacheEntryOpenCallback *aCallback,
              bool aReadOnly, bool aCheckOnAnyThread, bool aSecret);
+    // Special constructor for Callback objects added to the chain
+    // just to ensure proper defer dooming (recreation) of this entry.
+    Callback(CacheEntry* aEntry, bool aDoomWhenFoundInPinStatus);
     Callback(Callback const &aThat);
     ~Callback();
 
     // Called when this callback record changes it's owning entry,
     // mainly during recreation.
     void ExchangeEntry(CacheEntry* aEntry);
 
+    // Returns true when an entry is about to be "defer" doomed and this is
+    // a "defer" callback.
+    bool DeferDoom(bool *aDoom) const;
+
     // We are raising reference count here to take into account the pending
     // callback (that virtually holds a ref to this entry before it gets
     // it's pointer).
     RefPtr<CacheEntry> mEntry;
     nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
     nsCOMPtr<nsIThread> mTargetThread;
     bool mReadOnly : 1;
     bool mRevalidating : 1;
     bool mCheckOnAnyThread : 1;
     bool mRecheckAfterWrite : 1;
     bool mNotWanted : 1;
     bool mSecret : 1;
 
+    // These are set only for the defer-doomer Callback instance inserted
+    // to the callback chain.  When any of these is set and also any of
+    // the corressponding flags on the entry is set, this callback will
+    // indicate (via DeferDoom()) the entry have to be recreated/doomed.
+    bool mDoomWhenFoundPinned : 1;
+    bool mDoomWhenFoundNonPinned : 1;
+
     nsresult OnCheckThread(bool *aOnCheckThread) const;
     nsresult OnAvailThread(bool *aOnAvailThread) const;
   };
 
   // Since OnCacheEntryAvailable must be invoked on the main thread
   // we need a runnable for it...
   class AvailableCallbackRunnable : public nsRunnable
   {
@@ -269,38 +284,42 @@ private:
   // When mFileStatus is read and found success it is ensured there is mFile and
   // that it is after a successful call to Init().
   ::mozilla::Atomic<nsresult, ::mozilla::ReleaseAcquire> mFileStatus;
   nsCOMPtr<nsIURI> mURI;
   nsCString mEnhanceID;
   nsCString mStorageID;
 
   // Whether it's allowed to persist the data to disk
-  bool const mUseDisk;
-
+  bool const mUseDisk : 1;
   // Whether it should skip max size check.
-  bool const mSkipSizeCheck;
-
-  // Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved().
-  // Left as a standalone flag to not bother with locking (there is no need).
-  bool mIsDoomed;
+  bool const mSkipSizeCheck : 1;
 
   // Following flags are all synchronized with the cache entry lock.
 
   // Whether security info has already been looked up in metadata.
   bool mSecurityInfoLoaded : 1;
   // Prevents any callback invocation
   bool mPreventCallbacks : 1;
   // true: after load and an existing file, or after output stream has been opened.
   //       note - when opening an input stream, and this flag is false, output stream
   //       is open along ; this makes input streams on new entries behave correctly
   //       when EOF is reached (WOULD_BLOCK is returned).
   // false: after load and a new file, or dropped to back to false when a writer
   //        fails to open an output stream.
   bool mHasData : 1;
+  // The indication of pinning this entry was open with
+  bool mPinned : 1;
+  // Whether the pinning state of the entry is known (equals to the actual state
+  // of the cache file)
+  bool mPinningKnown : 1;
+
+  // Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved().
+  // Left as a standalone flag to not bother with locking (there is no need).
+  bool mIsDoomed;
 
   static char const * StateString(uint32_t aState);
 
   enum EState {      // transiting to:
     NOTLOADED = 0,   // -> LOADING | EMPTY
     LOADING = 1,     // -> EMPTY | READY
     EMPTY = 2,       // -> WRITING
     WRITING = 3,     // -> EMPTY | READY
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -97,17 +97,17 @@ public:
     mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk);
     return NS_OK;
   }
 
 protected:
   nsCOMPtr<CacheFileChunkListener> mCallback;
   nsresult                         mRV;
   uint32_t                         mChunkIdx;
-  RefPtr<CacheFileChunk>         mChunk;
+  RefPtr<CacheFileChunk>           mChunk;
 };
 
 
 class DoomFileHelper : public CacheFileIOListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
@@ -180,16 +180,17 @@ NS_INTERFACE_MAP_END_THREADSAFE
 
 CacheFile::CacheFile()
   : mLock("CacheFile.mLock")
   , mOpeningFile(false)
   , mReady(false)
   , mMemoryOnly(false)
   , mSkipSizeCheck(false)
   , mOpenAsMemoryOnly(false)
+  , mPinned(false)
   , mPriority(false)
   , mDataAccessed(false)
   , mDataIsDirty(false)
   , mWritingMetadata(false)
   , mPreloadWithoutInputStreams(true)
   , mPreloadChunkCount(0)
   , mStatus(NS_OK)
   , mDataSize(-1)
@@ -210,27 +211,31 @@ CacheFile::~CacheFile()
 }
 
 nsresult
 CacheFile::Init(const nsACString &aKey,
                 bool aCreateNew,
                 bool aMemoryOnly,
                 bool aSkipSizeCheck,
                 bool aPriority,
+                bool aPinned,
                 CacheFileListener *aCallback)
 {
   MOZ_ASSERT(!mListener);
   MOZ_ASSERT(!mHandle);
 
+  MOZ_ASSERT(!(aMemoryOnly && aPinned));
+
   nsresult rv;
 
   mKey = aKey;
   mOpenAsMemoryOnly = mMemoryOnly = aMemoryOnly;
   mSkipSizeCheck = aSkipSizeCheck;
   mPriority = aPriority;
+  mPinned = aPinned;
 
   // Some consumers (at least nsHTTPCompressConv) assume that Read() can read
   // such amount of data that was announced by Available().
   // CacheFileInputStream::Available() uses also preloaded chunks to compute
   // number of available bytes in the input stream, so we have to make sure the
   // preloadChunkCount won't change during CacheFile's lifetime since otherwise
   // we could potentially release some cached chunks that was used to calculate
   // available bytes but would not be available later during call to
@@ -239,62 +244,72 @@ CacheFile::Init(const nsACString &aKey,
 
   LOG(("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, "
        "priority=%d, listener=%p]", this, mKey.get(), aCreateNew, aMemoryOnly,
        aPriority, aCallback));
 
   if (mMemoryOnly) {
     MOZ_ASSERT(!aCallback);
 
-    mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
+    mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, false, mKey);
     mReady = true;
     mDataSize = mMetadata->Offset();
     return NS_OK;
   }
   else {
     uint32_t flags;
     if (aCreateNew) {
       MOZ_ASSERT(!aCallback);
       flags = CacheFileIOManager::CREATE_NEW;
 
       // make sure we can use this entry immediately
-      mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
+      mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey);
       mReady = true;
       mDataSize = mMetadata->Offset();
     } else {
       flags = CacheFileIOManager::CREATE;
     }
 
     if (mPriority) {
       flags |= CacheFileIOManager::PRIORITY;
     }
 
+    if (mPinned) {
+      flags |= CacheFileIOManager::PINNED;
+    }
+
     mOpeningFile = true;
     mListener = aCallback;
     rv = CacheFileIOManager::OpenFile(mKey, flags, this);
     if (NS_FAILED(rv)) {
       mListener = nullptr;
       mOpeningFile = false;
 
+      if (mPinned) {
+        LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
+             "but we want to pin, fail the file opening. [this=%p]", this));
+        return NS_ERROR_NOT_AVAILABLE;
+      }
+
       if (aCreateNew) {
         NS_WARNING("Forcing memory-only entry since OpenFile failed");
         LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
              "synchronously. We can continue in memory-only mode since "
              "aCreateNew == true. [this=%p]", this));
 
         mMemoryOnly = true;
       }
       else if (rv == NS_ERROR_NOT_INITIALIZED) {
         NS_WARNING("Forcing memory-only entry since CacheIOManager isn't "
                    "initialized.");
         LOG(("CacheFile::Init() - CacheFileIOManager isn't initialized, "
              "initializing entry as memory-only. [this=%p]", this));
 
         mMemoryOnly = true;
-        mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
+        mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey);
         mReady = true;
         mDataSize = mMetadata->Offset();
 
         RefPtr<NotifyCacheFileListenerEvent> ev;
         ev = new NotifyCacheFileListenerEvent(aCallback, NS_OK, true);
         rv = NS_DispatchToCurrentThread(ev);
         NS_ENSURE_SUCCESS(rv, rv);
       }
@@ -477,54 +492,54 @@ CacheFile::OnFileOpened(CacheFileHandle 
     if (mMemoryOnly) {
       // We can be here only in case the entry was initilized as createNew and
       // SetMemoryOnly() was called.
 
       // Just don't store the handle into mHandle and exit
       autoDoom.mAlreadyDoomed = true;
       return NS_OK;
     }
-    else if (NS_FAILED(aResult)) {
+
+    if (NS_FAILED(aResult)) {
       if (mMetadata) {
         // This entry was initialized as createNew, just switch to memory-only
         // mode.
         NS_WARNING("Forcing memory-only entry since OpenFile failed");
         LOG(("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() "
              "failed asynchronously. We can continue in memory-only mode since "
              "aCreateNew == true. [this=%p]", this));
 
         mMemoryOnly = true;
         return NS_OK;
       }
-      else if (aResult == NS_ERROR_FILE_INVALID_PATH) {
+
+      if (aResult == NS_ERROR_FILE_INVALID_PATH) {
         // CacheFileIOManager doesn't have mCacheDirectory, switch to
         // memory-only mode.
         NS_WARNING("Forcing memory-only entry since CacheFileIOManager doesn't "
                    "have mCacheDirectory.");
         LOG(("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have "
              "mCacheDirectory, initializing entry as memory-only. [this=%p]",
              this));
 
         mMemoryOnly = true;
-        mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
+        mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey);
         mReady = true;
         mDataSize = mMetadata->Offset();
 
         isNew = true;
         retval = NS_OK;
-      }
-      else {
+      } else {
         // CacheFileIOManager::OpenFile() failed for another reason.
         isNew = false;
         retval = aResult;
       }
 
       mListener.swap(listener);
-    }
-    else {
+    } else {
       mHandle = aHandle;
       if (NS_FAILED(mStatus)) {
         CacheFileIOManager::DoomFile(mHandle, nullptr);
       }
 
       if (mMetadata) {
         InitIndexEntry();
 
@@ -578,16 +593,17 @@ nsresult
 CacheFile::OnMetadataRead(nsresult aResult)
 {
   MOZ_ASSERT(mListener);
 
   LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08x]", this, aResult));
 
   bool isNew = false;
   if (NS_SUCCEEDED(aResult)) {
+    mPinned = mMetadata->Pinned();
     mReady = true;
     mDataSize = mMetadata->Offset();
     if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
       isNew = true;
       mMetadata->MarkDirty();
     } else {
       CacheFileAutoLock lock(this);
       PreloadChunks(0);
@@ -1965,17 +1981,18 @@ CacheFile::InitIndexEntry()
     return NS_OK;
 
   nsresult rv;
 
   // Bug 1201042 - will pass OriginAttributes directly.
   rv = CacheFileIOManager::InitIndexEntry(mHandle,
                                           mMetadata->OriginAttributes().mAppId,
                                           mMetadata->IsAnonymous(),
-                                          mMetadata->OriginAttributes().mInBrowser);
+                                          mMetadata->OriginAttributes().mInBrowser,
+                                          mPinned);
   NS_ENSURE_SUCCESS(rv, rv);
 
   uint32_t expTime;
   mMetadata->GetExpirationTime(&expTime);
 
   uint32_t frecency;
   mMetadata->GetFrecency(&frecency);
 
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -53,16 +53,17 @@ public:
 
   CacheFile();
 
   nsresult Init(const nsACString &aKey,
                 bool aCreateNew,
                 bool aMemoryOnly,
                 bool aSkipSizeCheck,
                 bool aPriority,
+                bool aPinned,
                 CacheFileListener *aCallback);
 
   NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
                               CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk) override;
 
@@ -98,16 +99,17 @@ public:
   nsresult GetFetchCount(uint32_t *_retval);
   // Called by upper layers to indicated the entry has been fetched,
   // i.e. delivered to the consumer.
   nsresult OnFetched();
 
   bool DataSize(int64_t* aSize);
   void Key(nsACString& aKey) { aKey = mKey; }
   bool IsDoomed();
+  bool IsPinned() const { return mPinned; }
   bool IsWriteInProgress();
 
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 private:
   friend class CacheFileIOManager;
@@ -191,28 +193,29 @@ private:
   nsresult InitIndexEntry();
 
   mozilla::Mutex mLock;
   bool           mOpeningFile;
   bool           mReady;
   bool           mMemoryOnly;
   bool           mSkipSizeCheck;
   bool           mOpenAsMemoryOnly;
+  bool           mPinned;
   bool           mPriority;
   bool           mDataAccessed;
   bool           mDataIsDirty;
   bool           mWritingMetadata;
   bool           mPreloadWithoutInputStreams;
   uint32_t       mPreloadChunkCount;
   nsresult       mStatus;
   int64_t        mDataSize;
   nsCString      mKey;
 
-  RefPtr<CacheFileHandle>    mHandle;
-  RefPtr<CacheFileMetadata>  mMetadata;
+  RefPtr<CacheFileHandle>      mHandle;
+  RefPtr<CacheFileMetadata>    mMetadata;
   nsCOMPtr<CacheFileListener>  mListener;
   nsCOMPtr<CacheFileIOListener>   mDoomAfterOpenListener;
 
   nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks;
   nsClassHashtable<nsUint32HashKey, ChunkListeners> mChunkListeners;
   nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mCachedChunks;
 
   nsTArray<CacheFileInputStream*> mInputs;
--- a/netwerk/cache2/CacheFileChunk.cpp
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -39,17 +39,17 @@ public:
     LOG(("NotifyUpdateListenerEvent::Run() [this=%p]", this));
 
     mCallback->OnChunkUpdated(mChunk);
     return NS_OK;
   }
 
 protected:
   nsCOMPtr<CacheFileChunkListener> mCallback;
-  RefPtr<CacheFileChunk>         mChunk;
+  RefPtr<CacheFileChunk>           mChunk;
 };
 
 bool
 CacheFileChunk::DispatchRelease()
 {
   if (NS_IsMainThread()) {
     return false;
   }
--- a/netwerk/cache2/CacheFileChunk.h
+++ b/netwerk/cache2/CacheFileChunk.h
@@ -145,17 +145,17 @@ private:
 
   char    *mBuf;
   uint32_t mBufSize;
 
   char               *mRWBuf;
   uint32_t            mRWBufSize;
   CacheHash::Hash16_t mReadHash;
 
-  RefPtr<CacheFile>              mFile; // is null if chunk is cached to
+  RefPtr<CacheFile>                mFile; // is null if chunk is cached to
                                           // prevent reference cycles
   nsCOMPtr<CacheFileChunkListener> mListener;
   nsTArray<ChunkListenerItem *>    mUpdateListeners;
   CacheFileUtils::ValidityMap      mValidityMap;
 };
 
 
 } // namespace net
--- a/netwerk/cache2/CacheFileContextEvictor.cpp
+++ b/netwerk/cache2/CacheFileContextEvictor.cpp
@@ -76,42 +76,59 @@ uint32_t
 CacheFileContextEvictor::ContextsCount()
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   return mEntries.Length();
 }
 
 nsresult
-CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo)
+CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo,
+                                    bool aPinned)
 {
-  LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p]",
-       this, aLoadContextInfo));
+  LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p, pinned=%d]",
+       this, aLoadContextInfo, aPinned));
 
   nsresult rv;
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   CacheFileContextEvictorEntry *entry = nullptr;
-  for (uint32_t i = 0; i < mEntries.Length(); ++i) {
-    if (mEntries[i]->mInfo->Equals(aLoadContextInfo)) {
-      entry = mEntries[i];
-      break;
+  if (aLoadContextInfo) {
+    for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+      if (mEntries[i]->mInfo &&
+          mEntries[i]->mInfo->Equals(aLoadContextInfo) &&
+          mEntries[i]->mPinned == aPinned) {
+        entry = mEntries[i];
+        break;
+      }
+    }
+  } else {
+    // Not providing load context info means we want to delete everything,
+    // so let's not bother with any currently running context cleanups
+    // for the same pinning state.
+    for (uint32_t i = mEntries.Length(); i > 0;) {
+      --i;
+      if (mEntries[i]->mInfo && mEntries[i]->mPinned == aPinned) {
+        RemoveEvictInfoFromDisk(mEntries[i]->mInfo, mEntries[i]->mPinned);
+        mEntries.RemoveElementAt(i);
+      }
     }
   }
 
   if (!entry) {
     entry = new CacheFileContextEvictorEntry();
     entry->mInfo = aLoadContextInfo;
+    entry->mPinned = aPinned;
     mEntries.AppendElement(entry);
   }
 
   entry->mTimeStamp = PR_Now() / PR_USEC_PER_MSEC;
 
-  PersistEvictionInfoToDisk(aLoadContextInfo);
+  PersistEvictionInfoToDisk(aLoadContextInfo, aPinned);
 
   if (mIndexIsUpToDate) {
     // Already existing context could be added again, in this case the iterator
     // would be recreated. Close the old iterator explicitely.
     if (entry->mIterator) {
       entry->mIterator->Close();
       entry->mIterator = nullptr;
     }
@@ -175,78 +192,82 @@ CacheFileContextEvictor::CacheIndexState
     CloseIterators();
   }
 
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::WasEvicted(const nsACString &aKey, nsIFile *aFile,
-                                    bool *_retval)
+                                    bool *aEvictedAsPinned, bool *aEvictedAsNonPinned)
 {
   LOG(("CacheFileContextEvictor::WasEvicted() [key=%s]",
        PromiseFlatCString(aKey).get()));
 
   nsresult rv;
 
+  *aEvictedAsPinned = false;
+  *aEvictedAsNonPinned = false;
+
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
   MOZ_ASSERT(info);
   if (!info) {
     LOG(("CacheFileContextEvictor::WasEvicted() - Cannot parse key!"));
-    *_retval = false;
     return NS_OK;
   }
 
-  CacheFileContextEvictorEntry *entry = nullptr;
   for (uint32_t i = 0; i < mEntries.Length(); ++i) {
-    if (info->Equals(mEntries[i]->mInfo)) {
-      entry = mEntries[i];
-      break;
+    CacheFileContextEvictorEntry *entry = mEntries[i];
+
+    if (entry->mInfo && !info->Equals(entry->mInfo)) {
+      continue;
+    }
+
+    PRTime lastModifiedTime;
+    rv = aFile->GetLastModifiedTime(&lastModifiedTime);
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFileContextEvictor::WasEvicted() - Cannot get last modified time"
+            ", returning false."));
+      return NS_OK;
+    }
+
+    if (lastModifiedTime > entry->mTimeStamp) {
+      // File has been modified since context eviction.
+      continue;
+    }
+
+    LOG(("CacheFileContextEvictor::WasEvicted() - evicted [pinning=%d, "
+         "mTimeStamp=%lld, lastModifiedTime=%lld]",
+         entry->mPinned, entry->mTimeStamp, lastModifiedTime));
+
+    if (entry->mPinned) {
+      *aEvictedAsPinned = true;
+    } else {
+      *aEvictedAsNonPinned = true;
     }
   }
 
-  if (!entry) {
-    LOG(("CacheFileContextEvictor::WasEvicted() - Didn't find equal context, "
-         "returning false."));
-    *_retval = false;
-    return NS_OK;
-  }
-
-  PRTime lastModifiedTime;
-  rv = aFile->GetLastModifiedTime(&lastModifiedTime);
-  if (NS_FAILED(rv)) {
-    LOG(("CacheFileContextEvictor::WasEvicted() - Cannot get last modified time"
-         ", returning false."));
-    *_retval = false;
-    return NS_OK;
-  }
-
-  *_retval = !(lastModifiedTime > entry->mTimeStamp);
-  LOG(("CacheFileContextEvictor::WasEvicted() - returning %s. [mTimeStamp=%lld,"
-       " lastModifiedTime=%lld]", *_retval ? "true" : "false",
-       mEntries[0]->mTimeStamp, lastModifiedTime));
-
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::PersistEvictionInfoToDisk(
-  nsILoadContextInfo *aLoadContextInfo)
+  nsILoadContextInfo *aLoadContextInfo, bool aPinned)
 {
   LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
        "loadContextInfo=%p]", this, aLoadContextInfo));
 
   nsresult rv;
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   nsCOMPtr<nsIFile> file;
-  rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
+  rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsAutoCString path;
   file->GetNativePath(path);
 
   PRFileDesc *fd;
@@ -263,27 +284,27 @@ CacheFileContextEvictor::PersistEviction
   LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Successfully "
        "created file. [path=%s]", path.get()));
 
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::RemoveEvictInfoFromDisk(
-  nsILoadContextInfo *aLoadContextInfo)
+  nsILoadContextInfo *aLoadContextInfo, bool aPinned)
 {
   LOG(("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
        "loadContextInfo=%p]", this, aLoadContextInfo));
 
   nsresult rv;
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   nsCOMPtr<nsIFile> file;
-  rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
+  rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsAutoCString path;
   file->GetNativePath(path);
 
   rv = file->Remove(false);
@@ -358,52 +379,73 @@ CacheFileContextEvictor::LoadEvictInfoFr
     rv = Base64Decode(encoded, decoded);
     if (NS_FAILED(rv)) {
       LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Base64 decoding "
            "failed. Removing the file. [file=%s]", leaf.get()));
       file->Remove(false);
       continue;
     }
 
-    nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(decoded);
+    bool pinned = decoded[0] == '\t';
+    if (pinned) {
+      decoded = Substring(decoded, 1);
+    }
 
-    if (!info) {
-      LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Cannot parse "
-           "context key, removing file. [contextKey=%s, file=%s]",
-           decoded.get(), leaf.get()));
-      file->Remove(false);
-      continue;
+    nsCOMPtr<nsILoadContextInfo> info;
+    if (!NS_LITERAL_CSTRING("*").Equals(decoded)) {
+      // "*" is indication of 'delete all', info left null will pass
+      // to CacheFileContextEvictor::AddContext and clear all the cache data.
+      info = CacheFileUtils::ParseKey(decoded);
+      if (!info) {
+        LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Cannot parse "
+             "context key, removing file. [contextKey=%s, file=%s]",
+             decoded.get(), leaf.get()));
+        file->Remove(false);
+        continue;
+      }
     }
 
+
     PRTime lastModifiedTime;
     rv = file->GetLastModifiedTime(&lastModifiedTime);
     if (NS_FAILED(rv)) {
       continue;
     }
 
     CacheFileContextEvictorEntry *entry = new CacheFileContextEvictorEntry();
     entry->mInfo = info;
+    entry->mPinned = pinned;
     entry->mTimeStamp = lastModifiedTime;
     mEntries.AppendElement(entry);
   }
 
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::GetContextFile(nsILoadContextInfo *aLoadContextInfo,
+                                        bool aPinned,
                                         nsIFile **_retval)
 {
   nsresult rv;
 
   nsAutoCString leafName;
   leafName.AssignLiteral(CONTEXT_EVICTION_PREFIX);
 
   nsAutoCString keyPrefix;
-  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
+  if (aPinned) {
+    // Mark pinned context files with a tab char at the start.
+    // Tab is chosen because it can never be used as a context key tag.
+    keyPrefix.Append('\t');
+  }
+  if (aLoadContextInfo) {
+    CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
+  } else {
+    keyPrefix.Append('*');
+  }
 
   nsAutoCString data64;
   rv = Base64Encode(keyPrefix, data64);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Replace '/' with '-' since '/' cannot be part of the filename.
@@ -525,17 +567,17 @@ CacheFileContextEvictor::EvictEntries()
     }
 
     SHA1Sum::Hash hash;
     rv = mEntries[0]->mIterator->GetNextHash(&hash);
     if (rv == NS_ERROR_NOT_AVAILABLE) {
       LOG(("CacheFileContextEvictor::EvictEntries() - No more entries left in "
            "iterator. [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
            mEntries[0]->mInfo.get()));
-      RemoveEvictInfoFromDisk(mEntries[0]->mInfo);
+      RemoveEvictInfoFromDisk(mEntries[0]->mInfo, mEntries[0]->mPinned);
       mEntries.RemoveElementAt(0);
       continue;
     } else if (NS_FAILED(rv)) {
       LOG(("CacheFileContextEvictor::EvictEntries() - Iterator failed to "
            "provide next hash (shutdown?), keeping eviction info on disk."
            " [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
            mEntries[0]->mInfo.get()));
       mEntries.RemoveElementAt(0);
@@ -552,16 +594,30 @@ CacheFileContextEvictor::EvictEntries()
     if (handle) {
       // We doom any active handle in CacheFileIOManager::EvictByContext(), so
       // this must be a new one. Skip it.
       LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since we "
            "found an active handle. [handle=%p]", handle.get()));
       continue;
     }
 
+    CacheIndex::EntryStatus status;
+    bool pinned;
+    rv = CacheIndex::HasEntry(hash, &status, &pinned);
+    // This must never fail, since eviction (this code) happens only when the index
+    // is up-to-date and thus the informatin is known.
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    if (pinned != mEntries[0]->mPinned) {
+      LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since pinning "
+           "doesn't match [evicting pinned=%d, entry pinned=%d]",
+           mEntries[0]->mPinned, pinned));
+      continue;
+    }
+
     nsAutoCString leafName;
     CacheFileIOManager::HashToStr(&hash, leafName);
 
     PRTime lastModifiedTime;
     nsCOMPtr<nsIFile> file;
     rv = mEntriesDir->Clone(getter_AddRefs(file));
     if (NS_SUCCEEDED(rv)) {
       rv = file->AppendNative(leafName);
--- a/netwerk/cache2/CacheFileContextEvictor.h
+++ b/netwerk/cache2/CacheFileContextEvictor.h
@@ -15,16 +15,17 @@ class nsILoadContextInfo;
 namespace mozilla {
 namespace net {
 
 class CacheIndexIterator;
 
 struct CacheFileContextEvictorEntry
 {
   nsCOMPtr<nsILoadContextInfo> mInfo;
+  bool                         mPinned;
   PRTime                       mTimeStamp; // in milliseconds
   RefPtr<CacheIndexIterator> mIterator;
 };
 
 class CacheFileContextEvictor
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheFileContextEvictor)
@@ -35,41 +36,42 @@ private:
   virtual ~CacheFileContextEvictor();
 
 public:
   nsresult Init(nsIFile *aCacheDirectory);
 
   // Returns number of contexts that are being evicted.
   uint32_t ContextsCount();
   // Start evicting given context.
-  nsresult AddContext(nsILoadContextInfo *aLoadContextInfo);
+  nsresult AddContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
   // CacheFileIOManager calls this method when CacheIndex's state changes. We
   // check whether the index is up to date and start or stop evicting according
   // to index's state.
   nsresult CacheIndexStateChanged();
   // CacheFileIOManager calls this method to check whether an entry file should
   // be considered as evicted. It returns true when there is a matching context
   // info to the given key and the last modified time of the entry file is
   // earlier than the time stamp of the time when the context was added to the
   // evictor.
-  nsresult WasEvicted(const nsACString &aKey, nsIFile *aFile, bool *_retval);
+  nsresult WasEvicted(const nsACString &aKey, nsIFile *aFile,
+                      bool *aEvictedAsPinned, bool *aEvictedAsNonPinned);
 
 private:
   // Writes information about eviction of the given context to the disk. This is
   // done for every context added to the evictor to be able to recover eviction
   // after a shutdown or crash. When the context file is found after startup, we
   // restore mTimeStamp from the last modified time of the file.
-  nsresult PersistEvictionInfoToDisk(nsILoadContextInfo *aLoadContextInfo);
+  nsresult PersistEvictionInfoToDisk(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
   // Once we are done with eviction for the given context, the eviction info is
   // removed from the disk.
-  nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo);
+  nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
   // Tries to load all contexts from the disk. This method is called just once
   // after startup.
   nsresult LoadEvictInfoFromDisk();
-  nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo,
+  nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo, bool aPinned,
                           nsIFile **_retval);
 
   void     CreateIterators();
   void     CloseIterators();
   void     StartEvicting();
   nsresult EvictEntries();
 
   // Whether eviction is in progress
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -103,42 +103,48 @@ CacheFileHandle::Release()
 
   return count;
 }
 
 NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END_THREADSAFE
 
-CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority)
+CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority, PinningStatus aPinning)
   : mHash(aHash)
   , mPriority(aPriority)
   , mClosed(false)
   , mSpecialFile(false)
   , mInvalid(false)
   , mFileExists(false)
+  , mPinning(aPinning)
+  , mDoomWhenFoundPinned(false)
+  , mDoomWhenFoundNonPinned(false)
   , mFileSize(-1)
   , mFD(nullptr)
 {
   // If we initialize mDoomed in the initialization list, that initialization is
   // not guaranteeded to be atomic.  Whereas this assignment here is guaranteed
   // to be atomic.  TSan will see this (atomic) assignment and be satisfied
   // that cross-thread accesses to mIsDoomed are properly synchronized.
   mIsDoomed = false;
   LOG(("CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]"
        , this, LOGSHA1(aHash)));
 }
 
-CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority)
+CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority, PinningStatus aPinning)
   : mHash(nullptr)
   , mPriority(aPriority)
   , mClosed(false)
   , mSpecialFile(true)
   , mInvalid(false)
   , mFileExists(false)
+  , mPinning(aPinning)
+  , mDoomWhenFoundPinned(false)
+  , mDoomWhenFoundNonPinned(false)
   , mFileSize(-1)
   , mFD(nullptr)
   , mKey(aKey)
 {
   // See comment above about the initialization of mIsDoomed.
   mIsDoomed = false;
   LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this,
        PromiseFlatCString(aKey).get()));
@@ -194,16 +200,42 @@ CacheFileHandle::FileSizeInK() const
     size = PR_UINT32_MAX;
   } else {
     size = static_cast<uint32_t>(size64);
   }
 
   return size;
 }
 
+bool
+CacheFileHandle::SetPinned(bool aPinned)
+{
+  LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]", this, aPinned));
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+  mPinning = aPinned
+    ? PinningStatus::PINNED
+    : PinningStatus::NON_PINNED;
+
+  if ((MOZ_UNLIKELY(mDoomWhenFoundPinned) && aPinned) ||
+      (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && !aPinned)) {
+
+    LOG(("  dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d",
+      bool(mDoomWhenFoundPinned), bool(mDoomWhenFoundNonPinned), aPinned));
+
+    mDoomWhenFoundPinned = false;
+    mDoomWhenFoundNonPinned = false;
+
+    return false;
+  }
+
+  return true;
+}
+
 // Memory reporting
 
 size_t
 CacheFileHandle::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
   size_t n = 0;
   nsCOMPtr<nsISizeOf> sizeOf;
 
@@ -355,17 +387,17 @@ CacheFileHandles::GetHandle(const SHA1Su
 
   handle.forget(_retval);
   return NS_OK;
 }
 
 
 nsresult
 CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash,
-                            bool aPriority,
+                            bool aPriority, CacheFileHandle::PinningStatus aPinning,
                             CacheFileHandle **_retval)
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
   MOZ_ASSERT(aHash);
 
 #ifdef DEBUG_HANDLES
   LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
 #endif
@@ -376,17 +408,17 @@ CacheFileHandles::NewHandle(const SHA1Su
 #ifdef DEBUG_HANDLES
   Log(entry);
 #endif
 
 #ifdef DEBUG
   entry->AssertHandlesState();
 #endif
 
-  RefPtr<CacheFileHandle> handle = new CacheFileHandle(entry->Hash(), aPriority);
+  RefPtr<CacheFileHandle> handle = new CacheFileHandle(entry->Hash(), aPriority, aPinning);
   entry->AddHandle(handle);
 
   LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
        "created new handle %p, entry=%p", LOGSHA1(aHash), handle.get(), entry));
 
   handle.forget(_retval);
   return NS_OK;
 }
@@ -571,27 +603,27 @@ public:
       }
       mIOMan = nullptr;
       if (mHandle) {
         if (mHandle->Key().IsEmpty()) {
           mHandle->Key() = mKey;
         }
       }
     }
+
     mCallback->OnFileOpened(mHandle, rv);
-
     return NS_OK;
   }
 
 protected:
   SHA1Sum::Hash                 mHash;
   uint32_t                      mFlags;
   nsCOMPtr<CacheFileIOListener> mCallback;
-  RefPtr<CacheFileIOManager>  mIOMan;
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileIOManager>    mIOMan;
+  RefPtr<CacheFileHandle>       mHandle;
   nsCString                     mKey;
 };
 
 class ReadEvent : public nsRunnable {
 public:
   ReadEvent(CacheFileHandle *aHandle, int64_t aOffset, char *aBuf,
             int32_t aCount, CacheFileIOListener *aCallback)
     : mHandle(aHandle)
@@ -621,17 +653,17 @@ public:
         mHandle, mOffset, mBuf, mCount);
     }
 
     mCallback->OnDataRead(mHandle, mBuf, rv);
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
   int64_t                       mOffset;
   char                         *mBuf;
   int32_t                       mCount;
   nsCOMPtr<CacheFileIOListener> mCallback;
 };
 
 class WriteEvent : public nsRunnable {
 public:
@@ -680,17 +712,17 @@ public:
       free(const_cast<char *>(mBuf));
       mBuf = nullptr;
     }
 
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
   int64_t                       mOffset;
   const char                   *mBuf;
   int32_t                       mCount;
   bool                          mValidate : 1;
   bool                          mTruncate : 1;
   nsCOMPtr<CacheFileIOListener> mCallback;
 };
 
@@ -724,19 +756,19 @@ public:
     if (mCallback) {
       mCallback->OnFileDoomed(mHandle, rv);
     }
 
     return NS_OK;
   }
 
 protected:
-  nsCOMPtr<CacheFileIOListener> mCallback;
-  nsCOMPtr<nsIEventTarget>      mTarget;
-  RefPtr<CacheFileHandle>     mHandle;
+  nsCOMPtr<CacheFileIOListener>              mCallback;
+  nsCOMPtr<nsIEventTarget>                   mTarget;
+  RefPtr<CacheFileHandle>                    mHandle;
 };
 
 class DoomFileByKeyEvent : public nsRunnable {
 public:
   DoomFileByKeyEvent(const nsACString &aKey,
                      CacheFileIOListener *aCallback)
     : mCallback(aCallback)
   {
@@ -772,17 +804,17 @@ public:
     }
 
     return NS_OK;
   }
 
 protected:
   SHA1Sum::Hash                 mHash;
   nsCOMPtr<CacheFileIOListener> mCallback;
-  RefPtr<CacheFileIOManager>  mIOMan;
+  RefPtr<CacheFileIOManager>    mIOMan;
 };
 
 class ReleaseNSPRHandleEvent : public nsRunnable {
 public:
   explicit ReleaseNSPRHandleEvent(CacheFileHandle *aHandle)
     : mHandle(aHandle)
   {
     MOZ_COUNT_CTOR(ReleaseNSPRHandleEvent);
@@ -800,17 +832,17 @@ public:
     if (mHandle->mFD && !mHandle->IsClosed()) {
       CacheFileIOManager::gInstance->ReleaseNSPRHandleInternal(mHandle);
     }
 
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
 };
 
 class TruncateSeekSetEOFEvent : public nsRunnable {
 public:
   TruncateSeekSetEOFEvent(CacheFileHandle *aHandle, int64_t aTruncatePos,
                           int64_t aEOFPos, CacheFileIOListener *aCallback)
     : mHandle(aHandle)
     , mTruncatePos(aTruncatePos)
@@ -841,17 +873,17 @@ public:
     if (mCallback) {
       mCallback->OnEOFSet(mHandle, rv);
     }
 
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
   int64_t                       mTruncatePos;
   int64_t                       mEOFPos;
   nsCOMPtr<CacheFileIOListener> mCallback;
 };
 
 class RenameFileEvent : public nsRunnable {
 public:
   RenameFileEvent(CacheFileHandle *aHandle, const nsACString &aNewName,
@@ -884,29 +916,30 @@ public:
     if (mCallback) {
       mCallback->OnFileRenamed(mHandle, rv);
     }
 
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
   nsCString                     mNewName;
   nsCOMPtr<CacheFileIOListener> mCallback;
 };
 
 class InitIndexEntryEvent : public nsRunnable {
 public:
   InitIndexEntryEvent(CacheFileHandle *aHandle, uint32_t aAppId,
-                      bool aAnonymous, bool aInBrowser)
+                      bool aAnonymous, bool aInBrowser, bool aPinning)
     : mHandle(aHandle)
     , mAppId(aAppId)
     , mAnonymous(aAnonymous)
     , mInBrowser(aInBrowser)
+    , mPinning(aPinning)
   {
     MOZ_COUNT_CTOR(InitIndexEntryEvent);
   }
 
 protected:
   ~InitIndexEntryEvent()
   {
     MOZ_COUNT_DTOR(InitIndexEntryEvent);
@@ -914,33 +947,34 @@ protected:
 
 public:
   NS_IMETHOD Run()
   {
     if (mHandle->IsClosed() || mHandle->IsDoomed()) {
       return NS_OK;
     }
 
-    CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser);
+    CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser, mPinning);
 
     // We cannot set the filesize before we init the entry. If we're opening
     // an existing entry file, frecency and expiration time will be set after
     // parsing the entry file, but we must set the filesize here since nobody is
     // going to set it if there is no write to the file.
     uint32_t sizeInK = mHandle->FileSizeInK();
     CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, &sizeInK);
 
     return NS_OK;
   }
 
 protected:
   RefPtr<CacheFileHandle> mHandle;
   uint32_t                  mAppId;
   bool                      mAnonymous;
   bool                      mInBrowser;
+  bool                      mPinning;
 };
 
 class UpdateIndexEntryEvent : public nsRunnable {
 public:
   UpdateIndexEntryEvent(CacheFileHandle *aHandle, const uint32_t *aFrecency,
                         const uint32_t *aExpirationTime)
     : mHandle(aHandle)
     , mHasFrecency(false)
@@ -1518,31 +1552,35 @@ CacheFileIOManager::OpenFileInternal(con
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (!mTreeCreated) {
     rv = CreateCacheTree();
     if (NS_FAILED(rv)) return rv;
   }
 
+  CacheFileHandle::PinningStatus pinning = aFlags & PINNED
+    ? CacheFileHandle::PinningStatus::PINNED
+    : CacheFileHandle::PinningStatus::NON_PINNED;
+
   nsCOMPtr<nsIFile> file;
   rv = GetFile(aHash, getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
 
   RefPtr<CacheFileHandle> handle;
   mHandles.GetHandle(aHash, getter_AddRefs(handle));
 
   if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
     if (handle) {
       rv = DoomFileInternal(handle);
       NS_ENSURE_SUCCESS(rv, rv);
       handle = nullptr;
     }
 
-    rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
+    rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning, getter_AddRefs(handle));
     NS_ENSURE_SUCCESS(rv, rv);
 
     bool exists;
     rv = file->Exists(&exists);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (exists) {
       CacheIndex::RemoveEntry(aHash);
@@ -1562,44 +1600,55 @@ CacheFileIOManager::OpenFileInternal(con
     handle->mFileSize = 0;
   }
 
   if (handle) {
     handle.swap(*_retval);
     return NS_OK;
   }
 
-  bool exists;
+  bool exists, evictedAsPinned = false, evictedAsNonPinned = false;
   rv = file->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (exists && mContextEvictor) {
     if (mContextEvictor->ContextsCount() == 0) {
       mContextEvictor = nullptr;
     } else {
-      bool wasEvicted = false;
-      mContextEvictor->WasEvicted(aKey, file, &wasEvicted);
-      if (wasEvicted) {
-        LOG(("CacheFileIOManager::OpenFileInternal() - Removing file since the "
-             "entry was evicted by EvictByContext()"));
-        exists = false;
-        file->Remove(false);
-        CacheIndex::RemoveEntry(aHash);
-      }
+      mContextEvictor->WasEvicted(aKey, file, &evictedAsPinned, &evictedAsNonPinned);
     }
   }
 
   if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
+  if (exists) {
+    // For existing files we determine the pinning status later, after the metadata gets parsed.
+    pinning = CacheFileHandle::PinningStatus::UNKNOWN;
+  }
+
+  rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning, getter_AddRefs(handle));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (exists) {
+    // If this file has been found evicted through the context file evictor above for
+    // any of pinned or non-pinned state, these calls ensure we doom the handle ASAP
+    // we know the real pinning state after metadta has been parsed.  DoomFileInternal
+    // on the |handle| doesn't doom right now, since the pinning state is unknown
+    // and we pass down a pinning restriction.
+    if (evictedAsPinned) {
+      rv = DoomFileInternal(handle, DOOM_WHEN_PINNED);
+      MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
+    }
+    if (evictedAsNonPinned) {
+      rv = DoomFileInternal(handle, DOOM_WHEN_NON_PINNED);
+      MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
+    }
+
     rv = file->GetFileSize(&handle->mFileSize);
     NS_ENSURE_SUCCESS(rv, rv);
 
     handle->mFileExists = true;
 
     CacheIndex::EnsureEntryExists(aHash);
   } else {
     handle->mFileSize = 0;
@@ -1647,17 +1696,17 @@ CacheFileIOManager::OpenSpecialFileInter
 
   if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
     if (handle) {
       rv = DoomFileInternal(handle);
       NS_ENSURE_SUCCESS(rv, rv);
       handle = nullptr;
     }
 
-    handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
+    handle = new CacheFileHandle(aKey, aFlags & PRIORITY, CacheFileHandle::PinningStatus::NON_PINNED);
     mSpecialHandles.AppendElement(handle);
 
     bool exists;
     rv = file->Exists(&exists);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (exists) {
       LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
@@ -1682,17 +1731,17 @@ CacheFileIOManager::OpenSpecialFileInter
   bool exists;
   rv = file->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
+  handle = new CacheFileHandle(aKey, aFlags & PRIORITY, CacheFileHandle::PinningStatus::NON_PINNED);
   mSpecialHandles.AppendElement(handle);
 
   if (exists) {
     rv = file->GetFileSize(&handle->mFileSize);
     NS_ENSURE_SUCCESS(rv, rv);
 
     handle->mFileExists = true;
   } else {
@@ -1979,27 +2028,62 @@ CacheFileIOManager::DoomFile(CacheFileHa
     ? CacheIOThread::OPEN_PRIORITY
     : CacheIOThread::OPEN);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
-CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle)
+CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle,
+                                     PinningDoomRestriction aPinningDoomRestriction)
 {
   LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle));
   aHandle->Log();
 
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
   nsresult rv;
 
   if (aHandle->IsDoomed()) {
     return NS_OK;
   }
 
+  if (aPinningDoomRestriction > NO_RESTRICTION) {
+    switch (aHandle->mPinning) {
+    case CacheFileHandle::PinningStatus::NON_PINNED:
+      if (MOZ_LIKELY(aPinningDoomRestriction != DOOM_WHEN_NON_PINNED)) {
+        LOG(("  not dooming, it's a non-pinned handle"));
+        return NS_OK;
+      }
+      // Doom now
+      break;
+
+    case CacheFileHandle::PinningStatus::PINNED:
+      if (MOZ_UNLIKELY(aPinningDoomRestriction != DOOM_WHEN_PINNED)) {
+        LOG(("  not dooming, it's a pinned handle"));
+        return NS_OK;
+      }
+      // Doom now
+      break;
+
+    case CacheFileHandle::PinningStatus::UNKNOWN:
+      if (MOZ_LIKELY(aPinningDoomRestriction == DOOM_WHEN_NON_PINNED)) {
+        LOG(("  doom when non-pinned set"));
+        aHandle->mDoomWhenFoundNonPinned = true;
+      } else if (MOZ_UNLIKELY(aPinningDoomRestriction == DOOM_WHEN_PINNED)) {
+        LOG(("  doom when pinned set"));
+        aHandle->mDoomWhenFoundPinned = true;
+      }
+
+      LOG(("  pinning status not known, deferring doom decision"));
+      return NS_OK;
+    }
+  }
+
   if (aHandle->mFileExists) {
     // we need to move the current file to the doomed directory
     if (aHandle->mFD) {
       ReleaseNSPRHandleInternal(aHandle);
     }
 
     // find unused filename
     nsCOMPtr<nsIFile> file;
@@ -2779,59 +2863,70 @@ CacheFileIOManager::EvictAllInternal()
 
   CacheIndex::RemoveAll();
 
   return NS_OK;
 }
 
 // static
 nsresult
-CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
+CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned)
 {
   LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
        aLoadContextInfo));
 
   nsresult rv;
   RefPtr<CacheFileIOManager> ioMan = gInstance;
 
   if (!ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsCOMPtr<nsIRunnable> ev;
-  ev = NS_NewRunnableMethodWithArg<nsCOMPtr<nsILoadContextInfo> >
-         (ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo);
+  ev = NS_NewRunnableMethodWithArgs<nsCOMPtr<nsILoadContextInfo>, bool>
+         (ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo, aPinned);
 
   rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
-CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo)
+CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo, bool aPinned)
 {
-  nsAutoCString suffix;
-  aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);
-  LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
-       "anonymous=%u, suffix=%s]", aLoadContextInfo, aLoadContextInfo->IsAnonymous(),
-       suffix.get()));
+  LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, pinned=%d]",
+      aLoadContextInfo, aPinned));
 
   nsresult rv;
 
-  MOZ_ASSERT(mIOThread->IsCurrentThread());
-
-  MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
-  if (aLoadContextInfo->IsPrivate()) {
-    return NS_ERROR_INVALID_ARG;
+  if (aLoadContextInfo) {
+    nsAutoCString suffix;
+    aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);
+    LOG(("  anonymous=%u, suffix=%s]", aLoadContextInfo->IsAnonymous(), suffix.get()));
+
+    MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+    MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
+    if (aLoadContextInfo->IsPrivate()) {
+      return NS_ERROR_INVALID_ARG;
+    }
   }
 
   if (!mCacheDirectory) {
+    // This is a kind of hack. Somebody called EvictAll() without a profile.
+    // This happens in xpcshell tests that use cache without profile. We need
+    // to notify observers in this case since the tests are waiting for it.
+    // Also notify for aPinned == true, those are interested as well.
+    if (!aLoadContextInfo) {
+      RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+      NS_DispatchToMainThread(r);
+    }
     return NS_ERROR_FILE_INVALID_PATH;
   }
 
   if (mShuttingDown) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (!mTreeCreated) {
@@ -2841,42 +2936,56 @@ CacheFileIOManager::EvictByContextIntern
     }
   }
 
   // Doom all active handles that matches the load context
   nsTArray<RefPtr<CacheFileHandle> > handles;
   mHandles.GetActiveHandles(&handles);
 
   for (uint32_t i = 0; i < handles.Length(); ++i) {
-    bool equals;
-    rv = CacheFileUtils::KeyMatchesLoadContextInfo(handles[i]->Key(),
-                                                   aLoadContextInfo,
-                                                   &equals);
-    if (NS_FAILED(rv)) {
-      LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
-           "handle! [handle=%p, key=%s]", handles[i].get(),
-           handles[i]->Key().get()));
-      MOZ_CRASH("Unexpected error!");
-    }
-
-    if (equals) {
-      rv = DoomFileInternal(handles[i]);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
-             " [handle=%p]", handles[i].get()));
+    CacheFileHandle* handle = handles[i];
+
+    if (aLoadContextInfo) {
+      bool equals;
+      rv = CacheFileUtils::KeyMatchesLoadContextInfo(handle->Key(),
+                                                     aLoadContextInfo,
+                                                     &equals);
+      if (NS_FAILED(rv)) {
+        LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
+             "handle! [handle=%p, key=%s]", handle, handle->Key().get()));
+        MOZ_CRASH("Unexpected error!");
+      }
+
+      if (!equals) {
+        continue;
       }
     }
+
+    // handle will be doomed only when pinning status is known and equal or
+    // doom decision will be deferred until pinning status is determined.
+    rv = DoomFileInternal(handle, aPinned
+                                  ? CacheFileIOManager::DOOM_WHEN_PINNED
+                                  : CacheFileIOManager::DOOM_WHEN_NON_PINNED);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
+            " [handle=%p]", handle));
+    }
+  }
+
+  if (!aLoadContextInfo) {
+    RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+    NS_DispatchToMainThread(r);
   }
 
   if (!mContextEvictor) {
     mContextEvictor = new CacheFileContextEvictor();
     mContextEvictor->Init(mCacheDirectory);
   }
 
-  mContextEvictor->AddContext(aLoadContextInfo);
+  mContextEvictor->AddContext(aLoadContextInfo, aPinned);
 
   return NS_OK;
 }
 
 // static
 nsresult
 CacheFileIOManager::CacheIndexStateChanged()
 {
@@ -3238,34 +3347,35 @@ CacheFileIOManager::FindTrashDirToRemove
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 // static
 nsresult
 CacheFileIOManager::InitIndexEntry(CacheFileHandle *aHandle,
                                    uint32_t         aAppId,
                                    bool             aAnonymous,
-                                   bool             aInBrowser)
+                                   bool             aInBrowser,
+                                   bool             aPinning)
 {
   LOG(("CacheFileIOManager::InitIndexEntry() [handle=%p, appId=%u, anonymous=%d"
-       ", inBrowser=%d]", aHandle, aAppId, aAnonymous, aInBrowser));
+       ", inBrowser=%d, pinned=%d]", aHandle, aAppId, aAnonymous, aInBrowser, aPinning));
 
   nsresult rv;
   RefPtr<CacheFileIOManager> ioMan = gInstance;
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (aHandle->IsSpecialFile()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   RefPtr<InitIndexEntryEvent> ev =
-    new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser);
+    new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser, aPinning);
   rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 // static
 nsresult
--- a/netwerk/cache2/CacheFileIOManager.h
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -24,45 +24,55 @@ class nsIFile;
 class nsITimer;
 class nsIDirectoryEnumerator;
 class nsILoadContextInfo;
 
 namespace mozilla {
 namespace net {
 
 class CacheFile;
+class CacheFileIOListener;
+
 #ifdef DEBUG_HANDLES
 class CacheFileHandlesEntry;
 #endif
 
 #define ENTRIES_DIR "entries"
 #define DOOMED_DIR  "doomed"
 #define TRASH_DIR   "trash"
 
 
 class CacheFileHandle : public nsISupports
 {
 public:
+  enum class PinningStatus : uint32_t {
+    UNKNOWN,
+    NON_PINNED,
+    PINNED
+  };
+
   NS_DECL_THREADSAFE_ISUPPORTS
   bool DispatchRelease();
 
-  CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority);
-  CacheFileHandle(const nsACString &aKey, bool aPriority);
-  CacheFileHandle(const CacheFileHandle &aOther);
+  CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority, PinningStatus aPinning);
+  CacheFileHandle(const nsACString &aKey, bool aPriority, PinningStatus aPinning);
   void Log();
   bool IsDoomed() const { return mIsDoomed; }
   const SHA1Sum::Hash *Hash() const { return mHash; }
   int64_t FileSize() const { return mFileSize; }
   uint32_t FileSizeInK() const;
   bool IsPriority() const { return mPriority; }
   bool FileExists() const { return mFileExists; }
   bool IsClosed() const { return mClosed; }
   bool IsSpecialFile() const { return mSpecialFile; }
   nsCString & Key() { return mKey; }
 
+  // Returns false when this handle has been doomed based on the pinning state update.
+  bool SetPinned(bool aPinned);
+
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 private:
   friend class CacheFileIOManager;
   friend class CacheFileHandles;
   friend class ReleaseNSPRHandleEvent;
@@ -74,29 +84,46 @@ private:
   bool                 mPriority;
   bool                 mClosed;
   bool                 mSpecialFile;
   bool                 mInvalid;
   bool                 mFileExists; // This means that the file should exists,
                                     // but it can be still deleted by OS/user
                                     // and then a subsequent OpenNSPRFileDesc()
                                     // will fail.
+
+  // For existing files this is always pre-set to UNKNOWN.  The status is udpated accordingly
+  // after the matadata has been parsed.
+  // For new files the flag is set according to which storage kind is opening
+  // the cache entry and remains so for the handle's lifetime.
+  // The status can only change from UNKNOWN (if set so initially) to one of PINNED or NON_PINNED
+  // and it stays unchanged afterwards.
+  // This status is only accessed on the IO thread.
+  PinningStatus        mPinning;
+  // Both initially false.  Can be raised to true only when this handle is to be doomed
+  // during the period when the pinning status is unknown.  After the pinning status
+  // determination we check these flags and possibly doom.
+  // These flags are only accessed on the IO thread.
+  bool                 mDoomWhenFoundPinned : 1;
+  bool                 mDoomWhenFoundNonPinned : 1;
+
   nsCOMPtr<nsIFile>    mFile;
   int64_t              mFileSize;
   PRFileDesc          *mFD;  // if null then the file doesn't exists on the disk
   nsCString            mKey;
 };
 
 class CacheFileHandles {
 public:
   CacheFileHandles();
   ~CacheFileHandles();
 
   nsresult GetHandle(const SHA1Sum::Hash *aHash, CacheFileHandle **_retval);
-  nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority, CacheFileHandle **_retval);
+  nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority,
+                     CacheFileHandle::PinningStatus aPinning, CacheFileHandle **_retval);
   void     RemoveHandle(CacheFileHandle *aHandlle);
   void     GetAllHandles(nsTArray<RefPtr<CacheFileHandle> > *_retval);
   void     GetActiveHandles(nsTArray<RefPtr<CacheFileHandle> > *_retval);
   void     ClearAll();
   uint32_t HandleCount();
 
 #ifdef DEBUG_HANDLES
   void     Log(CacheFileHandlesEntry *entry);
@@ -207,21 +234,22 @@ NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileI
 
 class CacheFileIOManager : public nsITimerCallback
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITIMERCALLBACK
 
   enum {
-    OPEN         = 0U,
-    CREATE       = 1U,
-    CREATE_NEW   = 2U,
-    PRIORITY     = 4U,
-    SPECIAL_FILE = 8U
+    OPEN         =  0U,
+    CREATE       =  1U,
+    CREATE_NEW   =  2U,
+    PRIORITY     =  4U,
+    SPECIAL_FILE =  8U,
+    PINNED       = 16U
   };
 
   CacheFileIOManager();
 
   static nsresult Init();
   static nsresult Shutdown();
   static nsresult OnProfile();
   static already_AddRefed<nsIEventTarget> IOTarget();
@@ -242,35 +270,50 @@ public:
   static nsresult OpenFile(const nsACString &aKey,
                            uint32_t aFlags, CacheFileIOListener *aCallback);
   static nsresult Read(CacheFileHandle *aHandle, int64_t aOffset,
                        char *aBuf, int32_t aCount,
                        CacheFileIOListener *aCallback);
   static nsresult Write(CacheFileHandle *aHandle, int64_t aOffset,
                         const char *aBuf, int32_t aCount, bool aValidate,
                         bool aTruncate, CacheFileIOListener *aCallback);
+  // PinningDoomRestriction:
+  // NO_RESTRICTION
+  //    no restriction is checked, the file is simply always doomed
+  // DOOM_WHEN_(NON)_PINNED, we branch based on the pinning status of the handle:
+  //   UNKNOWN: the handle is marked to be doomed when later found (non)pinned
+  //   PINNED/NON_PINNED: doom only when the restriction matches the pin status
+  //      and the handle has not yet been required to doom during the UNKNOWN
+  //      period
+  enum PinningDoomRestriction {
+    NO_RESTRICTION,
+    DOOM_WHEN_NON_PINNED,
+    DOOM_WHEN_PINNED
+  };
   static nsresult DoomFile(CacheFileHandle *aHandle,
                            CacheFileIOListener *aCallback);
   static nsresult DoomFileByKey(const nsACString &aKey,
                                 CacheFileIOListener *aCallback);
   static nsresult ReleaseNSPRHandle(CacheFileHandle *aHandle);
   static nsresult TruncateSeekSetEOF(CacheFileHandle *aHandle,
                                      int64_t aTruncatePos, int64_t aEOFPos,
                                      CacheFileIOListener *aCallback);
   static nsresult RenameFile(CacheFileHandle *aHandle,
                              const nsACString &aNewName,
                              CacheFileIOListener *aCallback);
   static nsresult EvictIfOverLimit();
   static nsresult EvictAll();
-  static nsresult EvictByContext(nsILoadContextInfo *aLoadContextInfo);
+  static nsresult EvictByContext(nsILoadContextInfo *aLoadContextInfo,
+                                 bool aPinning);
 
   static nsresult InitIndexEntry(CacheFileHandle *aHandle,
                                  uint32_t         aAppId,
                                  bool             aAnonymous,
-                                 bool             aInBrowser);
+                                 bool             aInBrowser,
+                                 bool             aPinning);
   static nsresult UpdateIndexEntry(CacheFileHandle *aHandle,
                                    const uint32_t  *aFrecency,
                                    const uint32_t  *aExpirationTime);
 
   static nsresult UpdateIndexEntry();
 
   enum EEnumerateMode {
     ENTRIES,
@@ -324,27 +367,29 @@ private:
                                    uint32_t aFlags,
                                    CacheFileHandle **_retval);
   nsresult CloseHandleInternal(CacheFileHandle *aHandle);
   nsresult ReadInternal(CacheFileHandle *aHandle, int64_t aOffset,
                         char *aBuf, int32_t aCount);
   nsresult WriteInternal(CacheFileHandle *aHandle, int64_t aOffset,
                          const char *aBuf, int32_t aCount, bool aValidate,
                          bool aTruncate);
-  nsresult DoomFileInternal(CacheFileHandle *aHandle);
+  nsresult DoomFileInternal(CacheFileHandle *aHandle,
+                            PinningDoomRestriction aPinningStatusRestriction = NO_RESTRICTION);
   nsresult DoomFileByKeyInternal(const SHA1Sum::Hash *aHash);
   nsresult ReleaseNSPRHandleInternal(CacheFileHandle *aHandle);
   nsresult TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
                                       int64_t aTruncatePos, int64_t aEOFPos);
   nsresult RenameFileInternal(CacheFileHandle *aHandle,
                               const nsACString &aNewName);
   nsresult EvictIfOverLimitInternal();
   nsresult OverLimitEvictionInternal();
   nsresult EvictAllInternal();
-  nsresult EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo);
+  nsresult EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo,
+                                  bool aPinning);
 
   nsresult TrashDirectory(nsIFile *aFile);
   static void OnTrashTimer(nsITimer *aTimer, void *aClosure);
   nsresult StartRemovingTrash();
   nsresult RemoveTrashInternal();
   nsresult FindTrashDirToRemove();
 
   nsresult CreateFile(CacheFileHandle *aHandle);
@@ -378,37 +423,37 @@ private:
   nsresult UpdateSmartCacheSize(int64_t aFreeSpace);
 
   // Memory reporting (private part)
   size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
 
   static CacheFileIOManager           *gInstance;
   TimeStamp                            mStartTime;
   bool                                 mShuttingDown;
-  RefPtr<CacheIOThread>              mIOThread;
+  RefPtr<CacheIOThread>                mIOThread;
   nsCOMPtr<nsIFile>                    mCacheDirectory;
 #if defined(MOZ_WIDGET_ANDROID)
   // On Android we add the active profile directory name between the path
   // and the 'cache2' leaf name.  However, to delete any leftover data from
   // times before we were doing it, we still need to access the directory
   // w/o the profile name in the path.  Here it is stored.
   nsCOMPtr<nsIFile>                    mCacheProfilelessDirectory;
 #endif
   bool                                 mTreeCreated;
   CacheFileHandles                     mHandles;
   nsTArray<CacheFileHandle *>          mHandlesByLastUsed;
   nsTArray<CacheFileHandle *>          mSpecialHandles;
-  nsTArray<RefPtr<CacheFile> >       mScheduledMetadataWrites;
+  nsTArray<RefPtr<CacheFile> >         mScheduledMetadataWrites;
   nsCOMPtr<nsITimer>                   mMetadataWritesTimer;
   bool                                 mOverLimitEvicting;
   bool                                 mRemovingTrashDirs;
   nsCOMPtr<nsITimer>                   mTrashTimer;
   nsCOMPtr<nsIFile>                    mTrashDir;
   nsCOMPtr<nsIDirectoryEnumerator>     mTrashDirEnumerator;
   nsTArray<nsCString>                  mFailedTrashDirs;
-  RefPtr<CacheFileContextEvictor>    mContextEvictor;
+  RefPtr<CacheFileContextEvictor>      mContextEvictor;
   TimeStamp                            mLastSmartSizeTime;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/cache2/CacheFileInputStream.h
+++ b/netwerk/cache2/CacheFileInputStream.h
@@ -48,17 +48,17 @@ private:
   void EnsureCorrectChunk(bool aReleaseOnly);
 
   // CanRead returns negative value when output stream truncates the data before
   // the input stream's mPos.
   void CanRead(int64_t *aCanRead, const char **aBuf);
   void NotifyListener();
   void MaybeNotifyListener();
 
-  RefPtr<CacheFile>      mFile;
+  RefPtr<CacheFile>        mFile;
   RefPtr<CacheFileChunk> mChunk;
   int64_t                  mPos;
   bool                     mClosed;
   nsresult                 mStatus;
   bool                     mWaitingForUpdate;
   int64_t                  mListeningForChunk;
 
   nsCOMPtr<nsIInputStreamCallback> mCallback;
--- a/netwerk/cache2/CacheFileMetadata.cpp
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -30,18 +30,16 @@ namespace net {
 #define kInitialHashArraySize 1
 
 // Initial elements buffer size.
 #define kInitialBufSize 64
 
 // Max size of elements in bytes.
 #define kMaxElementsSize 64*1024
 
-#define kCacheEntryVersion 1
-
 #define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))
 
 NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)
 
 CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString &aKey)
   : CacheMemoryConsumer(NORMAL)
   , mHandle(aHandle)
   , mHashArray(nullptr)
@@ -66,17 +64,17 @@ CacheFileMetadata::CacheFileMetadata(Cac
   mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
   mKey = aKey;
 
   DebugOnly<nsresult> rv;
   rv = ParseKey(aKey);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
-CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, const nsACString &aKey)
+CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, bool aPinned, const nsACString &aKey)
   : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL)
   , mHandle(nullptr)
   , mHashArray(nullptr)
   , mHashArraySize(0)
   , mHashCount(0)
   , mOffset(0)
   , mBuf(nullptr)
   , mBufSize(0)
@@ -88,16 +86,19 @@ CacheFileMetadata::CacheFileMetadata(boo
   , mFirstRead(true)
 {
   LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]",
        this, PromiseFlatCString(aKey).get()));
 
   MOZ_COUNT_CTOR(CacheFileMetadata);
   memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
   mMetaHdr.mVersion = kCacheEntryVersion;
+  if (aPinned) {
+    AddFlags(kCacheEntryIsPinned);
+  }
   mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
   mKey = aKey;
   mMetaHdr.mKeySize = mKey.Length();
 
   DebugOnly<nsresult> rv;
   rv = ParseKey(aKey);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
@@ -530,16 +531,39 @@ CacheFileMetadata::SetHash(uint32_t aInd
   NetworkEndian::writeUint16(&mHashArray[aIndex], aHash);
 
   DoMemoryReport(MemoryUsage());
 
   return NS_OK;
 }
 
 nsresult
+CacheFileMetadata::AddFlags(uint32_t aFlags)
+{
+  MarkDirty(false);
+  mMetaHdr.mFlags |= aFlags;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::RemoveFlags(uint32_t aFlags)
+{
+  MarkDirty(false);
+  mMetaHdr.mFlags &= ~aFlags;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetFlags(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mFlags;
+  return NS_OK;
+}
+
+nsresult
 CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime)
 {
   LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
        this, aExpirationTime));
 
   MarkDirty(false);
   mMetaHdr.mExpirationTime = aExpirationTime;
   return NS_OK;
@@ -809,21 +833,26 @@ CacheFileMetadata::InitEmptyMetadata()
     mBufSize = 0;
   }
   mOffset = 0;
   mMetaHdr.mVersion = kCacheEntryVersion;
   mMetaHdr.mFetchCount = 0;
   mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
   mMetaHdr.mKeySize = mKey.Length();
 
+  // Deliberately not touching the "kCacheEntryIsPinned" flag.
+
   DoMemoryReport(MemoryUsage());
 
   // We're creating a new entry. If there is any old data truncate it.
-  if (mHandle && mHandle->FileExists() && mHandle->FileSize()) {
-    CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
+  if (mHandle) {
+    mHandle->SetPinned(Pinned());
+    if (mHandle->FileExists() && mHandle->FileSize()) {
+      CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
+    }
   }
 }
 
 nsresult
 CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset,
                                  bool aHaveKey)
 {
   LOG(("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
@@ -848,22 +877,29 @@ CacheFileMetadata::ParseMetadata(uint32_
   if (keyOffset > metaposOffset) {
     LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
          this));
     return NS_ERROR_FILE_CORRUPTED;
   }
 
   mMetaHdr.ReadFromBuf(mBuf + hdrOffset);
 
-  if (mMetaHdr.mVersion != kCacheEntryVersion) {
+  if (mMetaHdr.mVersion == 1) {
+    // Backward compatibility before we've added flags to the header
+    keyOffset -= sizeof(uint32_t);
+  } else if (mMetaHdr.mVersion != kCacheEntryVersion) {
     LOG(("CacheFileMetadata::ParseMetadata() - Not a version we understand to. "
          "[version=0x%x, this=%p]", mMetaHdr.mVersion, this));
     return NS_ERROR_UNEXPECTED;
   }
 
+  // Update the version stored in the header to make writes
+  // store the header in the current version form.
+  mMetaHdr.mVersion = kCacheEntryVersion;
+
   uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1;
 
   if (elementsOffset > metaposOffset) {
     LOG(("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
          "[this=%p]", elementsOffset, this));
     return NS_ERROR_FILE_CORRUPTED;
   }
 
@@ -912,16 +948,24 @@ CacheFileMetadata::ParseMetadata(uint32_
     return NS_ERROR_FILE_CORRUPTED;
   }
 
   // check elements
   rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
   if (NS_FAILED(rv))
     return rv;
 
+  if (mHandle) {
+    if (!mHandle->SetPinned(Pinned())) {
+      LOG(("CacheFileMetadata::ParseMetadata() - handle was doomed for this "
+           "pinning state, truncate the file [this=%p, pinned=%d]", this, Pinned()));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+  }
+
   mHashArraySize = hashesLen;
   mHashCount = hashCount;
   if (mHashArraySize) {
     mHashArray = static_cast<CacheHash::Hash16_t *>(
                    moz_xmalloc(mHashArraySize));
     memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
   }
 
--- a/netwerk/cache2/CacheFileMetadata.h
+++ b/netwerk/cache2/CacheFileMetadata.h
@@ -14,73 +14,90 @@
 #include "nsAutoPtr.h"
 #include "nsString.h"
 
 class nsICacheEntryMetaDataVisitor;
 
 namespace mozilla {
 namespace net {
 
+// Flags stored in CacheFileMetadataHeader.mFlags
+
+// Whether an entry is a pinned entry (created with
+// nsICacheStorageService.pinningCacheStorage.)
+static const uint32_t kCacheEntryIsPinned = 1 << 0;
+
 // By multiplying with the current half-life we convert the frecency
 // to time independent of half-life value.  The range fits 32bits.
 // When decay time changes on next run of the browser, we convert
 // the frecency value to a correct internal representation again.
 // It might not be 100% accurate, but for the purpose it suffice.
 #define FRECENCY2INT(aFrecency) \
   ((uint32_t)((aFrecency) * CacheObserver::HalfLifeSeconds()))
 #define INT2FRECENCY(aInt) \
   ((double)(aInt) / (double)CacheObserver::HalfLifeSeconds())
 
 
+#define kCacheEntryVersion 2
+
+
 #pragma pack(push)
 #pragma pack(1)
 
 class CacheFileMetadataHeader {
 public:
   uint32_t        mVersion;
   uint32_t        mFetchCount;
   uint32_t        mLastFetched;
   uint32_t        mLastModified;
   uint32_t        mFrecency;
   uint32_t        mExpirationTime;
   uint32_t        mKeySize;
+  uint32_t        mFlags;
 
   void WriteToBuf(void *aBuf)
   {
     EnsureCorrectClassSize();
 
     uint8_t* ptr = static_cast<uint8_t*>(aBuf);
+    MOZ_ASSERT(mVersion == kCacheEntryVersion);
     NetworkEndian::writeUint32(ptr, mVersion); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mFetchCount); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mLastFetched); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mLastModified); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mFrecency); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mExpirationTime); ptr += sizeof(uint32_t);
-    NetworkEndian::writeUint32(ptr, mKeySize);
+    NetworkEndian::writeUint32(ptr, mKeySize); ptr += sizeof(uint32_t);
+    NetworkEndian::writeUint32(ptr, mFlags);
   }
 
   void ReadFromBuf(const void *aBuf)
   {
     EnsureCorrectClassSize();
 
     const uint8_t* ptr = static_cast<const uint8_t*>(aBuf);
     mVersion = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mFetchCount = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mLastFetched = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mLastModified = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mFrecency = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mExpirationTime = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
-    mKeySize = BigEndian::readUint32(ptr);
+    mKeySize = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
+    if (mVersion >= kCacheEntryVersion) {
+      mFlags = BigEndian::readUint32(ptr);
+    } else {
+      mFlags = 0;
+    }
   }
 
   inline void EnsureCorrectClassSize()
   {
     static_assert((sizeof(mVersion) + sizeof(mFetchCount) +
       sizeof(mLastFetched) + sizeof(mLastModified) + sizeof(mFrecency) +
-      sizeof(mExpirationTime) + sizeof(mKeySize)) ==
+      sizeof(mExpirationTime) + sizeof(mKeySize)) + sizeof(mFlags) ==
       sizeof(CacheFileMetadataHeader),
       "Unexpected sizeof(CacheFileMetadataHeader)!");
   }
 };
 
 #pragma pack(pop)
 
 
@@ -109,39 +126,44 @@ class CacheFileMetadata : public CacheFi
                         , public CacheMemoryConsumer
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   CacheFileMetadata(CacheFileHandle *aHandle,
                     const nsACString &aKey);
   CacheFileMetadata(bool aMemoryOnly,
+                    bool aPinned,
                     const nsACString &aKey);
   CacheFileMetadata();
 
   void SetHandle(CacheFileHandle *aHandle);
 
   nsresult GetKey(nsACString &_retval);
 
   nsresult ReadMetadata(CacheFileMetadataListener *aListener);
   uint32_t CalcMetadataSize(uint32_t aElementsSize, uint32_t aHashCount);
   nsresult WriteMetadata(uint32_t aOffset,
                          CacheFileMetadataListener *aListener);
   nsresult SyncReadMetadata(nsIFile *aFile);
 
-  bool     IsAnonymous() { return mAnonymous; }
+  bool     IsAnonymous() const { return mAnonymous; }
   mozilla::OriginAttributes const & OriginAttributes() const { return mOriginAttributes; }
+  bool     Pinned() const      { return !!(mMetaHdr.mFlags & kCacheEntryIsPinned); }
 
   const char * GetElement(const char *aKey);
   nsresult     SetElement(const char *aKey, const char *aValue);
   nsresult     Visit(nsICacheEntryMetaDataVisitor *aVisitor);
 
   CacheHash::Hash16_t GetHash(uint32_t aIndex);
   nsresult            SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash);
 
+  nsresult AddFlags(uint32_t aFlags);
+  nsresult RemoveFlags(uint32_t aFlags);
+  nsresult GetFlags(uint32_t *_retval);
   nsresult SetExpirationTime(uint32_t aExpirationTime);
   nsresult GetExpirationTime(uint32_t *_retval);
   nsresult SetFrecency(uint32_t aFrecency);
   nsresult GetFrecency(uint32_t *_retval);
   nsresult GetLastModified(uint32_t *_retval);
   nsresult GetLastFetched(uint32_t *_retval);
   nsresult GetFetchCount(uint32_t *_retval);
   // Called by upper layers to indicate the entry this metadata belongs
@@ -170,17 +192,17 @@ private:
   virtual ~CacheFileMetadata();
 
   void     InitEmptyMetadata();
   nsresult ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset, bool aHaveKey);
   nsresult CheckElements(const char *aBuf, uint32_t aSize);
   nsresult EnsureBuffer(uint32_t aSize);
   nsresult ParseKey(const nsACString &aKey);
 
-  RefPtr<CacheFileHandle>           mHandle;
+  RefPtr<CacheFileHandle>             mHandle;
   nsCString                           mKey;
   CacheHash::Hash16_t                *mHashArray;
   uint32_t                            mHashArraySize;
   uint32_t                            mHashCount;
   int64_t                             mOffset;
   char                               *mBuf; // used for parsing, then points
                                             // to elements
   uint32_t                            mBufSize;
--- a/netwerk/cache2/CacheFileOutputStream.h
+++ b/netwerk/cache2/CacheFileOutputStream.h
@@ -45,17 +45,17 @@ private:
   virtual ~CacheFileOutputStream();
 
   nsresult CloseWithStatusLocked(nsresult aStatus);
   void ReleaseChunk();
   void EnsureCorrectChunk(bool aReleaseOnly);
   void FillHole();
   void NotifyListener();
 
-  RefPtr<CacheFile>      mFile;
+  RefPtr<CacheFile>        mFile;
   RefPtr<CacheFileChunk> mChunk;
   RefPtr<CacheOutputCloseListener> mCloseListener;
   int64_t                  mPos;
   bool                     mClosed;
   nsresult                 mStatus;
 
   nsCOMPtr<nsIOutputStreamCallback> mCallback;
   uint32_t                          mCallbackFlags;
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -700,21 +700,22 @@ CacheIndex::EnsureEntryExists(const SHA1
   return NS_OK;
 }
 
 // static
 nsresult
 CacheIndex::InitEntry(const SHA1Sum::Hash *aHash,
                       uint32_t             aAppId,
                       bool                 aAnonymous,
-                      bool                 aInBrowser)
+                      bool                 aInBrowser,
+                      bool                 aPinned)
 {
   LOG(("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, appId=%u, "
-       "anonymous=%d, inBrowser=%d]", LOGSHA1(aHash), aAppId, aAnonymous,
-       aInBrowser));
+       "anonymous=%d, inBrowser=%d, pinned=%d]", LOGSHA1(aHash), aAppId,
+       aAnonymous, aInBrowser, aPinned));
 
   RefPtr<CacheIndex> index = gInstance;
 
   if (!index) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
@@ -794,20 +795,20 @@ CacheIndex::InitEntry(const SHA1Sum::Has
         updated->MarkFresh();
       } else {
         entry->InitNew();
         entry->MarkFresh();
       }
     }
 
     if (updated) {
-      updated->Init(aAppId, aAnonymous, aInBrowser);
+      updated->Init(aAppId, aAnonymous, aInBrowser, aPinned);
       updated->MarkDirty();
     } else {
-      entry->Init(aAppId, aAnonymous, aInBrowser);
+      entry->Init(aAppId, aAnonymous, aInBrowser, aPinned);
       entry->MarkDirty();
     }
   }
 
   index->StartUpdatingIndexIfNeeded();
   index->WriteIndexToDiskIfNeeded();
 
   return NS_OK;
@@ -1103,36 +1104,47 @@ CacheIndex::RemoveAll()
     file->Remove(false);
   }
 
   return NS_OK;
 }
 
 // static
 nsresult
-CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval)
+CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval, bool *_pinned)
 {
   LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get()));
 
+  SHA1Sum sum;
+  SHA1Sum::Hash hash;
+  sum.update(aKey.BeginReading(), aKey.Length());
+  sum.finish(hash);
+
+  return HasEntry(hash, _retval, _pinned);
+}
+
+// static
+nsresult
+CacheIndex::HasEntry(const SHA1Sum::Hash &hash, EntryStatus *_retval, bool *_pinned)
+{
   RefPtr<CacheIndex> index = gInstance;
 
   if (!index) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   CacheIndexAutoLock lock(index);
 
   if (!index->IsIndexUsable()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  SHA1Sum sum;
-  SHA1Sum::Hash hash;
-  sum.update(aKey.BeginReading(), aKey.Length());
-  sum.finish(hash);
+  if (_pinned) {
+    *_pinned = false;
+  }
 
   const CacheIndexEntry *entry = nullptr;
 
   switch (index->mState) {
     case READING:
     case WRITING:
       entry = index->mPendingUpdates.GetEntry(hash);
       // no break
@@ -1158,16 +1170,19 @@ CacheIndex::HasEntry(const nsACString &a
     if (entry->IsRemoved()) {
       if (entry->IsFresh()) {
         *_retval = DOES_NOT_EXIST;
       } else {
         *_retval = DO_NOT_KNOW;
       }
     } else {
       *_retval = EXISTS;
+      if (_pinned && entry->IsPinned()) {
+        *_pinned = true;
+      }
     }
   }
 
   LOG(("CacheIndex::HasEntry() - result is %u", *_retval));
   return NS_OK;
 }
 
 // static
@@ -1188,25 +1203,29 @@ CacheIndex::GetEntryForEviction(bool aIg
   if (!index->IsIndexUsable()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   SHA1Sum::Hash hash;
   bool foundEntry = false;
   uint32_t i;
 
-  // find first non-forced valid entry with the lowest frecency
+  // find first non-forced valid and unpinned entry with the lowest frecency
   index->mFrecencyArray.Sort(FrecencyComparator());
   for (i = 0; i < index->mFrecencyArray.Length(); ++i) {
     memcpy(&hash, &index->mFrecencyArray[i]->mHash, sizeof(SHA1Sum::Hash));
 
     if (IsForcedValidEntry(&hash)) {
       continue;
     }
 
+    if (CacheIndexEntry::IsPinned(index->mFrecencyArray[i])) {
+      continue;
+    }
+
     if (aIgnoreEmptyEntries &&
         !CacheIndexEntry::GetFileSize(index->mFrecencyArray[i])) {
       continue;
     }
 
     foundEntry = true;
     break;
   }
@@ -2570,17 +2589,18 @@ CacheIndex::InitEntryFromDiskData(CacheI
 {
   aEntry->InitNew();
   aEntry->MarkDirty();
   aEntry->MarkFresh();
 
   // Bug 1201042 - will pass OriginAttributes directly.
   aEntry->Init(aMetaData->OriginAttributes().mAppId,
                aMetaData->IsAnonymous(),
-               aMetaData->OriginAttributes().mInBrowser);
+               aMetaData->OriginAttributes().mInBrowser,
+               aMetaData->Pinned());
 
   uint32_t expirationTime;
   aMetaData->GetExpirationTime(&expirationTime);
   aEntry->SetExpirationTime(expirationTime);
 
   uint32_t frecency;
   aMetaData->GetFrecency(&frecency);
   aEntry->SetFrecency(frecency);
--- a/netwerk/cache2/CacheIndex.h
+++ b/netwerk/cache2/CacheIndex.h
@@ -143,32 +143,35 @@ public:
   void InitNew()
   {
     mRec->mFrecency = 0;
     mRec->mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
     mRec->mAppId = nsILoadContextInfo::NO_APP_ID;
     mRec->mFlags = 0;
   }
 
-  void Init(uint32_t aAppId, bool aAnonymous, bool aInBrowser)
+  void Init(uint32_t aAppId, bool aAnonymous, bool aInBrowser, bool aPinned)
   {
     MOZ_ASSERT(mRec->mFrecency == 0);
     MOZ_ASSERT(mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME);
     MOZ_ASSERT(mRec->mAppId == nsILoadContextInfo::NO_APP_ID);
     // When we init the entry it must be fresh and may be dirty
     MOZ_ASSERT((mRec->mFlags & ~kDirtyMask) == kFreshMask);
 
     mRec->mAppId = aAppId;
     mRec->mFlags |= kInitializedMask;
     if (aAnonymous) {
       mRec->mFlags |= kAnonymousMask;
     }
     if (aInBrowser) {
       mRec->mFlags |= kInBrowserMask;
     }
+    if (aPinned) {
+      mRec->mFlags |= kPinnedMask;
+    }
   }
 
   const SHA1Sum::Hash * Hash() const { return &mRec->mHash; }
 
   bool IsInitialized() const { return !!(mRec->mFlags & kInitializedMask); }
 
   uint32_t AppId() const { return mRec->mAppId; }
   bool     Anonymous() const { return !!(mRec->mFlags & kAnonymousMask); }
@@ -179,16 +182,18 @@ public:
 
   bool IsDirty() const { return !!(mRec->mFlags & kDirtyMask); }
   void MarkDirty() { mRec->mFlags |= kDirtyMask; }
   void ClearDirty() { mRec->mFlags &= ~kDirtyMask; }
 
   bool IsFresh() const { return !!(mRec->mFlags & kFreshMask); }
   void MarkFresh() { mRec->mFlags |= kFreshMask; }
 
+  bool IsPinned() const { return !!(mRec->mFlags & kPinnedMask); }
+
   void     SetFrecency(uint32_t aFrecency) { mRec->mFrecency = aFrecency; }
   uint32_t GetFrecency() const { return mRec->mFrecency; }
 
   void     SetExpirationTime(uint32_t aExpirationTime)
   {
     mRec->mExpirationTime = aExpirationTime;
   }
   uint32_t GetExpirationTime() const { return mRec->mExpirationTime; }
@@ -205,16 +210,20 @@ public:
     mRec->mFlags |= aFileSize;
   }
   // Returns filesize in kilobytes.
   uint32_t GetFileSize() const { return GetFileSize(mRec); }
   static uint32_t GetFileSize(CacheIndexRecord *aRec)
   {
     return aRec->mFlags & kFileSizeMask;
   }
+  static uint32_t IsPinned(CacheIndexRecord *aRec)
+  {
+    return aRec->mFlags & kPinnedMask;
+  }
   bool     IsFileEmpty() const { return GetFileSize() == 0; }
 
   void WriteToBuf(void *aBuf)
   {
     CacheIndexRecord *dst = reinterpret_cast<CacheIndexRecord *>(aBuf);
 
     // Copy the whole record to the buffer.
     memcpy(aBuf, mRec, sizeof(CacheIndexRecord));
@@ -296,17 +305,20 @@ private:
   // information in index file on disk.
   static const uint32_t kDirtyMask       = 0x08000000;
 
   // This flag is set when the information about the entry is fresh, i.e.
   // we've created or opened this entry during this session, or we've seen
   // this entry during update or build process.
   static const uint32_t kFreshMask       = 0x04000000;
 
-  static const uint32_t kReservedMask    = 0x03000000;
+  // Indicates a pinned entry.
+  static const uint32_t kPinnedMask      = 0x02000000;
+
+  static const uint32_t kReservedMask    = 0x01000000;
 
   // FileSize in kilobytes
   static const uint32_t kFileSizeMask    = 0x00FFFFFF;
 
   nsAutoPtr<CacheIndexRecord> mRec;
 };
 
 class CacheIndexEntryUpdate : public CacheIndexEntry
@@ -605,17 +617,18 @@ public:
   // index is outdated.
   static nsresult EnsureEntryExists(const SHA1Sum::Hash *aHash);
 
   // Initialize the entry. It MUST be present in index. Call to AddEntry() or
   // EnsureEntryExists() must precede the call to this method.
   static nsresult InitEntry(const SHA1Sum::Hash *aHash,
                             uint32_t             aAppId,
                             bool                 aAnonymous,
-                            bool                 aInBrowser);
+                            bool                 aInBrowser,
+                            bool                 aPinned);
 
   // Remove entry from index. The entry should be present in index.
   static nsresult RemoveEntry(const SHA1Sum::Hash *aHash);
 
   // Update some information in entry. The entry MUST be present in index and
   // MUST be initialized. Call to AddEntry() or EnsureEntryExists() and to
   // InitEntry() must precede the call to this method.
   // Pass nullptr if the value didn't change.
@@ -630,22 +643,26 @@ public:
   enum EntryStatus {
     EXISTS         = 0,
     DOES_NOT_EXIST = 1,
     DO_NOT_KNOW    = 2
   };
 
   // Returns status of the entry in index for the given key. It can be called
   // on any thread.
-  static nsresult HasEntry(const nsACString &aKey, EntryStatus *_retval);
+  // If _pinned is non-null, it's filled with pinning status of the entry.
+  static nsresult HasEntry(const nsACString &aKey, EntryStatus *_retval,
+                           bool *_pinned = nullptr);
+  static nsresult HasEntry(const SHA1Sum::Hash &hash, EntryStatus *_retval,
+                           bool *_pinned = nullptr);
 
   // Returns a hash of the least important entry that should be evicted if the
   // cache size is over limit and also returns a total number of all entries in
-  // the index minus the number of forced valid entries that we encounter
-  // when searching (see below)
+  // the index minus the number of forced valid entries and unpinned entries
+  // that we encounter when searching (see below)
   static nsresult GetEntryForEviction(bool aIgnoreEmptyEntries, SHA1Sum::Hash *aHash, uint32_t *aCnt);
 
   // Checks if a cache entry is currently forced valid. Used to prevent an entry
   // (that has been forced valid) from being evicted when the cache size reaches
   // its limit.
   static bool IsForcedValidEntry(const SHA1Sum::Hash *aHash);
 
   // Returns cache size in kB.
@@ -964,31 +981,31 @@ private:
   uint32_t                  mSkipEntries;
   // Number of entries that should be written to disk. This is number of entries
   // in hashtable that are initialized and are not marked as removed when writing
   // begins.
   uint32_t                  mProcessEntries;
   char                     *mRWBuf;
   uint32_t                  mRWBufSize;
   uint32_t                  mRWBufPos;
-  RefPtr<CacheHash>       mRWHash;
+  RefPtr<CacheHash>         mRWHash;
 
   // Reading of journal succeeded if true.
   bool                      mJournalReadSuccessfully;
 
   // Handle used for writing and reading index file.
   RefPtr<CacheFileHandle> mIndexHandle;
   // Handle used for reading journal file.
   RefPtr<CacheFileHandle> mJournalHandle;
   // Used to check the existence of the file during reading process.
   RefPtr<CacheFileHandle> mTmpHandle;
 
-  RefPtr<FileOpenHelper>  mIndexFileOpener;
-  RefPtr<FileOpenHelper>  mJournalFileOpener;
-  RefPtr<FileOpenHelper>  mTmpFileOpener;
+  RefPtr<FileOpenHelper>    mIndexFileOpener;
+  RefPtr<FileOpenHelper>    mJournalFileOpener;
+  RefPtr<FileOpenHelper>    mTmpFileOpener;
 
   // Directory enumerator used when building and updating index.
   nsCOMPtr<nsIDirectoryEnumerator> mDirEnumerator;
 
   // Main index hashtable.
   nsTHashtable<CacheIndexEntry> mIndex;
 
   // We cannot add, remove or change any entry in mIndex in states READING and
--- a/netwerk/cache2/CacheIndexIterator.h
+++ b/netwerk/cache2/CacheIndexIterator.h
@@ -44,17 +44,17 @@ protected:
   bool ShouldBeNewAdded() { return mAddNew; }
   virtual void AddRecord(CacheIndexRecord *aRecord);
   virtual void AddRecords(const nsTArray<CacheIndexRecord *> &aRecords);
   bool RemoveRecord(CacheIndexRecord *aRecord);
   bool ReplaceRecord(CacheIndexRecord *aOldRecord,
                      CacheIndexRecord *aNewRecord);
 
   nsresult                     mStatus;
-  RefPtr<CacheIndex>         mIndex;
+  RefPtr<CacheIndex>           mIndex;
   nsTArray<CacheIndexRecord *> mRecords;
   bool                         mAddNew;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/cache2/CacheStorage.cpp
+++ b/netwerk/cache2/CacheStorage.cpp
@@ -21,21 +21,23 @@
 namespace mozilla {
 namespace net {
 
 NS_IMPL_ISUPPORTS(CacheStorage, nsICacheStorage)
 
 CacheStorage::CacheStorage(nsILoadContextInfo* aInfo,
                            bool aAllowDisk,
                            bool aLookupAppCache,
-                           bool aSkipSizeCheck)
+                           bool aSkipSizeCheck,
+                           bool aPinning)
 : mLoadContextInfo(GetLoadContextInfo(aInfo))
 , mWriteToDisk(aAllowDisk)
 , mLookupAppCache(aLookupAppCache)
 , mSkipSizeCheck(aSkipSizeCheck)
+, mPinning(aPinning)
 {
 }
 
 CacheStorage::~CacheStorage()
 {
 }
 
 NS_IMETHODIMP CacheStorage::AsyncOpenURI(nsIURI *aURI,
--- a/netwerk/cache2/CacheStorage.h
+++ b/netwerk/cache2/CacheStorage.h
@@ -48,31 +48,34 @@ class CacheStorage : public nsICacheStor
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICACHESTORAGE
 
 public:
   CacheStorage(nsILoadContextInfo* aInfo,
                bool aAllowDisk,
                bool aLookupAppCache,
-               bool aSkipSizeCheck);
+               bool aSkipSizeCheck,
+               bool aPinning);
 
 protected:
   virtual ~CacheStorage();
 
   nsresult ChooseApplicationCache(nsIURI* aURI, nsIApplicationCache** aCache);
 
   RefPtr<LoadContextInfo> mLoadContextInfo;
   bool mWriteToDisk : 1;
   bool mLookupAppCache : 1;
   bool mSkipSizeCheck: 1;
+  bool mPinning : 1;
 
 public:
   nsILoadContextInfo* LoadInfo() const { return mLoadContextInfo; }
   bool WriteToDisk() const { return mWriteToDisk && !mLoadContextInfo->IsPrivate(); }
   bool LookupAppCache() const { return mLookupAppCache; }
   bool SkipSizeCheck() const { return mSkipSizeCheck; }
+  bool Pinning() const { return mPinning; }
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -98,17 +98,18 @@ CacheStorageService::MemoryPool::Limit()
 
   MOZ_CRASH("Bad pool type");
   return 0;
 }
 
 NS_IMPL_ISUPPORTS(CacheStorageService,
                   nsICacheStorageService,
                   nsIMemoryReporter,
-                  nsITimerCallback)
+                  nsITimerCallback,
+                  nsICacheTesting)
 
 CacheStorageService* CacheStorageService::sSelf = nullptr;
 
 CacheStorageService::CacheStorageService()
 : mLock("CacheStorageService.mLock")
 , mForcedValidEntriesLock("CacheStorageService.mForcedValidEntriesLock")
 , mShutdown(false)
 , mDiskPool(MemoryPool::DISK)
@@ -531,17 +532,17 @@ void CacheStorageService::DropPrivateBro
 
   if (mShutdown)
     return;
 
   nsTArray<nsCString> keys;
   sGlobalEntryTables->EnumerateRead(&CollectPrivateContexts, &keys);
 
   for (uint32_t i = 0; i < keys.Length(); ++i)
-    DoomStorageEntries(keys[i], nullptr, true, nullptr);
+    DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
 }
 
 namespace {
 
 class CleaupCacheDirectoriesRunnable : public nsRunnable
 {
 public:
   NS_DECL_NSIRUNNABLE
@@ -676,17 +677,17 @@ nsresult CacheStorageService::Dispatch(n
 NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(nsILoadContextInfo *aLoadContextInfo,
                                                       nsICacheStorage * *_retval)
 {
   NS_ENSURE_ARG(aLoadContextInfo);
   NS_ENSURE_ARG(_retval);
 
   nsCOMPtr<nsICacheStorage> storage;
   if (CacheObserver::UseNewCache()) {
-    storage = new CacheStorage(aLoadContextInfo, false, false, false);
+    storage = new CacheStorage(aLoadContextInfo, false, false, false, false);
   }
   else {
     storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
@@ -701,26 +702,47 @@ NS_IMETHODIMP CacheStorageService::DiskC
   // TODO save some heap granularity - cache commonly used storages.
 
   // When disk cache is disabled, still provide a storage, but just keep stuff
   // in memory.
   bool useDisk = CacheObserver::UseDiskCache();
 
   nsCOMPtr<nsICacheStorage> storage;
   if (CacheObserver::UseNewCache()) {
-    storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache, false);
+    storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache, false /* size limit */, false /* don't pin */);
   }
   else {
     storage = new _OldStorage(aLoadContextInfo, useDisk, aLookupAppCache, false, nullptr);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
 
+NS_IMETHODIMP CacheStorageService::PinningCacheStorage(nsILoadContextInfo *aLoadContextInfo,
+                                                       nsICacheStorage * *_retval)
+{
+  NS_ENSURE_ARG(aLoadContextInfo);
+  NS_ENSURE_ARG(_retval);
+
+  if (!CacheObserver::UseNewCache()) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  // When disk cache is disabled don't pretend we cache.
+  if (!CacheObserver::UseDiskCache()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsICacheStorage> storage = new CacheStorage(
+    aLoadContextInfo, true /* use disk */, false /* no appcache */, true /* ignore size checks */, true /* pin */);
+  storage.forget(_retval);
+  return NS_OK;
+}
+
 NS_IMETHODIMP CacheStorageService::AppCacheStorage(nsILoadContextInfo *aLoadContextInfo,
                                                    nsIApplicationCache *aApplicationCache,
                                                    nsICacheStorage * *_retval)
 {
   NS_ENSURE_ARG(aLoadContextInfo);
   NS_ENSURE_ARG(_retval);
 
   nsCOMPtr<nsICacheStorage> storage;
@@ -740,17 +762,17 @@ NS_IMETHODIMP CacheStorageService::AppCa
 NS_IMETHODIMP CacheStorageService::SynthesizedCacheStorage(nsILoadContextInfo *aLoadContextInfo,
                                                            nsICacheStorage * *_retval)
 {
   NS_ENSURE_ARG(aLoadContextInfo);
   NS_ENSURE_ARG(_retval);
 
   nsCOMPtr<nsICacheStorage> storage;
   if (CacheObserver::UseNewCache()) {
-    storage = new CacheStorage(aLoadContextInfo, false, false, true /* skip size checks for synthesized cache */);
+    storage = new CacheStorage(aLoadContextInfo, false, false, true /* skip size checks for synthesized cache */, false /* no pinning */);
   }
   else {
     storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
@@ -763,22 +785,25 @@ NS_IMETHODIMP CacheStorageService::Clear
     {
       mozilla::MutexAutoLock lock(mLock);
 
       NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
 
       nsTArray<nsCString> keys;
       sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys);
 
-      for (uint32_t i = 0; i < keys.Length(); ++i)
-        DoomStorageEntries(keys[i], nullptr, true, nullptr);
+      for (uint32_t i = 0; i < keys.Length(); ++i) {
+        DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
+      }
+
+      // Passing null as a load info means to evict all contexts.
+      // EvictByContext() respects the entry pinning.  EvictAll() does not.
+      rv = CacheFileIOManager::EvictByContext(nullptr, false);
+      NS_ENSURE_SUCCESS(rv, rv);
     }
-
-    rv = CacheFileIOManager::EvictAll();
-    NS_ENSURE_SUCCESS(rv, rv);
   } else {
     nsCOMPtr<nsICacheService> serv =
         do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = serv->EvictEntries(nsICache::STORE_ANYWHERE);
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -808,16 +833,39 @@ NS_IMETHODIMP CacheStorageService::Purge
   }
 
   nsCOMPtr<nsIRunnable> event =
     new PurgeFromMemoryRunnable(this, what);
 
   return Dispatch(event);
 }
 
+NS_IMETHODIMP CacheStorageService::PurgeFromMemoryRunnable::Run()
+{
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    if (observerService) {
+      observerService->NotifyObservers(nullptr, "cacheservice:purge-memory-pools", nullptr);
+    }
+
+    return NS_OK;
+  }
+
+  if (mService) {
+    // TODO not all flags apply to both pools
+    mService->Pool(true).PurgeAll(mWhat);
+    mService->Pool(false).PurgeAll(mWhat);
+    mService = nullptr;
+  }
+
+  NS_DispatchToMainThread(this);
+  return NS_OK;
+}
+
 NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption(
   nsICacheStorageConsumptionObserver* aObserver)
 {
   NS_ENSURE_ARG(aObserver);
 
   nsresult rv;
 
   if (CacheObserver::UseNewCache()) {
@@ -919,17 +967,17 @@ CacheStorageService::UnregisterEntry(Cac
   MOZ_ASSERT(mShutdown || (removedFrecency && removedExpiration));
 
   // Note: aEntry->CanRegister() since now returns false
   aEntry->SetRegistered(false);
 }
 
 static bool
 AddExactEntry(CacheEntryTable* aEntries,
-              nsCString const& aKey,
+              nsACString const& aKey,
               CacheEntry* aEntry,
               bool aOverwrite)
 {
   RefPtr<CacheEntry> existingEntry;
   if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
     bool equals = existingEntry == aEntry;
     LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
     return equals; // Already there...
@@ -937,17 +985,17 @@ AddExactEntry(CacheEntryTable* aEntries,
 
   LOG(("AddExactEntry [entry=%p put]", aEntry));
   aEntries->Put(aKey, aEntry);
   return true;
 }
 
 static bool
 RemoveExactEntry(CacheEntryTable* aEntries,
-                 nsCString const& aKey,
+                 nsACString const& aKey,
                  CacheEntry* aEntry,
                  bool aOverwrite)
 {
   RefPtr<CacheEntry> existingEntry;
   if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
     LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
     return false; // Already removed...
   }
@@ -1352,27 +1400,30 @@ CacheStorageService::AddStorageEntry(Cac
   NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   NS_ENSURE_ARG(aStorage);
 
   nsAutoCString contextKey;
   CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
 
   return AddStorageEntry(contextKey, aURI, aIdExtension,
-                         aStorage->WriteToDisk(), aStorage->SkipSizeCheck(),
+                         aStorage->WriteToDisk(),
+                         aStorage->SkipSizeCheck(),
+                         aStorage->Pinning(),
                          aCreateIfNotExist, aReplace,
                          aResult);
 }
 
 nsresult
 CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
                                      nsIURI* aURI,
                                      const nsACString & aIdExtension,
                                      bool aWriteToDisk,
                                      bool aSkipSizeCheck,
+                                     bool aPin,
                                      bool aCreateIfNotExist,
                                      bool aReplace,
                                      CacheEntryHandle** aResult)
 {
   NS_ENSURE_ARG(aURI);
 
   nsresult rv;
 
@@ -1397,22 +1448,18 @@ CacheStorageService::AddStorageEntry(nsC
       entries = new CacheEntryTable(CacheEntryTable::ALL_ENTRIES);
       sGlobalEntryTables->Put(aContextKey, entries);
       LOG(("  new storage entries table for context '%s'", aContextKey.BeginReading()));
     }
 
     bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
 
     if (entryExists && !aReplace) {
-      // check whether the file is already doomed or we want to turn this entry
-      // to a memory-only.
-      if (MOZ_UNLIKELY(entry->IsFileDoomed())) {
-        LOG(("  file already doomed, replacing the entry"));
-        aReplace = true;
-      } else if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
+      // check whether we want to turn this entry to a memory-only.
+      if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
         LOG(("  entry is persistnet but we want mem-only, replacing it"));
         aReplace = true;
       }
     }
 
     // If truncate is demanded, delete and doom the current entry
     if (entryExists && aReplace) {
       entries->Remove(entryKey);
@@ -1425,17 +1472,17 @@ CacheStorageService::AddStorageEntry(nsC
 
       entry = nullptr;
       entryExists = false;
     }
 
     // Ensure entry for the particular URL, if not read/only
     if (!entryExists && (aCreateIfNotExist || aReplace)) {
       // Entry is not in the hashtable or has just been truncated...
-      entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk, aSkipSizeCheck);
+      entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk, aSkipSizeCheck, aPin);
       entries->Put(entryKey, entry);
       LOG(("  new entry %p for %s", entry.get(), entryKey.get()));
     }
 
     if (entry) {
       // Here, if this entry was not for a long time referenced by any consumer,
       // gets again first 'handles count' reference.
       handle = entry->NewHandle();
@@ -1663,70 +1710,79 @@ CacheStorageService::DoomStorageEntries(
   NS_ENSURE_ARG(aStorage);
 
   nsAutoCString contextKey;
   CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
 
   mozilla::MutexAutoLock lock(mLock);
 
   return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
-                            aStorage->WriteToDisk(), aCallback);
+                            aStorage->WriteToDisk(), aStorage->Pinning(),
+                            aCallback);
 }
 
 nsresult
 CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
                                         nsILoadContextInfo* aContext,
                                         bool aDiskStorage,
+                                        bool aPinned,
                                         nsICacheEntryDoomCallback* aCallback)
 {
+  LOG(("CacheStorageService::DoomStorageEntries [context=%s]", aContextKey.BeginReading()));
+
   mLock.AssertCurrentThreadOwns();
 
   NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   nsAutoCString memoryStorageID(aContextKey);
   AppendMemoryStorageID(memoryStorageID);
 
   if (aDiskStorage) {
     LOG(("  dooming disk+memory storage of %s", aContextKey.BeginReading()));
 
-    // Just remove all entries, CacheFileIOManager will take care of the files.
-    sGlobalEntryTables->Remove(aContextKey);
-    sGlobalEntryTables->Remove(memoryStorageID);
+    // Walk one by one and remove entries according their pin status
+    CacheEntryTable *diskEntries, *memoryEntries;
+    if (sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
+      sGlobalEntryTables->Get(memoryStorageID, &memoryEntries);
+
+      for (auto iter = diskEntries->Iter(); !iter.Done(); iter.Next()) {
+        auto entry = iter.Data();
+        if (entry->DeferOrBypassRemovalOnPinStatus(aPinned)) {
+          continue;
+        }
+
+        if (memoryEntries) {
+          RemoveExactEntry(memoryEntries, iter.Key(), entry, false);
+        }
+        iter.Remove();
+      }
+    }
 
     if (aContext && !aContext->IsPrivate()) {
       LOG(("  dooming disk entries"));
-      CacheFileIOManager::EvictByContext(aContext);
+      CacheFileIOManager::EvictByContext(aContext, aPinned);
     }
   } else {
     LOG(("  dooming memory-only storage of %s", aContextKey.BeginReading()));
 
-    class MemoryEntriesRemoval {
-    public:
-      static PLDHashOperator EvictEntry(const nsACString& aKey,
-                                        CacheEntry* aEntry,
-                                        void* aClosure)
-      {
-        CacheEntryTable* entries = static_cast<CacheEntryTable*>(aClosure);
-        nsCString key(aKey);
-        RemoveExactEntry(entries, key, aEntry, false);
-        return PL_DHASH_NEXT;
-      }
-    };
-
     // Remove the memory entries table from the global tables.
     // Since we store memory entries also in the disk entries table
     // we need to remove the memory entries from the disk table one
     // by one manually.
     nsAutoPtr<CacheEntryTable> memoryEntries;
     sGlobalEntryTables->RemoveAndForget(memoryStorageID, memoryEntries);
 
-    CacheEntryTable* entries;
-    sGlobalEntryTables->Get(aContextKey, &entries);
-    if (memoryEntries && entries)
-      memoryEntries->EnumerateRead(&MemoryEntriesRemoval::EvictEntry, entries);
+    CacheEntryTable* diskEntries;
+    sGlobalEntryTables->Get(aContextKey, &diskEntries);
+    if (memoryEntries && diskEntries) {
+      for (auto iter = memoryEntries->Iter(); !iter.Done(); iter.Next()) {
+        auto entry = iter.Data();
+        RemoveExactEntry(diskEntries, iter.Key(), entry, false);
+      }
+    }
   }
 
   // An artificial callback.  This is a candidate for removal tho.  In the new
   // cache any 'doom' or 'evict' function ensures that the entry or entries
   // being doomed is/are not accessible after the function returns.  So there is
   // probably no need for a callback - has no meaning.  But for compatibility
   // with the old cache that is still in the tree we keep the API similar to be
   // able to make tests as well as other consumers work for now.
@@ -1793,19 +1849,16 @@ CacheStorageService::CacheFileDoomed(nsI
 
   RefPtr<CacheEntry> entry;
   if (!entries->Get(entryKey, getter_AddRefs(entry)))
     return;
 
   if (!entry->IsFileDoomed())
     return;
 
-  if (entry->IsReferenced())
-    return;
-
   // Need to remove under the lock to avoid possible race leading
   // to duplication of the entry per its key.
   RemoveExactEntry(entries, entryKey, entry, false);
   entry->DoomAlreadyRemoved();
 }
 
 bool
 CacheStorageService::GetCacheEntryInfo(nsILoadContextInfo* aLoadContextInfo,
@@ -2105,10 +2158,51 @@ CacheStorageService::CollectReports(nsIM
     data.mHandleReport = aHandleReport;
     data.mData = aData;
     sGlobalEntryTables->EnumerateRead(&ReportStorageMemory, &data);
   }
 
   return NS_OK;
 }
 
+// nsICacheTesting
+
+NS_IMETHODIMP
+CacheStorageService::IOThreadSuspender::Run()
+{
+  MonitorAutoLock mon(mMon);
+  mon.Wait();
+  return NS_OK;
+}
+
+void
+CacheStorageService::IOThreadSuspender::Notify()
+{
+  MonitorAutoLock mon(mMon);
+  mon.Notify();
+}
+
+NS_IMETHODIMP
+CacheStorageService::SuspendCacheIOThread(uint32_t aLevel)
+{
+  RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
+  if (!thread) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  MOZ_ASSERT(!mActiveIOSuspender);
+  mActiveIOSuspender = new IOThreadSuspender();
+  return thread->Dispatch(mActiveIOSuspender, aLevel);
+}
+
+NS_IMETHODIMP
+CacheStorageService::ResumeCacheIOThread()
+{
+  MOZ_ASSERT(mActiveIOSuspender);
+
+  RefPtr<IOThreadSuspender> suspender;
+  suspender.swap(mActiveIOSuspender);
+  suspender->Notify();
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/cache2/CacheStorageService.h
+++ b/netwerk/cache2/CacheStorageService.h
@@ -2,18 +2,19 @@
  * 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 CacheStorageService__h__
 #define CacheStorageService__h__
 
 #include "nsICacheStorageService.h"
 #include "nsIMemoryReporter.h"
+#include "nsITimer.h"
+#include "nsICacheTesting.h"
 
-#include "nsITimer.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsProxyRelease.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/TimeStamp.h"
@@ -60,22 +61,24 @@ protected:
   explicit CacheMemoryConsumer(uint32_t aFlags);
   ~CacheMemoryConsumer() { DoMemoryReport(0); }
   void DoMemoryReport(uint32_t aCurrentSize);
 };
 
 class CacheStorageService final : public nsICacheStorageService
                                 , public nsIMemoryReporter
                                 , public nsITimerCallback
+                                , public nsICacheTesting
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICACHESTORAGESERVICE
   NS_DECL_NSIMEMORYREPORTER
   NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSICACHETESTING
 
   CacheStorageService();
 
   void Shutdown();
   void DropPrivateBrowsingEntries();
 
   // Takes care of deleting any pending trashes for both cache1 and cache2
   // as well as the cache directory of an inactive cache version when requested.
@@ -268,22 +271,24 @@ private:
    * pool.
    */
   void PurgeOverMemoryLimit();
 
 private:
   nsresult DoomStorageEntries(nsCSubstring const& aContextKey,
                               nsILoadContextInfo* aContext,
                               bool aDiskStorage,
+                              bool aPin,
                               nsICacheEntryDoomCallback* aCallback);
   nsresult AddStorageEntry(nsCSubstring const& aContextKey,
                            nsIURI* aURI,
                            const nsACString & aIdExtension,
                            bool aWriteToDisk,
                            bool aSkipSizeCheck,
+                           bool aPin,
                            bool aCreateIfNotExist,
                            bool aReplace,
                            CacheEntryHandle** aResult);
 
   static CacheStorageService* sSelf;
 
   mozilla::Mutex mLock;
   mozilla::Mutex mForcedValidEntriesLock;
@@ -339,33 +344,42 @@ private:
   {
   public:
     PurgeFromMemoryRunnable(CacheStorageService* aService, uint32_t aWhat)
       : mService(aService), mWhat(aWhat) { }
 
   private:
     virtual ~PurgeFromMemoryRunnable() { }
 
-    NS_IMETHOD Run()
-    {
-      // TODO not all flags apply to both pools
-      mService->Pool(true).PurgeAll(mWhat);
-      mService->Pool(false).PurgeAll(mWhat);
-      return NS_OK;
-    }
+    NS_IMETHOD Run() override;
 
     RefPtr<CacheStorageService> mService;
     uint32_t mWhat;
   };
 
   // Used just for telemetry purposes, accessed only on the management thread.
   // Note: not included in the memory reporter, this is not expected to be huge
   // and also would be complicated to report since reporting happens on the main
   // thread but this table is manipulated on the management thread.
   nsDataHashtable<nsCStringHashKey, mozilla::TimeStamp> mPurgeTimeStamps;
+
+  // nsICacheTesting
+  class IOThreadSuspender : public nsRunnable
+  {
+  public:
+    IOThreadSuspender() : mMon("IOThreadSuspender") { }
+    void Notify();
+  private:
+    virtual ~IOThreadSuspender() { }
+    NS_IMETHOD Run() override;
+
+    Monitor mMon;
+  };
+
+  RefPtr<IOThreadSuspender> mActiveIOSuspender;
 };
 
 template<class T>
 void ProxyRelease(nsCOMPtr<T> &object, nsIThread* thread)
 {
   T* release;
   object.forget(&release);
 
--- a/netwerk/cache2/moz.build
+++ b/netwerk/cache2/moz.build
@@ -6,16 +6,17 @@
 
 XPIDL_SOURCES += [
     'nsICacheEntry.idl',
     'nsICacheEntryDoomCallback.idl',
     'nsICacheEntryOpenCallback.idl',
     'nsICacheStorage.idl',
     'nsICacheStorageService.idl',
     'nsICacheStorageVisitor.idl',
+    'nsICacheTesting.idl',
 ]
 
 XPIDL_MODULE = 'necko_cache2'
 
 EXPORTS += [
     'CacheObserver.h',
     'CacheStorageService.h',
 ]
--- a/netwerk/cache2/nsICacheStorageService.idl
+++ b/netwerk/cache2/nsICacheStorageService.idl
@@ -8,17 +8,17 @@ interface nsICacheStorage;
 interface nsILoadContextInfo;
 interface nsIApplicationCache;
 interface nsIEventTarget;
 interface nsICacheStorageConsumptionObserver;
 
 /**
  * Provides access to particual cache storages of the network URI cache.
  */
-[scriptable, uuid(9c9dc1d6-533e-4716-9ad8-11e08c3763b3)]
+[scriptable, uuid(ae29c44b-fbc3-4552-afaf-0a157ce771e7)]
 interface nsICacheStorageService : nsISupports
 {
   /**
    * Get storage where entries will only remain in memory, never written
    * to the disk.
    *
    * NOTE: Any existing disk entry for [URL|id-extension] will be doomed
    * prior opening an entry using this memory-only storage.  Result of
@@ -38,16 +38,23 @@ interface nsICacheStorageService : nsISu
    * @param aLookupAppCache
    *    When set true (for top level document loading channels) app cache will
    *    be first to check on to find entries in.
    */
   nsICacheStorage diskCacheStorage(in nsILoadContextInfo aLoadContextInfo,
                                    in bool aLookupAppCache);
 
   /**
+   * Get storage where entries will be written to disk and marked as pinned.
+   * These pinned entries are immune to over limit eviction and call of clear()
+   * on this service.
+   */
+  nsICacheStorage pinningCacheStorage(in nsILoadContextInfo aLoadContextInfo);
+
+  /**
    * Get storage for a specified application cache obtained using some different
    * mechanism.
    *
    * @param aLoadContextInfo
    *    Mandatory reference to a load context information.
    * @param aApplicationCache
    *    Optional reference to an existing appcache.  When left null, this will
    *    work with offline cache as a whole.
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/nsICacheTesting.idl
@@ -0,0 +1,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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This is an internal interface used only for testing purposes.
+ *
+ * THIS IS NOT AN API TO BE USED BY EXTENSIONS! ONLY USED BY MOZILLA TESTS.
+ */
+[scriptable, builtinclass, uuid(4e8ba935-92e1-4a74-944b-b1a2f02a7480)]
+interface nsICacheTesting : nsISupports
+{
+  void suspendCacheIOThread(in uint32_t aLevel);
+  void resumeCacheIOThread();
+};
--- a/netwerk/protocol/http/PackagedAppUtils.js
+++ b/netwerk/protocol/http/PackagedAppUtils.js
@@ -9,16 +9,17 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const PACKAGEDAPPUTILS_CONTRACTID = "@mozilla.org/network/packaged-app-utils;1";
 const PACKAGEDAPPUTILS_CID = Components.ID("{fe8f1c2e-3c13-11e5-9a3f-bbf47d1e6697}");
 
 function PackagedAppUtils() {
   this.packageIdentifier = '';
+  this.packageOrigin = '';
 }
 
 var DEBUG = 0
 function debug(s) {
   if (DEBUG) {
     dump("-*- PackagedAppUtils: " + s + "\n");
   }
 }
@@ -44,29 +45,30 @@ PackagedAppUtils.prototype = {
     if (!signature) {
       debug("No signature in header");
       aCallback.fireVerifiedEvent(true, false);
       return;
     }
     debug("Signature: " + signature);
 
     try {
-      // Base64 decode
-      signature = atob(signature);
-
       // Remove header
       let manifestBody = aManifest.substr(aManifest.indexOf('\r\n\r\n') + 4);
       debug("manifestBody: " + manifestBody);
 
       // Parse manifest, store resource hashes
       let manifestObj = JSON.parse(manifestBody);
+      this.packageIdentifier = manifestObj["package-identifier"];
+      this.packageOrigin = manifestObj["moz-package-origin"];
       this.resources = manifestObj["moz-resources"];
-      this.packageIdentifier = manifestObj["package-identifier"];
+
+      // Base64 decode
+      signature = atob(signature);
     } catch (e) {
-      debug("JSON parsing failure");
+      debug("Manifest parsing failure");
       aCallback.fireVerifiedEvent(true, false);
       return;
     }
 
     let manifestStream = Cc["@mozilla.org/io/string-input-stream;1"]
                            .createInstance(Ci.nsIStringInputStream);
     let signatureStream = Cc["@mozilla.org/io/string-input-stream;1"]
                             .createInstance(Ci.nsIStringInputStream);
--- a/netwerk/protocol/http/PackagedAppVerifier.cpp
+++ b/netwerk/protocol/http/PackagedAppVerifier.cpp
@@ -76,21 +76,23 @@ NS_IMETHODIMP PackagedAppVerifier::Init(
   mListener = aListener;
   mState = STATE_UNKNOWN;
   mSignature = aSignature;
   mIsPackageSigned = false;
   mPackageCacheEntry = aPackageCacheEntry;
   mIsFirstResource = true;
   mManifest = EmptyCString();
 
-  nsAutoCString originNoSuffix;
-  OriginAttributes().PopulateFromOrigin(aPackageOrigin, originNoSuffix);
-  mBypassVerification = (originNoSuffix ==
+  OriginAttributes().PopulateFromOrigin(aPackageOrigin, mPackageOrigin);
+  mBypassVerification = (mPackageOrigin ==
       Preferences::GetCString("network.http.signed-packages.trusted-origin"));
 
+  LOG(("mBypassVerification = %d\n", mBypassVerification));
+  LOG(("mPackageOrigin = %s\n", mPackageOrigin.get()));
+
   nsresult rv;
   mPackagedAppUtils = do_CreateInstance(NS_PACKAGEDAPPUTILS_CONTRACTID, &rv);
   if (NS_FAILED(rv)) {
     LOG(("create packaged app utils failed"));
     return rv;
   }
 
   return NS_OK;
@@ -354,16 +356,26 @@ PackagedAppVerifier::OnManifestVerified(
   }
 
 
   if (!aSuccess && mBypassVerification) {
     aSuccess = true;
     LOG(("Developer mode! Treat junk signature valid."));
   }
 
+  if (aSuccess && !mSignature.IsEmpty()) {
+    // Get the package location from the manifest
+    nsAutoCString packageOrigin;
+    mPackagedAppUtils->GetPackageOrigin(packageOrigin);
+    if (packageOrigin != mPackageOrigin) {
+      aSuccess = false;
+      LOG(("moz-package-location doesn't match:\nFrom: %s\nManifest: %s\n", mPackageOrigin.get(), packageOrigin.get()));
+    }
+  }
+
   // Only when the manifest verified and package has signature would we
   // regard this package is signed.
   mIsPackageSigned = aSuccess && !mSignature.IsEmpty();
 
   mState = aSuccess ? STATE_MANIFEST_VERIFIED_OK
                     : STATE_MANIFEST_VERIFIED_FAILED;
 
   // Obtain the package identifier from manifest if the package is signed.
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -257,16 +257,17 @@ nsHttpChannel::nsHttpChannel()
     , mRequestTimeInitialized(false)
     , mCacheEntryIsReadOnly(false)
     , mCacheEntryIsWriteOnly(false)
     , mCacheEntriesToWaitFor(0)
     , mHasQueryString(0)
     , mConcurentCacheAccess(0)
     , mIsPartialRequest(0)
     , mHasAutoRedirectVetoNotifier(0)
+    , mPinCacheContent(0)
     , mIsPackagedAppResource(0)
     , mIsCorsPreflightDone(0)
     , mPushedStream(nullptr)
     , mLocalBlocklist(false)
     , mWarningReporter(nullptr)
     , mDidReval(false)
 {
     LOG(("Creating nsHttpChannel [this=%p]\n", this));
@@ -2979,16 +2980,20 @@ nsHttpChannel::OpenCacheEntry(bool isHtt
     } else if (PossiblyIntercepted()) {
         // The synthesized cache has less restrictions on file size and so on.
         rv = cacheStorageService->SynthesizedCacheStorage(info,
             getter_AddRefs(cacheStorage));
     } else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
         rv = cacheStorageService->MemoryCacheStorage(info, // ? choose app cache as well...
             getter_AddRefs(cacheStorage));
     }
+    else if (mPinCacheContent) {
+        rv = cacheStorageService->PinningCacheStorage(info,
+            getter_AddRefs(cacheStorage));
+    }
     else {
         rv = cacheStorageService->DiskCacheStorage(info,
             !mPostID && (mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)),
             getter_AddRefs(cacheStorage));
     }
     NS_ENSURE_SUCCESS(rv, rv);
 
     if ((mClassOfService & nsIClassOfService::Leader) ||
@@ -6429,16 +6434,36 @@ nsHttpChannel::SetCacheOnlyMetadata(bool
     mCacheOnlyMetadata = aOnlyMetadata;
     if (aOnlyMetadata) {
         mLoadFlags |= LOAD_ONLY_IF_MODIFIED;
     }
 
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsHttpChannel::GetPin(bool *aPin)
+{
+    NS_ENSURE_ARG(aPin);
+    *aPin = mPinCacheContent;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetPin(bool aPin)
+{
+    LOG(("nsHttpChannel::SetPin [this=%p pin=%d]\n",
+        this, aPin));
+
+    ENSURE_CALLED_BEFORE_CONNECT();
+
+    mPinCacheContent = aPin;
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIResumableChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::ResumeAt(uint64_t aStartPos,
                         const nsACString& aEntityID)
 {
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -492,16 +492,19 @@ private:
     // when true, after we finish read from cache we must check all data
     // had been loaded from cache. If not, then an error has to be propagated
     // to the consumer.
     uint32_t                          mConcurentCacheAccess : 1;
     // whether the request is setup be byte-range
     uint32_t                          mIsPartialRequest : 1;
     // true iff there is AutoRedirectVetoNotifier on the stack
     uint32_t                          mHasAutoRedirectVetoNotifier : 1;
+    // consumers set this to true to use cache pinning, this has effect
+    // only when the channel is in an app context (load context has an appid)
+    uint32_t                          mPinCacheContent : 1;
     // Whether fetching the content is meant to be handled by the
     // packaged app service, which behaves like a caching layer.
     // Upon successfully fetching the package, the resource will be placed in
     // the cache, and served by calling OnCacheEntryAvailable.
     uint32_t                          mIsPackagedAppResource : 1;
     // True if CORS preflight has been performed
     uint32_t                          mIsCorsPreflightDone : 1;
 
--- a/netwerk/test/mochitests/signed_web_packaged_app.sjs
+++ b/netwerk/test/mochitests/signed_web_packaged_app.sjs
@@ -6,78 +6,76 @@ function handleRequest(request, response
 {
   response.setHeader("Content-Type", "application/package", false);
   response.write(signedPackage);
   return;
 }
 
 // The package content
 // getData formats it as described at http://www.w3.org/TR/web-packaging/#streamable-package-format
-var signedPackage = [
-  "manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDkxMDA4MDQzNVoXDTM1MDkxMDA4MDQzNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts8whjOzEbn/w1xkFJ67af7F/JPujBK91oyJekh2schIMzFau9pY8S1AiJQoJCulOJCJfUc8hBLKBZiGAkii+4Gpx6cVqMLe6C22MdD806Soxn8Dg4dQqbIvPuI4eeVKu5CEk80PW/BaFMmRvRHO62C7PILuH6yZeGHC4P7dTKpsk4CLxh/jRGXLC8jV2BCW0X+3BMbHBg53NoI9s1Gs7KGYnfOHbBP5wEFAa00RjHnubUaCdEBlC8Kl4X7p0S4RGb3rsB08wgFe9EmSZHIgcIm+SuVo7N4qqbI85qo2ulU6J8NN7ZtgMPHzrMhzgAgf/KnqPqwDIxnNmRNJmHTUYwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAukH6cJUUj5faa8CuPCqrEa0PoLY4SYNnff9NI+TTAHkB9l+kOcFl5eo2EQOcWmZKYi7QLlWC4jy/KQYattO9FMaxiOQL4FAc6ZIbNyfwWBzZWyr5syYJTTTnkLq8A9pCKarN49+FqhJseycU+8EhJEJyP5pv5hLvDNTTHOQ6SXhASsiX8cjo3AY4bxA5pWeXuTZ459qDxOnQd+GrOe4dIeqflk0hA2xYKe3SfF+QlK8EO370B8Dj8RX230OATM1E3OtYyALe34KW3wM9Qm9rb0eViDnVyDiCWkhhQnw5yPg/XQfloug2itRYuCnfUoRt8xfeHgwz2Ymz8cUADn3KpTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQIwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MTAwMTIxMTEwNlowIwYJKoZIhvcNAQkEMRYEFHAisUYrrt+gBxYFhZ5KQQusOmN3MA0GCSqGSIb3DQEBAQUABIIBACHW4V0BsPWOvWrGOTRj6mPpNbH/JI1bN2oyqQZrpUQoaBY+BbYxO7TY4Uwe+aeIR/TTPJznOMF/dl3Bna6TPabezU4ylg7TVFI6W7zC5f5DZKp+Xv6uTX6knUzbbW1fkJqMtE8hGUzYXc3/C++Ci6kuOzrpWOhk6DpJHeUO/ioV56H0+QK/oMAjYpEsOohaPqvTPNOBhMQ0OQP3bmuJ6HcjZ/oz96PpzXUPKT1tDe6VykIYkV5NvtC8Tu2lDbYvp9ug3gyDgdyNSV47y5i/iWkzEhsAJB+9Z50wKhplnkxxVHEXkB/6tmfvExvQ28gLd/VbaEGDX2ljCaTSUjhD0o0=\r",
-  "--7B0MKBI3UH\r",
-  "Content-Location: manifest.webapp\r",
-  "Content-Type: application/x-web-app-manifest+json\r",
-  "\r",
-  "{",
-  "  \"name\": \"My App\",",
-  "  \"moz-resources\": [",
-  "    {",
-  "      \"src\": \"page2.html\",",
-  "      \"integrity\": \"JREF3JbXGvZ+I1KHtoz3f46ZkeIPrvXtG4VyFQrJ7II=\"",
-  "    },",
-  "    {",
-  "      \"src\": \"index.html\",",
-  "      \"integrity\": \"zEubR310nePwd30NThIuoCxKJdnz7Mf5z+dZHUbH1SE=\"",
-  "    },",
-  "    {",
-  "      \"src\": \"scripts/script.js\",",
-  "      \"integrity\": \"6TqtNArQKrrsXEQWu3D9ZD8xvDRIkhyV6zVdTcmsT5Q=\"",
-  "    },",
-  "    {",
-  "      \"src\": \"scripts/library.js\",",
-  "      \"integrity\": \"TN2ByXZiaBiBCvS4MeZ02UyNi44vED+KjdjLInUl4o8=\"",
-  "    }",
-  "  ],",
-  "  \"moz-permissions\": [",
-  "    {",
-  "      \"systemXHR\": {",
-  "        \"description\": \"Needed to download stuff\"",
-  "      },",
-  "      \"devicestorage:pictures\": {",
-  "        \"description\": \"Need to load pictures\"",
-  "      }",
-  "    }",
-  "  ],",
-  "  \"package-identifier\": \"611FC2FE-491D-4A47-B3B3-43FBDF6F404F\",",
-  "  \"moz-package-location\": \"https://example.com/myapp/app.pak\",",
-  "  \"description\": \"A great app!\"",
-  "}\r",
-  "--7B0MKBI3UH\r",
-  "Content-Location: page2.html\r",
-  "Content-Type: text/html\r",
-  "\r",
-  "<html>",
-  "  page2.html",
-  "</html>",
-  "\r",
-  "--7B0MKBI3UH\r",
-  "Content-Location: index.html\r",
-  "Content-Type: text/html\r",
-  "\r",
-  "<html>",
-  "  Last updated: 2015/10/01 14:10 PST",
-  "</html>",
-  "\r",
-  "--7B0MKBI3UH\r",
-  "Content-Location: scripts/script.js\r",
-  "Content-Type: text/javascript\r",
-  "\r",
-  "// script.js",
-  "\r",
-  "--7B0MKBI3UH\r",
-  "Content-Location: scripts/library.js\r",
-  "Content-Type: text/javascript\r",
-  "\r",
-  "// library.js",
-  "\r",
-  "--7B0MKBI3UH--"
-].join("\n");
+var signedPackage = `manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDkxMDA4MDQzNVoXDTM1MDkxMDA4MDQzNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts8whjOzEbn/w1xkFJ67af7F/JPujBK91oyJekh2schIMzFau9pY8S1AiJQoJCulOJCJfUc8hBLKBZiGAkii+4Gpx6cVqMLe6C22MdD806Soxn8Dg4dQqbIvPuI4eeVKu5CEk80PW/BaFMmRvRHO62C7PILuH6yZeGHC4P7dTKpsk4CLxh/jRGXLC8jV2BCW0X+3BMbHBg53NoI9s1Gs7KGYnfOHbBP5wEFAa00RjHnubUaCdEBlC8Kl4X7p0S4RGb3rsB08wgFe9EmSZHIgcIm+SuVo7N4qqbI85qo2ulU6J8NN7ZtgMPHzrMhzgAgf/KnqPqwDIxnNmRNJmHTUYwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAukH6cJUUj5faa8CuPCqrEa0PoLY4SYNnff9NI+TTAHkB9l+kOcFl5eo2EQOcWmZKYi7QLlWC4jy/KQYattO9FMaxiOQL4FAc6ZIbNyfwWBzZWyr5syYJTTTnkLq8A9pCKarN49+FqhJseycU+8EhJEJyP5pv5hLvDNTTHOQ6SXhASsiX8cjo3AY4bxA5pWeXuTZ459qDxOnQd+GrOe4dIeqflk0hA2xYKe3SfF+QlK8EO370B8Dj8RX230OATM1E3OtYyALe34KW3wM9Qm9rb0eViDnVyDiCWkhhQnw5yPg/XQfloug2itRYuCnfUoRt8xfeHgwz2Ymz8cUADn3KpTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQIwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MTAyODExMTIwMlowIwYJKoZIhvcNAQkEMRYEFENKTXRUkdej+EPd/oKRhz0Cp13zMA0GCSqGSIb3DQEBAQUABIIBAFCr+i8cwTiwzzCVjzZZI2NAqu8dnYOAJjkhD02tJjBCbvehEhXW6pP/Gk8+oyx2zoV87zbw9xBGcEU9b3ulbggdFR56S3C3w+eTbeOXMcx7A8mn9vvsoMJm+/rkT4DgEUU1iaM7pdwH48CKJOnAZP5FkjRvpRBh8TgfcDbusXveYTwG5LVpDp8856+9FBzvZ7wLz9iWDvlT/EFxfWOnGduAJunQ9qQm+pWu5cvSTwWasCMYmiPRlsuBhU9Fx7LtlXIHtE2nYYQVMTMDE58z/mzT34W0bnneecrghHREhb90UvdlUZJ2q3Jahsa3718WUGPTp7ZYwYaPBy7ryoOoWSA=\r
+--7IYGY9UDJB\r
+Content-Location: manifest.webapp\r
+Content-Type: application/x-web-app-manifest+json\r
+\r
+{
+  "moz-package-origin": "http://mochi.test:8888",
+  "name": "My App",
+  "moz-resources": [
+    {
+      "src": "page2.html",
+      "integrity": "JREF3JbXGvZ+I1KHtoz3f46ZkeIPrvXtG4VyFQrJ7II="
+    },
+    {
+      "src": "index.html",
+      "integrity": "IjQ2S/V9qsC7wW5uv/Niq40M1aivvqH5+1GKRwUnyRg="
+    },
+    {
+      "src": "scripts/script.js",
+      "integrity": "6TqtNArQKrrsXEQWu3D9ZD8xvDRIkhyV6zVdTcmsT5Q="
+    },
+    {
+      "src": "scripts/library.js",
+      "integrity": "TN2ByXZiaBiBCvS4MeZ02UyNi44vED+KjdjLInUl4o8="
+    }
+  ],
+  "moz-permissions": [
+    {
+      "systemXHR": {
+        "description": "Needed to download stuff"
+      },
+      "devicestorage:pictures": {
+        "description": "Need to load pictures"
+      }
+    }
+  ],
+  "package-identifier": "09bc9714-7ab6-4320-9d20-fde4c237522c",
+  "description": "A great app!"
+}\r
+--7IYGY9UDJB\r
+Content-Location: page2.html\r
+Content-Type: text/html\r
+\r
+<html>
+  page2.html
+</html>
+\r
+--7IYGY9UDJB\r
+Content-Location: index.html\r
+Content-Type: text/html\r
+\r
+<html>
+  Last updated: 2015/10/28
+</html>
+\r
+--7IYGY9UDJB\r
+Content-Location: scripts/script.js\r
+Content-Type: text/javascript\r
+\r
+// script.js
+\r
+--7IYGY9UDJB\r
+Content-Location: scripts/library.js\r
+Content-Type: text/javascript\r
+\r
+// library.js
+\r
+--7IYGY9UDJB--`;
--- a/netwerk/test/unit/head_cache.js
+++ b/netwerk/test/unit/head_cache.js
@@ -45,16 +45,17 @@ function createURI(urispec)
 function getCacheStorage(where, lci, appcache)
 {
   if (!lci) lci = LoadContextInfo.default;
   var svc = get_cache_service();
   switch (where) {
     case "disk": return svc.diskCacheStorage(lci, false);
     case "memory": return svc.memoryCacheStorage(lci);
     case "appcache": return svc.appCacheStorage(lci, appcache);
+    case "pin": return svc.pinningCacheStorage(lci);
   }
   return null;
 }
 
 function asyncOpenCacheEntry(key, where, flags, lci, callback, appcache)
 {
   key = createURI(key);
 
--- a/netwerk/test/unit/head_cache2.js
+++ b/netwerk/test/unit/head_cache2.js
@@ -44,16 +44,18 @@ const NOTWANTED =       1 << 11;
 // Tell the cache to wait for the entry to be completely written first
 const COMPLETE =        1 << 12;
 // Don't write meta/data and don't set valid in the callback, consumer will do it manually
 const DONTFILL =        1 << 13;
 // Used in combination with METAONLY, don't call setValid() on the entry after metadata has been set
 const DONTSETVALID =    1 << 14;
 // Notify before checking the data, useful for proper callback ordering checks
 const NOTIFYBEFOREREAD = 1 << 15;
+// It's allowed to not get an existing entry (result of opening is undetermined)
+const MAYBE_NEW =       1 << 16;
 
 var log_c2 = true;
 function LOG_C2(o, m)
 {
   if (!log_c2) return;
   if (!m)
     dump("TEST-INFO | CACHE2: " + o + "\n");
   else
@@ -145,16 +147,20 @@ OpenCallback.prototype =
       return Ci.nsICacheEntryOpenCallback.RECHECK_AFTER_WRITE_FINISHED;
     }
 
     LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
     return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
   },
   onCacheEntryAvailable: function(entry, isnew, appCache, status)
   {
+    if ((this.behavior & MAYBE_NEW) && isnew) {
+      this.behavior |= NEW;
+    }
+
     LOG_C2(this, "onCacheEntryAvailable, " + this.behavior);
     do_check_true(!this.onAvailPassed);
     this.onAvailPassed = true;
 
     do_check_eq(isnew, !!(this.behavior & NEW));
 
     if (this.behavior & (NOTFOUND|NOTWANTED)) {
       do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
@@ -202,19 +208,23 @@ OpenCallback.prototype =
           entry.close();
           if (self.behavior & WAITFORWRITE)
             self.goon(entry);
 
           return;
         }
         do_execute_soon(function() { // emulate more network latency
           if (self.behavior & DOOMED) {
+            LOG_C2(self, "checking doom state");
             try {
               var os = entry.openOutputStream(0);
-              do_check_true(false);
+              // Unfortunately, in the undetermined state we cannot even check whether the entry
+              // is actually doomed or not.
+              os.close();
+              do_check_true(!!(self.behavior & MAYBE_NEW));
             } catch (ex) {
               do_check_true(true);
             }
             if (self.behavior & WAITFORWRITE)
               self.goon(entry);
             return;
           }
 
@@ -253,19 +263,19 @@ OpenCallback.prototype =
         entry.close();
       });
     }
   },
   selfCheck: function()
   {
     LOG_C2(this, "selfCheck");
 
-    do_check_true(this.onCheckPassed);
+    do_check_true(this.onCheckPassed || (this.behavior & MAYBE_NEW));
     do_check_true(this.onAvailPassed);
-    do_check_true(this.onDataCheckPassed);
+    do_check_true(this.onDataCheckPassed || (this.behavior & MAYBE_NEW));
   },
   throwAndNotify: function(entry)
   {
     LOG_C2(this, "Throwing");
     var self = this;
     do_execute_soon(function() {
       LOG_C2(self, "Notifying");
       self.goon(entry);
@@ -381,16 +391,20 @@ MultipleCallbacks.prototype =
     if (--this.pending == 0)
     {
       var self = this;
       if (this.delayed)
         do_execute_soon(function() { self.goon(); });
       else
         this.goon();
     }
+  },
+  add: function()
+  {
+    ++this.pending;
   }
 }
 
 function MultipleCallbacks(number, goon, delayed)
 {
   this.pending = number;
   this.goon = goon;
   this.delayed = delayed;
rename from netwerk/test/unit/test_cache2-28-concurrent_read_resumable_entry_size_zero.js
rename to netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
rename from netwerk/test/unit/test_cache2-29-concurrent_read_non-resumable_entry_size_zero.js
rename to netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30a-entry-pinning.js
@@ -0,0 +1,28 @@
+function run_test()
+{
+  do_get_profile();
+
+  // Open for write, write
+  asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, LoadContextInfo.default,
+    new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
+      // Open for read and check
+      asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+        new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+
+          // Now clear the whole cache
+          get_cache_service().clear();
+
+          // The pinned entry should be intact
+          asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+            new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+              finish_cache2_test();
+            })
+          );
+
+        })
+      );
+    })
+  );
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js
@@ -0,0 +1,34 @@
+function run_test()
+{
+  do_get_profile();
+  var lci = LoadContextInfo.default;
+
+  // Open a pinned entry for write, write
+  asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+    new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
+
+      // Now clear the disk storage, that should leave the pinned  entry in the cache
+      var diskStorage = getCacheStorage("disk", lci);
+      diskStorage.asyncEvictStorage(null);
+
+      // Open for read and check, it should still be there
+      asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+        new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+
+          // Now clear the pinning storage, entry should be gone
+          var pinningStorage = getCacheStorage("pin", lci);
+          pinningStorage.asyncEvictStorage(null);
+
+          asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+            new OpenCallback(NEW, "", "", function(entry) {
+              finish_cache2_test();
+            })
+          );
+
+        })
+      );
+    })
+  );
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js
@@ -0,0 +1,129 @@
+/*
+
+This is a complex test checking the internal "deferred doom" functionality in both CacheEntry and CacheFileHandle.
+
+- We create a batch of 10 non-pinned and 10 pinned entries, write something to them.
+- Then we purge them from memory, so they have to reload from disk.
+- After that the IO thread is suspended not to process events on the READ (3) level.  This forces opening operation and eviction
+  sync operations happen before we know actual pinning status of already cached entries.
+- We async-open the same batch of the 10+10 entries again, all should open as existing with the expected, previously stored
+  content
+- After all these entries are made to open, we clear the cache.  This does some synchronous operations on the entries
+  being open and also on the handles being in an already open state (but before the entry metadata has started to be read.)
+  Expected is to leave the pinned entries only.
+- Now, we resume the IO thread, so it start reading.  One could say this is a hack, but this can very well happen in reality
+  on slow disk or when a large number of entries is about to be open at once.  Suspending the IO thread is just doing this
+  simulation is a fully deterministic way and actually very easily and elegantly.
+- After the resume we want to open all those 10+10 entries once again (no purgin involved this time.).  It is expected
+  to open all the pinning entries intact and loose all the non-pinned entries (get them as new and empty again.)
+
+*/
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
+
+function run_test()
+{
+  do_get_profile();
+  var lci = LoadContextInfo.default;
+  var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
+  do_check_true(testingInterface);
+
+  var mc = new MultipleCallbacks(1, function() {
+    // (2)
+
+    mc = new MultipleCallbacks(1, finish_cache2_test);
+    // Release all references to cache entries so that they can be purged
+    // Calling gc() four times is needed to force it to actually release
+    // entries that are obviously unreferenced.  Yeah, I know, this is wacky...
+    gc();
+    gc();
+    do_execute_soon(() => {
+      gc();
+      gc();
+      log_("purging");
+
+      // Invokes cacheservice:purge-memory-pools when done.
+      get_cache_service().purgeFromMemory(Ci.nsICacheStorageService.PURGE_EVERYTHING); // goes to (3)
+    });
+  }, true);
+
+  // (1), here we start
+
+  var i;
+  for (i = 0; i < kENTRYCOUNT; ++i) {
+    log_("first set of opens");
+
+    // Callbacks 1-20
+    mc.add();
+    asyncOpenCacheEntry("http://pinned" + i + "/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+      new OpenCallback(NEW|WAITFORWRITE, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+    mc.add();
+    asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+      new OpenCallback(NEW|WAITFORWRITE, "m" + i, "d" + i, function(entry) { mc.fired(); }));
+  }
+
+  mc.fired(); // Goes to (2)
+
+  var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+  os.addObserver({
+    observe: function(subject, topic, data)
+    {
+      // (3)
+
+      log_("after purge, second set of opens");
+      // Prevent the I/O thread from reading the data.  We first want to schedule clear of the cache.
+      // This deterministically emulates a slow hard drive.
+      testingInterface.suspendCacheIOThread(3);
+
+      // All entries should load
+      // Callbacks 21-40
+      for (i = 0; i < kENTRYCOUNT; ++i) {
+        mc.add();
+        asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+        // Unfortunately we cannot ensure that entries existing in the cache will be delivered to the consumer
+        // when soon after are evicted by some cache API call.  It's better to not ensure getting an entry
+        // than allowing to get an entry that was just evicted from the cache.  Entries may be delievered
+        // as new, but are already doomed.  Output stream cannot be openned, or the file handle is already
+        // writing to a doomed file.
+        //
+        // The API now just ensures that entries removed by any of the cache eviction APIs are never more
+        // available to consumers.
+        mc.add();
+        asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(MAYBE_NEW|DOOMED, "m" + i, "d" + i, function(entry) { mc.fired(); }));
+      }
+
+      log_("clearing");
+      // Now clear everything except pinned, all entries are in state of reading
+      get_cache_service().clear();
+      log_("cleared");
+
+      // Resume reading the cache data, only now the pinning status on entries will be discovered,
+      // the deferred dooming code will trigger.
+      testingInterface.resumeCacheIOThread();
+
+      log_("third set of opens");
+      // Now open again.  Pinned entries should be there, disk entries should be the renewed entries.
+      // Callbacks 41-60
+      for (i = 0; i < kENTRYCOUNT; ++i) {
+        mc.add();
+        asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+        mc.add();
+        asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NEW, "m2" + i, "d2" + i, function(entry) { mc.fired(); }));
+      }
+
+      mc.fired(); // Finishes this test
+    }
+  }, "cacheservice:purge-memory-pools", false);
+
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js
@@ -0,0 +1,107 @@
+/*
+
+This test exercises the CacheFileContextEvictor::WasEvicted API and code using it.
+
+- We store 10+10 (pinned and non-pinned) entries to the cache, wait for them being written.
+- Then we purge the memory pools.
+- Now the IO thread is suspended on the EVICT (8) level to prevent actual deletion of the files.
+- Index is disabled.
+- We do clear() of the cache, this creates the "ce_*" file and posts to the EVICT level
+  the eviction loop mechanics.
+- We open again those 10+10 entries previously stored.
+- IO is resumed
+- We expect to get all the pinned and
+  loose all the non-pinned (common) entries.
+
+*/
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
+
+function run_test()
+{
+  do_get_profile();
+  var lci = LoadContextInfo.default;
+  var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
+  do_check_true(testingInterface);
+
+  var mc = new MultipleCallbacks(1, function() {
+    // (2)
+
+    mc = new MultipleCallbacks(1, finish_cache2_test);
+    // Release all references to cache entries so that they can be purged
+    // Calling gc() four times is needed to force it to actually release
+    // entries that are obviously unreferenced.  Yeah, I know, this is wacky...
+    gc();
+    gc();
+    do_execute_soon(() => {
+      gc();
+      gc();
+      log_("purging");
+
+      // Invokes cacheservice:purge-memory-pools when done.
+      get_cache_service().purgeFromMemory(Ci.nsICacheStorageService.PURGE_EVERYTHING); // goes to (3)
+    });
+  }, true);
+
+  // (1), here we start
+
+  var i;
+  for (i = 0; i < kENTRYCOUNT; ++i) {
+    log_("first set of opens");
+
+    // Callbacks 1-20
+    mc.add();
+    asyncOpenCacheEntry("http://pinned" + i + "/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+      new OpenCallback(NEW|WAITFORWRITE, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+    mc.add();
+    asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+      new OpenCallback(NEW|WAITFORWRITE, "m" + i, "d" + i, function(entry) { mc.fired(); }));
+  }
+
+  mc.fired(); // Goes to (2)
+
+  var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+  os.addObserver({
+    observe: function(subject, topic, data)
+    {
+      // (3)
+
+      log_("after purge");
+      // Prevent the I/O thread from evicting physically the data.  We first want to re-open the entries.
+      // This deterministically emulates a slow hard drive.
+      testingInterface.suspendCacheIOThread(8);
+
+      log_("clearing");
+      // Now clear everything except pinned.  Stores the "ce_*" file and schedules background eviction.
+      get_cache_service().clear();
+      log_("cleared");
+
+      log_("second set of opens");
+      // Now open again.  Pinned entries should be there, disk entries should be the renewed entries.
+      // Callbacks 21-40
+      for (i = 0; i < kENTRYCOUNT; ++i) {
+        mc.add();
+        asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+        mc.add();
+        asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NEW, "m2" + i, "d2" + i, function(entry) { mc.fired(); }));
+      }
+
+      // Resume IO, this will just pop-off the CacheFileContextEvictor::EvictEntries() because of
+      // an early check on CacheIOThread::YieldAndRerun() in that method.
+      // CacheFileIOManager::OpenFileInternal should now run and CacheFileContextEvictor::WasEvicted
+      // should be checked on.
+      testingInterface.resumeCacheIOThread();
+
+      mc.fired(); // Finishes this test
+    }
+  }, "cacheservice:purge-memory-pools", false);
+
+
+  do_test_pending();
+}
--- a/netwerk/test/unit/test_cache_jar.js
+++ b/netwerk/test/unit/test_cache_jar.js
@@ -94,17 +94,18 @@ function run_test() {
   httpserv.registerPathHandler("/cached", cached_handler);
   httpserv.start(-1);
   gTests = run_all_tests();
   gTests.next();
 }
 
 function doneFirstLoad(req, buffer, expected) {
   // Load it again, make sure it hits the cache
-  var chan = makeChan(URL, 0, false);
+  var nc = req.notificationCallbacks.getInterface(Ci.nsILoadContext);
+  var chan = makeChan(URL, nc.appId, nc.isInBrowserElement);
   chan.asyncOpen(new ChannelListener(doneSecondLoad, expected), null);
 }
 
 function doneSecondLoad(req, buffer, expected) {
   do_check_eq(handlers_called, expected);
   try {
     gTests.next();
   } catch (x) {
--- a/netwerk/test/unit/test_packaged_app_channel.js
+++ b/netwerk/test/unit/test_packaged_app_channel.js
@@ -90,20 +90,20 @@ var testData = {
       str += this.content[i].data + "\r\n";
     }
 
     str += "--" + this.token + "--";
     return str;
   }
 }
 
-var badSignature = "manifest-signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\r\n"; 
-var goodSignature = "manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDkxMDA4MDQzNVoXDTM1MDkxMDA4MDQzNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts8whjOzEbn/w1xkFJ67af7F/JPujBK91oyJekh2schIMzFau9pY8S1AiJQoJCulOJCJfUc8hBLKBZiGAkii+4Gpx6cVqMLe6C22MdD806Soxn8Dg4dQqbIvPuI4eeVKu5CEk80PW/BaFMmRvRHO62C7PILuH6yZeGHC4P7dTKpsk4CLxh/jRGXLC8jV2BCW0X+3BMbHBg53NoI9s1Gs7KGYnfOHbBP5wEFAa00RjHnubUaCdEBlC8Kl4X7p0S4RGb3rsB08wgFe9EmSZHIgcIm+SuVo7N4qqbI85qo2ulU6J8NN7ZtgMPHzrMhzgAgf/KnqPqwDIxnNmRNJmHTUYwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAukH6cJUUj5faa8CuPCqrEa0PoLY4SYNnff9NI+TTAHkB9l+kOcFl5eo2EQOcWmZKYi7QLlWC4jy/KQYattO9FMaxiOQL4FAc6ZIbNyfwWBzZWyr5syYJTTTnkLq8A9pCKarN49+FqhJseycU+8EhJEJyP5pv5hLvDNTTHOQ6SXhASsiX8cjo3AY4bxA5pWeXuTZ459qDxOnQd+GrOe4dIeqflk0hA2xYKe3SfF+QlK8EO370B8Dj8RX230OATM1E3OtYyALe34KW3wM9Qm9rb0eViDnVyDiCWkhhQnw5yPg/XQfloug2itRYuCnfUoRt8xfeHgwz2Ymz8cUADn3KpTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQIwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MTAwMTIxMTEwNlowIwYJKoZIhvcNAQkEMRYEFHAisUYrrt+gBxYFhZ5KQQusOmN3MA0GCSqGSIb3DQEBAQUABIIBACHW4V0BsPWOvWrGOTRj6mPpNbH/JI1bN2oyqQZrpUQoaBY+BbYxO7TY4Uwe+aeIR/TTPJznOMF/dl3Bna6TPabezU4ylg7TVFI6W7zC5f5DZKp+Xv6uTX6knUzbbW1fkJqMtE8hGUzYXc3/C++Ci6kuOzrpWOhk6DpJHeUO/ioV56H0+QK/oMAjYpEsOohaPqvTPNOBhMQ0OQP3bmuJ6HcjZ/oz96PpzXUPKT1tDe6VykIYkV5NvtC8Tu2lDbYvp9ug3gyDgdyNSV47y5i/iWkzEhsAJB+9Z50wKhplnkxxVHEXkB/6tmfvExvQ28gLd/VbaEGDX2ljCaTSUjhD0o0=\r\n";
+var badSignature = "manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDkxMDA4MDQzNVoXDTM1MDkxMDA4MDQzNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts8whjOzEbn/w1xkFJ67af7F/JPujBK91oyJekh2schIMzFau9pY8S1AiJQoJCulOJCJfUc8hBLKBZiGAkii+4Gpx6cVqMLe6C22MdD806Soxn8Dg4dQqbIvPuI4eeVKu5CEk80PW/BaFMmRvRHO62C7PILuH6yZeGHC4P7dTKpsk4CLxh/jRGXLC8jV2BCW0X+3BMbHBg53NoI9s1Gs7KGYnfOHbBP5wEFAa00RjHnubUaCdEBlC8Kl4X7p0S4RGb3rsB08wgFe9EmSZHIgcIm+SuVo7N4qqbI85qo2ulU6J8NN7ZtgMPHzrMhzgAgf/KnqPqwDIxnNmRNJmHTUYwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAukH6cJUUj5faa8CuPCqrEa0PoLY4SYNnff9NI+TTAHkB9l+kOcFl5eo2EQOcWmZKYi7QLlWC4jy/KQYattO9FMaxiOQL4FAc6ZIbNyfwWBzZWyr5syYJTTTnkLq8A9pCKarN49+FqhJseycU+8EhJEJyP5pv5hLvDNTTHOQ6SXhASsiX8cjo3AY4bxA5pWeXuTZ459qDxOnQd+GrOe4dIeqflk0hA2xYKe3SfF+QlK8EO370B8Dj8RX230OATM1E3OtYyALe34KW3wM9Qm9rb0eViDnVyDiCWkhhQnw5yPg/XQfloug2itRYuCnfUoRt8xfeHgwz2Ymz8cUADn3KpTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQIwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MTAwMTIxMTEwNlowIwYJKoZIhvcNAQkEMRYEFHAisUYrrt+gBxYFhZ5KQQusOmN3MA0GCSqGSIb3DQEBAQUABIIBACHW4V0BsPWOvWrGOTRj6mPpNbH/JI1bN2oyqQZrpUQoaBY+BbYxO7TY4Uwe+aeIR/TTPJznOMF/dl3Bna6TPabezU4ylg7TVFI6W7zC5f5DZKp+Xv6uTX6knUzbbW1fkJqMtE8hGUzYXc3/C++Ci6kuOzrpWOhk6DpJHeUO/ioV56H0+QK/oMAjYpEsOohaPqvTPNOBhMQ0OQP3bmuJ6HcjZ/oz96PpzXUPKT1tDe6VykIYkV5NvtC8Tu2lDbYvp9ug3gyDgdyNSV47y5i/iWkzEhsAJB+9Z50wKhplnkxxVHEXkB/6tmfvExvQ28gLd/VbaEGDX2ljCaTSUjhD0o0=\r\n";
 
-var packageContent = [
+function packageContent(origin) {
+  return [
   "--7B0MKBI3UH\r",
   "Content-Location: manifest.webapp\r",
   "Content-Type: application/x-web-app-manifest+json\r",
   "\r",
   "{",
   "  \"name\": \"My App\",",
   "  \"moz-resources\": [",
   "    {",
@@ -129,17 +129,17 @@ var packageContent = [
   "        \"description\": \"Needed to download stuff\"",
   "      },",
   "      \"devicestorage:pictures\": {",
   "        \"description\": \"Need to load pictures\"",
   "      }",
   "    }",
   "  ],",
   "  \"package-identifier\": \"611FC2FE-491D-4A47-B3B3-43FBDF6F404F\",",
-  "  \"moz-package-location\": \"https://example.com/myapp/app.pak\",",
+  "  \"moz-package-origin\": \"" + origin + "\",",
   "  \"description\": \"A great app!\"",
   "}\r",
   "--7B0MKBI3UH\r",
   "Content-Location: page2.html\r",
   "Content-Type: text/html\r",
   "\r",
   "<html>",
   "  page2.html",
@@ -162,16 +162,17 @@ var packageContent = [
   "--7B0MKBI3UH\r",
   "Content-Location: scripts/library.js\r",
   "Content-Type: text/javascript\r",
   "\r",
   "// library.js",
   "\r",
   "--7B0MKBI3UH--"
 ].join("\n");
+}
 
 function contentHandler(metadata, response)
 {
   response.setHeader("Content-Type", 'application/package');
   var body = testData.getData();
   response.bodyOutputStream.write(body, body.length);
 }
 
@@ -179,24 +180,24 @@ function regularContentHandler(metadata,
 {
   var body = "response";
   response.bodyOutputStream.write(body, body.length);
 }
 
 function contentHandlerWithBadSignature(metadata, response)
 {
   response.setHeader("Content-Type", 'application/package');
-  var body = badSignature + packageContent;
+  var body = badSignature + packageContent(uri);
   response.bodyOutputStream.write(body, body.length);
 }
 
 function contentHandlerWithGoodSignature(metadata, response)
 {
   response.setHeader("Content-Type", 'application/package');
-  var body = goodSignature + packageContent;
+  var body = goodSignature + packageContent(uri);
   response.bodyOutputStream.write(body, body.length);
 }
 
 var httpserver = null;
 var originalPref = false;
 var originalSignedAppEnabled = false;
 
 function run_test()
@@ -217,17 +218,16 @@ function run_test()
   do_register_cleanup(reset_pref);
 
   add_test(test_channel);
   add_test(test_channel_no_notificationCallbacks);
   add_test(test_channel_uris);
 
   add_test(test_channel_with_bad_signature_from_trusted_origin);
   add_test(test_channel_with_bad_signature);
-  add_test(test_channel_with_good_signature);
 
   // run tests
   run_next_test();
 }
 
 function test_channel_with_bad_signature() {
   var channel = make_channel(uri+"/package_with_bad_signature!//index.html");
   channel.notificationCallbacks = new LoadContextCallback(1024, false, false, false);
--- a/netwerk/test/unit/test_packaged_app_service.js
+++ b/netwerk/test/unit/test_packaged_app_service.js
@@ -115,17 +115,18 @@ var testData = {
       str += this.content[i].data + "\r\n";
     }
 
     str += "--" + this.token + "--";
     return str;
   }
 }
 
-var signedPackage = [
+function signedPackage(origin) {
+  return [
   "manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDkxMDA4MDQzNVoXDTM1MDkxMDA4MDQzNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts8whjOzEbn/w1xkFJ67af7F/JPujBK91oyJekh2schIMzFau9pY8S1AiJQoJCulOJCJfUc8hBLKBZiGAkii+4Gpx6cVqMLe6C22MdD806Soxn8Dg4dQqbIvPuI4eeVKu5CEk80PW/BaFMmRvRHO62C7PILuH6yZeGHC4P7dTKpsk4CLxh/jRGXLC8jV2BCW0X+3BMbHBg53NoI9s1Gs7KGYnfOHbBP5wEFAa00RjHnubUaCdEBlC8Kl4X7p0S4RGb3rsB08wgFe9EmSZHIgcIm+SuVo7N4qqbI85qo2ulU6J8NN7ZtgMPHzrMhzgAgf/KnqPqwDIxnNmRNJmHTUYwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAukH6cJUUj5faa8CuPCqrEa0PoLY4SYNnff9NI+TTAHkB9l+kOcFl5eo2EQOcWmZKYi7QLlWC4jy/KQYattO9FMaxiOQL4FAc6ZIbNyfwWBzZWyr5syYJTTTnkLq8A9pCKarN49+FqhJseycU+8EhJEJyP5pv5hLvDNTTHOQ6SXhASsiX8cjo3AY4bxA5pWeXuTZ459qDxOnQd+GrOe4dIeqflk0hA2xYKe3SfF+QlK8EO370B8Dj8RX230OATM1E3OtYyALe34KW3wM9Qm9rb0eViDnVyDiCWkhhQnw5yPg/XQfloug2itRYuCnfUoRt8xfeHgwz2Ymz8cUADn3KpTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQIwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MTAwMTIxMTEwNlowIwYJKoZIhvcNAQkEMRYEFHAisUYrrt+gBxYFhZ5KQQusOmN3MA0GCSqGSIb3DQEBAQUABIIBACHW4V0BsPWOvWrGOTRj6mPpNbH/JI1bN2oyqQZrpUQoaBY+BbYxO7TY4Uwe+aeIR/TTPJznOMF/dl3Bna6TPabezU4ylg7TVFI6W7zC5f5DZKp+Xv6uTX6knUzbbW1fkJqMtE8hGUzYXc3/C++Ci6kuOzrpWOhk6DpJHeUO/ioV56H0+QK/oMAjYpEsOohaPqvTPNOBhMQ0OQP3bmuJ6HcjZ/oz96PpzXUPKT1tDe6VykIYkV5NvtC8Tu2lDbYvp9ug3gyDgdyNSV47y5i/iWkzEhsAJB+9Z50wKhplnkxxVHEXkB/6tmfvExvQ28gLd/VbaEGDX2ljCaTSUjhD0o0=\r",
   "--7B0MKBI3UH\r",
   "Content-Location: manifest.webapp\r",
   "Content-Type: application/x-web-app-manifest+json\r",
   "\r",
   "{",
   "  \"name\": \"My App\",",
   "  \"moz-resources\": [",
@@ -152,17 +153,17 @@ var signedPackage = [
   "        \"description\": \"Needed to download stuff\"",
   "      },",
   "      \"devicestorage:pictures\": {",
   "        \"description\": \"Need to load pictures\"",
   "      }",
   "    }",
   "  ],",
   "  \"package-identifier\": \"611FC2FE-491D-4A47-B3B3-43FBDF6F404F\",",
-  "  \"moz-package-location\": \"https://example.com/myapp/app.pak\",",
+  "  \"moz-package-origin\": \"" + origin + "\",",
   "  \"description\": \"A great app!\"",
   "}\r",
   "--7B0MKBI3UH\r",
   "Content-Location: page2.html\r",
   "Content-Type: text/html\r",
   "\r",
   "<html>",
   "  page2.html",
@@ -185,16 +186,17 @@ var signedPackage = [
   "--7B0MKBI3UH\r",
   "Content-Location: scripts/library.js\r",
   "Content-Type: text/javascript\r",
   "\r",
   "// library.js",
   "\r",
   "--7B0MKBI3UH--"
 ].join("\n");
+};
 
 XPCOMUtils.defineLazyGetter(this, "uri", function() {
   return "http://localhost:" + httpserver.identity.primaryPort;
 });
 
 // The active http server initialized in run_test
 var httpserver = null;
 // The packaged app service initialized in run_test
@@ -566,39 +568,54 @@ function test_worse_package_5() {
   test_worse_package(5, true);
 }
 
 //-----------------------------------------------------------------------------
 
 function signedPackagedAppContentHandler(metadata, response)
 {
   response.setHeader("Content-Type", 'application/package');
-  var body = signedPackage;
+  var body = signedPackage(uri);
   response.bodyOutputStream.write(body, body.length);
 }
 
 // Used as a stub when the cache listener is not important.
 var dummyCacheListener = {
   QueryInterface: function (iid) {
     if (iid.equals(Ci.nsICacheEntryOpenCallback) ||
         iid.equals(Ci.nsISupports))
       return this;
     throw Cr.NS_ERROR_NO_INTERFACE;
   },
   onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
   onCacheEntryAvailable: function () {}
 };
 
+function setTrustedOrigin() {
+  let pref = "network.http.signed-packages.trusted-origin";
+  ok(!!Ci.nsISupportsString, "Ci.nsISupportsString");
+  let origin = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+  origin.data = uri;
+  gPrefs.setComplexValue(pref, Ci.nsISupportsString, origin);
+}
+
+function resetTrustedOrigin() {
+  gPrefs.clearUserPref("network.http.signed-packages.trusted-origin");
+}
+
 function test_signed_package_callback()
 {
+  setTrustedOrigin();
+
   packagePath = "/signedPackage";
   let url = uri + packagePath + "!//index.html";
   let channel = getChannelForURL(url, {
     onStartSignedPackageRequest: function(aPackageId) {
       ok(true, "onStartSignedPackageRequest is notifited as expected");
+      resetTrustedOrigin();
       run_next_test();
     },
 
     getInterface: function (iid) {
       return this.QueryInterface(iid);
     },
 
     QueryInterface: function (iid) {
--- a/netwerk/test/unit/test_packaged_app_verifier.js
+++ b/netwerk/test/unit/test_packaged_app_verifier.js
@@ -172,17 +172,18 @@ function test_invalid_signature(aBypassV
 
   let verificationResult = aBypassVerification; // Verification always success in developer mode.
   let isPackageSigned = aBypassVerification;   // Package is always considered as signed in developer mode.
 
   const kPackagedId = '611FC2FE-491D-4A47-B3B3-43FBDF6F404F';
   const kManifestContent = 'Content-Location: manifest.webapp\r\n' +
                            'Content-Type: application/x-web-app-manifest+json\r\n' +
                            '\r\n' +
-                           '{ "package-identifier": "' + kPackagedId + '" }';
+                           '{ "package-identifier": "' + kPackagedId + '",\n' +
+                           '  "moz-package-origin": "' + kOrigin + '" }';
 
   const expectedCallbacks = [
   // URL                      statusCode   verificationResult     content
     [kOrigin + '/manifest',   Cr.NS_OK,    verificationResult,    kManifestContent],
     [kOrigin + '/1.html',     Cr.NS_OK,    verificationResult, 'abc'],
     [kOrigin + '/2.js',       Cr.NS_OK,    verificationResult, 'abc'],
     [kOrigin + '/3.jpg',      Cr.NS_OK,    verificationResult, 'abc'],
     [kOrigin + '/4.html',     Cr.NS_OK,    verificationResult, 'abc'],
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -65,18 +65,22 @@ support-files =
 skip-if = os == "android"
 [test_cache2-27-force-valid-for.js]
 [test_cache2-28-last-access-attrs.js]
 # This test will be fixed in bug 1067931
 skip-if = true
 [test_cache2-28a-OPEN_SECRETLY.js]
 # This test will be fixed in bug 1067931
 skip-if = true
-[test_cache2-28-concurrent_read_resumable_entry_size_zero.js]
-[test_cache2-29-concurrent_read_non-resumable_entry_size_zero.js]
+[test_cache2-29a-concurrent_read_resumable_entry_size_zero.js]
+[test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js]
+[test_cache2-30a-entry-pinning.js]
+[test_cache2-30b-pinning-storage-clear.js]
+[test_cache2-30c-pinning-deferred-doom.js]
+[test_cache2-30d-pinning-WasEvicted-API.js]
 [test_partial_response_entry_size_smart_shrink.js]
 [test_304_responses.js]
 [test_421.js]
 [test_cacheForOfflineUse_no-store.js]
 [test_307_redirect.js]
 [test_NetUtil.js]
 [test_URIs.js]
 [test_URIs2.js]
--- a/python/mach_commands.py
+++ b/python/mach_commands.py
@@ -234,35 +234,51 @@ class MachCommands(MachCommandBase):
         if not nodePath:
             return 1
 
         npmPath = self.getNodeOrNpmPath("npm")
         if not npmPath:
             return 1
 
         # Install eslint.
-        print("Installing eslint...")
-        with open(os.devnull, "w") as fnull:
-            subprocess.call([npmPath, "install", "eslint", "-g"],
-                            stdout=fnull, stderr=fnull)
+        success = self.callProcess("eslint",
+                                   [npmPath, "install", "eslint", "-g"])
+        if not success:
+            return 1
 
         # Install eslint-plugin-mozilla.
-        print("")
-        print("Installing eslint-plugin-mozilla...")
-        with open(os.devnull, "w") as fnull:
-            subprocess.call([npmPath, "link"],
-                            cwd="testing/eslint-plugin-mozilla",
-                            stdout=fnull, stderr=fnull)
+        success = self.callProcess("eslint-plugin-mozilla",
+                                   [npmPath, "link"],
+                                   "testing/eslint-plugin-mozilla")
+        if not success:
+            return 1
 
         # Install eslint-plugin-react.
-        print("")
-        print("Installing eslint-plugin-react...")
-        with open(os.devnull, "w") as fnull:
-            subprocess.call([npmPath, "install", "-g", "eslint-plugin-react"],
-                            stdout=fnull, stderr=fnull)
+        success = self.callProcess("eslint-plugin-react",
+                                   [npmPath, "install", "eslint-plugin-react", "-g"])
+        if not success:
+            return 1
+
+        print("\nESLint and approved plugins installed successfully!")
+
+    def callProcess(self, name, cmd, cwd=None):
+        print("\nInstalling %s using \"%s\"..." % (name, " ".join(cmd)))
+
+        try:
+            with open(os.devnull, "w") as fnull:
+                subprocess.check_call(cmd, cwd=cwd, stdout=fnull)
+        except subprocess.CalledProcessError:
+            if cwd:
+                print("\nError installing %s in the %s folder, aborting." % (name, cwd))
+            else:
+                print("\nError installing %s, aborting." % name)
+
+            return False
+
+        return True
 
     def getPossibleNodePathsWin(self):
         """
         Return possible nodejs paths on Windows.
         """
         if platform.system() != "Windows":
             return []
 
@@ -296,17 +312,17 @@ class MachCommands(MachCommandBase):
             print(NODE_NOT_FOUND_MESSAGE)
         elif filename == "npm":
             print(NPM_NOT_FOUND_MESSAGE)
 
         if platform.system() == "Windows":
             appPaths = self.getPossibleNodePathsWin()
 
             for p in appPaths:
-                print("  - " + p)
+                print("  - %s" % p)
         elif platform.system() == "Darwin":
             print("  - /usr/local/bin/node")
         elif platform.system() == "Linux":
             print("  - /usr/bin/nodejs")
 
         return None
 
     def is_valid(self, path):
--- a/testing/mozbase/mozdevice/mozdevice/adb.py
+++ b/testing/mozbase/mozdevice/mozdevice/adb.py
@@ -1101,17 +1101,17 @@ class ADBDevice(ADBCommand):
         if invalid_buffers:
             raise ADBError('Invalid logcat buffers %s not in %s ' % (
                 list(invalid_buffers), list(valid_buffers)))
         args = []
         for b in buffers:
             args.extend(['-b', b])
         return args
 
-    def clear_logcat(self, timeout=None, buffers=["main"]):
+    def clear_logcat(self, timeout=None, buffers=[]):
         """Clears logcat via adb logcat -c.
 
         :param timeout: The maximum time in
             seconds for any spawned adb process to complete before
             throwing an ADBTimeoutError.  This timeout is per
             adb call. The total time spent may exceed this
             value. If it is not specified, the value set
             in the ADBDevice constructor is used.
@@ -1119,29 +1119,30 @@ class ADBDevice(ADBCommand):
         :param list buffers: Log buffers to clear. Valid buffers are
             "radio", "events", and "main". Defaults to "main".
         :raises: * ADBTimeoutError
                  * ADBError
         """
         buffers = self._get_logcat_buffer_args(buffers)
         cmds = ["logcat", "-c"] + buffers
         self.command_output(cmds, timeout=timeout)
+        self.shell_output("log logcat cleared", timeout=timeout)
 
     def get_logcat(self,
                    filter_specs=[
                        "dalvikvm:I",
                        "ConnectivityService:S",
                        "WifiMonitor:S",
                        "WifiStateTracker:S",
                        "wpa_supplicant:S",
                        "NetworkStateTracker:S"],
                    format="time",
                    filter_out_regexps=[],
                    timeout=None,
-                   buffers=["main"]):
+                   buffers=[]):
         """Returns the contents of the logcat file as a list of strings.
 
         :param list filter_specs: Optional logcat messages to
             be included.
         :param str format: Optional logcat format.
         :param list filterOutRexps: Optional logcat messages to be
             excluded.
         :param timeout: The maximum time in
@@ -1421,33 +1422,40 @@ class ADBDevice(ADBCommand):
                  * ADBRootError
                  * ADBError
         """
         path = posixpath.normpath(path)
         if parents:
             if self._mkdir_p is None or self._mkdir_p:
                 # Use shell_bool to catch the possible
                 # non-zero exitcode if -p is not supported.
-                if self.shell_bool('mkdir -p %s' % path, timeout=timeout):
+                if self.shell_bool('mkdir -p %s' % path, timeout=timeout,
+                                   root=root):
                     self._mkdir_p = True
                     return
             # mkdir -p is not supported. create the parent
             # directories individually.
-            if not self.is_dir(posixpath.dirname(path)):
+            if not self.is_dir(posixpath.dirname(path), root=root):
                 parts = path.split('/')
                 name = "/"
                 for part in parts[:-1]:
                     if part != "":
                         name = posixpath.join(name, part)
-                        if not self.is_dir(name):
+                        if not self.is_dir(name, root=root):
                             # Use shell_output to allow any non-zero
                             # exitcode to raise an ADBError.
                             self.shell_output('mkdir %s' % name,
                                               timeout=timeout, root=root)
-        self.shell_output('mkdir %s' % path, timeout=timeout, root=root)
+
+        # If parents is True and the directory does exist, we don't
+        # need to do anything. Otherwise we call mkdir. If the
+        # directory already exists or if it is a file instead of a
+        # directory, mkdir will fail and we will raise an ADBError.
+        if not parents or not self.is_dir(path, root=root):
+            self.shell_output('mkdir %s' % path, timeout=timeout, root=root)
         if not self.is_dir(path, timeout=timeout, root=root):
             raise ADBError('mkdir %s Failed' % path)
 
     def push(self, local, remote, timeout=None):
         """Pushes a file or directory to the device.
 
         :param str local: The name of the local file or
             directory name.
@@ -1840,17 +1848,23 @@ class ADBDevice(ADBCommand):
         :raises: * ADBTimeoutError
                  * ADBError
 
         reboot() reboots the device, issues an adb wait-for-device in order to
         wait for the device to complete rebooting, then calls is_device_ready()
         to determine if the device has completed booting.
         """
         self.command_output(["reboot"], timeout=timeout)
-        self.command_output(["wait-for-device"], timeout=timeout)
+        # command_output automatically inserts a 'wait-for-device'
+        # argument to adb. Issuing an empty command is the same as adb
+        # -s <device> wait-for-device. We don't send an explicit
+        # 'wait-for-device' since that would add duplicate
+        # 'wait-for-device' arguments which is an error in newer
+        # versions of adb.
+        self.command_output([], timeout=timeout)
         return self.is_device_ready(timeout=timeout)
 
     @abstractmethod
     def is_device_ready(self, timeout=None):
         """Abstract class that returns True if the device is ready.
 
         :param timeout: optional integer specifying the maximum time in
             seconds for any spawned adb process to complete before
--- a/testing/mozbase/mozdevice/mozdevice/adb_android.py
+++ b/testing/mozbase/mozdevice/mozdevice/adb_android.py
@@ -73,17 +73,23 @@ class ADBAndroid(ADBDevice):
             throwing an ADBTimeoutError.
             This timeout is per adb call. The total time spent
             may exceed this value. If it is not specified, the value
             set in the ADB constructor is used.
         :type timeout: integer or None
         :raises: * ADBTimeoutError
                  * ADBError
         """
-        self.command_output(["wait-for-device"], timeout=timeout)
+        # command_output automatically inserts a 'wait-for-device'
+        # argument to adb. Issuing an empty command is the same as adb
+        # -s <device> wait-for-device. We don't send an explicit
+        # 'wait-for-device' since that would add duplicate
+        # 'wait-for-device' arguments which is an error in newer
+        # versions of adb.
+        self.command_output([], timeout=timeout)
         pm_error_string = "Error: Could not access the Package Manager"
         pm_list_commands = ["packages", "permission-groups", "permissions",
                             "instrumentation", "features", "libraries"]
         ready_path = os.path.join(self.test_root, "ready")
         for attempt in range(self._device_ready_retry_attempts):
             failure = 'Unknown failure'
             success = True
             try:
@@ -126,17 +132,19 @@ class ADBAndroid(ADBDevice):
             This timeout is per adb call. The total time spent
             may exceed this value. If it is not specified, the value
             set in the ADB constructor is used.
         :type timeout: integer or None
         :raises: * ADBTimeoutError
                  * ADBError
         """
         try:
-            self.shell_output('svc power stayon true', timeout=timeout)
+            self.shell_output('svc power stayon true',
+                              timeout=timeout,
+                              root=True)
         except ADBError, e:
             # Executing this via adb shell errors, but not interactively.
             # Any other exitcode is a real error.
             if 'exitcode: 137' not in e.message:
                 raise
             self._logger.warning('Unable to set power stayon true: %s' % e)
 
     # Application management methods
--- a/toolkit/components/downloads/ApplicationReputation.cpp
+++ b/toolkit/components/downloads/ApplicationReputation.cpp
@@ -591,17 +591,17 @@ PendingLookup::GenerateWhitelistStringsF
   for (int i = 1; i < aChain.element_size(); ++i) {
     // Get the issuer.
     nsCOMPtr<nsIX509Cert> issuer;
     rv = certDB->ConstructX509(
       const_cast<char *>(aChain.element(i).certificate().data()),
       aChain.element(i).certificate().size(), getter_AddRefs(issuer));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsresult rv = GenerateWhitelistStringsForPair(signer, issuer);
+    rv = GenerateWhitelistStringsForPair(signer, issuer);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
 nsresult
 PendingLookup::GenerateWhitelistStrings()
 {
@@ -701,34 +701,34 @@ nsresult
 PendingLookup::DoLookupInternal()
 {
   // We want to check the target URI, its referrer, and associated redirects
   // against the local lists.
   nsCOMPtr<nsIURI> uri;
   nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCString spec;
-  rv = GetStrippedSpec(uri, spec);
+  nsCString sourceSpec;
+  rv = GetStrippedSpec(uri, sourceSpec);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mAnylistSpecs.AppendElement(spec);
+  mAnylistSpecs.AppendElement(sourceSpec);
 
   ClientDownloadRequest_Resource* resource = mRequest.add_resources();
-  resource->set_url(spec.get());
+  resource->set_url(sourceSpec.get());
   resource->set_type(ClientDownloadRequest::DOWNLOAD_URL);
 
   nsCOMPtr<nsIURI> referrer = nullptr;
   rv = mQuery->GetReferrerURI(getter_AddRefs(referrer));
   if (referrer) {
-    nsCString spec;
-    rv = GetStrippedSpec(referrer, spec);
+    nsCString referrerSpec;
+    rv = GetStrippedSpec(referrer, referrerSpec);
     NS_ENSURE_SUCCESS(rv, rv);
-    mAnylistSpecs.AppendElement(spec);
-    resource->set_referrer(spec.get());
+    mAnylistSpecs.AppendElement(referrerSpec);
+    resource->set_referrer(referrerSpec.get());
   }
   nsCOMPtr<nsIArray> redirects;
   rv = mQuery->GetRedirects(getter_AddRefs(redirects));
   if (redirects) {
     AddRedirects(redirects);
   } else {
     LOG(("ApplicationReputation: Got no redirects [this=%p]", this));
   }
@@ -785,38 +785,38 @@ PendingLookup::ParseCertificates(nsIArra
   nsresult rv = aSigArray->Enumerate(getter_AddRefs(chains));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool hasMoreChains = false;
   rv = chains->HasMoreElements(&hasMoreChains);
   NS_ENSURE_SUCCESS(rv, rv);
 
   while (hasMoreChains) {
-    nsCOMPtr<nsISupports> supports;
-    rv = chains->GetNext(getter_AddRefs(supports));
+    nsCOMPtr<nsISupports> chainSupports;
+    rv = chains->GetNext(getter_AddRefs(chainSupports));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsIX509CertList> certList = do_QueryInterface(supports, &rv);
+    nsCOMPtr<nsIX509CertList> certList = do_QueryInterface(chainSupports, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     safe_browsing::ClientDownloadRequest_CertificateChain* certChain =
       mRequest.mutable_signature()->add_certificate_chain();
     nsCOMPtr<nsISimpleEnumerator> chainElt;
     rv = certList->GetEnumerator(getter_AddRefs(chainElt));
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Each chain may have multiple certificates.
     bool hasMoreCerts = false;
     rv = chainElt->HasMoreElements(&hasMoreCerts);
     while (hasMoreCerts) {
-      nsCOMPtr<nsISupports> supports;
-      rv = chainElt->GetNext(getter_AddRefs(supports));
+      nsCOMPtr<nsISupports> certSupports;
+      rv = chainElt->GetNext(getter_AddRefs(certSupports));
       NS_ENSURE_SUCCESS(rv, rv);
 
-      nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(supports, &rv);
+      nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(certSupports, &rv);
       NS_ENSURE_SUCCESS(rv, rv);
 
       uint8_t* data = nullptr;
       uint32_t len = 0;
       rv = cert->GetRawDER(&len, &data);
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Add this certificate to the protobuf to send remotely.
--- a/toolkit/components/downloads/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/nsDownloadManager.cpp
@@ -489,58 +489,62 @@ nsDownloadManager::InitFileDB()
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Finally, update the schemaVersion variable and the database schema
       schemaVersion = 2;
       rv = mDBConn->SetSchemaVersion(schemaVersion);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // Fallthrough to the next upgrade
+    MOZ_FALLTHROUGH;
 
   case 2: // Add referrer column to the database
     {
       rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "ALTER TABLE moz_downloads "
         "ADD COLUMN referrer TEXT"));
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Finally, update the schemaVersion variable and the database schema
       schemaVersion = 3;
       rv = mDBConn->SetSchemaVersion(schemaVersion);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // Fallthrough to the next upgrade
+    MOZ_FALLTHROUGH;
 
   case 3: // This version adds a column to the database (entityID)
     {
       rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "ALTER TABLE moz_downloads "
         "ADD COLUMN entityID TEXT"));
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Finally, update the schemaVersion variable and the database schema
       schemaVersion = 4;
       rv = mDBConn->SetSchemaVersion(schemaVersion);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // Fallthrough to the next upgrade
+    MOZ_FALLTHROUGH;
 
   case 4: // This version adds a column to the database (tempPath)
     {
       rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "ALTER TABLE moz_downloads "
         "ADD COLUMN tempPath TEXT"));
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Finally, update the schemaVersion variable and the database schema
       schemaVersion = 5;
       rv = mDBConn->SetSchemaVersion(schemaVersion);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // Fallthrough to the next upgrade
+    MOZ_FALLTHROUGH;
 
   case 5: // This version adds two columns for tracking transfer progress
     {
       rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "ALTER TABLE moz_downloads "
         "ADD COLUMN currBytes INTEGER NOT NULL DEFAULT 0"));
       NS_ENSURE_SUCCESS(rv, rv);
 
@@ -550,16 +554,17 @@ nsDownloadManager::InitFileDB()
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Finally, update the schemaVersion variable and the database schema
       schemaVersion = 6;
       rv = mDBConn->SetSchemaVersion(schemaVersion);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // Fallthrough to the next upgrade
+    MOZ_FALLTHROUGH;
 
   case 6: // This version adds three columns to DB (MIME type related info)
     {
       rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "ALTER TABLE moz_downloads "
         "ADD COLUMN mimeType TEXT"));
       NS_ENSURE_SUCCESS(rv, rv);
 
@@ -574,30 +579,32 @@ nsDownloadManager::InitFileDB()
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Finally, update the schemaVersion variable and the database schema
       schemaVersion = 7;
       rv = mDBConn->SetSchemaVersion(schemaVersion);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // Fallthrough to next upgrade
+    MOZ_FALLTHROUGH;
 
   case 7: // This version adds a column to remember to auto-resume downloads
     {
       rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "ALTER TABLE moz_downloads "
         "ADD COLUMN autoResume INTEGER NOT NULL DEFAULT 0"));
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Finally, update the schemaVersion variable and the database schema
       schemaVersion = 8;
       rv = mDBConn->SetSchemaVersion(schemaVersion);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // Fallthrough to the next upgrade
+    MOZ_FALLTHROUGH;
 
     // Warning: schema versions >=8 must take into account that they can
     // be operating on schemas from unknown, future versions that have
     // been downgraded. Operations such as adding columns may fail,
     // since the column may already exist.
 
   case 8: // This version adds a column for GUIDs
     {
@@ -622,32 +629,34 @@ nsDownloadManager::InitFileDB()
       schemaVersion = 9;
       rv = mDBConn->SetSchemaVersion(schemaVersion);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // Fallthrough to the next upgrade
 
   // Extra sanity checking for developers
 #ifndef DEBUG
+    MOZ_FALLTHROUGH;
   case DM_SCHEMA_VERSION:
 #endif
     break;
 
   case 0:
     {
       NS_WARNING("Could not get download database's schema version!");
 
       // The table may still be usable - someone may have just messed with the
       // schema version, so let's just treat this like a downgrade and verify
       // that the needed columns are there.  If they aren't there, we'll drop
       // the table anyway.
       rv = mDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // Fallthrough to downgrade check
+    MOZ_FALLTHROUGH;
 
   // Downgrading
   // If columns have been added to the table, we can still use the ones we
   // understand safely.  If columns have been deleted or alterd, we just
   // drop the table and start from scratch.  If you change how a column
   // should be interpreted, make sure you also change its name so this
   // check will catch it.
   default:
@@ -1260,27 +1269,27 @@ nsDownloadManager::GetDownloadFromDB(moz
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Handle situations where we load a download from a database that has been
   // used in an older version and not gone through the upgrade path (ie. it
   // contains empty GUID entries).
   if (dl->mGUID.IsEmpty()) {
     rv = GenerateGUID(dl->mGUID);
     NS_ENSURE_SUCCESS(rv, rv);
-    nsCOMPtr<mozIStorageStatement> stmt;
+    nsCOMPtr<mozIStorageStatement> updateStmt;
     rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
                                     "UPDATE moz_downloads SET guid = :guid "
                                     "WHERE id = :id"),
-                                  getter_AddRefs(stmt));
+                                  getter_AddRefs(updateStmt));
     NS_ENSURE_SUCCESS(rv, rv);
-    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), dl->mGUID);
+    rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), dl->mGUID);
     NS_ENSURE_SUCCESS(rv, rv);
-    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), dl->mID);
+    rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), dl->mID);
     NS_ENSURE_SUCCESS(rv, rv);
-    rv = stmt->Execute();
+    rv = updateStmt->Execute();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Addrefing and returning
   dl.forget(retVal);
   return NS_OK;
 }
 
@@ -1475,19 +1484,16 @@ nsDownloadManager::GetUserDownloadsDirec
   rv = prefBranch->GetIntPref(NS_PREF_FOLDERLIST,
                               &val);
   NS_ENSURE_SUCCESS(rv, rv);
 
   switch(val) {
     case 0: // Desktop
       {
         nsCOMPtr<nsIFile> downloadDir;
-        nsCOMPtr<nsIProperties> dirService =
-           do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
-        NS_ENSURE_SUCCESS(rv, rv);
         rv = dirService->Get(NS_OS_DESKTOP_DIR,
                              NS_GET_IID(nsIFile),
                              getter_AddRefs(downloadDir));
         NS_ENSURE_SUCCESS(rv, rv);
         downloadDir.forget(aResult);
         return NS_OK;
       }
       break;
@@ -1799,17 +1805,16 @@ nsDownloadManager::RetryDownload(const n
 {
   RefPtr<nsDownload> dl;
   nsresult rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return RetryDownload(dl);
 }
 
-
 NS_IMETHODIMP
 nsDownloadManager::RetryDownload(uint32_t aID)
 {
   NS_ENSURE_STATE(!mUseJSTransfer);
 
   NS_WARNING("Using integer IDs without compat mode enabled");
 
   RefPtr<nsDownload> dl;
--- a/toolkit/crashreporter/tools/symbolstore.py
+++ b/toolkit/crashreporter/tools/symbolstore.py
@@ -26,21 +26,21 @@ import errno
 import sys
 import platform
 import os
 import re
 import shutil
 import textwrap
 import fnmatch
 import subprocess
+import time
 import ctypes
 import urlparse
 import concurrent.futures
 import multiprocessing
-import collections
 
 from optparse import OptionParser
 from xml.dom.minidom import parse
 
 from mozpack.copier import FileRegistry
 from mozpack.manifests import (
     InstallManifest,
     UnreadableInstallManifest,
@@ -390,23 +390,23 @@ class Dumper:
     |dump_syms| and a directory to store symbols in--|symbol_path|.
     Optionally takes a list of processor architectures to process from
     each debug file--|archs|, the full path to the top source
     directory--|srcdir|, for generating relative source file names,
     and an option to copy debug info files alongside the dumped
     symbol files--|copy_debug|, mostly useful for creating a
     Microsoft Symbol Server from the resulting output.
 
-    You don't want to use this directly if you intend to call
-    ProcessDir.  Instead, call GetPlatformSpecificDumper to
-    get an instance of a subclass.
- 
+    You don't want to use this directly if you intend to process files.
+    Instead, call GetPlatformSpecificDumper to get an instance of a
+    subclass.
+
     Processing is performed asynchronously via worker processes; in
     order to wait for processing to finish and cleanup correctly, you
-    must call Finish after all Process/ProcessDir calls have been made.
+    must call Finish after all ProcessFiles calls have been made.
     You must also call Dumper.GlobalInit before creating or using any
     instances."""
     def __init__(self, dump_syms, symbol_path,
                  archs=None,
                  srcdirs=[],
                  copy_debug=False,
                  vcsinfo=False,
                  srcsrv=False,
@@ -550,36 +550,50 @@ class Dumper:
                 res = job.result()
             except Exception as e:
                 self.output(sys.stderr, 'Job raised exception: %s' % e)
                 continue
             callback(res)
         if stop_pool:
             JobPool.shutdown()
 
-    def Process(self, file_or_dir):
-        """Process a file or all the (valid) files in a directory; processing is performed
-        asynchronously, and Finish must be called to wait for it complete and cleanup."""
+    def Process(self, *args):
+        """Process files recursively in args."""
+        # We collect all files to process first then sort by size to schedule
+        # larger files first because larger files tend to take longer and we
+        # don't like long pole stragglers.
+        files = set()
+        for arg in args:
+            for f in self.get_files_to_process(arg):
+                files.add(f)
+
+        for f in sorted(files, key=os.path.getsize, reverse=True):
+            self.ProcessFiles((f,))
+
+    def get_files_to_process(self, file_or_dir):
+        """Generate the files to process from an input."""
         if os.path.isdir(file_or_dir) and not self.ShouldSkipDir(file_or_dir):
-            self.ProcessDir(file_or_dir)
+            for f in self.get_files_to_process_in_dir(file_or_dir):
+                yield f
         elif os.path.isfile(file_or_dir):
-            self.ProcessFiles((file_or_dir,))
+            yield file_or_dir
 
-    def ProcessDir(self, dir):
-        """Process all the valid files in this directory.  Valid files
-        are determined by calling ShouldProcess; processing is performed
-        asynchronously, and Finish must be called to wait for it complete and cleanup."""
-        for root, dirs, files in os.walk(dir):
+    def get_files_to_process_in_dir(self, path):
+        """Generate the files to process in a directory.
+
+        Valid files are are determined by calling ShouldProcess.
+        """
+        for root, dirs, files in os.walk(path):
             for d in dirs[:]:
                 if self.ShouldSkipDir(d):
                     dirs.remove(d)
             for f in files:
                 fullpath = os.path.join(root, f)
                 if self.ShouldProcess(fullpath):
-                    self.ProcessFiles((fullpath,))
+                    yield fullpath
 
     def SubmitJob(self, file_key, func_name, args, callback):
         """Submits a job to the pool of workers"""
         JobPool.submit((self, Dumper.lock, Dumper.srcdirRepoInfo, func_name, args), callback)
 
     def ProcessFilesFinished(self, res):
         """Callback from multiprocesing when ProcessFilesWork finishes;
         run the cleanup work, if any"""
@@ -601,16 +615,17 @@ class Dumper:
         # tries to get the vcs root from the .mozconfig first - if it's not set
         # the tinderbox vcs path will be assigned further down
         vcs_root = os.environ.get("SRCSRV_ROOT")
         for arch_num, arch in enumerate(self.archs):
             self.files_record[files] = 0 # record that we submitted jobs for this tuple of files
             self.SubmitJob(files[-1], 'ProcessFilesWork', args=(files, arch_num, arch, vcs_root, after, after_arg), callback=self.ProcessFilesFinished)
 
     def ProcessFilesWork(self, files, arch_num, arch, vcs_root, after, after_arg):
+        t_start = time.time()
         self.output_pid(sys.stderr, "Worker processing files: %s" % (files,))
 
         # our result is a status, a cleanup function, an argument to that function, and the tuple of files we were called on
         result = { 'status' : False, 'after' : after, 'after_arg' : after_arg, 'files' : files }
 
         sourceFileStream = ''
         code_id, code_file = None, None
         for file in files:
@@ -687,16 +702,20 @@ class Dumper:
             except StopIteration:
                 pass
             except Exception as e:
                 self.output(sys.stderr, "Unexpected error: %s" % (str(e),))
                 raise
             if result['status']:
                 # we only need 1 file to work
                 break
+
+        elapsed = time.time() - t_start
+        self.output_pid(sys.stderr, 'Worker finished processing %s in %.2fs' %
+                        (files, elapsed))
         return result
 
 # Platform-specific subclasses.  For the most part, these just have
 # logic to determine what files to extract symbols from.
 
 class Dumper_Win32(Dumper):
     fixedFilenameCaseCache = {}
 
@@ -888,16 +907,17 @@ class Dumper_Mac(Dumper):
         if result['status']:
             # kick off new jobs per-arch with our new list of files
             Dumper.ProcessFiles(self, result['files'], after=AfterMac, after_arg=result['files'][0])
 
     def ProcessFilesWorkMac(self, file):
         """dump_syms on Mac needs to be run on a dSYM bundle produced
         by dsymutil(1), so run dsymutil here and pass the bundle name
         down to the superclass method instead."""
+        t_start = time.time()
         self.output_pid(sys.stderr, "Worker running Mac pre-processing on file: %s" % (file,))
 
         # our return is a status and a tuple of files to dump symbols for
         # the extra files are fallbacks; as soon as one is dumped successfully, we stop
         result = { 'status' : False, 'files' : None, 'file_key' : file }
         dsymbundle = file + ".dSYM"
         if os.path.exists(dsymbundle):
             shutil.rmtree(dsymbundle)
@@ -916,16 +936,19 @@ class Dumper_Mac(Dumper):
             # dsymutil won't produce a .dSYM for files without symbols
             self.output_pid(sys.stderr, "No symbols found in file: %s" % (file,))
             result['status'] = False
             result['files'] = (file, )
             return result
 
         result['status'] = True
         result['files'] = (dsymbundle, file)
+        elapsed = time.time() - t_start
+        self.output_pid(sys.stderr, 'Worker finished processing %s in %.2fs' %
+                        (file, elapsed))
         return result
 
     def CopyDebug(self, file, debug_file, guid, code_file, code_id):
         """ProcessFiles has already produced a dSYM bundle, so we should just
         copy that to the destination directory. However, we'll package it
         into a .tar.bz2 because the debug symbols are pretty huge, and
         also because it's a bundle, so it's a directory. |file| here is the
         dSYM bundle, and |debug_file| is the original filename."""
@@ -997,18 +1020,18 @@ to canonical locations in the source rep
                                        copy_debug=options.copy_debug,
                                        archs=options.archs,
                                        srcdirs=options.srcdir,
                                        vcsinfo=options.vcsinfo,
                                        srcsrv=options.srcsrv,
                                        exclude=options.exclude,
                                        repo_manifest=options.repo_manifest,
                                        file_mapping=file_mapping)
-    for arg in args[2:]:
-        dumper.Process(arg)
+
+    dumper.Process(*args[2:])
     dumper.Finish()
 
 # run main if run directly
 if __name__ == "__main__":
     # set up the multiprocessing infrastructure before we start;
     # note that this needs to be in the __main__ guard, or else Windows will choke
     Dumper.GlobalInit()
 
--- a/toolkit/crashreporter/tools/unit-symbolstore.py
+++ b/toolkit/crashreporter/tools/unit-symbolstore.py
@@ -66,16 +66,41 @@ class HelperMixin(object):
     def add_test_files(self, files):
         for f in files:
             f = os.path.join(self.test_dir, f)
             d = os.path.dirname(f)
             if d and not os.path.exists(d):
                 os.makedirs(d)
             writer(f)
 
+class TestSizeOrder(HelperMixin, unittest.TestCase):
+    def test_size_order(self):
+        """
+        Test that files are processed ordered by size on disk.
+        """
+        processed = []
+        def mock_process_file(filenames):
+            for filename in filenames:
+                processed.append((filename[len(self.test_dir):] if filename.startswith(self.test_dir) else filename).replace('\\', '/'))
+            return True
+        for f, size in (('a/one', 10), ('b/c/two', 30), ('c/three', 20)):
+            f = os.path.join(self.test_dir, f)
+            d = os.path.dirname(f)
+            if d and not os.path.exists(d):
+                os.makedirs(d)
+            open(f, 'wb').write('x' * size)
+        d = symbolstore.GetPlatformSpecificDumper(dump_syms="dump_syms",
+                                                  symbol_path="symbol_path")
+        d.ShouldProcess = lambda f: True
+        d.ProcessFiles = mock_process_file
+        d.Process(self.test_dir)
+        d.Finish(stop_pool=False)
+        self.assertEqual(processed, ['b/c/two', 'c/three', 'a/one'])
+
+
 class TestExclude(HelperMixin, unittest.TestCase):
     def test_exclude_wildcard(self):
         """
         Test that using an exclude list with a wildcard pattern works.
         """
         processed = []
         def mock_process_file(filenames):
             for filename in filenames:
@@ -88,16 +113,17 @@ class TestExclude(HelperMixin, unittest.
         d.ProcessFiles = mock_process_file
         d.Process(self.test_dir)
         d.Finish(stop_pool=False)
         processed.sort()
         expected = add_extension(["bar", "abc/xyz", "def/asdf"])
         expected.sort()
         self.assertEqual(processed, expected)
 
+
     def test_exclude_filenames(self):
         """
         Test that excluding a filename without a wildcard works.
         """
         processed = []
         def mock_process_file(filenames):
             for filename in filenames:
                 processed.append((filename[len(self.test_dir):] if filename.startswith(self.test_dir) else filename).replace('\\', '/'))
@@ -109,16 +135,17 @@ class TestExclude(HelperMixin, unittest.
         d.ProcessFiles = mock_process_file
         d.Process(self.test_dir)
         d.Finish(stop_pool=False)
         processed.sort()
         expected = add_extension(["bar", "abc/bar", "def/bar"])
         expected.sort()
         self.assertEqual(processed, expected)
 
+
 def mock_dump_syms(module_id, filename, extra=[]):
     return ["MODULE os x86 %s %s" % (module_id, filename)
             ] + extra + [
             "FILE 0 foo.c",
             "PUBLIC xyz 123"]
 
 
 class TestCopyDebug(HelperMixin, unittest.TestCase):
--- a/widget/IMEData.h
+++ b/widget/IMEData.h
@@ -108,17 +108,16 @@ struct nsIMEUpdatePreference final
   bool WantDuringDeactive() const
   {
     return !!(mWantUpdates & NOTIFY_DURING_DEACTIVE);
   }
 
   Notifications mWantUpdates;
 };
 
-
 /**
  * Contains IMEStatus plus information about the current 
  * input context that the IME can use as hints if desired.
  */
 
 namespace mozilla {
 namespace widget {
 
@@ -420,16 +419,17 @@ struct IMENotification final
       case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
         mMouseButtonEventData.mEventMessage = eVoidEvent;
         mMouseButtonEventData.mOffset = UINT32_MAX;
         mMouseButtonEventData.mCursorPos.Set(nsIntPoint(0, 0));
         mMouseButtonEventData.mCharRect.Set(nsIntRect(0, 0, 0, 0));
         mMouseButtonEventData.mButton = -1;
         mMouseButtonEventData.mButtons = 0;
         mMouseButtonEventData.mModifiers = 0;
+        break;
       default:
         break;
     }
   }
 
   void Assign(const IMENotification& aOther)
   {
     bool changingMessage = mMessage != aOther.mMessage;
--- a/widget/nsNativeTheme.cpp
+++ b/widget/nsNativeTheme.cpp
@@ -41,19 +41,17 @@ nsNativeTheme::nsNativeTheme()
 NS_IMPL_ISUPPORTS(nsNativeTheme, nsITimerCallback)
 
 nsIPresShell *
 nsNativeTheme::GetPresShell(nsIFrame* aFrame)
 {
   if (!aFrame)
     return nullptr;
 
-  // this is a workaround for the egcs 1.1.2 not inlining
-  // aFrame->PresContext(), which causes an undefined symbol
-  nsPresContext *context = aFrame->StyleContext()->RuleNode()->PresContext();
+  nsPresContext* context = aFrame->PresContext();
   return context ? context->GetPresShell() : nullptr;
 }
 
 EventStates
 nsNativeTheme::GetContentState(nsIFrame* aFrame, uint8_t aWidgetType)
 {
   if (!aFrame)
     return EventStates();