Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 06 Sep 2016 15:42:31 +0200
changeset 354118 cbaece1227e69e8b7bf7192cc36390a17aa41587
parent 354117 bb04b5569ab96f761384ca2b6d7206aa2daac416 (current diff)
parent 354094 bec2b7e3c0eb2adf0370f94802eca96e41f5879b (diff)
child 354119 47b96bd9bad4e5e78b3d40ebacd190c44cfe26e0
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -6217,17 +6217,17 @@ private:
   RefPtr<Factory> mFactory;
   RefPtr<FullDatabaseMetadata> mMetadata;
   RefPtr<FileManager> mFileManager;
   RefPtr<DirectoryLock> mDirectoryLock;
   nsTHashtable<nsPtrHashKey<TransactionBase>> mTransactions;
   nsTHashtable<nsPtrHashKey<MutableFile>> mMutableFiles;
   RefPtr<DatabaseConnection> mConnection;
   const PrincipalInfo mPrincipalInfo;
-  const OptionalContentId mOptionalContentParentId;
+  const Maybe<ContentParentId> mOptionalContentParentId;
   const nsCString mGroup;
   const nsCString mOrigin;
   const nsCString mId;
   const nsString mFilePath;
   uint32_t mActiveMutableFileCount;
   const uint32_t mTelemetryId;
   const PersistenceType mPersistenceType;
   const bool mFileHandleDisabled;
@@ -6237,17 +6237,17 @@ private:
   bool mActorWasAlive;
   bool mActorDestroyed;
   bool mMetadataCleanedUp;
 
 public:
   // Created by OpenDatabaseOp.
   Database(Factory* aFactory,
            const PrincipalInfo& aPrincipalInfo,
-           const OptionalContentId& aOptionalContentParentId,
+           const Maybe<ContentParentId>& aOptionalContentParentId,
            const nsACString& aGroup,
            const nsACString& aOrigin,
            uint32_t aTelemetryId,
            FullDatabaseMetadata* aMetadata,
            FileManager* aFileManager,
            already_AddRefed<DirectoryLock> aDirectoryLock,
            bool aFileHandleDisabled,
            bool aChromeWriteAccessAllowed);
@@ -6276,21 +6276,19 @@ public:
   GetPrincipalInfo() const
   {
     return mPrincipalInfo;
   }
 
   bool
   IsOwnedByProcess(ContentParentId aContentParentId) const
   {
-    MOZ_ASSERT(mOptionalContentParentId.type() != OptionalContentId::T__None);
-
     return
-      mOptionalContentParentId.type() == OptionalContentId::TContentParentId &&
-      mOptionalContentParentId.get_ContentParentId() == aContentParentId;
+      mOptionalContentParentId &&
+      mOptionalContentParentId.value() == aContentParentId;
   }
 
   const nsCString&
   Group() const
   {
     return mGroup;
   }
 
@@ -7446,17 +7444,17 @@ struct FactoryOp::MaybeBlockedDatabaseIn
 class OpenDatabaseOp final
   : public FactoryOp
 {
   friend class Database;
   friend class VersionChangeTransaction;
 
   class VersionChangeOp;
 
-  OptionalContentId mOptionalContentParentId;
+  Maybe<ContentParentId> mOptionalContentParentId;
 
   RefPtr<FullDatabaseMetadata> mMetadata;
 
   uint64_t mRequestedVersion;
   RefPtr<FileManager> mFileManager;
 
   RefPtr<Database> mDatabase;
   RefPtr<VersionChangeTransaction> mVersionChangeTransaction;
@@ -7471,20 +7469,17 @@ class OpenDatabaseOp final
 public:
   OpenDatabaseOp(Factory* aFactory,
                  already_AddRefed<ContentParent> aContentParent,
                  const CommonFactoryRequestParams& aParams);
 
   bool
   IsOtherProcessActor() const
   {
-    MOZ_ASSERT(mOptionalContentParentId.type() != OptionalContentId::T__None);
-
-    return mOptionalContentParentId.type() ==
-             OptionalContentId::TContentParentId;
+    return mOptionalContentParentId.isSome();
   }
 
 private:
   ~OpenDatabaseOp()
   {
     MOZ_ASSERT(!mVersionChangeOp);
   }
 
@@ -13531,17 +13526,17 @@ WaitForTransactionsHelper::Run()
 }
 
 /*******************************************************************************
  * Database
  ******************************************************************************/
 
 Database::Database(Factory* aFactory,
                    const PrincipalInfo& aPrincipalInfo,
-                   const OptionalContentId& aOptionalContentParentId,
+                   const Maybe<ContentParentId>& aOptionalContentParentId,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
                    uint32_t aTelemetryId,
                    FullDatabaseMetadata* aMetadata,
                    FileManager* aFileManager,
                    already_AddRefed<DirectoryLock> aDirectoryLock,
                    bool aFileHandleDisabled,
                    bool aChromeWriteAccessAllowed)
@@ -20756,19 +20751,17 @@ OpenDatabaseOp::OpenDatabaseOp(Factory* 
   , mMetadata(new FullDatabaseMetadata(aParams.metadata()))
   , mRequestedVersion(aParams.metadata().version())
   , mVersionChangeOp(nullptr)
   , mTelemetryId(0)
 {
   if (mContentParent) {
     // This is a little scary but it looks safe to call this off the main thread
     // for now.
-    mOptionalContentParentId = mContentParent->ChildID();
-  } else {
-    mOptionalContentParentId = void_t();
+    mOptionalContentParentId = Some(mContentParent->ChildID());
   }
 }
 
 void
 OpenDatabaseOp::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnOwningThread();
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -3638,17 +3638,17 @@ ContentParent::DeallocPPresentationParen
   RefPtr<PresentationParent> actor =
   dont_AddRef(static_cast<PresentationParent*>(aActor));
   return true;
 }
 
 bool
 ContentParent::RecvPPresentationConstructor(PPresentationParent* aActor)
 {
-  return static_cast<PresentationParent*>(aActor)->Init();
+  return static_cast<PresentationParent*>(aActor)->Init(mChildID);
 }
 
 PFlyWebPublishedServerParent*
 ContentParent::AllocPFlyWebPublishedServerParent(const nsString& name,
                                                  const FlyWebPublishOptions& params)
 {
   RefPtr<FlyWebPublishedServerParent> actor =
     new FlyWebPublishedServerParent(name, params);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -319,22 +319,16 @@ union MaybeFileDesc {
     void_t;
 };
 
 union FileDescOrError {
     FileDescriptor;
     nsresult;
 };
 
-union OptionalContentId
-{
-  ContentParentId;
-  void_t;
-};
-
 struct DomainPolicyClone
 {
     bool        active;
     URIParams[] blacklist;
     URIParams[] whitelist;
     URIParams[] superBlacklist;
     URIParams[] superWhitelist;
 };
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -867,18 +867,18 @@ nsresult MediaDecoderStateMachine::Init(
     OwnerThread(), __func__, this,
     &MediaDecoderStateMachine::OnCDMProxyReady,
     &MediaDecoderStateMachine::OnCDMProxyNotReady));
 #endif
 
   nsresult rv = mReader->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  OwnerThread()->Dispatch(NewRunnableMethod<State>(
-    this, &MediaDecoderStateMachine::EnterState, mState.Ref()));
+  OwnerThread()->Dispatch(
+    NewRunnableMethod(this, &MediaDecoderStateMachine::EnterState));
 
   return NS_OK;
 }
 
 void
 MediaDecoderStateMachine::SetMediaDecoderReaderWrapperCallback()
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -1058,39 +1058,39 @@ MediaDecoderStateMachine::SetState(State
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mState == aState) {
     return;
   }
 
   DECODER_LOG("MDSM state: %s -> %s", ToStateStr(), ToStateStr(aState));
 
-  ExitState(mState);
+  ExitState();
   mState = aState;
-  EnterState(mState);
+  EnterState();
 }
 
 void
-MediaDecoderStateMachine::ExitState(State aState)
+MediaDecoderStateMachine::ExitState()
 {
   MOZ_ASSERT(OnTaskQueue());
-  switch (aState) {
+  switch (mState) {
     case DECODER_STATE_COMPLETED:
       mSentPlaybackEndedEvent = false;
       break;
     default:
       break;
   }
 }
 
 void
-MediaDecoderStateMachine::EnterState(State aState)
+MediaDecoderStateMachine::EnterState()
 {
   MOZ_ASSERT(OnTaskQueue());
-  switch (aState) {
+  switch (mState) {
     case DECODER_STATE_DECODING_METADATA:
       ReadMetadata();
       break;
     case DECODER_STATE_DORMANT:
       DiscardSeekTaskIfExist();
       if (IsPlaying()) {
         StopPlayback();
       }
@@ -2270,135 +2270,140 @@ MediaDecoderStateMachine::FinishShutdown
 void
 MediaDecoderStateMachine::RunStateMachine()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   mDelayedScheduler.Reset(); // Must happen on state machine task queue.
   mDispatchedStateMachine = false;
 
-  MediaResource* resource = mResource;
-  NS_ENSURE_TRUE_VOID(resource);
-
   switch (mState) {
-    case DECODER_STATE_SHUTDOWN:
-    case DECODER_STATE_DORMANT:
-    case DECODER_STATE_WAIT_FOR_CDM:
-    case DECODER_STATE_DECODING_METADATA:
-    case DECODER_STATE_DECODING_FIRSTFRAME:
+    case DECODER_STATE_DECODING:
+      StepDecoding();
+      return;
+    case DECODER_STATE_BUFFERING:
+      StepBuffering();
+      return;
+    case DECODER_STATE_COMPLETED:
+      StepCompleted();
+      return;
+    default:
       return;
-
-    case DECODER_STATE_DECODING: {
-      if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying())
-      {
-        // We're playing, but the element/decoder is in paused state. Stop
-        // playing!
-        StopPlayback();
-      }
-
-      // Start playback if necessary so that the clock can be properly queried.
-      MaybeStartPlayback();
-
-      UpdatePlaybackPositionPeriodically();
-      NS_ASSERTION(!IsPlaying() ||
-                   IsStateMachineScheduled(),
-                   "Must have timer scheduled");
-      MaybeStartBuffering();
+  }
+}
+
+void
+MediaDecoderStateMachine::StepDecoding()
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying()) {
+    // We're playing, but the element/decoder is in paused state. Stop
+    // playing!
+    StopPlayback();
+  }
+
+  // Start playback if necessary so that the clock can be properly queried.
+  MaybeStartPlayback();
+
+  UpdatePlaybackPositionPeriodically();
+
+  NS_ASSERTION(!IsPlaying() ||
+               IsStateMachineScheduled(),
+               "Must have timer scheduled");
+
+  MaybeStartBuffering();
+}
+
+void
+MediaDecoderStateMachine::StepBuffering()
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  TimeStamp now = TimeStamp::Now();
+  NS_ASSERTION(!mBufferingStart.IsNull(), "Must know buffering start time.");
+
+  // With buffering heuristics we will remain in the buffering state if
+  // we've not decoded enough data to begin playback, or if we've not
+  // downloaded a reasonable amount of data inside our buffering time.
+  if (mReader->UseBufferingHeuristics()) {
+    TimeDuration elapsed = now - mBufferingStart;
+    bool isLiveStream = mResource->IsLiveStream();
+    if ((isLiveStream || !CanPlayThrough()) &&
+        elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
+        (mQuickBuffering ? HasLowDecodedData(mQuickBufferingLowDataThresholdUsecs)
+                         : HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
+        mResource->IsExpectingMoreData()) {
+      DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
+                  mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
+                  (mQuickBuffering ? "(quick exit)" : ""));
+      ScheduleStateMachineIn(USECS_PER_S);
       return;
     }
-
-    case DECODER_STATE_BUFFERING: {
-      TimeStamp now = TimeStamp::Now();
-      NS_ASSERTION(!mBufferingStart.IsNull(), "Must know buffering start time.");
-
-      // With buffering heuristics we will remain in the buffering state if
-      // we've not decoded enough data to begin playback, or if we've not
-      // downloaded a reasonable amount of data inside our buffering time.
-      if (mReader->UseBufferingHeuristics()) {
-        TimeDuration elapsed = now - mBufferingStart;
-        bool isLiveStream = resource->IsLiveStream();
-        if ((isLiveStream || !CanPlayThrough()) &&
-              elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
-              (mQuickBuffering ? HasLowDecodedData(mQuickBufferingLowDataThresholdUsecs)
-                               : HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
-              mResource->IsExpectingMoreData())
-        {
-          DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
-                      mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
-                      (mQuickBuffering ? "(quick exit)" : ""));
-          ScheduleStateMachineIn(USECS_PER_S);
-          return;
-        }
-      } else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
-        MOZ_ASSERT(mReader->IsWaitForDataSupported(),
-                   "Don't yet have a strategy for non-heuristic + non-WaitForData");
-        DispatchDecodeTasksIfNeeded();
-        MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mReader->IsRequestingAudioData() || mReader->IsWaitingAudioData());
-        MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mReader->IsRequestingVideoData() || mReader->IsWaitingVideoData());
-        DECODER_LOG("In buffering mode, waiting to be notified: outOfAudio: %d, "
-                    "mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
-                    OutOfDecodedAudio(), AudioRequestStatus(),
-                    OutOfDecodedVideo(), VideoRequestStatus());
-        return;
-      }
-
-      DECODER_LOG("Changed state from BUFFERING to DECODING");
-      DECODER_LOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
-      SetState(DECODER_STATE_DECODING);
-
-      NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
-      return;
-    }
-
-    case DECODER_STATE_SEEKING: {
-      return;
-    }
-
-    case DECODER_STATE_COMPLETED: {
-      if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying()) {
-        StopPlayback();
-      }
-      // Play the remaining media. We want to run AdvanceFrame() at least
-      // once to ensure the current playback position is advanced to the
-      // end of the media, and so that we update the readyState.
-      if ((HasVideo() && !mVideoCompleted) ||
-          (HasAudio() && !mAudioCompleted)) {
-        // Start playback if necessary to play the remaining media.
-        MaybeStartPlayback();
-        UpdatePlaybackPositionPeriodically();
-        NS_ASSERTION(!IsPlaying() ||
-                     IsStateMachineScheduled(),
-                     "Must have timer scheduled");
-        return;
-      }
-
-      // StopPlayback in order to reset the IsPlaying() state so audio
-      // is restarted correctly.
-      StopPlayback();
-
-      if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
-          !mSentPlaybackEndedEvent)
-      {
-        int64_t clockTime = std::max(AudioEndTime(), VideoEndTime());
-        clockTime = std::max(int64_t(0), std::max(clockTime, Duration().ToMicroseconds()));
-        UpdatePlaybackPosition(clockTime);
-
-        // Ensure readyState is updated before firing the 'ended' event.
-        UpdateNextFrameStatus();
-
-        mOnPlaybackEvent.Notify(MediaEventType::PlaybackEnded);
-
-        mSentPlaybackEndedEvent = true;
-
-        // MediaSink::GetEndTime() must be called before stopping playback.
-        StopMediaSink();
-      }
-
-      return;
-    }
+  } else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
+    MOZ_ASSERT(mReader->IsWaitForDataSupported(),
+               "Don't yet have a strategy for non-heuristic + non-WaitForData");
+    DispatchDecodeTasksIfNeeded();
+    MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mReader->IsRequestingAudioData() || mReader->IsWaitingAudioData());
+    MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mReader->IsRequestingVideoData() || mReader->IsWaitingVideoData());
+    DECODER_LOG("In buffering mode, waiting to be notified: outOfAudio: %d, "
+                "mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
+                OutOfDecodedAudio(), AudioRequestStatus(),
+                OutOfDecodedVideo(), VideoRequestStatus());
+    return;
+  }
+
+  DECODER_LOG("Changed state from BUFFERING to DECODING");
+  DECODER_LOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
+  SetState(DECODER_STATE_DECODING);
+  NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
+}
+
+void
+MediaDecoderStateMachine::StepCompleted()
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying()) {
+    StopPlayback();
+  }
+
+  // Play the remaining media. We want to run AdvanceFrame() at least
+  // once to ensure the current playback position is advanced to the
+  // end of the media, and so that we update the readyState.
+  if ((HasVideo() && !mVideoCompleted) ||
+      (HasAudio() && !mAudioCompleted)) {
+    // Start playback if necessary to play the remaining media.
+    MaybeStartPlayback();
+    UpdatePlaybackPositionPeriodically();
+    NS_ASSERTION(!IsPlaying() ||
+                 IsStateMachineScheduled(),
+                 "Must have timer scheduled");
+    return;
+  }
+
+  // StopPlayback in order to reset the IsPlaying() state so audio
+  // is restarted correctly.
+  StopPlayback();
+
+  if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
+      !mSentPlaybackEndedEvent) {
+    int64_t clockTime = std::max(AudioEndTime(), VideoEndTime());
+    clockTime = std::max(int64_t(0), std::max(clockTime, Duration().ToMicroseconds()));
+    UpdatePlaybackPosition(clockTime);
+
+    // Ensure readyState is updated before firing the 'ended' event.
+    UpdateNextFrameStatus();
+
+    mOnPlaybackEvent.Notify(MediaEventType::PlaybackEnded);
+
+    mSentPlaybackEndedEvent = true;
+
+    // MediaSink::GetEndTime() must be called before stopping playback.
+    StopMediaSink();
   }
 }
 
 void
 MediaDecoderStateMachine::Reset(TrackSet aTracks)
 {
   MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("MediaDecoderStateMachine::Reset");
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -351,18 +351,18 @@ private:
   // and aborting all pending operations on the decode task queue.
   void Reset(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack,
                                          TrackInfo::kVideoTrack));
 
 protected:
   virtual ~MediaDecoderStateMachine();
 
   void SetState(State aState);
-  void ExitState(State aState);
-  void EnterState(State aState);
+  void ExitState();
+  void EnterState();
 
   void BufferedRangeUpdated();
 
   void ReaderSuspendedChanged();
 
   // Inserts MediaData* samples into their respective MediaQueues.
   // aSample must not be null.
 
@@ -551,16 +551,22 @@ protected:
   // Completes the seek operation, moves onto the next appropriate state.
   void SeekCompleted();
 
   // Queries our state to see whether the decode has finished for all streams.
   bool CheckIfDecodeComplete();
 
   // Performs one "cycle" of the state machine.
   void RunStateMachine();
+  // Perform one cycle of the DECODING state.
+  void StepDecoding();
+  // Perform one cycle of the BUFFERING state.
+  void StepBuffering();
+  // Perform one cycle of the COMPLETED state.
+  void StepCompleted();
 
   bool IsStateMachineScheduled() const;
 
   // These return true if the respective stream's decode has not yet reached
   // the end of stream.
   bool IsAudioDecoding();
   bool IsVideoDecoding();
 
--- a/dom/presentation/PresentationDeviceManager.cpp
+++ b/dom/presentation/PresentationDeviceManager.cpp
@@ -183,17 +183,17 @@ PresentationDeviceManager::GetAvailableD
   for (uint32_t i = 0; i < mDevices.Length(); ++i) {
     if (presentationUrls.IsEmpty()) {
       devices->AppendElement(mDevices[i], false);
       continue;
     }
 
     for (uint32_t j = 0; j < presentationUrls.Length(); ++j) {
       bool isSupported;
-      if (NS_SUCCEEDED(mDevices[i]->IsRequestedUrlSupported(presentationUrls[i],
+      if (NS_SUCCEEDED(mDevices[i]->IsRequestedUrlSupported(presentationUrls[j],
                                                             &isSupported)) &&
           isSupported) {
         devices->AppendElement(mDevices[i], false);
         break;
       }
     }
   }
 
--- a/dom/presentation/PresentationRequest.cpp
+++ b/dom/presentation/PresentationRequest.cpp
@@ -194,19 +194,25 @@ PresentationRequest::StartWithDevice(con
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if(NS_WARN_IF(!service)) {
     promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
     return promise.forget();
   }
 
+  // Get xul:browser element in parent process or nsWindowRoot object in child
+  // process. If it's in child process, the corresponding xul:browser element
+  // will be obtained at PresentationRequestParent::DoRequest in its parent
+  // process.
+  nsCOMPtr<nsIDOMEventTarget> handler =
+    do_QueryInterface(GetOwner()->GetChromeEventHandler());
   nsCOMPtr<nsIPresentationServiceCallback> callback =
     new PresentationRequesterCallback(this, id, promise);
-  rv = service->StartSession(mUrls, id, origin, aDeviceId, GetOwner()->WindowID(), callback);
+  rv = service->StartSession(mUrls, id, origin, aDeviceId, GetOwner()->WindowID(), handler, callback);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -106,47 +106,51 @@ class PresentationDeviceRequest final : 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONDEVICEREQUEST
 
   PresentationDeviceRequest(const nsTArray<nsString>& aUrls,
                             const nsAString& aId,
                             const nsAString& aOrigin,
                             uint64_t aWindowId,
+                            nsIDOMEventTarget* aEventTarget,
                             nsIPresentationServiceCallback* aCallback);
 
 private:
   virtual ~PresentationDeviceRequest() = default;
   nsresult CreateSessionInfo(nsIPresentationDevice* aDevice,
                              const nsAString& aSelectedRequestUrl);
 
   nsTArray<nsString> mRequestUrls;
   nsString mId;
   nsString mOrigin;
   uint64_t mWindowId;
+  nsWeakPtr mChromeEventHandler;
   nsCOMPtr<nsIPresentationServiceCallback> mCallback;
 };
 
 LazyLogModule gPresentationLog("Presentation");
 
 } // namespace dom
 } // namespace mozilla
 
 NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
 
 PresentationDeviceRequest::PresentationDeviceRequest(
                                       const nsTArray<nsString>& aUrls,
                                       const nsAString& aId,
                                       const nsAString& aOrigin,
                                       uint64_t aWindowId,
+                                      nsIDOMEventTarget* aEventTarget,
                                       nsIPresentationServiceCallback* aCallback)
   : mRequestUrls(aUrls)
   , mId(aId)
   , mOrigin(aOrigin)
   , mWindowId(aWindowId)
+  , mChromeEventHandler(do_GetWeakReference(aEventTarget))
   , mCallback(aCallback)
 {
   MOZ_ASSERT(!mRequestUrls.IsEmpty());
   MOZ_ASSERT(!mId.IsEmpty());
   MOZ_ASSERT(!mOrigin.IsEmpty());
   MOZ_ASSERT(mCallback);
 }
 
@@ -159,16 +163,24 @@ PresentationDeviceRequest::GetOrigin(nsA
 
 NS_IMETHODIMP
 PresentationDeviceRequest::GetRequestURLs(nsIArray** aUrls)
 {
   return ConvertURLArrayHelper(mRequestUrls, aUrls);
 }
 
 NS_IMETHODIMP
+PresentationDeviceRequest::GetChromeEventHandler(nsIDOMEventTarget** aChromeEventHandler)
+{
+  nsCOMPtr<nsIDOMEventTarget> handler(do_QueryReferent(mChromeEventHandler));
+  handler.forget(aChromeEventHandler);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (NS_WARN_IF(!aDevice)) {
     MOZ_ASSERT(false, "|aDevice| should noe be null.");
     mCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
     return NS_ERROR_INVALID_ARG;
   }
@@ -629,30 +641,32 @@ PresentationService::IsAppInstalled(nsIU
 }
 
 NS_IMETHODIMP
 PresentationService::StartSession(const nsTArray<nsString>& aUrls,
                                   const nsAString& aSessionId,
                                   const nsAString& aOrigin,
                                   const nsAString& aDeviceId,
                                   uint64_t aWindowId,
+                                  nsIDOMEventTarget* aEventTarget,
                                   nsIPresentationServiceCallback* aCallback)
 {
   PRES_DEBUG("%s:id[%s]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get());
 
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aCallback);
   MOZ_ASSERT(!aSessionId.IsEmpty());
   MOZ_ASSERT(!aUrls.IsEmpty());
 
   nsCOMPtr<nsIPresentationDeviceRequest> request =
     new PresentationDeviceRequest(aUrls,
                                   aSessionId,
                                   aOrigin,
                                   aWindowId,
+                                  aEventTarget,
                                   aCallback);
 
   if (aDeviceId.IsVoid()) {
     // Pop up a prompt and ask user to select a device.
     nsCOMPtr<nsIPresentationDevicePrompt> prompt =
       do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID);
     if (NS_WARN_IF(!prompt)) {
       return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
--- a/dom/presentation/interfaces/nsIPresentationDevicePrompt.idl
+++ b/dom/presentation/interfaces/nsIPresentationDevicePrompt.idl
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIArray;
+interface nsIDOMEventTarget;
 interface nsIPresentationDevice;
 
 %{C++
 #define PRESENTATION_DEVICE_PROMPT_CONTRACTID "@mozilla.org/presentation-device/prompt;1"
 %}
 
 /*
  * The information and callbacks for device selection
@@ -18,16 +19,19 @@ interface nsIPresentationDevice;
 interface nsIPresentationDeviceRequest : nsISupports
 {
   // The origin which initiate the request.
   readonly attribute DOMString origin;
 
   // The array of candidate URLs.
   readonly attribute nsIArray requestURLs;
 
+  // The XUL browser element that the request was originated in.
+  readonly attribute nsIDOMEventTarget chromeEventHandler;
+
   /*
    * Callback after selecting a device
    * @param device The selected device.
    */
   void select(in nsIPresentationDevice device);
 
   /*
    * Callback after selection failed or canceled by user.
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
+interface nsIDOMEventTarget;
 interface nsIInputStream;
 interface nsIPresentationAvailabilityListener;
 interface nsIPresentationRespondingListener;
 interface nsIPresentationSessionListener;
 
 %{C++
 #define PRESENTATION_SERVICE_CID \
   { 0x1d9bb10c, 0xc0ab, 0x4fe8, \
@@ -54,31 +55,35 @@ interface nsIPresentationService : nsISu
   /*
    * Start a new presentation session and display a prompt box which asks users
    * to select a device.
    *
    * @param urls: The candidate Urls of presenting page. Only one url would be used.
    * @param sessionId: An ID to identify presentation session.
    * @param origin: The url of requesting page.
    * @param deviceId: The specified device of handling this request, null string
-                      for prompt device selection dialog.
+   *                  for prompt device selection dialog.
    * @param windowId: The inner window ID associated with the presentation
    *                  session. (0 implies no window ID since no actual window
    *                  uses 0 as its ID. Generally it's the case the window is
    *                  located in different process from this service)
+   * @param eventTarget: The chrome event handler, in particular XUL browser
+   *                     element in parent process, that the request was
+   *                     originated in.
    * @param callback: Invoke the callback when the operation is completed.
    *                  NotifySuccess() is called with |id| if a session is
    *                  established successfully with the selected device.
    *                  Otherwise, NotifyError() is called with a error message.
    */
   [noscript] void startSession(in URLArrayRef urls,
                                in DOMString sessionId,
                                in DOMString origin,
                                in DOMString deviceId,
                                in unsigned long long windowId,
+                               in nsIDOMEventTarget eventTarget,
                                in nsIPresentationServiceCallback callback);
 
   /*
    * Send the message to the session.
    *
    * @param sessionId: An ID to identify presentation session.
    * @param role: Identify the function called by controller or receiver.
    * @param data: the message being sent out.
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -5,26 +5,29 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PContent;
 include protocol PPresentationRequest;
 include protocol PPresentationBuilder;
 
 include InputStreamParams;
 
+using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
+
 namespace mozilla {
 namespace dom {
 
 struct StartSessionRequest
 {
   nsString[] urls;
   nsString sessionId;
   nsString origin;
   nsString deviceId;
   uint64_t windowId;
+  TabId tabId;
 };
 
 struct SendSessionMessageRequest
 {
   nsString sessionId;
   uint8_t role;
   nsString data;
 };
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PPresentation.h"
+#include "mozilla/dom/TabParent.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsIPresentationListener.h"
 #include "PresentationCallbacks.h"
 #include "PresentationChild.h"
 #include "PresentationContentSessionInfo.h"
 #include "PresentationIPCService.h"
@@ -52,29 +53,35 @@ PresentationIPCService::~PresentationIPC
 }
 
 NS_IMETHODIMP
 PresentationIPCService::StartSession(const nsTArray<nsString>& aUrls,
                                      const nsAString& aSessionId,
                                      const nsAString& aOrigin,
                                      const nsAString& aDeviceId,
                                      uint64_t aWindowId,
+                                     nsIDOMEventTarget* aEventTarget,
                                      nsIPresentationServiceCallback* aCallback)
 {
   if (aWindowId != 0) {
     AddRespondingSessionId(aWindowId,
                            aSessionId,
                            nsIPresentationService::ROLE_CONTROLLER);
   }
 
+  nsPIDOMWindowInner* window =
+    nsGlobalWindow::GetInnerWindowWithId(aWindowId)->AsInner();
+  TabId tabId = TabParent::GetTabIdFrom(window->GetDocShell());
+
   return SendRequest(aCallback, StartSessionRequest(aUrls,
                                                     nsString(aSessionId),
                                                     nsString(aOrigin),
                                                     nsString(aDeviceId),
-                                                    aWindowId));
+                                                    aWindowId,
+                                                    tabId));
 }
 
 NS_IMETHODIMP
 PresentationIPCService::SendSessionMessage(const nsAString& aSessionId,
                                            uint8_t aRole,
                                            const nsAString& aData)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DCPresentationChannelDescription.h"
+#include "mozilla/dom/ContentProcessManager.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/Unused.h"
 #include "nsIPresentationDeviceManager.h"
 #include "nsServiceManagerUtils.h"
 #include "PresentationBuilderParent.h"
 #include "PresentationParent.h"
 #include "PresentationService.h"
 #include "PresentationSessionInfo.h"
@@ -31,20 +32,21 @@ PresentationParent::PresentationParent()
 }
 
 /* virtual */ PresentationParent::~PresentationParent()
 {
   MOZ_COUNT_DTOR(PresentationParent);
 }
 
 bool
-PresentationParent::Init()
+PresentationParent::Init(ContentParentId aContentParentId)
 {
   MOZ_ASSERT(!mService);
   mService = do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  mChildId = aContentParentId;
   return NS_WARN_IF(!mService) ? false : true;
 }
 
 void
 PresentationParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   mActorDestroyed = true;
 
@@ -103,17 +105,17 @@ PresentationParent::RecvPPresentationReq
   return NS_WARN_IF(NS_FAILED(rv)) ? false : true;
 }
 
 PPresentationRequestParent*
 PresentationParent::AllocPPresentationRequestParent(
   const PresentationIPCRequest& aRequest)
 {
   MOZ_ASSERT(mService);
-  RefPtr<PresentationRequestParent> actor = new PresentationRequestParent(mService);
+  RefPtr<PresentationRequestParent> actor = new PresentationRequestParent(mService, mChildId);
   return actor.forget().take();
 }
 
 bool
 PresentationParent::DeallocPPresentationRequestParent(
   PPresentationRequestParent* aActor)
 {
   RefPtr<PresentationRequestParent> actor =
@@ -305,18 +307,20 @@ PresentationParent::RecvNotifyTransportC
 }
 
 /*
  * Implementation of PresentationRequestParent
  */
 
 NS_IMPL_ISUPPORTS(PresentationRequestParent, nsIPresentationServiceCallback)
 
-PresentationRequestParent::PresentationRequestParent(nsIPresentationService* aService)
+PresentationRequestParent::PresentationRequestParent(nsIPresentationService* aService,
+                                                     ContentParentId aContentParentId)
   : mService(aService)
+  , mChildId(aContentParentId)
 {
   MOZ_COUNT_CTOR(PresentationRequestParent);
 }
 
 PresentationRequestParent::~PresentationRequestParent()
 {
   MOZ_COUNT_DTOR(PresentationRequestParent);
 }
@@ -329,19 +333,28 @@ PresentationRequestParent::ActorDestroy(
 }
 
 nsresult
 PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   mNeedRegisterBuilder = true;
   mSessionId = aRequest.sessionId();
+
+  nsCOMPtr<nsIDOMEventTarget> eventTarget;
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  RefPtr<TabParent> tp =
+    cpm->GetTopLevelTabParentByProcessAndTabId(mChildId, aRequest.tabId());
+  if (tp) {
+    eventTarget = do_QueryInterface(tp->GetOwnerElement());
+  }
+  
   return mService->StartSession(aRequest.urls(), aRequest.sessionId(),
                                 aRequest.origin(), aRequest.deviceId(),
-                                aRequest.windowId(), this);
+                                aRequest.windowId(), eventTarget, this);
 }
 
 nsresult
 PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest)
 {
   MOZ_ASSERT(mService);
 
   // Validate the accessibility (primarily for receiver side) so that a
--- a/dom/presentation/ipc/PresentationParent.h
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -2,16 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_PresentationParent_h__
 #define mozilla_dom_PresentationParent_h__
 
+#include "mozilla/dom/ipc/IdType.h"
 #include "mozilla/dom/PPresentationBuilderParent.h"
 #include "mozilla/dom/PPresentationParent.h"
 #include "mozilla/dom/PPresentationRequestParent.h"
 #include "nsIPresentationListener.h"
 #include "nsIPresentationService.h"
 #include "nsIPresentationSessionTransportBuilder.h"
 
 namespace mozilla {
@@ -25,17 +26,17 @@ class PresentationParent final : public 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONAVAILABILITYLISTENER
   NS_DECL_NSIPRESENTATIONSESSIONLISTENER
   NS_DECL_NSIPRESENTATIONRESPONDINGLISTENER
 
   PresentationParent();
 
-  bool Init();
+  bool Init(ContentParentId aContentParentId);
 
   bool RegisterTransportBuilder(const nsString& aSessionId, const uint8_t& aRole);
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual bool
   RecvPPresentationRequestConstructor(PPresentationRequestParent* aActor,
                                       const PresentationIPCRequest& aRequest) override;
@@ -81,28 +82,30 @@ public:
 private:
   virtual ~PresentationParent();
 
   bool mActorDestroyed = false;
   nsCOMPtr<nsIPresentationService> mService;
   nsTArray<nsString> mSessionIdsAtController;
   nsTArray<nsString> mSessionIdsAtReceiver;
   nsTArray<uint64_t> mWindowIds;
+  ContentParentId mChildId;
 };
 
 class PresentationRequestParent final : public PPresentationRequestParent
                                       , public nsIPresentationServiceCallback
 {
   friend class PresentationParent;
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONSERVICECALLBACK
 
-  explicit PresentationRequestParent(nsIPresentationService* aService);
+  explicit PresentationRequestParent(nsIPresentationService* aService,
+                                     ContentParentId aContentParentId);
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
 private:
   virtual ~PresentationRequestParent();
 
   nsresult SendResponse(nsresult aResult);
 
@@ -117,14 +120,15 @@ private:
   nsresult DoRequest(const ReconnectSessionRequest& aRequest);
 
   nsresult DoRequest(const BuildTransportRequest& aRequest);
 
   bool mActorDestroyed = false;
   bool mNeedRegisterBuilder = false;
   nsString mSessionId;
   nsCOMPtr<nsIPresentationService> mService;
+  ContentParentId mChildId;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationParent_h__
--- a/gfx/thebes/gfxAndroidPlatform.cpp
+++ b/gfx/thebes/gfxAndroidPlatform.cpp
@@ -168,16 +168,17 @@ IsJapaneseLocale()
 
 void
 gfxAndroidPlatform::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
                                            Script aRunScript,
                                            nsTArray<const char*>& aFontList)
 {
     static const char kDroidSansJapanese[] = "Droid Sans Japanese";
     static const char kMotoyaLMaru[] = "MotoyaLMaru";
+    static const char kNotoSansCJKJP[] = "Noto Sans CJK JP";
     static const char kNotoColorEmoji[] = "Noto Color Emoji";
 #ifdef MOZ_WIDGET_GONK
     static const char kFirefoxEmoji[] = "Firefox Emoji";
 #endif
 
     if (aNextCh == 0xfe0fu) {
         // if char is followed by VS16, try for a color emoji glyph
 #ifdef MOZ_WIDGET_GONK
@@ -224,22 +225,24 @@ gfxAndroidPlatform::GetCommonFallbackFon
             aFontList.AppendElement("Droid Sans Georgian");
             break;
         case 0x12: case 0x13:
             aFontList.AppendElement("Droid Sans Ethiopic");
             break;
         case 0xf9: case 0xfa:
             if (IsJapaneseLocale()) {
                 aFontList.AppendElement(kMotoyaLMaru);
+                aFontList.AppendElement(kNotoSansCJKJP);
                 aFontList.AppendElement(kDroidSansJapanese);
             }
             break;
         default:
             if (block >= 0x2e && block <= 0x9f && IsJapaneseLocale()) {
                 aFontList.AppendElement(kMotoyaLMaru);
+                aFontList.AppendElement(kNotoSansCJKJP);
                 aFontList.AppendElement(kDroidSansJapanese);
             }
             break;
         }
     }
     // and try Droid Sans Fallback as a last resort
     aFontList.AppendElement("Droid Sans Fallback");
 }
--- a/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
@@ -80,21 +80,25 @@ class ActionBarTextSelection implements 
     @Override
     public boolean dismiss() {
         // We do not call endActionMode() here because this is already handled by the activity.
         return false;
     }
 
     @Override
     public void destroy() {
-        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
-            "TextSelection:ActionbarInit",
-            "TextSelection:ActionbarStatus",
-            "TextSelection:ActionbarUninit",
-            "TextSelection:Update");
+        if (anchorHandle == null) {
+            Log.e(LOGTAG, "Do not unregister TextSelection:* listeners since anchorHandle is null");
+        } else {
+            EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+                    "TextSelection:ActionbarInit",
+                    "TextSelection:ActionbarStatus",
+                    "TextSelection:ActionbarUninit",
+                    "TextSelection:Update");
+        }
     }
 
     @Override
     public void handleMessage(final String event, final JSONObject message) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 try {
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconResponse.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/IconResponse.java
@@ -65,17 +65,21 @@ public class IconResponse {
     private Bitmap bitmap;
     private int color;
     private boolean fromNetwork;
     private boolean fromMemory;
     private boolean fromDisk;
     private boolean generated;
     private String url;
 
-    private IconResponse(@NonNull Bitmap bitmap) {
+    private IconResponse(Bitmap bitmap) {
+        if (bitmap == null) {
+            throw new NullPointerException("Bitmap is null");
+        }
+
         this.bitmap = bitmap;
         this.color = 0;
         this.url = null;
         this.fromNetwork = false;
         this.fromMemory = false;
         this.fromDisk = false;
         this.generated = false;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconTask.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/IconTask.java
@@ -18,17 +18,17 @@ import org.mozilla.gecko.util.ThreadUtil
 import java.util.List;
 import java.util.concurrent.Callable;
 
 /**
  * Task that will be run by the IconRequestExecutor for every icon request.
  */
 /* package-private */ class IconTask implements Callable<IconResponse> {
     private static final String LOGTAG = "Gecko/IconTask";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private final List<Preparer> preparers;
     private final List<IconLoader> loaders;
     private final List<Processor> processors;
     private final IconLoader generator;
     private final IconRequest request;
 
     /* package-private */ IconTask(
--- a/mobile/android/base/java/org/mozilla/gecko/icons/decoders/LoadFaviconResult.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/decoders/LoadFaviconResult.java
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.icons.decoders;
 
 import android.graphics.Bitmap;
+import android.support.annotation.Nullable;
 import android.util.Log;
 import android.util.SparseArray;
 
 import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -72,16 +73,17 @@ public class LoadFaviconResult {
         } catch (OutOfMemoryError e) {
             Log.w(LOGTAG, "Out of memory re-compressing favicon.");
         }
 
         Log.w(LOGTAG, "Favicon re-compression failed.");
         return null;
     }
 
+    @Nullable
     public Bitmap getBestBitmap(int targetWidthAndHeight) {
         final SparseArray<Bitmap> iconMap = new SparseArray<>();
         final List<Integer> sizes = new ArrayList<>();
 
         while (bitmapsDecoded.hasNext()) {
             final Bitmap b = bitmapsDecoded.next();
 
             // It's possible to receive null, most likely due to OOM or a zero-sized image,
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.icons.loader;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.icons.decoders.FaviconDecoder;
 import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
 import org.mozilla.gecko.icons.IconRequest;
 import org.mozilla.gecko.icons.IconResponse;
@@ -55,19 +56,22 @@ public class IconDownloader implements I
         }
 
         try {
             LoadFaviconResult result = downloadAndDecodeImage(request.getContext(), iconUrl);
             if (result == null) {
                 return null;
             }
 
-            return IconResponse.createFromNetwork(
-                    result.getBestBitmap(request.getTargetSize()),
-                    iconUrl);
+            final Bitmap bitmap = result.getBestBitmap(request.getTargetSize());
+            if (bitmap == null) {
+                return null;
+            }
+
+            return IconResponse.createFromNetwork(bitmap, iconUrl);
         } catch (Exception e) {
             Log.e(LOGTAG, "Error reading favicon", e);
         } catch (OutOfMemoryError e) {
             Log.e(LOGTAG, "Insufficient memory to process favicon");
         }
 
         return null;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/icons/storage/DiskStorage.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/storage/DiskStorage.java
@@ -61,17 +61,17 @@ public class DiskStorage {
     private DiskLruCache cache;
 
     private DiskStorage(Context context) {
         this.context = context.getApplicationContext();
     }
 
     @CheckResult
     private synchronized DiskLruCache ensureCacheIsReady() throws IOException {
-        if (cache == null) {
+        if (cache == null || cache.isClosed()) {
             cache = DiskLruCache.open(
                     new File(context.getCacheDir(), "icons"),
                     CACHE_VERSION,
                     1,
                     DISK_CACHE_SIZE);
         }
 
         return cache;
@@ -230,16 +230,17 @@ public class DiskStorage {
     /**
      * Remove all entries from this cache.
      */
     public void evictAll() {
         try {
             final DiskLruCache cache = ensureCacheIsReady();
 
             cache.delete();
+
         } catch (IOException e) {
             Log.w(LOGTAG, "IOException while accessing disk cache", e);
         }
     }
 
     /**
      * Create a key for this URL using the given prefix.
      *
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4215,24 +4215,25 @@ pref("font.name.sans-serif.he", "Clear S
 pref("font.name.monospace.he", "Droid Sans Mono");
 pref("font.name-list.serif.he", "Noto Serif");
 pref("font.name-list.sans-serif.he", "Droid Sans Hebrew, Clear Sans, Droid Sans");
 
 pref("font.name.serif.ja", "Charis SIL Compact");
 pref("font.name.sans-serif.ja", "Clear Sans");
 pref("font.name.monospace.ja", "MotoyaLMaru");
 pref("font.name-list.serif.ja", "Noto Serif, Droid Serif");
-pref("font.name-list.sans-serif.ja", "Clear Sans, Roboto, Droid Sans, MotoyaLMaru, MotoyaLCedar, Noto Sans JP, Droid Sans Japanese");
-pref("font.name-list.monospace.ja", "MotoyaLMaru, MotoyaLCedar, Droid Sans Mono");
+pref("font.name-list.sans-serif.ja", "Clear Sans, Roboto, Droid Sans, MotoyaLMaru, MotoyaLCedar, Noto Sans JP, Noto Sans CJK JP, Droid Sans Japanese");
+pref("font.name-list.monospace.ja", "MotoyaLMaru, MotoyaLCedar, Droid Sans Mono CJK JP, Droid Sans Mono");
 
 pref("font.name.serif.ko", "Charis SIL Compact");
 pref("font.name.sans-serif.ko", "Clear Sans");
 pref("font.name.monospace.ko", "Droid Sans Mono");
 pref("font.name-list.serif.ko", "Noto Serif, Droid Serif, HYSerif");
-pref("font.name-list.sans-serif.ko", "SmartGothic, NanumGothic, Noto Sans KR, DroidSansFallback, Droid Sans Fallback");
+pref("font.name-list.sans-serif.ko", "SmartGothic, NanumGothic, Noto Sans KR, Noto Sans CJK KR, DroidSansFallback, Droid Sans Fallback");
+pref("font.name-list.monospace.ko", "Noto Sans Mono CJK KR");
 
 pref("font.name.serif.th", "Charis SIL Compact");
 pref("font.name.sans-serif.th", "Clear Sans");
 pref("font.name.monospace.th", "Droid Sans Mono");
 pref("font.name-list.serif.th", "Noto Serif, Droid Serif");
 pref("font.name-list.sans-serif.th", "Droid Sans Thai, Clear Sans, Droid Sans");
 
 pref("font.name.serif.x-cyrillic", "Charis SIL Compact");
@@ -4252,32 +4253,32 @@ pref("font.name.sans-serif.x-western", "
 pref("font.name.monospace.x-western", "Droid Sans Mono");
 pref("font.name-list.serif.x-western", "Noto Serif, Droid Serif");
 pref("font.name-list.sans-serif.x-western", "Clear Sans, Roboto, Droid Sans");
 
 pref("font.name.serif.zh-CN", "Charis SIL Compact");
 pref("font.name.sans-serif.zh-CN", "Clear Sans");
 pref("font.name.monospace.zh-CN", "Droid Sans Mono");
 pref("font.name-list.serif.zh-CN", "Noto Serif, Droid Serif, Droid Sans Fallback");
-pref("font.name-list.sans-serif.zh-CN", "Roboto, Droid Sans, Noto Sans SC, Droid Sans Fallback");
-pref("font.name-list.monospace.zh-CN", "Droid Sans Fallback");
+pref("font.name-list.sans-serif.zh-CN", "Roboto, Droid Sans, Noto Sans SC, Noto Sans CJK SC, Droid Sans Fallback");
+pref("font.name-list.monospace.zh-CN", "Noto Sans Mono CJK SC, Droid Sans Fallback");
 
 pref("font.name.serif.zh-HK", "Charis SIL Compact");
 pref("font.name.sans-serif.zh-HK", "Clear Sans");
 pref("font.name.monospace.zh-HK", "Droid Sans Mono");
 pref("font.name-list.serif.zh-HK", "Noto Serif, Droid Serif, Droid Sans Fallback");
-pref("font.name-list.sans-serif.zh-HK", "Roboto, Droid Sans, Noto Sans TC, Noto Sans SC, Droid Sans Fallback");
-pref("font.name-list.monospace.zh-HK", "Droid Sans Fallback");
+pref("font.name-list.sans-serif.zh-HK", "Roboto, Droid Sans, Noto Sans TC, Noto Sans SC, Noto Sans CJK TC, Droid Sans Fallback");
+pref("font.name-list.monospace.zh-HK", "Noto Sans Mono CJK TC, Droid Sans Fallback");
 
 pref("font.name.serif.zh-TW", "Charis SIL Compact");
 pref("font.name.sans-serif.zh-TW", "Clear Sans");
 pref("font.name.monospace.zh-TW", "Droid Sans Mono");
 pref("font.name-list.serif.zh-TW", "Noto Serif, Droid Serif, Droid Sans Fallback");
-pref("font.name-list.sans-serif.zh-TW", "Roboto, Droid Sans, Noto Sans TC, Noto Sans SC, Droid Sans Fallback");
-pref("font.name-list.monospace.zh-TW", "Droid Sans Fallback");
+pref("font.name-list.sans-serif.zh-TW", "Roboto, Droid Sans, Noto Sans TC, Noto Sans SC, Noto Sans CJK TC, Droid Sans Fallback");
+pref("font.name-list.monospace.zh-TW", "Noto Sans Mono CJK TC, Droid Sans Fallback");
 
 pref("font.name.serif.x-math", "Latin Modern Math");
 pref("font.name-list.serif.x-math", "Latin Modern Math, XITS Math, Cambria Math, Libertinus Math, DejaVu Math TeX Gyre, TeX Gyre Bonum Math, TeX Gyre Pagella Math, TeX Gyre Schola, TeX Gyre Termes Math, STIX Math, Asana Math, STIXGeneral, DejaVu Serif, DejaVu Sans, Charis SIL Compact");
 pref("font.name.sans-serif.x-math", "Clear Sans");
 pref("font.name.monospace.x-math", "Droid Sans Mono");
 
 #endif
 
--- a/toolkit/components/url-classifier/Classifier.cpp
+++ b/toolkit/components/url-classifier/Classifier.cpp
@@ -11,16 +11,17 @@
 #include "nsIInputStream.h"
 #include "nsISeekableStream.h"
 #include "nsIFile.h"
 #include "nsNetCID.h"
 #include "nsPrintfCString.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Logging.h"
+#include "mozilla/SyncRunnable.h"
 
 // MOZ_LOG=UrlClassifierDbService:5
 extern mozilla::LazyLogModule gUrlClassifierDbServiceLog;
 #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
 
 #define STORE_DIRECTORY      NS_LITERAL_CSTRING("safebrowsing")
 #define TO_DELETE_DIR_SUFFIX NS_LITERAL_CSTRING("-to_delete")
@@ -73,40 +74,184 @@ Classifier::SplitTables(const nsACString
     }
     begin = iter;
     if (begin != end) {
       begin++;
     }
   }
 }
 
+static nsresult
+DeriveProviderFromPref(const nsACString& aTableName, nsCString& aProviderName)
+{
+  // Check all preferences "browser.safebrowsing.provider.[provider].list"
+  // to see which one contains |aTableName|.
+
+  nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(prefs, NS_ERROR_FAILURE);
+  nsCOMPtr<nsIPrefBranch> prefBranch;
+  nsresult rv = prefs->GetBranch("browser.safebrowsing.provider.",
+                                  getter_AddRefs(prefBranch));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // We've got a pref branch for "browser.safebrowsing.provider.".
+  // Enumerate all children prefs and parse providers.
+  uint32_t childCount;
+  char** childArray;
+  rv = prefBranch->GetChildList("", &childCount, &childArray);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Collect providers from childArray.
+  nsTHashtable<nsCStringHashKey> providers;
+  for (uint32_t i = 0; i < childCount; i++) {
+    nsCString child(childArray[i]);
+    auto dotPos = child.FindChar('.');
+    if (dotPos < 0) {
+      continue;
+    }
+
+    nsDependentCSubstring provider = Substring(child, 0, dotPos);
+
+    providers.PutEntry(provider);
+  }
+  NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(childCount, childArray);
+
+  // Now we have all providers. Check which one owns |aTableName|.
+  // e.g. The owning lists of provider "google" is defined in
+  // "browser.safebrowsing.provider.google.lists".
+  for (auto itr = providers.Iter(); !itr.Done(); itr.Next()) {
+    auto entry = itr.Get();
+    nsCString provider(entry->GetKey());
+    nsPrintfCString owninListsPref("%s.lists", provider.get());
+
+    nsXPIDLCString owningLists;
+    nsresult rv = prefBranch->GetCharPref(owninListsPref.get(),
+                                          getter_Copies(owningLists));
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    // We've got the owning lists (represented as string) of |provider|.
+    // Parse the string and see if |aTableName| is included.
+    nsTArray<nsCString> tables;
+    Classifier::SplitTables(owningLists, tables);
+    if (tables.Contains(aTableName)) {
+      aProviderName = provider;
+      return NS_OK;
+    }
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
+// Lookup the provider name by table name on non-main thread.
+// On main thread, just call DeriveProviderFromPref() instead
+// but DeriveProviderFromPref is supposed to used internally.
+static nsCString
+GetProviderByTableName(const nsACString& aTableName)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "GetProviderByTableName MUST be called "
+                                 "on non-main thread.");
+  nsCString providerName;
+
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([&aTableName,
+                                                    &providerName] () -> void {
+    nsresult rv = DeriveProviderFromPref(aTableName, providerName);
+    if (NS_FAILED(rv)) {
+      LOG(("No provider found for %s", nsCString(aTableName).get()));
+    }
+  });
+
+  nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+  SyncRunnable::DispatchToThread(mainThread, r);
+
+  return providerName;
+}
+
+nsresult
+Classifier::GetPrivateStoreDirectory(nsIFile* aRootStoreDirectory,
+                                     const nsACString& aTableName,
+                                     nsIFile** aPrivateStoreDirectory)
+{
+  NS_ENSURE_ARG_POINTER(aPrivateStoreDirectory);
+
+  if (!StringEndsWith(aTableName, NS_LITERAL_CSTRING("-proto"))) {
+    // Only V4 table names (ends with '-proto') would be stored
+    // to per-provider sub-directory.
+    nsCOMPtr<nsIFile>(aRootStoreDirectory).forget(aPrivateStoreDirectory);
+    return NS_OK;
+  }
+
+  nsCString providerName = GetProviderByTableName(aTableName);
+  if (providerName.IsEmpty()) {
+    // When failing to get provider, just store in the root folder.
+    nsCOMPtr<nsIFile>(aRootStoreDirectory).forget(aPrivateStoreDirectory);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIFile> providerDirectory;
+
+  // Clone first since we are gonna create a new directory.
+  nsresult rv = aRootStoreDirectory->Clone(getter_AddRefs(providerDirectory));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Append the provider name to the root store directory.
+  rv = providerDirectory->AppendNative(providerName);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Ensure existence of the provider directory.
+  bool dirExists;
+  rv = providerDirectory->Exists(&dirExists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!dirExists) {
+    LOG(("Creating private directory for %s", nsCString(aTableName).get()));
+    rv = providerDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+    NS_ENSURE_SUCCESS(rv, rv);
+    providerDirectory.forget(aPrivateStoreDirectory);
+    return rv;
+  }
+
+  // Store directory exists. Check if it's a directory.
+  bool isDir;
+  rv = providerDirectory->IsDirectory(&isDir);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!isDir) {
+    return NS_ERROR_FILE_DESTINATION_NOT_DIR;
+  }
+
+  providerDirectory.forget(aPrivateStoreDirectory);
+
+  return NS_OK;
+}
+
 Classifier::Classifier()
 {
 }
 
 Classifier::~Classifier()
 {
   Close();
 }
 
 nsresult
 Classifier::SetupPathNames()
 {
   // Get the root directory where to store all the databases.
-  nsresult rv = mCacheDirectory->Clone(getter_AddRefs(mStoreDirectory));
+  nsresult rv = mCacheDirectory->Clone(getter_AddRefs(mRootStoreDirectory));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = mStoreDirectory->AppendNative(STORE_DIRECTORY);
+  rv = mRootStoreDirectory->AppendNative(STORE_DIRECTORY);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Make sure LookupCaches (which are persistent and survive updates)
   // are reading/writing in the right place. We will be moving their
   // files "underneath" them during backup/restore.
   for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
-    mLookupCaches[i]->UpdateDirHandle(mStoreDirectory);
+    mLookupCaches[i]->UpdateRootDirHandle(mRootStoreDirectory);
   }
 
   // Directory where to move a backup before an update.
   rv = mCacheDirectory->Clone(getter_AddRefs(mBackupDirectory));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mBackupDirectory->AppendNative(STORE_DIRECTORY + BACKUP_DIR_SUFFIX);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -122,25 +267,25 @@ Classifier::SetupPathNames()
   return NS_OK;
 }
 
 nsresult
 Classifier::CreateStoreDirectory()
 {
   // Ensure the safebrowsing directory exists.
   bool storeExists;
-  nsresult rv = mStoreDirectory->Exists(&storeExists);
+  nsresult rv = mRootStoreDirectory->Exists(&storeExists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!storeExists) {
-    rv = mStoreDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+    rv = mRootStoreDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
     NS_ENSURE_SUCCESS(rv, rv);
   } else {
     bool storeIsDir;
-    rv = mStoreDirectory->IsDirectory(&storeIsDir);
+    rv = mRootStoreDirectory->IsDirectory(&storeIsDir);
     NS_ENSURE_SUCCESS(rv, rv);
     if (!storeIsDir)
       return NS_ERROR_FILE_DESTINATION_NOT_DIR;
   }
 
   return NS_OK;
 }
 
@@ -184,17 +329,17 @@ Classifier::Close()
   DropStores();
 }
 
 void
 Classifier::Reset()
 {
   DropStores();
 
-  mStoreDirectory->Remove(true);
+  mRootStoreDirectory->Remove(true);
   mBackupDirectory->Remove(true);
   mToDeleteDirectory->Remove(true);
 
   CreateStoreDirectory();
 
   mTableFreshness.Clear();
   RegenActiveTables();
 }
@@ -210,17 +355,17 @@ Classifier::ResetTables(const nsTArray<n
 
   RegenActiveTables();
 }
 
 void
 Classifier::DeleteTables(const nsTArray<nsCString>& aTables)
 {
   nsCOMPtr<nsISimpleEnumerator> entries;
-  nsresult rv = mStoreDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+  nsresult rv = mRootStoreDirectory->GetDirectoryEntries(getter_AddRefs(entries));
   NS_ENSURE_SUCCESS_VOID(rv);
 
   bool hasMore;
   while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
     nsCOMPtr<nsISupports> supports;
     rv = entries->GetNext(getter_AddRefs(supports));
     NS_ENSURE_SUCCESS_VOID(rv);
 
@@ -243,17 +388,17 @@ Classifier::DeleteTables(const nsTArray<
 }
 
 void
 Classifier::TableRequest(nsACString& aResult)
 {
   nsTArray<nsCString> tables;
   ActiveTables(tables);
   for (uint32_t i = 0; i < tables.Length(); i++) {
-    HashStore store(tables[i], mStoreDirectory);
+    HashStore store(tables[i], mRootStoreDirectory);
 
     nsresult rv = store.Open();
     if (NS_FAILED(rv))
       continue;
 
     aResult.Append(store.TableName());
     aResult.Append(';');
 
@@ -497,17 +642,17 @@ Classifier::RegenActiveTables()
 {
   mActiveTablesCache.Clear();
 
   nsTArray<nsCString> foundTables;
   ScanStoreDir(foundTables);
 
   for (uint32_t i = 0; i < foundTables.Length(); i++) {
     nsCString table(foundTables[i]);
-    HashStore store(table, mStoreDirectory);
+    HashStore store(table, mRootStoreDirectory);
 
     nsresult rv = store.Open();
     if (NS_FAILED(rv))
       continue;
 
     LookupCache *lookupCache = GetLookupCache(store.TableName());
     if (!lookupCache) {
       continue;
@@ -528,17 +673,17 @@ Classifier::RegenActiveTables()
 
   return NS_OK;
 }
 
 nsresult
 Classifier::ScanStoreDir(nsTArray<nsCString>& aTables)
 {
   nsCOMPtr<nsISimpleEnumerator> entries;
-  nsresult rv = mStoreDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+  nsresult rv = mRootStoreDirectory->GetDirectoryEntries(getter_AddRefs(entries));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool hasMore;
   while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
     nsCOMPtr<nsISupports> supports;
     rv = entries->GetNext(getter_AddRefs(supports));
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -592,23 +737,23 @@ Classifier::BackupTables()
   // dir always has a valid, complete copy, instead of a partial one,
   // because that's the one we will copy over the normal store dir.
 
   nsCString backupDirName;
   nsresult rv = mBackupDirectory->GetNativeLeafName(backupDirName);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCString storeDirName;
-  rv = mStoreDirectory->GetNativeLeafName(storeDirName);
+  rv = mRootStoreDirectory->GetNativeLeafName(storeDirName);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = mStoreDirectory->MoveToNative(nullptr, backupDirName);
+  rv = mRootStoreDirectory->MoveToNative(nullptr, backupDirName);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = mStoreDirectory->CopyToNative(nullptr, storeDirName);
+  rv = mRootStoreDirectory->CopyToNative(nullptr, storeDirName);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // We moved some things to new places, so move the handles around, too.
   rv = SetupPathNames();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
@@ -635,25 +780,25 @@ Classifier::RecoverBackups()
 {
   bool backupExists;
   nsresult rv = mBackupDirectory->Exists(&backupExists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (backupExists) {
     // Remove the safebrowsing dir if it exists
     nsCString storeDirName;
-    rv = mStoreDirectory->GetNativeLeafName(storeDirName);
+    rv = mRootStoreDirectory->GetNativeLeafName(storeDirName);
     NS_ENSURE_SUCCESS(rv, rv);
 
     bool storeExists;
-    rv = mStoreDirectory->Exists(&storeExists);
+    rv = mRootStoreDirectory->Exists(&storeExists);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (storeExists) {
-      rv = mStoreDirectory->Remove(true);
+      rv = mRootStoreDirectory->Remove(true);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // Move the backup to the store location
     rv = mBackupDirectory->MoveToNative(nullptr, storeDirName);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // mBackupDirectory now points to storeDir, fix up.
@@ -695,17 +840,17 @@ Classifier::CheckValidUpdate(nsTArray<Ta
  * This will consume+delete updates from the passed nsTArray.
 */
 nsresult
 Classifier::UpdateHashStore(nsTArray<TableUpdate*>* aUpdates,
                             const nsACString& aTable)
 {
   LOG(("Classifier::UpdateHashStore(%s)", PromiseFlatCString(aTable).get()));
 
-  HashStore store(aTable, mStoreDirectory);
+  HashStore store(aTable, mRootStoreDirectory);
 
   if (!CheckValidUpdate(aUpdates, store.TableName())) {
     return NS_OK;
   }
 
   nsresult rv = store.Open();
   NS_ENSURE_SUCCESS(rv, rv);
   rv = store.BeginUpdate();
@@ -816,17 +961,17 @@ LookupCache *
 Classifier::GetLookupCache(const nsACString& aTable)
 {
   for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
     if (mLookupCaches[i]->TableName().Equals(aTable)) {
       return mLookupCaches[i];
     }
   }
 
-  UniquePtr<LookupCache> cache(new LookupCache(aTable, mStoreDirectory));
+  UniquePtr<LookupCache> cache(new LookupCache(aTable, mRootStoreDirectory));
   nsresult rv = cache->Init();
   if (NS_FAILED(rv)) {
     return nullptr;
   }
   rv = cache->Open();
   if (NS_FAILED(rv)) {
     if (rv == NS_ERROR_FILE_CORRUPTED) {
       Reset();
--- a/toolkit/components/url-classifier/Classifier.h
+++ b/toolkit/components/url-classifier/Classifier.h
@@ -80,16 +80,30 @@ public:
    * and mask the real entry being requested
    */
   nsresult ReadNoiseEntries(const Prefix& aPrefix,
                             const nsACString& aTableName,
                             uint32_t aCount,
                             PrefixArray* aNoiseEntries);
   static void SplitTables(const nsACString& str, nsTArray<nsCString>& tables);
 
+  // Given a root store directory, return a private store directory
+  // based on the table name. To avoid migration issue, the private
+  // store directory is only different from root directory for V4 tables.
+  //
+  // For V4 tables (suffixed by '-proto'), the private directory would
+  // be [root directory path]/[provider]. The provider of V4 tables is
+  // 'google4'.
+  //
+  // Note that if the table name is not owned by any provider, just use
+  // the root directory.
+  static nsresult GetPrivateStoreDirectory(nsIFile* aRootStoreDirectory,
+                                           const nsACString& aTableName,
+                                           nsIFile** aPrivateStoreDirectory);
+
 private:
   void DropStores();
   void DeleteTables(const nsTArray<nsCString>& aTables);
 
   nsresult CreateStoreDirectory();
   nsresult SetupPathNames();
   nsresult RecoverBackups();
   nsresult CleanToDelete();
@@ -106,17 +120,17 @@ private:
   LookupCache *GetLookupCache(const nsACString& aTable);
 
   bool CheckValidUpdate(nsTArray<TableUpdate*>* aUpdates,
                         const nsACString& aTable);
 
   // Root dir of the Local profile.
   nsCOMPtr<nsIFile> mCacheDirectory;
   // Main directory where to store the databases.
-  nsCOMPtr<nsIFile> mStoreDirectory;
+  nsCOMPtr<nsIFile> mRootStoreDirectory;
   // Used for atomically updating the other dirs.
   nsCOMPtr<nsIFile> mBackupDirectory;
   nsCOMPtr<nsIFile> mToDeleteDirectory;
   nsCOMPtr<nsICryptoHash> mCryptoHash;
   nsTArray<LookupCache*> mLookupCaches;
   nsTArray<nsCString> mActiveTablesCache;
   uint32_t mHashKey;
   // Stores the last time a given table was updated (seconds).
--- a/toolkit/components/url-classifier/HashStore.cpp
+++ b/toolkit/components/url-classifier/HashStore.cpp
@@ -33,16 +33,17 @@
 #include "nsICryptoHash.h"
 #include "nsISeekableStream.h"
 #include "nsIStreamConverterService.h"
 #include "nsNetUtil.h"
 #include "nsCheckSummedOutputStream.h"
 #include "prio.h"
 #include "mozilla/Logging.h"
 #include "zlib.h"
+#include "Classifier.h"
 
 // Main store for SafeBrowsing protocol data. We store
 // known add/sub chunks, prefixes and completions in memory
 // during an update, and serialize to disk.
 // We do not store the add prefixes, those are retrieved by
 // decompressing the PrefixSet cache whenever we need to apply
 // an update.
 //
@@ -174,22 +175,28 @@ TableUpdateV4::NewPrefixes(int32_t aSize
 void
 TableUpdateV4::NewRemovalIndices(const uint32_t* aIndices, size_t aNumOfIndices)
 {
   for (size_t i = 0; i < aNumOfIndices; i++) {
     mRemovalIndiceArray.AppendElement(aIndices[i]);
   }
 }
 
-HashStore::HashStore(const nsACString& aTableName, nsIFile* aStoreDir)
+HashStore::HashStore(const nsACString& aTableName, nsIFile* aRootStoreDir)
   : mTableName(aTableName)
-  , mStoreDirectory(aStoreDir)
   , mInUpdate(false)
   , mFileSize(0)
 {
+  nsresult rv = Classifier::GetPrivateStoreDirectory(aRootStoreDir,
+                                                     aTableName,
+                                                     getter_AddRefs(mStoreDirectory));
+  if (NS_FAILED(rv)) {
+    LOG(("Failed to get private store directory for %s", mTableName.get()));
+    mStoreDirectory = aRootStoreDir;
+  }
 }
 
 HashStore::~HashStore()
 {
 }
 
 nsresult
 HashStore::Reset()
--- a/toolkit/components/url-classifier/HashStore.h
+++ b/toolkit/components/url-classifier/HashStore.h
@@ -175,17 +175,17 @@ private:
 
   PrefixesStringMap mPrefixesMap;
   RemovalIndiceArray mRemovalIndiceArray;
 };
 
 // There is one hash store per table.
 class HashStore {
 public:
-  HashStore(const nsACString& aTableName, nsIFile* aStoreFile);
+  HashStore(const nsACString& aTableName, nsIFile* aRootStoreFile);
   ~HashStore();
 
   const nsCString& TableName() const { return mTableName; }
 
   nsresult Open();
   // Add Prefixes are stored partly in the PrefixSet (contains the
   // Prefix data organized for fast lookup/low RAM usage) and partly in the
   // HashStore (Add Chunk numbers - only used for updates, slow retrieval).
@@ -286,14 +286,17 @@ private:
   SubPrefixArray mSubPrefixes;
 
   // See bug 806422 for background. We must be able to distinguish between
   // updates from the completion server and updates from the regular server.
   AddCompleteArray mAddCompletes;
   SubCompleteArray mSubCompletes;
 
   uint32_t mFileSize;
+
+  // For gtest to inspect private members.
+  friend class PerProviderDirectoryTestUtils;
 };
 
 } // namespace safebrowsing
 } // namespace mozilla
 
 #endif
--- a/toolkit/components/url-classifier/LookupCache.cpp
+++ b/toolkit/components/url-classifier/LookupCache.cpp
@@ -5,16 +5,17 @@
 
 #include "LookupCache.h"
 #include "HashStore.h"
 #include "nsISeekableStream.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Logging.h"
 #include "nsNetUtil.h"
 #include "prprf.h"
+#include "Classifier.h"
 
 // We act as the main entry point for all the real lookups,
 // so note that those are not done to the actual HashStore.
 // The latter solely exists to store the data needed to handle
 // the updates from the protocol.
 
 // This module provides a front for PrefixSet, mUpdateCompletions,
 // and mGetHashCache, which together contain everything needed to
@@ -34,21 +35,22 @@
 // MOZ_LOG=UrlClassifierDbService:5
 extern mozilla::LazyLogModule gUrlClassifierDbServiceLog;
 #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
 
 namespace mozilla {
 namespace safebrowsing {
 
-LookupCache::LookupCache(const nsACString& aTableName, nsIFile* aStoreDir)
+LookupCache::LookupCache(const nsACString& aTableName, nsIFile* aRootStoreDir)
   : mPrimed(false)
   , mTableName(aTableName)
-  , mStoreDirectory(aStoreDir)
+  , mRootStoreDirectory(aRootStoreDir)
 {
+  UpdateRootDirHandle(mRootStoreDirectory);
 }
 
 nsresult
 LookupCache::Init()
 {
   mPrefixSet = new nsUrlClassifierPrefixSet();
   nsresult rv = mPrefixSet->Init(mTableName);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -70,19 +72,42 @@ LookupCache::Open()
   LOG(("Loading PrefixSet"));
   rv = LoadPrefixSet();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
-LookupCache::UpdateDirHandle(nsIFile* aStoreDirectory)
+LookupCache::UpdateRootDirHandle(nsIFile* aNewRootStoreDirectory)
 {
-  return aStoreDirectory->Clone(getter_AddRefs(mStoreDirectory));
+  nsresult rv;
+
+  if (aNewRootStoreDirectory != mRootStoreDirectory) {
+    rv = aNewRootStoreDirectory->Clone(getter_AddRefs(mRootStoreDirectory));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = Classifier::GetPrivateStoreDirectory(mRootStoreDirectory,
+                                            mTableName,
+                                            getter_AddRefs(mStoreDirectory));
+
+  if (NS_FAILED(rv)) {
+    LOG(("Failed to get private store directory for %s", mTableName.get()));
+    mStoreDirectory = mRootStoreDirectory;
+  }
+
+  if (LOG_ENABLED()) {
+    nsString path;
+    mStoreDirectory->GetPath(path);
+    LOG(("Private store directory for %s is %s", mTableName.get(),
+                                                 NS_ConvertUTF16toUTF8(path).get()));
+  }
+
+  return rv;
 }
 
 nsresult
 LookupCache::Reset()
 {
   LOG(("LookupCache resetting"));
 
   nsCOMPtr<nsIFile> prefixsetFile;
@@ -231,17 +256,17 @@ void
 LookupCache::ClearCache()
 {
   mGetHashCache.Clear();
 }
 
 nsresult
 LookupCache::ReadCompletions()
 {
-  HashStore store(mTableName, mStoreDirectory);
+  HashStore store(mTableName, mRootStoreDirectory);
 
   nsresult rv = store.Open();
   NS_ENSURE_SUCCESS(rv, rv);
 
   mUpdateCompletions.Clear();
 
   const AddCompleteArray& addComplete = store.AddCompletes();
   for (uint32_t i = 0; i < addComplete.Length(); i++) {
--- a/toolkit/components/url-classifier/LookupCache.h
+++ b/toolkit/components/url-classifier/LookupCache.h
@@ -99,17 +99,17 @@ public:
   ~LookupCache();
 
   const nsCString &TableName() const { return mTableName; }
 
   nsresult Init();
   nsresult Open();
   // The directory handle where we operate will
   // be moved away when a backup is made.
-  nsresult UpdateDirHandle(nsIFile* aStoreDirectory);
+  nsresult UpdateRootDirHandle(nsIFile* aRootStoreDirectory);
   // This will Clear() the passed arrays when done.
   nsresult Build(AddPrefixArray& aAddPrefixes,
                  AddCompleteArray& aAddCompletes);
   nsresult AddCompletionsToCache(AddCompleteArray& aAddCompletes);
   nsresult GetPrefixes(FallibleTArray<uint32_t>& aAddPrefixes);
   void ClearUpdatedCompletions();
   void ClearCache();
 
@@ -129,21 +129,25 @@ private:
   nsresult LoadPrefixSet();
   nsresult LoadCompletions();
   // Construct a Prefix Set with known prefixes.
   // This will Clear() aAddPrefixes when done.
   nsresult ConstructPrefixSet(AddPrefixArray& aAddPrefixes);
 
   bool mPrimed;
   nsCString mTableName;
+  nsCOMPtr<nsIFile> mRootStoreDirectory;
   nsCOMPtr<nsIFile> mStoreDirectory;
   // Set of prefixes known to be in the database
   RefPtr<nsUrlClassifierPrefixSet> mPrefixSet;
   // Full length hashes obtained in update request
   CompletionArray mUpdateCompletions;
   // Full length hashes obtained in gethash request
   CompletionArray mGetHashCache;
+
+  // For gtest to inspect private members.
+  friend class PerProviderDirectoryTestUtils;
 };
 
 } // namespace safebrowsing
 } // namespace mozilla
 
 #endif
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -46,16 +46,49 @@
 #include "Classifier.h"
 #include "ProtocolParser.h"
 #include "mozilla/Attributes.h"
 #include "nsIPrincipal.h"
 #include "Classifier.h"
 #include "ProtocolParser.h"
 #include "nsContentUtils.h"
 
+namespace mozilla {
+namespace safebrowsing {
+
+nsresult
+TablesToResponse(const nsACString& tables)
+{
+  if (tables.IsEmpty()) {
+    return NS_OK;
+  }
+
+  // We don't check mCheckMalware and friends because BuildTables never
+  // includes a table that is not enabled.
+  if (FindInReadable(NS_LITERAL_CSTRING("-malware-"), tables)) {
+    return NS_ERROR_MALWARE_URI;
+  }
+  if (FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) {
+    return NS_ERROR_PHISHING_URI;
+  }
+  if (FindInReadable(NS_LITERAL_CSTRING("-unwanted-"), tables)) {
+    return NS_ERROR_UNWANTED_URI;
+  }
+  if (FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) {
+    return NS_ERROR_TRACKING_URI;
+  }
+  if (FindInReadable(NS_LITERAL_CSTRING("-block-"), tables)) {
+    return NS_ERROR_BLOCKED_URI;
+  }
+  return NS_OK;
+}
+
+} // namespace safebrowsing
+} // namespace mozilla
+
 using namespace mozilla;
 using namespace mozilla::safebrowsing;
 
 // MOZ_LOG=UrlClassifierDbService:5
 LazyLogModule gUrlClassifierDbServiceLog("UrlClassifierDbService");
 #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
 
@@ -168,43 +201,16 @@ nsUrlClassifierDBServiceWorker::DoLocalL
   // We ignore failures from Check because we'd rather return the
   // results that were found than fail.
   mClassifier->Check(spec, tables, gFreshnessGuarantee, *results);
 
   LOG(("Found %d results.", results->Length()));
   return NS_OK;
 }
 
-static nsresult
-TablesToResponse(const nsACString& tables)
-{
-  if (tables.IsEmpty()) {
-    return NS_OK;
-  }
-
-  // We don't check mCheckMalware and friends because BuildTables never
-  // includes a table that is not enabled.
-  if (FindInReadable(NS_LITERAL_CSTRING("-malware-"), tables)) {
-    return NS_ERROR_MALWARE_URI;
-  }
-  if (FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) {
-    return NS_ERROR_PHISHING_URI;
-  }
-  if (FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) {
-    return NS_ERROR_TRACKING_URI;
-  }
-  if (FindInReadable(NS_LITERAL_CSTRING("-unwanted-"), tables)) {
-    return NS_ERROR_UNWANTED_URI;
-  }
-  if (FindInReadable(NS_LITERAL_CSTRING("-block-"), tables)) {
-    return NS_ERROR_BLOCKED_URI;
-  }
-  return NS_OK;
-}
-
 static nsCString
 ProcessLookupResults(LookupResultArray* results)
 {
   // Build a stringified list of result tables.
   nsTArray<nsCString> tables;
   for (uint32_t i = 0; i < results->Length(); i++) {
     LookupResult& result = results->ElementAt(i);
     MOZ_ASSERT(!result.mNoise, "Lookup results should not have noise added");
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.h
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.h
@@ -40,16 +40,20 @@ class nsUrlClassifierDBServiceWorker;
 class nsIThread;
 class nsIURI;
 class UrlClassifierDBServiceWorkerProxy;
 namespace mozilla {
 namespace safebrowsing {
 class Classifier;
 class ProtocolParser;
 class TableUpdate;
+
+nsresult
+TablesToResponse(const nsACString& tables);
+
 } // namespace safebrowsing
 } // namespace mozilla
 
 // This is a proxy class that just creates a background thread and delagates
 // calls to the background thread.
 class nsUrlClassifierDBService final : public nsIUrlClassifierDBService,
                                        public nsIURIClassifier,
                                        public nsIObserver
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/gtest/TestPerProviderDirectory.cpp
@@ -0,0 +1,108 @@
+#include "LookupCache.h"
+#include "HashStore.h"
+#include "gtest/gtest.h"
+#include "nsIThread.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace safebrowsing {
+
+class PerProviderDirectoryTestUtils {
+public:
+  template<typename T>
+  static nsIFile* InspectStoreDirectory(const T& aT)
+  {
+    return aT.mStoreDirectory;
+  }
+};
+
+} // end of namespace safebrowsing
+} // end of namespace mozilla
+
+using namespace mozilla;
+using namespace mozilla::safebrowsing;
+
+template<typename Function>
+void RunTestInNewThread(Function&& aFunction) {
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(mozilla::Forward<Function>(aFunction));
+  nsCOMPtr<nsIThread> testingThread;
+  nsresult rv = NS_NewThread(getter_AddRefs(testingThread), r);
+  ASSERT_EQ(rv, NS_OK);
+  testingThread->Shutdown();
+}
+
+template<typename T>
+void VerifyPrivateStorePath(const char* aTableName,
+                            const char* aProvider,
+                            nsIFile* aRootDir,
+                            bool aUsePerProviderStore)
+{
+  nsString rootStorePath;
+  nsresult rv = aRootDir->GetPath(rootStorePath);
+  EXPECT_EQ(rv, NS_OK);
+
+  T target(nsCString(aTableName), aRootDir);
+
+  nsIFile* privateStoreDirectory =
+    PerProviderDirectoryTestUtils::InspectStoreDirectory(target);
+
+  nsString privateStorePath;
+  rv = privateStoreDirectory->GetPath(privateStorePath);
+  ASSERT_EQ(rv, NS_OK);
+
+  nsString expectedPrivateStorePath = rootStorePath;
+
+  if (aUsePerProviderStore) {
+    // Use API to append "provider" to the root directoy path
+    nsCOMPtr<nsIFile> expectedPrivateStoreDir;
+    rv = aRootDir->Clone(getter_AddRefs(expectedPrivateStoreDir));
+    ASSERT_EQ(rv, NS_OK);
+
+    expectedPrivateStoreDir->AppendNative(nsCString(aProvider));
+    rv = expectedPrivateStoreDir->GetPath(expectedPrivateStorePath);
+    ASSERT_EQ(rv, NS_OK);
+  }
+
+  printf("table: %s\nprovider: %s\nroot path: %s\nprivate path: %s\n\n",
+         aTableName,
+         aProvider,
+         NS_ConvertUTF16toUTF8(rootStorePath).get(),
+         NS_ConvertUTF16toUTF8(privateStorePath).get());
+
+  ASSERT_TRUE(privateStorePath == expectedPrivateStorePath);
+}
+
+TEST(PerProviderDirectory, LookupCache)
+{
+  RunTestInNewThread([] () -> void {
+    nsCOMPtr<nsIFile> rootDir;
+    NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(rootDir));
+
+    // For V2 tables (NOT ending with '-proto'), root directory should be
+    // used as the private store.
+    VerifyPrivateStorePath<LookupCache>("goog-phish-shavar", "google", rootDir, false);
+
+    // For V4 tables, if provider is found, use per-provider subdirectory;
+    // If not found, use root directory.
+    VerifyPrivateStorePath<LookupCache>("goog-noprovider-proto", "", rootDir, false);
+    VerifyPrivateStorePath<LookupCache>("goog-phish-proto", "google4", rootDir, true);
+  });
+}
+
+TEST(PerProviderDirectory, HashStore)
+{
+  RunTestInNewThread([] () -> void {
+    nsCOMPtr<nsIFile> rootDir;
+    NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(rootDir));
+
+    // For V2 tables (NOT ending with '-proto'), root directory should be
+    // used as the private store.
+    VerifyPrivateStorePath<HashStore>("goog-phish-shavar", "google", rootDir, false);
+
+    // For V4 tables, if provider is found, use per-provider subdirectory;
+    // If not found, use root directory.
+    VerifyPrivateStorePath<HashStore>("goog-noprovider-proto", "", rootDir, false);
+    VerifyPrivateStorePath<HashStore>("goog-phish-proto", "google4", rootDir, true);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/gtest/TestTable.cpp
@@ -0,0 +1,47 @@
+#include "gtest/gtest.h"
+#include "nsUrlClassifierDBService.h"
+
+using namespace mozilla::safebrowsing;
+
+void
+TestResponseCode(const char* table, nsresult result)
+{
+  nsCString tableName(table);
+  ASSERT_EQ(TablesToResponse(tableName), result);
+}
+
+TEST(UrlClassifierTable, ResponseCode)
+{
+  // malware URIs.
+  TestResponseCode("goog-malware-shavar", NS_ERROR_MALWARE_URI);
+  TestResponseCode("test-malware-simple", NS_ERROR_MALWARE_URI);
+  TestResponseCode("goog-phish-shavar,test-malware-simple", NS_ERROR_MALWARE_URI);
+  TestResponseCode("test-malware-simple,mozstd-track-digest256,mozplugin-block-digest256", NS_ERROR_MALWARE_URI);
+
+  // phish URIs.
+  TestResponseCode("goog-phish-shavar", NS_ERROR_PHISHING_URI);
+  TestResponseCode("test-phish-simple", NS_ERROR_PHISHING_URI);
+  TestResponseCode("test-phish-simple,mozplugin-block-digest256", NS_ERROR_PHISHING_URI);
+  TestResponseCode("mozstd-track-digest256,test-phish-simple,goog-unwanted-shavar", NS_ERROR_PHISHING_URI);
+
+  // unwanted URIs.
+  TestResponseCode("goog-unwanted-shavar", NS_ERROR_UNWANTED_URI);
+  TestResponseCode("test-unwanted-simple", NS_ERROR_UNWANTED_URI);
+  TestResponseCode("mozplugin-unwanted-digest256,mozfull-track-digest256", NS_ERROR_UNWANTED_URI);
+  TestResponseCode("test-block-simple,mozfull-track-digest256,test-unwanted-simple", NS_ERROR_UNWANTED_URI);
+
+  // track URIs.
+  TestResponseCode("test-track-simple", NS_ERROR_TRACKING_URI);
+  TestResponseCode("mozstd-track-digest256", NS_ERROR_TRACKING_URI);
+  TestResponseCode("test-block-simple,mozstd-track-digest256", NS_ERROR_TRACKING_URI);
+
+  // block URIs
+  TestResponseCode("test-block-simple", NS_ERROR_BLOCKED_URI);
+  TestResponseCode("mozplugin-block-digest256", NS_ERROR_BLOCKED_URI);
+  TestResponseCode("mozplugin2-block-digest256", NS_ERROR_BLOCKED_URI);
+
+  TestResponseCode("test-trackwhite-simple", NS_OK);
+  TestResponseCode("mozstd-trackwhite-digest256", NS_OK);
+  TestResponseCode("goog-badbinurl-shavar", NS_OK);
+  TestResponseCode("goog-downloadwhite-digest256", NS_OK);
+}
--- a/toolkit/components/url-classifier/tests/gtest/moz.build
+++ b/toolkit/components/url-classifier/tests/gtest/moz.build
@@ -5,14 +5,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 LOCAL_INCLUDES += [
     '../..',
 ]
 
 UNIFIED_SOURCES += [
     'TestChunkSet.cpp',
+    'TestPerProviderDirectory.cpp',
     'TestSafebrowsingHash.cpp',
     'TestSafeBrowsingProtobuf.cpp',
+    'TestTable.cpp',
     'TestUrlClassifierUtils.cpp',
 ]
 
 FINAL_LIBRARY = 'xul-gtest'
--- a/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
+++ b/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
@@ -240,16 +240,32 @@ checkUrls: function(urls, expected, cb, 
                                 }, true);
     } else {
       cb();
     }
   };
   doLookup();
 },
 
+checkTables: function(url, expected, cb)
+{
+  var principal = secMan.createCodebasePrincipal(iosvc.newURI("http://" + url, null, null), {});
+  dbservice.lookup(principal, allTables, function(tables) {
+    // Rebuild tables in a predictable order.
+    var parts = tables.split(",");
+    while (parts[parts.length - 1] == '') {
+      parts.pop();
+    }
+    parts.sort();
+    tables = parts.join(",");
+    do_check_eq(tables, expected);
+    cb();
+  }, true);
+},
+
 urlsDontExist: function(urls, cb)
 {
   this.checkUrls(urls, '', cb);
 },
 
 urlsExist: function(urls, cb)
 {
   this.checkUrls(urls, 'test-phish-simple', cb);
@@ -280,16 +296,22 @@ subsDontExist: function(urls, cb)
   // XXX: there's no interface for checking items in the subs table
   cb();
 },
 
 subsExist: function(urls, cb)
 {
   // XXX: there's no interface for checking items in the subs table
   cb();
+},
+
+urlExistInMultipleTables: function(data, cb)
+{
+  this.checkTables(data["url"], data["tables"], cb);
+  cb();
 }
 
 };
 
 /**
  * Check a set of assertions against the gAssertions table.
  */
 function checkAssertions(assertions, doneCallback)
--- a/toolkit/components/url-classifier/tests/unit/test_streamupdater.js
+++ b/toolkit/components/url-classifier/tests/unit/test_streamupdater.js
@@ -172,16 +172,48 @@ function testMultipleTables() {
     "malwareUrlsExist" : add3Urls,
     "unwantedUrlsExist" : add4Urls,
     "blockedUrlsExist" : add6Urls
   };
 
   doTest([update], assertions, false);
 }
 
+function testUrlInMultipleTables() {
+  var add1Urls = [ "foo-forward.com/a" ];
+
+  var update = "n:1000\n";
+  update += "i:test-phish-simple\n";
+
+  var update1 = buildBareUpdate(
+    [{ "chunkNum" : 1,
+       "urls" : add1Urls }]);
+  update += "u:data:," + encodeURIComponent(update1) + "\n";
+
+  update += "i:test-malware-simple\n";
+  var update2 = buildBareUpdate(
+    [{ "chunkNum" : 2,
+       "urls" : add1Urls }]);
+  update += "u:data:," + encodeURIComponent(update2) + "\n";
+
+  update += "i:test-unwanted-simple\n";
+  var update3 = buildBareUpdate(
+    [{ "chunkNum" : 3,
+       "urls" : add1Urls }]);
+  update += "u:data:," + encodeURIComponent(update3) + "\n";
+
+  var assertions = {
+    "tableData" : "test-malware-simple;a:2\ntest-phish-simple;a:1\ntest-unwanted-simple;a:3",
+    "urlExistInMultipleTables" : { url: add1Urls,
+                                   tables: "test-malware-simple,test-phish-simple,test-unwanted-simple" }
+  };
+
+  doTest([update], assertions, false);
+}
+
 function Observer(callback) {
   this.observe = callback;
 }
 
 Observer.prototype =
 {
 QueryInterface: function(iid)
 {
@@ -243,13 +275,14 @@ function testReset() {
 function run_test()
 {
   runTests([
     testSimpleForward,
     testNestedForward,
     testInvalidUrlForward,
     testErrorUrlForward,
     testMultipleTables,
+    testUrlInMultipleTables,
     testReset
   ]);
 }
 
 do_test_pending();
--- a/toolkit/library/gtest/moz.build
+++ b/toolkit/library/gtest/moz.build
@@ -1,18 +1,30 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-Libxul('xul-gtest')
-
 FINAL_TARGET = 'dist/bin/gtest'
 
 USE_LIBS += [
     'static:xul',
+    # xul-gtest is an intermediate static library. It is used as FINAL_TARGET
+    # for gtest code.
+    # If the FINAL_TARGET were the library in this directory, then the gtest
+    # code would end up before static:xul, and before StaticXULComponentStart,
+    # which needs to stay first.
+    'xul-gtest',
 ]
 
+# This needs to come after static:xul to avoid things like libfallible coming
+# before StaticXULComponentStart.
+Libxul('xul-gtest-real')
+
 if CONFIG['MOZ_RUST']:
     USE_LIBS += [
         'gkrust-gtest',
     ]
+
+DIRS += [
+    'static',
+]
new file mode 100644
--- /dev/null
+++ b/toolkit/library/gtest/static/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library('xul-gtest')
+
+Libxul_defines()
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -1,15 +1,22 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 @template
+def Libxul_defines():
+    LIBRARY_DEFINES['MOZILLA_INTERNAL_API'] = True
+    LIBRARY_DEFINES['IMPL_LIBXUL'] = True
+    if not CONFIG['JS_SHARED_LIBRARY']:
+        LIBRARY_DEFINES['STATIC_EXPORTABLE_JS_API'] = True
+
+@template
 def Libxul(name):
     if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):
         # This is going to be a framework named "XUL", not an ordinary library named
         # "libxul.dylib"
         GeckoFramework(name, linkage=None)
         SHARED_LIBRARY_NAME = 'XUL'
     else:
         GeckoSharedLibrary(name, linkage=None)
@@ -46,20 +53,17 @@ def Libxul(name):
             '/xpcom/base',
         ]
         # config/version.mk says $(srcdir)/$(RCINCLUDE), and this needs to
         # be valid in both toolkit/library and toolkit/library/gtest.
         # Eventually, the make backend would do its own path canonicalization
         # and config/version.mk would lift the $(srcdir)
         RCINCLUDE = '$(DEPTH)/toolkit/library/xulrunner.rc'
 
-    LIBRARY_DEFINES['MOZILLA_INTERNAL_API'] = True
-    LIBRARY_DEFINES['IMPL_LIBXUL'] = True
-    if not CONFIG['JS_SHARED_LIBRARY']:
-        LIBRARY_DEFINES['STATIC_EXPORTABLE_JS_API'] = True
+    Libxul_defines()
 
     if CONFIG['MOZ_NEEDS_LIBATOMIC']:
         OS_LIBS += ['atomic']
 
     # This option should go away in bug 1290972, but we need to wait until
     # Rust 1.12 has been released.
     if CONFIG['MOZ_RUST'] and CONFIG['OS_ARCH'] == 'Darwin':
         LDFLAGS += ['-Wl,-no_compact_unwind']
--- a/widget/tests/test_keycodes.xul
+++ b/widget/tests/test_keycodes.xul
@@ -2035,16 +2035,30 @@ function* runKeyEventTests()
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_HELP,
                    modifiers:{}, chars:""},
                   nsIDOMKeyEvent.DOM_VK_HELP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_SLEEP,
                    modifiers:{}, chars:""},
                   nsIDOMKeyEvent.DOM_VK_SLEEP, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     // XXX TODO: we cannot test Home, Up, PageUp, Left, Right, End, Down, PageDown, Ins and Del.
 
+    // Backspace and Enter are handled with special path in mozilla::widget::NativeKey.  So, let's test them with modifiers too.
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+                   modifiers:{ctrlKey:1}, chars:"\u007F"},
+                  nsIDOMKeyEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+                   modifiers:{altKey:1}, chars:"\u0008"},
+                  nsIDOMKeyEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+                   modifiers:{ctrlKey:1}, chars:"\n"},
+                  nsIDOMKeyEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+                   modifiers:{altKey:1}, chars:"\r"},
+                  nsIDOMKeyEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
     // US
     // Alphabet
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
                    modifiers:{}, chars:"a"},
                   nsIDOMKeyEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
                    modifiers:{shiftKey:1}, chars:"A"},
                   nsIDOMKeyEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
--- a/widget/windows/GfxInfo.cpp
+++ b/widget/windows/GfxInfo.cpp
@@ -926,22 +926,29 @@ GfxInfo::GetGfxDriverInfo()
       DRIVER_EQUAL, V(8,832,0,0), "FEATURE_FAILURE_BUG_1099252");
 
     // Bug 1118695
     APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7,
       (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
       GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
       DRIVER_EQUAL, V(8,783,2,2000), "FEATURE_FAILURE_BUG_1118695");
 
-    // Bug 1198815
+    // Bug 1198815 and 1267970
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows,
       (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
       nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
-      DRIVER_BETWEEN_INCLUSIVE, V(15,200,0,0), V(15,200,1062,1004),
-      "FEATURE_FAILURE_BUG_1198815", "15.200.0.0-15.200.1062.1004");
+      DRIVER_BETWEEN_INCLUSIVE, V(15,200,0,0), V(15,301,2301,1002),
+      "FEATURE_FAILURE_BUG_1198815_1267970", "15.200.0.0-15.301.2301.1002");
+
+    // Bug 1267970
+    APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows,
+      (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+      nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+      DRIVER_BETWEEN_INCLUSIVE, V(16,100,0,0), V(16,300,2311,0),
+      "FEATURE_FAILURE_BUG_1267970", "16.100.0.0-16.300.2311.0");
 
     /*
      * Bug 783517 - crashes in AMD driver on Windows 8
      */
     APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows8,
       (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
       GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
       DRIVER_BETWEEN_INCLUSIVE_START, V(8,982,0,0), V(8,983,0,0),
--- a/widget/windows/KeyboardLayout.cpp
+++ b/widget/windows/KeyboardLayout.cpp
@@ -133,16 +133,181 @@ static const char* kVirtualKeyName[] = {
   "VK_OEM_ENLW", "VK_OEM_BACKTAB", "VK_ATTN", "VK_CRSEL",
   "VK_EXSEL", "VK_EREOF", "VK_PLAY", "VK_ZOOM",
   "VK_NONAME", "VK_PA1", "VK_OEM_CLEAR", "0xFF"
 };
 
 static_assert(sizeof(kVirtualKeyName) / sizeof(const char*) == 0x100,
   "The virtual key name must be defined just 256 keys");
 
+static const nsCString
+GetCharacterCodeName(WPARAM aCharCode)
+{
+  switch (aCharCode) {
+    case 0x0000:
+      return NS_LITERAL_CSTRING("NULL (0x0000)");
+    case 0x0008:
+      return NS_LITERAL_CSTRING("BACKSPACE (0x0008)");
+    case 0x0009:
+      return NS_LITERAL_CSTRING("CHARACTER TABULATION (0x0009)");
+    case 0x000A:
+      return NS_LITERAL_CSTRING("LINE FEED (0x000A)");
+    case 0x000B:
+      return NS_LITERAL_CSTRING("LINE TABULATION (0x000B)");
+    case 0x000C:
+      return NS_LITERAL_CSTRING("FORM FEED (0x000C)");
+    case 0x000D:
+      return NS_LITERAL_CSTRING("CARRIAGE RETURN (0x000D)");
+    case 0x0018:
+      return NS_LITERAL_CSTRING("CANCEL (0x0018)");
+    case 0x001B:
+      return NS_LITERAL_CSTRING("ESCAPE (0x001B)");
+    case 0x0020:
+      return NS_LITERAL_CSTRING("SPACE (0x0020)");
+    case 0x007F:
+      return NS_LITERAL_CSTRING("DELETE (0x007F)");
+    case 0x00A0:
+      return NS_LITERAL_CSTRING("NO-BREAK SPACE (0x00A0)");
+    case 0x00AD:
+      return NS_LITERAL_CSTRING("SOFT HYPHEN (0x00AD)");
+    case 0x2000:
+      return NS_LITERAL_CSTRING("EN QUAD (0x2000)");
+    case 0x2001:
+      return NS_LITERAL_CSTRING("EM QUAD (0x2001)");
+    case 0x2002:
+      return NS_LITERAL_CSTRING("EN SPACE (0x2002)");
+    case 0x2003:
+      return NS_LITERAL_CSTRING("EM SPACE (0x2003)");
+    case 0x2004:
+      return NS_LITERAL_CSTRING("THREE-PER-EM SPACE (0x2004)");
+    case 0x2005:
+      return NS_LITERAL_CSTRING("FOUR-PER-EM SPACE (0x2005)");
+    case 0x2006:
+      return NS_LITERAL_CSTRING("SIX-PER-EM SPACE (0x2006)");
+    case 0x2007:
+      return NS_LITERAL_CSTRING("FIGURE SPACE (0x2007)");
+    case 0x2008:
+      return NS_LITERAL_CSTRING("PUNCTUATION SPACE (0x2008)");
+    case 0x2009:
+      return NS_LITERAL_CSTRING("THIN SPACE (0x2009)");
+    case 0x200A:
+      return NS_LITERAL_CSTRING("HAIR SPACE (0x200A)");
+    case 0x200B:
+      return NS_LITERAL_CSTRING("ZERO WIDTH SPACE (0x200B)");
+    case 0x200C:
+      return NS_LITERAL_CSTRING("ZERO WIDTH NON-JOINER (0x200C)");
+    case 0x200D:
+      return NS_LITERAL_CSTRING("ZERO WIDTH JOINER (0x200D)");
+    case 0x200E:
+      return NS_LITERAL_CSTRING("LEFT-TO-RIGHT MARK (0x200E)");
+    case 0x200F:
+      return NS_LITERAL_CSTRING("RIGHT-TO-LEFT MARK (0x200F)");
+    case 0x2029:
+      return NS_LITERAL_CSTRING("PARAGRAPH SEPARATOR (0x2029)");
+    case 0x202A:
+      return NS_LITERAL_CSTRING("LEFT-TO-RIGHT EMBEDDING (0x202A)");
+    case 0x202B:
+      return NS_LITERAL_CSTRING("RIGHT-TO-LEFT EMBEDDING (0x202B)");
+    case 0x202D:
+      return NS_LITERAL_CSTRING("LEFT-TO-RIGHT OVERRIDE (0x202D)");
+    case 0x202E:
+      return NS_LITERAL_CSTRING("RIGHT-TO-LEFT OVERRIDE (0x202E)");
+    case 0x202F:
+      return NS_LITERAL_CSTRING("NARROW NO-BREAK SPACE (0x202F)");
+    case 0x205F:
+      return NS_LITERAL_CSTRING("MEDIUM MATHEMATICAL SPACE (0x205F)");
+    case 0x2060:
+      return NS_LITERAL_CSTRING("WORD JOINER (0x2060)");
+    case 0x2066:
+      return NS_LITERAL_CSTRING("LEFT-TO-RIGHT ISOLATE (0x2066)");
+    case 0x2067:
+      return NS_LITERAL_CSTRING("RIGHT-TO-LEFT ISOLATE (0x2067)");
+    case 0x3000:
+      return NS_LITERAL_CSTRING("IDEOGRAPHIC SPACE (0x3000)");
+    case 0xFEFF:
+      return NS_LITERAL_CSTRING("ZERO WIDTH NO-BREAK SPACE (0xFEFF)");
+    default: {
+      if (aCharCode < ' ' ||
+          (aCharCode >= 0x80 && aCharCode < 0xA0)) {
+        return nsPrintfCString("control (0x%04X)", aCharCode);
+      }
+      if (NS_IS_HIGH_SURROGATE(aCharCode)) {
+        return nsPrintfCString("high surrogate (0x%04X)", aCharCode);
+      }
+      if (NS_IS_LOW_SURROGATE(aCharCode)) {
+        return nsPrintfCString("low surrogate (0x%04X)", aCharCode);
+      }
+      return IS_IN_BMP(aCharCode) ?
+        nsPrintfCString("'%s' (0x%04X)",
+          NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(), aCharCode) :
+        nsPrintfCString("'%s' (0x%08X)",
+          NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(), aCharCode);
+    }
+  }
+}
+
+static const nsCString
+GetCharacterCodeName(char16_t* aChars, uint32_t aLength)
+{
+  if (!aLength) {
+    return NS_LITERAL_CSTRING("");
+  }
+  nsAutoCString result;
+  for (uint32_t i = 0; i < aLength; ++i) {
+    if (!result.IsEmpty()) {
+      result.AppendLiteral(", ");
+    } else {
+      result.AssignLiteral("\"");
+    }
+    result.Append(GetCharacterCodeName(aChars[i]));
+  }
+  result.AppendLiteral("\"");
+  return result;
+}
+
+class MOZ_STACK_CLASS GetShiftStateName final : public nsAutoCString
+{
+public:
+  explicit GetShiftStateName(VirtualKey::ShiftState aShiftState)
+  {
+    if (!aShiftState) {
+      AssignLiteral("none");
+      return;
+    }
+    if (aShiftState & VirtualKey::STATE_SHIFT) {
+      AssignLiteral("Shift");
+      aShiftState &= ~VirtualKey::STATE_SHIFT;
+    }
+    if (aShiftState & VirtualKey::STATE_CONTROL) {
+      MaybeAppendSeparator();
+      AssignLiteral("Ctrl");
+      aShiftState &= ~VirtualKey::STATE_CONTROL;
+    }
+    if (aShiftState & VirtualKey::STATE_ALT) {
+      MaybeAppendSeparator();
+      AssignLiteral("Alt");
+      aShiftState &= ~VirtualKey::STATE_ALT;
+    }
+    if (aShiftState & VirtualKey::STATE_CAPSLOCK) {
+      MaybeAppendSeparator();
+      AssignLiteral("CapsLock");
+      aShiftState &= ~VirtualKey::STATE_CAPSLOCK;
+    }
+    MOZ_ASSERT(!aShiftState);
+  }
+
+private:
+  void MaybeAppendSeparator()
+  {
+    if (!IsEmpty()) {
+      AppendLiteral(" | ");
+    }
+  }
+};
+
 // Unique id counter associated with a keydown / keypress events. Used in
 // identifing keypress events for removal from async event dispatch queue
 // in metrofx after preventDefault is called on keydown events.
 static uint32_t sUniqueKeyEventId = 0;
 
 struct DeadKeyEntry
 {
   char16_t BaseChar;
@@ -1037,17 +1202,18 @@ NativeKey::InitWithAppCommand()
       GetScanCodeWithExtendedFlag());
 }
 
 // static
 bool
 NativeKey::IsControlChar(char16_t aChar)
 {
   static const char16_t U_SPACE = 0x20;
-  return aChar < U_SPACE;
+  static const char16_t U_DELETE = 0x7F;
+  return aChar < U_SPACE || aChar == U_DELETE;
 }
 
 bool
 NativeKey::IsFollowedByDeadCharMessage() const
 {
   MSG nextMsg;
   if (mFakeCharMsgs) {
     nextMsg = mFakeCharMsgs->ElementAt(0).GetCharMsg(mMsg.hwnd);
@@ -2515,16 +2681,20 @@ NativeKey::DispatchKeyPressEventForFollo
 
 /*****************************************************************************
  * mozilla::widget::KeyboardLayout
  *****************************************************************************/
 
 KeyboardLayout* KeyboardLayout::sInstance = nullptr;
 nsIIdleServiceInternal* KeyboardLayout::sIdleService = nullptr;
 
+// This log is very noisy if you don't want to retrieve the mapping table
+// of specific keyboard layout.  LogLevel::Debug and LogLevel::Verbose are
+// used to log the layout mapping.  If you need to log some behavior of
+// KeyboardLayout class, you should use LogLevel::Info or lower level.
 LazyLogModule sKeyboardLayoutLogger("KeyboardLayoutWidgets");
 
 // static
 KeyboardLayout*
 KeyboardLayout::GetInstance()
 {
   if (!sInstance) {
     sInstance = new KeyboardLayout();
@@ -2672,22 +2842,21 @@ KeyboardLayout::InitNativeKey(NativeKey&
       UniCharsAndModifiers deadChars =
         mVirtualKeys[virtualKeyIndex].GetNativeUniChars(shiftState);
       NS_ASSERTION(deadChars.mLength == 1,
                    "dead key must generate only one character");
       aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_Dead;
       return;
     }
 
-    // Dead key followed by another dead key causes inputting both character.
-    // However, at keydown message handling, we need to forget the first
-    // dead key because there is no guarantee coming WM_KEYUP for the second
-    // dead key before next WM_KEYDOWN.  E.g., due to auto key repeat or
-    // pressing another dead key before releasing current key.  Therefore,
-    // we can set only a character for current key for keyup event.
+    // At keydown message handling, we need to forget the first dead key
+    // because there is no guarantee coming WM_KEYUP for the second dead
+    // key before next WM_KEYDOWN.  E.g., due to auto key repeat or pressing
+    // another dead key before releasing current key.  Therefore, we can
+    // set only a character for current key for keyup event.
     if (mActiveDeadKey < 0) {
       aNativeKey.mCommittedCharsAndModifiers =
         mVirtualKeys[virtualKeyIndex].GetUniChars(shiftState);
       return;
     }
 
     int32_t activeDeadKeyIndex = GetKeyIndex(mActiveDeadKey);
     if (activeDeadKeyIndex < 0 || activeDeadKeyIndex >= NS_NUM_OF_KEYS) {
@@ -2699,66 +2868,112 @@ KeyboardLayout::InitNativeKey(NativeKey&
       NS_WARNING(warning.get());
 #ifdef MOZ_CRASHREPORTER
       CrashReporter::AppendAppNotesToCrashReport(
                        NS_LITERAL_CSTRING("\n") + warning);
 #endif // #ifdef MOZ_CRASHREPORTER
 #endif // #if defined(DEBUG) || defined(MOZ_CRASHREPORTER)
       MOZ_CRASH("Trying to reference out of range of mVirtualKeys");
     }
+
+    // Dead key followed by another dead key may cause a composed character
+    // (e.g., "Russian - Mnemonic" keyboard layout's 's' -> 'c').
+    if (MaybeInitNativeKeyWithCompositeChar(aNativeKey, aModKeyState)) {
+      return;
+    }
+
+    // Otherwise, dead key followed by another dead key causes inputting both
+    // character.
     UniCharsAndModifiers prevDeadChars =
       mVirtualKeys[activeDeadKeyIndex].GetUniChars(mDeadKeyShiftState);
     UniCharsAndModifiers newChars =
       mVirtualKeys[virtualKeyIndex].GetUniChars(shiftState);
     // But keypress events should be fired for each committed character.
     aNativeKey.mCommittedCharsAndModifiers = prevDeadChars + newChars;
     if (isKeyDown) {
       DeactivateDeadKeyState();
     }
     return;
   }
 
+  if (MaybeInitNativeKeyWithCompositeChar(aNativeKey, aModKeyState)) {
+    return;
+  }
+
   UniCharsAndModifiers baseChars =
     mVirtualKeys[virtualKeyIndex].GetUniChars(shiftState);
   if (mActiveDeadKey < 0) {
     // No dead-keys are active. Just return the produced characters.
     aNativeKey.mCommittedCharsAndModifiers = baseChars;
     return;
   }
 
-  // Dead-key was active. See if pressed base character does produce
-  // valid composite character.
   int32_t activeDeadKeyIndex = GetKeyIndex(mActiveDeadKey);
-  char16_t compositeChar = (baseChars.mLength == 1 && baseChars.mChars[0]) ?
-    mVirtualKeys[activeDeadKeyIndex].GetCompositeChar(mDeadKeyShiftState,
-                                                      baseChars.mChars[0]) : 0;
-  if (compositeChar) {
-    // Active dead-key and base character does produce exactly one
-    // composite character.
-    aNativeKey.mCommittedCharsAndModifiers.Append(compositeChar,
-                                                  baseChars.mModifiers[0]);
-    if (isKeyDown) {
-      DeactivateDeadKeyState();
-    }
+  if (NS_WARN_IF(activeDeadKeyIndex < 0)) {
     return;
   }
 
   // There is no valid dead-key and base character combination.
   // Return dead-key character followed by base character.
   UniCharsAndModifiers deadChars =
     mVirtualKeys[activeDeadKeyIndex].GetUniChars(mDeadKeyShiftState);
   // But keypress events should be fired for each committed character.
   aNativeKey.mCommittedCharsAndModifiers = deadChars + baseChars;
   if (isKeyDown) {
     DeactivateDeadKeyState();
   }
 
   return;
 }
 
+bool
+KeyboardLayout::MaybeInitNativeKeyWithCompositeChar(
+                  NativeKey& aNativeKey,
+                  const ModifierKeyState& aModKeyState)
+{
+  if (mActiveDeadKey < 0) {
+    return false;
+  }
+
+  int32_t activeDeadKeyIndex = GetKeyIndex(mActiveDeadKey);
+  if (NS_WARN_IF(activeDeadKeyIndex < 0)) {
+    return false;
+  }
+
+  int32_t virtualKeyIndex = GetKeyIndex(aNativeKey.mOriginalVirtualKeyCode);
+  if (NS_WARN_IF(virtualKeyIndex < 0)) {
+    return false;
+  }
+
+  uint8_t shiftState =
+    VirtualKey::ModifiersToShiftState(aModKeyState.GetModifiers());
+
+  UniCharsAndModifiers baseChars =
+    mVirtualKeys[virtualKeyIndex].GetUniChars(shiftState);
+  if (baseChars.IsEmpty() || !baseChars.mChars[0]) {
+    return false;
+  }
+
+  char16_t compositeChar =
+    mVirtualKeys[activeDeadKeyIndex].GetCompositeChar(mDeadKeyShiftState,
+                                                      baseChars.mChars[0]);
+  if (!compositeChar) {
+    return false;
+  }
+
+  // Active dead-key and base character does produce exactly one composite
+  // character.
+  aNativeKey.mCommittedCharsAndModifiers.Append(compositeChar,
+                                                baseChars.mModifiers[0]);
+  if (aNativeKey.IsKeyDownMessage()) {
+    DeactivateDeadKeyState();
+  }
+  return true;
+}
+
 UniCharsAndModifiers
 KeyboardLayout::GetUniCharsAndModifiers(
                   uint8_t aVirtualKey,
                   const ModifierKeyState& aModKeyState) const
 {
   UniCharsAndModifiers result;
   int32_t key = GetKeyIndex(aVirtualKey);
   if (key < 0) {
@@ -2774,16 +2989,19 @@ KeyboardLayout::LoadLayout(HKL aLayout)
   mIsPendingToRestoreKeyboardLayout = false;
 
   if (mKeyboardLayout == aLayout) {
     return;
   }
 
   mKeyboardLayout = aLayout;
 
+  MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
+    ("KeyboardLayout::LoadLayout(aLayout=0x%08X)", aLayout));
+
   BYTE kbdState[256];
   memset(kbdState, 0, sizeof(kbdState));
 
   BYTE originalKbdState[256];
   // Bitfield with all shift states that have at least one dead-key.
   uint16_t shiftStatesWithDeadKeys = 0;
   // Bitfield with all shift states that produce any possible dead-key base
   // characters.
@@ -2815,22 +3033,33 @@ KeyboardLayout::LoadLayout(HKL aLayout)
         shiftStatesWithDeadKeys |= (1 << shiftState);
         // Repeat dead-key to deactivate it and get its character
         // representation.
         char16_t deadChar[2];
         ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)deadChar,
                             ArrayLength(deadChar), 0, mKeyboardLayout);
         NS_ASSERTION(ret == 2, "Expecting twice repeated dead-key character");
         mVirtualKeys[vki].SetDeadChar(shiftState, deadChar[0]);
+
+        MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
+          ("  %s (%d): DeadChar(%s, %s) (ret=%d)",
+           kVirtualKeyName[virtualKey], vki,
+           GetShiftStateName(shiftState).get(),
+           GetCharacterCodeName(deadChar, 1).get(), ret));
       } else {
         if (ret == 1) {
           // dead-key can pair only with exactly one base character.
           shiftStatesWithBaseChars |= (1 << shiftState);
         }
         mVirtualKeys[vki].SetNormalChars(shiftState, uniChars, ret);
+        MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+          ("  %s (%d): NormalChar(%s, %s) (ret=%d)",
+           kVirtualKeyName[virtualKey], vki,
+           GetShiftStateName(shiftState).get(),
+           GetCharacterCodeName(uniChars, ret).get(), ret));
       }
     }
   }
 
   // Now process each dead-key to find all its base characters and resulting
   // composite characters.
   for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) {
     if (!(shiftStatesWithDeadKeys & (1 << shiftState))) {
@@ -2854,30 +3083,30 @@ KeyboardLayout::LoadLayout(HKL aLayout)
         }
         mVirtualKeys[vki].AttachDeadKeyTable(shiftState, dkt);
       }
     }
   }
 
   ::SetKeyboardState(originalKbdState);
 
-  if (MOZ_LOG_TEST(sKeyboardLayoutLogger, LogLevel::Debug)) {
+  if (MOZ_LOG_TEST(sKeyboardLayoutLogger, LogLevel::Verbose)) {
     static const UINT kExtendedScanCode[] = { 0x0000, 0xE000 };
     static const UINT kMapType =
       IsVistaOrLater() ? MAPVK_VSC_TO_VK_EX : MAPVK_VSC_TO_VK;
-    MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
-           ("Logging virtual keycode values for scancode (0x%p)...",
-            mKeyboardLayout));
+    MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+      ("Logging virtual keycode values for scancode (0x%p)...",
+       mKeyboardLayout));
     for (uint32_t i = 0; i < ArrayLength(kExtendedScanCode); i++) {
       for (uint32_t j = 1; j <= 0xFF; j++) {
         UINT scanCode = kExtendedScanCode[i] + j;
         UINT virtualKeyCode =
           ::MapVirtualKeyEx(scanCode, kMapType, mKeyboardLayout);
-        MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
-               ("0x%04X, %s", scanCode, kVirtualKeyName[virtualKeyCode]));
+        MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+          ("0x%04X, %s", scanCode, kVirtualKeyName[virtualKeyCode]));
       }
       // XP and Server 2003 don't support 0xE0 prefix of the scancode.
       // Therefore, we don't need to continue on them.
       if (!IsVistaOrLater()) {
         break;
       }
     }
   }
@@ -3082,36 +3311,87 @@ KeyboardLayout::GetDeadKeyCombinations(u
           ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)compositeChars,
                         ArrayLength(compositeChars), 0, mKeyboardLayout);
         switch (ret) {
           case 0:
             // This key combination does not produce any characters. The
             // dead-key is still in active state.
             break;
           case 1: {
-            // Exactly one composite character produced. Now, when dead-key
-            // is not active, repeat the last character one more time to
-            // determine the base character.
             char16_t baseChars[5];
             ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)baseChars,
                                 ArrayLength(baseChars), 0, mKeyboardLayout);
-            NS_ASSERTION(ret == 1, "One base character expected");
-            if (ret == 1 && entries < aMaxEntries &&
-                AddDeadKeyEntry(baseChars[0], compositeChars[0],
-                                aDeadKeyArray, entries)) {
-              entries++;
+            if (entries < aMaxEntries) {
+              switch (ret) {
+                case 1:
+                  // Exactly one composite character produced. Now, when
+                  // dead-key is not active, repeat the last character one more
+                  // time to determine the base character.
+                  if (AddDeadKeyEntry(baseChars[0], compositeChars[0],
+                                      aDeadKeyArray, entries)) {
+                    entries++;
+                  }
+                  deadKeyActive = false;
+                  break;
+                case -1: {
+                  // If pressing another dead-key produces different character,
+                  // we should register the dead-key entry with first character
+                  // produced by current key.
+
+                  // First inactivate the dead-key state completely.
+                  deadKeyActive =
+                    EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState);
+                  if (NS_WARN_IF(deadKeyActive)) {
+                    MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Error,
+                      ("  failed to deactivating the dead-key state..."));
+                    break;
+                  }
+                  for (int32_t i = 0; i < 5; ++i) {
+                    ret = ::ToUnicodeEx(virtualKey, 0, kbdState,
+                                        (LPWSTR)baseChars,
+                                        ArrayLength(baseChars),
+                                        0, mKeyboardLayout);
+                    if (ret >= 0) {
+                      break;
+                    }
+                  }
+                  if (ret > 0 &&
+                      AddDeadKeyEntry(baseChars[0], compositeChars[0],
+                                      aDeadKeyArray, entries)) {
+                    entries++;
+                  }
+                  // Inactivate dead-key state for current virtual keycode.
+                  EnsureDeadKeyActive(false, virtualKey, kbdState);
+                  break;
+                }
+                default:
+                  NS_WARN_IF("File a bug for this dead-key handling!");
+                  deadKeyActive = false;
+                  break;
+              }
             }
-            deadKeyActive = false;
+            MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
+              ("  %s -> %s (%d): DeadKeyEntry(%s, %s) (ret=%d)",
+               kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+               GetCharacterCodeName(compositeChars, 1).get(),
+               ret <= 0 ? "''" :
+                 GetCharacterCodeName(baseChars, std::min(ret, 5)).get(), ret));
             break;
           }
           default:
             // 1. Unexpected dead-key. Dead-key chaining is not supported.
             // 2. More than one character generated. This is not a valid
             //    dead-key and base character combination.
             deadKeyActive = false;
+            MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+              ("  %s -> %s (%d): Unsupport dead key type(%s) (ret=%d)",
+               kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+               ret <= 0 ? "''" :
+                 GetCharacterCodeName(compositeChars,
+                                      std::min(ret, 5)).get(), ret));
             break;
         }
       }
     }
   }
 
   if (deadKeyActive) {
     deadKeyActive = EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState);
--- a/widget/windows/KeyboardLayout.h
+++ b/widget/windows/KeyboardLayout.h
@@ -600,16 +600,25 @@ public:
   /**
    * IsSysKey() returns true if aVirtualKey with aModKeyState causes WM_SYSKEY*
    * or WM_SYS*CHAR messages.
    */
   bool IsSysKey(uint8_t aVirtualKey,
                 const ModifierKeyState& aModKeyState) const;
 
   /**
+   * MaybeInitNativeKeyWithCompositeChar() may initialize aNativeKey with
+   * proper composite character when dead key produces a composite character.
+   * Otherwise, just returns false.
+   */
+  bool MaybeInitNativeKeyWithCompositeChar(
+         NativeKey& aNativeKey,
+         const ModifierKeyState& aModKeyState);
+
+  /**
    * GetUniCharsAndModifiers() returns characters which is inputted by the
    * aVirtualKey with aModKeyState.  This method isn't stateful.
    */
   UniCharsAndModifiers GetUniCharsAndModifiers(
                          uint8_t aVirtualKey,
                          const ModifierKeyState& aModKeyState) const;
 
   /**