Bug 1474130 - Implement ScrollPosition/Privacy/DocCapability listeners in C++ r=peterv
authorAlphan Chen <alchen@mozilla.com>
Sat, 04 May 2019 10:42:52 +0000
changeset 472766 3288c43195a26565edec3d59a8f8bce6018be592
parent 472765 4bb1186d2d9e4bfa6c0238fab94b3d60e72aee55
child 472767 59306f187449c5f35614f7e86293dd53ec1f5422
push id35978
push usershindli@mozilla.com
push dateTue, 07 May 2019 09:44:39 +0000
treeherdermozilla-central@7aee5a30dd15 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1474130
milestone68.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
Bug 1474130 - Implement ScrollPosition/Privacy/DocCapability listeners in C++ r=peterv Differential Revision: https://phabricator.services.mozilla.com/D23057
browser/base/content/browser.js
browser/components/sessionstore/ContentSessionStore.jsm
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/TabStateFlusher.jsm
dom/base/nsFrameLoader.cpp
dom/base/nsFrameLoader.h
dom/ipc/BrowserChild.cpp
dom/ipc/BrowserChild.h
dom/ipc/BrowserParent.cpp
dom/ipc/BrowserParent.h
dom/ipc/PBrowser.ipdl
dom/webidl/FrameLoader.webidl
toolkit/components/sessionstore/SessionStoreListener.cpp
toolkit/components/sessionstore/SessionStoreListener.h
toolkit/components/sessionstore/moz.build
xpfe/appshell/nsIXULBrowserWindow.idl
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5270,16 +5270,99 @@ var XULBrowserWindow = {
     if (tab) {
       SessionStore.navigateAndRestore(tab, {}, aIndex);
       return;
     }
 
     throw new Error("Trying to navigateAndRestore a browser which was " +
                     "not attached to this tabbrowser is unsupported");
   },
+
+  // data for updating sessionStore
+  _sessionData: {},
+
+  composeChildren: function XWB_composeScrollPositionsData(
+      aPositions, aDescendants, aStartIndex, aNumberOfDescendants) {
+    let children = [];
+    let lastIndexOfNonNullbject = -1;
+    for (let i = 0; i < aNumberOfDescendants; i++) {
+      let currentIndex = aStartIndex + i;
+      let obj = {};
+      let objWithData = false;
+      if (aPositions[currentIndex]) {
+        obj.scroll = aPositions[currentIndex];
+        objWithData = true;
+      }
+      if (aDescendants[currentIndex]) {
+        let descendantsTree = this.composeChildren(aPositions, aDescendants, currentIndex + 1, aDescendants[currentIndex]);
+        i += aDescendants[currentIndex];
+        if (descendantsTree) {
+          obj.children = descendantsTree;
+          objWithData = true;
+        }
+      }
+
+      if (objWithData) {
+        lastIndexOfNonNullbject = children.length;
+        children.push(obj);
+      } else {
+        children.push(null);
+      }
+    }
+
+    if (lastIndexOfNonNullbject == -1) {
+      return null;
+    }
+
+    return children.slice(0, lastIndexOfNonNullbject + 1);
+  },
+
+  updateScrollPositions: function XWB_updateScrollPositions(aPositions, aDescendants) {
+    let obj = {};
+    let objWithData = false;
+
+    if (aPositions[0]) {
+      obj.scroll = aPositions[0];
+      objWithData = true;
+    }
+
+    if (aPositions.length > 1) {
+      let children = this.composeChildren(aPositions, aDescendants, 1, aDescendants[0]);
+      if (children) {
+        obj.children = children;
+        objWithData = true;
+      }
+    }
+
+    if (objWithData) {
+      this._sessionData.scroll = obj;
+    } else {
+      this._sessionData.scroll = null;
+    }
+  },
+
+  updateDocShellCaps: function XWB_updateDocShellCaps(aDisCaps) {
+    this._sessionData.disallow = aDisCaps ? aDisCaps : null;
+  },
+
+  updateIsPrivate: function XWB_updateIsPrivate(aIsPrivate) {
+    this._sessionData.isPrivate = aIsPrivate;
+  },
+
+  updateSessionStore: function XWB_updateSessionStore(aBrowser, aFlushId) {
+    let tab = gBrowser.getTabForBrowser(aBrowser);
+    if (tab) {
+      SessionStore.updateSessionStoreFromTablistener(tab, {
+        data: this._sessionData,
+        flushID: aFlushId,
+        isFinal: false,
+      });
+      this._sessionData = {};
+    }
+  },
 };
 
 var LinkTargetDisplay = {
   get DELAY_SHOW() {
      delete this.DELAY_SHOW;
      return this.DELAY_SHOW = Services.prefs.getIntPref("browser.overlink-delay");
   },
 
--- a/browser/components/sessionstore/ContentSessionStore.jsm
+++ b/browser/components/sessionstore/ContentSessionStore.jsm
@@ -290,56 +290,16 @@ class SessionHistoryListener extends Han
     this.collect();
   }
 }
 SessionHistoryListener.prototype.QueryInterface =
   ChromeUtils.generateQI([Ci.nsISHistoryListener,
                           Ci.nsISupportsWeakReference]);
 
 /**
- * Listens for scroll position changes. Whenever the user scrolls the top-most
- * frame we update the scroll position and will restore it when requested.
- *
- * Causes a SessionStore:update message to be sent that contains the current
- * scroll positions as a tree of strings. If no frame of the whole frame tree
- * is scrolled this will return null so that we don't tack a property onto
- * the tabData object in the parent process.
- *
- * Example:
- *   {scroll: "100,100", children: [null, null, {scroll: "200,200"}]}
- */
-class ScrollPositionListener extends Handler {
-  constructor(store) {
-    super(store);
-
-    SessionStoreUtils.addDynamicFrameFilteredListener(
-        this.mm, "mozvisualscroll", this,
-        /* capture */ false, /* system group */ true);
-
-    this.stateChangeNotifier.addObserver(this);
-  }
-
-  handleEvent() {
-    this.messageQueue.push("scroll", () => this.collect());
-  }
-
-  onPageLoadCompleted() {
-    this.messageQueue.push("scroll", () => this.collect());
-  }
-
-  onPageLoadStarted() {
-    this.messageQueue.push("scroll", () => null);
-  }
-
-  collect() {
-    return SessionStoreUtils.collectScrollPosition(this.mm.content);
-  }
-}
-
-/**
  * Listens for changes to input elements. Whenever the value of an input
  * element changes we will re-collect data for the current frame tree and send
  * a message to the parent process.
  *
  * Causes a SessionStore:update message to be sent that contains the form data
  * for all reachable frames.
  *
  * Example:
@@ -368,49 +328,16 @@ class FormDataListener extends Handler {
   }
 
   collect() {
     return SessionStoreUtils.collectFormData(this.mm.content);
   }
 }
 
 /**
- * Listens for changes to docShell capabilities. Whenever a new load is started
- * we need to re-check the list of capabilities and send message when it has
- * changed.
- *
- * Causes a SessionStore:update message to be sent that contains the currently
- * disabled docShell capabilities (all nsIDocShell.allow* properties set to
- * false) as a string - i.e. capability names separate by commas.
- */
-class DocShellCapabilitiesListener extends Handler {
-  constructor(store) {
-    super(store);
-
-    /**
-     * This field is used to compare the last docShell capabilities to the ones
-     * that have just been collected. If nothing changed we won't send a message.
-     */
-    this._latestCapabilities = "";
-
-    this.stateChangeNotifier.addObserver(this);
-  }
-
-  onPageLoadStarted() {
-    let caps = SessionStoreUtils.collectDocShellCapabilities(this.mm.docShell);
-
-    // Send new data only when the capability list changes.
-    if (caps != this._latestCapabilities) {
-      this._latestCapabilities = caps;
-      this.messageQueue.push("disallow", () => caps || null);
-    }
-  }
-}
-
-/**
  * Listens for changes to the DOMSessionStorage. Whenever new keys are added,
  * existing ones removed or changed, or the storage is cleared we will send a
  * message to the parent process containing up-to-date sessionStorage data.
  *
  * Causes a SessionStore:update message to be sent that contains the current
  * DOMSessionStorage contents. The data is a nested object using host names
  * as keys and per-host DOMSessionStorage data as values.
  */
@@ -532,48 +459,16 @@ class SessionStorageListener extends Han
 
   onPageLoadStarted() {
     this.resetEventListener();
     this.collect();
   }
 }
 
 /**
- * Listen for changes to the privacy status of the tab.
- * By definition, tabs start in non-private mode.
- *
- * Causes a SessionStore:update message to be sent for
- * field "isPrivate". This message contains
- *  |true| if the tab is now private
- *  |null| if the tab is now public - the field is therefore
- *  not saved.
- */
-class PrivacyListener extends Handler {
-  constructor(store) {
-    super(store);
-
-    this.mm.docShell.addWeakPrivacyTransitionObserver(this);
-
-    // Check that value at startup as it might have
-    // been set before the frame script was loaded.
-    if (this.mm.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing) {
-      this.messageQueue.push("isPrivate", () => true);
-    }
-  }
-
-  // Ci.nsIPrivacyTransitionObserver
-  privateModeChanged(enabled) {
-    this.messageQueue.push("isPrivate", () => enabled || null);
-  }
-}
-PrivacyListener.prototype.QueryInterface =
-  ChromeUtils.generateQI([Ci.nsIPrivacyTransitionObserver,
-                          Ci.nsISupportsWeakReference]);
-
-/**
  * A message queue that takes collected data and will take care of sending it
  * to the chrome process. It allows flushing using synchronous messages and
  * takes care of any race conditions that might occur because of that. Changes
  * will be batched if they're pushed in quick succession to avoid a message
  * flood.
  */
 class MessageQueue extends Handler {
   constructor(store) {
@@ -821,19 +716,16 @@ class ContentSessionStore {
                                   return new ContentRestore(mm);
                                 });
 
     this.handlers = [
       new EventListener(this),
       new FormDataListener(this),
       new SessionHistoryListener(this),
       new SessionStorageListener(this),
-      new ScrollPositionListener(this),
-      new DocShellCapabilitiesListener(this),
-      new PrivacyListener(this),
       this.stateChangeNotifier,
       this.messageQueue,
     ];
 
     MESSAGES.forEach(m => mm.addMessageListener(m, this));
 
     // If we're browsing from the tab crashed UI to a blacklisted URI that keeps
     // this browser non-remote, we'll handle that in a pagehide event.
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -364,16 +364,20 @@ var SessionStore = {
   reviveAllCrashedTabs() {
     return SessionStoreInternal.reviveAllCrashedTabs();
   },
 
   navigateAndRestore(tab, loadArguments, historyIndex) {
     return SessionStoreInternal.navigateAndRestore(tab, loadArguments, historyIndex);
   },
 
+  updateSessionStoreFromTablistener(aTab, aData) {
+    return SessionStoreInternal.updateSessionStoreFromTablistener(aTab, aData);
+  },
+
   getSessionHistory(tab, updatedCallback) {
     return SessionStoreInternal.getSessionHistory(tab, updatedCallback);
   },
 
   undoCloseById(aClosedId, aIncludePrivate) {
     return SessionStoreInternal.undoCloseById(aClosedId, aIncludePrivate);
   },
 
@@ -813,16 +817,30 @@ var SessionStoreInternal = {
         }
         break;
       case "http-on-may-change-process":
         this.onMayChangeProcess(aSubject);
         break;
     }
   },
 
+  updateSessionStoreFromTablistener(aTab, aData) {
+    let browser = aTab.linkedBrowser;
+    let win = browser.ownerGlobal;
+    TabState.update(browser, aData);
+    this.saveStateDelayed(win);
+
+    if (aData.flushID) {
+      // This is an update kicked off by an async flush request. Notify the
+      // TabStateFlusher so that it can finish the request and notify its
+      // consumer that's waiting for the flush to be done.
+      TabStateFlusher.resolve(browser, aData.flushID);
+    }
+  },
+
   /**
    * This method handles incoming messages sent by the session store content
    * script via the Frame Message Manager or Parent Process Message Manager,
    * and thus enables communication with OOP tabs.
    */
   receiveMessage(aMessage) {
     // If we got here, that means we're dealing with a frame message
     // manager message, so the target will be a <xul:browser>.
--- a/browser/components/sessionstore/TabStateFlusher.jsm
+++ b/browser/components/sessionstore/TabStateFlusher.jsm
@@ -69,36 +69,51 @@ var TabStateFlusher = Object.freeze({
 
 var TabStateFlusherInternal = {
   // Stores the last request ID.
   _lastRequestID: 0,
 
   // A map storing all active requests per browser.
   _requests: new WeakMap(),
 
+  // A map storing if there is requests to native listener per browser.
+  _requestsToNativeListener: new WeakMap(),
+
   /**
    * Requests an async flush for the given browser. Returns a promise that will
    * resolve when we heard back from the content process and the parent has
    * all the latest data.
    */
   flush(browser) {
     let id = ++this._lastRequestID;
+    let requestNativeListener = false;
+    if (browser && browser.frameLoader) {
+      /*
+        Request native listener to flush the tabState.
+        True if the flush is involved async ipc call.
+       */
+      requestNativeListener = browser.frameLoader.requestTabStateFlush(id);
+    }
+
     let mm = browser.messageManager;
     mm.sendAsyncMessage("SessionStore:flush", {id});
 
     // Retrieve active requests for given browser.
     let permanentKey = browser.permanentKey;
     let perBrowserRequests = this._requests.get(permanentKey) || new Map();
+    let perBrowserRequestsToNative = this._requestsToNativeListener.get(permanentKey) || new Map();
 
     return new Promise(resolve => {
       // Store resolve() so that we can resolve the promise later.
       perBrowserRequests.set(id, resolve);
+      perBrowserRequestsToNative.set(id, requestNativeListener);
 
       // Update the flush requests stored per browser.
       this._requests.set(permanentKey, perBrowserRequests);
+      this._requestsToNativeListener.set(permanentKey, perBrowserRequestsToNative);
     });
   },
 
   /**
    * Requests an async flush for all non-lazy browsers of a given window.
    * Returns a Promise that will resolve when we've heard back from all browsers.
    */
   flushWindow(window) {
@@ -125,16 +140,28 @@ var TabStateFlusherInternal = {
    *        event that a flush failed.
    */
   resolve(browser, flushID, success = true, message = "") {
     // Nothing to do if there are no pending flushes for the given browser.
     if (!this._requests.has(browser.permanentKey)) {
       return;
     }
 
+    // Check if there is request to native listener for given browser.
+    let perBrowserRequestsForNativeListener = this._requestsToNativeListener.get(browser.permanentKey);
+    if (!perBrowserRequestsForNativeListener.has(flushID)) {
+      return;
+    }
+    let waitForNextResolve = perBrowserRequestsForNativeListener.get(flushID);
+    if (waitForNextResolve) {
+      perBrowserRequestsForNativeListener.set(flushID, false);
+      this._requestsToNativeListener.set(browser.permanentKey, perBrowserRequestsForNativeListener);
+      return;
+    }
+
     // Retrieve active requests for given browser.
     let perBrowserRequests = this._requests.get(browser.permanentKey);
     if (!perBrowserRequests.has(flushID)) {
       return;
     }
 
     if (!success) {
       Cu.reportError("Failed to flush browser: " + message);
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -79,16 +79,17 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/PresShellInlines.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ChromeMessageSender.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FrameLoaderBinding.h"
 #include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
+#include "mozilla/dom/SessionStoreListener.h"
 #include "mozilla/gfx/CrossProcessPaint.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layout/RenderFrame.h"
 #include "mozilla/ServoCSSParser.h"
 #include "mozilla/ServoStyleSet.h"
 #include "nsGenericHTMLFrameElement.h"
 #include "GeckoProfiler.h"
 
@@ -1922,16 +1923,21 @@ void nsFrameLoader::DestroyDocShell() {
     mBrowserBridgeChild = nullptr;
   }
 
   // Fire the "unload" event if we're in-process.
   if (mChildMessageManager) {
     mChildMessageManager->FireUnloadEvent();
   }
 
+  if (mSessionStoreListener) {
+    mSessionStoreListener->RemoveListeners();
+    mSessionStoreListener = nullptr;
+  }
+
   // Destroy the docshell.
   if (GetDocShell()) {
     GetDocShell()->Destroy();
   }
 
   mBrowsingContext = nullptr;
 
   if (mChildMessageManager) {
@@ -3003,16 +3009,23 @@ nsresult nsFrameLoader::EnsureMessageMan
     MOZ_ASSERT(GetDocShell(),
                "MaybeCreateDocShell succeeded, but null docShell");
     if (!GetDocShell()) {
       return NS_ERROR_FAILURE;
     }
     mChildMessageManager = InProcessBrowserChildMessageManager::Create(
         GetDocShell(), mOwnerContent, mMessageManager);
     NS_ENSURE_TRUE(mChildMessageManager, NS_ERROR_UNEXPECTED);
+
+    // Set up a TabListener for sessionStore
+    if (XRE_IsParentProcess()) {
+      mSessionStoreListener = new TabListener(GetDocShell(), mOwnerContent);
+      rv = mSessionStoreListener->Init();
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
   }
   return NS_OK;
 }
 
 nsresult nsFrameLoader::ReallyLoadFrameScripts() {
   nsresult rv = EnsureMessageManager();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -3149,16 +3162,32 @@ void nsFrameLoader::RequestUpdatePositio
     nsresult rv = browserParent->UpdatePosition();
 
     if (NS_FAILED(rv)) {
       aRv.Throw(rv);
     }
   }
 }
 
+bool nsFrameLoader::RequestTabStateFlush(uint32_t aFlushId) {
+  if (mSessionStoreListener) {
+    mSessionStoreListener->ForceFlushFromParent(aFlushId);
+    // No async ipc call is involved in parent only case
+    return false;
+  }
+
+  // If remote browsing (e10s), handle this with the BrowserParent.
+  if (mBrowserParent) {
+    Unused << mBrowserParent->SendFlushTabState(aFlushId);
+    return true;
+  }
+
+  return false;
+}
+
 void nsFrameLoader::Print(uint64_t aOuterWindowID,
                           nsIPrintSettings* aPrintSettings,
                           nsIWebProgressListener* aProgressListener,
                           ErrorResult& aRv) {
 #if defined(NS_PRINTING)
   if (mBrowserParent) {
     RefPtr<embedding::PrintingParent> printingParent =
         mBrowserParent->Manager()->GetPrintingParent();
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -47,16 +47,17 @@ class nsIWebProgressListener;
 
 namespace mozilla {
 
 class OriginAttributes;
 
 namespace dom {
 class ChromeMessageSender;
 class ContentParent;
+class TabListener;
 class InProcessBrowserChildMessageManager;
 class MessageSender;
 class PBrowserParent;
 class ProcessMessageManager;
 class Promise;
 class BrowserParent;
 class MutableTabContext;
 class BrowserBridgeChild;
@@ -186,16 +187,18 @@ class nsFrameLoader final : public nsStu
 
   void ActivateFrameEvent(const nsAString& aType, bool aCapture,
                           mozilla::ErrorResult& aRv);
 
   void RequestNotifyAfterRemotePaint();
 
   void RequestUpdatePosition(mozilla::ErrorResult& aRv);
 
+  bool RequestTabStateFlush(uint32_t aFlushId);
+
   void Print(uint64_t aOuterWindowID, nsIPrintSettings* aPrintSettings,
              nsIWebProgressListener* aProgressListener,
              mozilla::ErrorResult& aRv);
 
   already_AddRefed<mozilla::dom::Promise> DrawSnapshot(
       double aX, double aY, double aW, double aH, double aScale,
       const nsAString& aBackgroundColor, mozilla::ErrorResult& aRv);
 
@@ -487,16 +490,18 @@ class nsFrameLoader final : public nsStu
   // This is used when this refers to a remote sub frame
   RefPtr<mozilla::dom::BrowserBridgeChild> mBrowserBridgeChild;
 
   // Holds the last known size of the frame.
   mozilla::ScreenIntSize mLazySize;
 
   RefPtr<mozilla::dom::ParentSHistory> mParentSHistory;
 
+  RefPtr<mozilla::dom::TabListener> mSessionStoreListener;
+
   bool mDepthTooGreat : 1;
   bool mIsTopLevelContent : 1;
   bool mDestroyCalled : 1;
   bool mNeedsAsyncDestroy : 1;
   bool mInSwap : 1;
   bool mInShow : 1;
   bool mHideCalled : 1;
   // True when the object is created for an element which the parser has
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -25,16 +25,17 @@
 #include "mozilla/dom/LoadURIOptionsBinding.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/PaymentRequestChild.h"
 #include "mozilla/dom/PBrowser.h"
 #include "mozilla/dom/WindowProxyHolder.h"
 #include "mozilla/dom/BrowserBridgeChild.h"
+#include "mozilla/dom/SessionStoreListener.h"
 #include "mozilla/gfx/CrossProcessPaint.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "mozilla/layers/APZCTreeManagerChild.h"
 #include "mozilla/layers/APZEventState.h"
@@ -611,16 +612,20 @@ nsresult BrowserChild::Init(mozIDOMWindo
 
   mIPCOpen = true;
 
   // Recording/replaying processes use their own compositor.
   if (recordreplay::IsRecordingOrReplaying()) {
     mPuppetWidget->CreateCompositor();
   }
 
+  mSessionStoreListener = new TabListener(docShell, nullptr);
+  rv = mSessionStoreListener->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 void BrowserChild::NotifyTabContextUpdated(bool aIsPreallocated) {
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
   MOZ_ASSERT(docShell);
 
   if (!docShell) {
@@ -964,16 +969,21 @@ void BrowserChild::DestroyWindow() {
     mStatusFilter = nullptr;
   }
 
   if (mCoalescedMouseEventFlusher) {
     mCoalescedMouseEventFlusher->RemoveObserver();
     mCoalescedMouseEventFlusher = nullptr;
   }
 
+  if (mSessionStoreListener) {
+    mSessionStoreListener->RemoveListeners();
+    mSessionStoreListener = nullptr;
+  }
+
   // In case we don't have chance to process all entries, clean all data in
   // the queue.
   while (mToBeDispatchedMouseData.GetSize() > 0) {
     UniquePtr<CoalescedMouseData> data(
         static_cast<CoalescedMouseData*>(mToBeDispatchedMouseData.PopFront()));
     data.reset();
   }
 
@@ -1907,16 +1917,22 @@ void BrowserChild::RequestEditCommands(n
 
 mozilla::ipc::IPCResult BrowserChild::RecvNativeSynthesisResponse(
     const uint64_t& aObserverId, const nsCString& aResponse) {
   mozilla::widget::AutoObserverNotifier::NotifySavedObserver(aObserverId,
                                                              aResponse.get());
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult BrowserChild::RecvFlushTabState(
+    const uint32_t& aFlushId) {
+  UpdateSessionStore(aFlushId);
+  return IPC_OK();
+}
+
 // In case handling repeated keys takes much time, we skip firing new ones.
 bool BrowserChild::SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent) {
   if (mRepeatedKeyEventTime.IsNull() || !aEvent.CanSkipInRemoteProcess() ||
       (aEvent.mMessage != eKeyDown && aEvent.mMessage != eKeyPress)) {
     mRepeatedKeyEventTime = TimeStamp();
     mSkipKeyPress = false;
     return false;
   }
@@ -3588,16 +3604,43 @@ nsresult BrowserChild::PrepareProgressLi
       rv = classifiedChannel->GetMatchedList(matchedList);
       NS_ENSURE_SUCCESS(rv, rv);
       aRequestData.matchedList() = std::move(matchedList);
     }
   }
   return NS_OK;
 }
 
+bool BrowserChild::UpdateSessionStore(uint32_t aFlushId) {
+  if (!mSessionStoreListener) {
+    return false;
+  }
+  RefPtr<ContentSessionStore> store = mSessionStoreListener->GetSessionStore();
+
+  Maybe<nsCString> docShellCaps;
+  if (store->IsDocCapChanged()) {
+    docShellCaps.emplace(store->GetDocShellCaps());
+  }
+
+  Maybe<bool> privatedMode;
+  if (store->IsPrivateChanged()) {
+    privatedMode.emplace(store->GetPrivateModeEnabled());
+  }
+
+  nsTArray<int32_t> positionDescendants;
+  nsTArray<nsCString> positions;
+  if (store->IsScrollPositionChanged()) {
+    store->GetScrollPositions(positions, positionDescendants);
+  }
+
+  Unused << SendSessionStoreUpdate(docShellCaps, privatedMode, positions,
+                                   positionDescendants, aFlushId);
+  return true;
+}
+
 BrowserChildMessageManager::BrowserChildMessageManager(
     BrowserChild* aBrowserChild)
     : ContentFrameMessageManager(new nsFrameMessageManager(aBrowserChild)),
       mBrowserChild(aBrowserChild) {}
 
 BrowserChildMessageManager::~BrowserChildMessageManager() {}
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BrowserChildMessageManager)
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -77,16 +77,18 @@ struct AutoCacheNativeKeyCommands;
 
 namespace dom {
 
 class BrowserChild;
 class TabGroup;
 class ClonedMessageData;
 class CoalescedMouseData;
 class CoalescedWheelData;
+class ContentSessionStore;
+class TabListener;
 class RequestData;
 class WebProgressData;
 
 class BrowserChildMessageManager : public ContentFrameMessageManager,
                                    public nsIMessageSender,
                                    public DispatcherTrait,
                                    public nsSupportsWeakReference {
  public:
@@ -391,16 +393,19 @@ class BrowserChild final : public Browse
   virtual mozilla::ipc::IPCResult RecvNormalPriorityRealTouchMoveEvent(
       const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid,
       const uint64_t& aInputBlockId,
       const nsEventStatus& aApzResponse) override;
 
   virtual mozilla::ipc::IPCResult RecvNativeSynthesisResponse(
       const uint64_t& aObserverId, const nsCString& aResponse) override;
 
+  virtual mozilla::ipc::IPCResult RecvFlushTabState(
+      const uint32_t& aFlushId) override;
+
   virtual mozilla::ipc::IPCResult RecvPluginEvent(
       const WidgetPluginEvent& aEvent) override;
 
   virtual mozilla::ipc::IPCResult RecvCompositionEvent(
       const mozilla::WidgetCompositionEvent& aEvent) override;
 
   virtual mozilla::ipc::IPCResult RecvNormalPriorityCompositionEvent(
       const mozilla::WidgetCompositionEvent& aEvent) override;
@@ -681,16 +686,18 @@ class BrowserChild final : public Browse
   // open or is warming tabs up. There can also be zero BrowserChilds in this
   // state. Note that this function should only be called if HasVisibleTabs()
   // returns true.
   static const nsTHashtable<nsPtrHashKey<BrowserChild>>& GetVisibleTabs() {
     MOZ_ASSERT(HasVisibleTabs());
     return *sVisibleTabs;
   }
 
+  bool UpdateSessionStore(uint32_t aFlushId);
+
  protected:
   virtual ~BrowserChild();
 
   virtual PWindowGlobalChild* AllocPWindowGlobalChild(
       const WindowGlobalInit& aInit) override;
 
   virtual bool DeallocPWindowGlobalChild(PWindowGlobalChild* aActor) override;
 
@@ -895,16 +902,17 @@ class BrowserChild final : public Browse
   nsClassHashtable<nsUint32HashKey, CoalescedMouseData> mCoalescedMouseData;
 
   nsDeque mToBeDispatchedMouseData;
 
   CoalescedWheelData mCoalescedWheelData;
   RefPtr<CoalescedMouseMoveFlusher> mCoalescedMouseEventFlusher;
 
   RefPtr<layers::IAPZCTreeManager> mApzcTreeManager;
+  RefPtr<TabListener> mSessionStoreListener;
 
   // The most recently seen layer observer epoch in RecvSetDocShellIsActive.
   layers::LayersObserverEpoch mLayersObserverEpoch;
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   // The handle associated with the native window that contains this tab
   uintptr_t mNativeWindowHandle;
 #endif  // defined(XP_WIN)
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -2386,16 +2386,41 @@ void BrowserParent::ReconstructWebProgre
         MakeAndAddRef<RemoteWebProgress>(aManager, 0, 0, 0, false, false);
   }
 
   aOutRequest = MakeAndAddRef<RemoteWebProgressRequest>(
       aRequestData.requestURI(), aRequestData.originalRequestURI(),
       aRequestData.matchedList());
 }
 
+mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
+    const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
+    const nsTArray<nsCString>& aPositions,
+    const nsTArray<int32_t>& aPositionDescendants, const uint32_t& aFlushId) {
+  nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow = GetXULBrowserWindow();
+  if (!xulBrowserWindow) {
+    return IPC_OK();
+  }
+
+  if (aDocShellCaps.isSome()) {
+    xulBrowserWindow->UpdateDocShellCaps(aDocShellCaps.value());
+  }
+
+  if (aPrivatedMode.isSome()) {
+    xulBrowserWindow->UpdateIsPrivate(aPrivatedMode.value());
+  }
+
+  if (aPositions.Length() != 0) {
+    xulBrowserWindow->UpdateScrollPositions(aPositions, aPositionDescendants);
+  }
+
+  xulBrowserWindow->UpdateSessionStore(mFrameElement, aFlushId);
+  return IPC_OK();
+}
+
 bool BrowserParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent) {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return true;
   }
   if (NS_WARN_IF(!mContentCache.HandleQueryContentEvent(aEvent, widget)) ||
       NS_WARN_IF(!aEvent.mSucceeded)) {
     return true;
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -190,16 +190,21 @@ class BrowserParent final : public PBrow
       const RequestData& aRequestData, const uint32_t& aEvent);
 
   void ReconstructWebProgressAndRequest(
       nsIWebProgress* aManager, const Maybe<WebProgressData>& aWebProgressData,
       const RequestData& aRequestData,
       nsCOMPtr<nsIWebProgress>& aOutWebProgress,
       nsCOMPtr<nsIRequest>& aOutRequest);
 
+  mozilla::ipc::IPCResult RecvSessionStoreUpdate(
+      const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
+      const nsTArray<nsCString>& aPositions,
+      const nsTArray<int32_t>& aPositionDescendants, const uint32_t& aFlushId);
+
   mozilla::ipc::IPCResult RecvBrowserFrameOpenWindow(
       PBrowserParent* aOpener, const nsString& aURL, const nsString& aName,
       bool aForceNoReferrer, const nsString& aFeatures,
       BrowserFrameOpenWindowResolver&& aResolve);
 
   mozilla::ipc::IPCResult RecvSyncMessage(
       const nsString& aMessage, const ClonedMessageData& aData,
       InfallibleTArray<CpowEntry>&& aCpows, nsIPrincipal* aPrincipal,
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -551,18 +551,25 @@ parent:
 
     async OnStatusChange(WebProgressData? aWebProgressData,
                          RequestData aRequestData, nsresult aStatus,
                          nsString aMessage);
 
     async OnContentBlockingEvent(WebProgressData? aWebProgressData,
                                  RequestData aRequestData, uint32_t aEvent);
 
+    async SessionStoreUpdate(nsCString? aDocShellCaps,
+                             bool? aPrivatedMode,
+                             nsCString[] aPositions,
+                             int32_t[] aPositionDescendants,
+                             uint32_t aFlushId);
+
 child:
     async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
+    async FlushTabState(uint32_t aFlushId);
 
 parent:
 
     /**
      * Child informs the parent that the graphics objects are ready for
      * compositing.  This is sent when all pending changes have been
      * sent to the compositor and are ready to be shown on the next composite.
      * @see PCompositor
--- a/dom/webidl/FrameLoader.webidl
+++ b/dom/webidl/FrameLoader.webidl
@@ -97,16 +97,22 @@ interface FrameLoader {
 
   /**
    * Force a remote browser to recompute its dimension and screen position.
    */
   [Throws]
   void requestUpdatePosition();
 
   /**
+   * Force a TabStateFlush from native sessionStoreListeners.
+   * Return true if the flush requires async ipc call.
+   */
+  boolean requestTabStateFlush(unsigned long aFlushId);
+
+  /**
    * Print the current document.
    *
    * @param aOuterWindowID the ID of the outer window to print
    * @param aPrintSettings optional print settings to use; printSilent can be
    *                       set to prevent prompting.
    * @param aProgressListener optional print progress listener.
    */
   [Throws]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreListener.cpp
@@ -0,0 +1,520 @@
+/* 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/PresShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/SessionStoreListener.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "nsIBrowser.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsITimer.h"
+#include "nsIXULBrowserWindow.h"
+#include "nsIXULWindow.h"
+#include "nsIWebProgress.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// This pref controls whether or not we send updates to the parent on a timeout
+// or not, and should only be used for tests or debugging.
+static const char kTimeOutDisable[] =
+    "browser.sessionstore.debug.no_auto_updates";
+// Timeout for waiting an idle period to send data.
+static const char kPrefInterval[] = "browser.sessionstore.interval";
+
+NS_IMPL_CYCLE_COLLECTION(ContentSessionStore, mDocShell)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ContentSessionStore, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ContentSessionStore, Release)
+
+ContentSessionStore::ContentSessionStore(nsIDocShell* aDocShell)
+    : mDocShell(aDocShell),
+      mPrivateChanged(false),
+      mIsPrivate(false),
+      mScrollChanged(NO_CHANGE),
+      mDocCapChanged(false) {
+  MOZ_ASSERT(mDocShell);
+  // Check that value at startup as it might have
+  // been set before the frame script was loaded.
+  if (NS_SUCCEEDED(nsDocShell::Cast(mDocShell)->GetUsePrivateBrowsing(
+          &mPrivateChanged)) &&
+      mPrivateChanged) {
+    mIsPrivate = true;
+  }
+}
+
+nsCString ContentSessionStore::CollectDocShellCapabilities() {
+  bool allow;
+  nsCString aRetVal;
+
+#define TRY_ALLOWPROP(y)                        \
+  PR_BEGIN_MACRO                                \
+  nsresult rv = mDocShell->GetAllow##y(&allow); \
+  if (NS_SUCCEEDED(rv) && !allow) {             \
+    if (!aRetVal.IsEmpty()) {                   \
+      aRetVal.Append(',');                      \
+    }                                           \
+    aRetVal.Append(#y);                         \
+  }                                             \
+  PR_END_MACRO
+
+  TRY_ALLOWPROP(Plugins);
+  // Bug 1328013 : Don't collect "AllowJavascript" property
+  // TRY_ALLOWPROP(Javascript);
+  TRY_ALLOWPROP(MetaRedirects);
+  TRY_ALLOWPROP(Subframes);
+  TRY_ALLOWPROP(Images);
+  TRY_ALLOWPROP(Media);
+  TRY_ALLOWPROP(DNSPrefetch);
+  TRY_ALLOWPROP(WindowControl);
+  TRY_ALLOWPROP(Auth);
+  TRY_ALLOWPROP(ContentRetargeting);
+  TRY_ALLOWPROP(ContentRetargetingOnChildren);
+#undef TRY_ALLOWPROP
+  return aRetVal;
+}
+
+void ContentSessionStore::OnPrivateModeChanged(bool aEnabled) {
+  mPrivateChanged = true;
+  mIsPrivate = aEnabled;
+}
+
+nsCString ContentSessionStore::GetDocShellCaps() {
+  mDocCapChanged = false;
+  return mDocCaps;
+}
+
+bool ContentSessionStore::GetPrivateModeEnabled() {
+  mPrivateChanged = false;
+  return mIsPrivate;
+}
+
+void ContentSessionStore::OnDocumentStart() {
+  mScrollChanged = PAGELOADEDSTART;
+  nsCString caps = CollectDocShellCapabilities();
+  if (!mDocCaps.Equals(caps)) {
+    mDocCaps = caps;
+    mDocCapChanged = true;
+  }
+}
+
+void ContentSessionStore::OnDocumentEnd() { mScrollChanged = WITH_CHANGE; }
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TabListener)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIPrivacyTransitionObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(TabListener, mDocShell, mSessionStore, mOwnerContent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TabListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TabListener)
+
+TabListener::TabListener(nsIDocShell* aDocShell, Element* aElement)
+    : mDocShell(aDocShell),
+      mSessionStore(new ContentSessionStore(aDocShell)),
+      mOwnerContent(aElement),
+      mProgressListenerRegistered(false),
+      mEventListenerRegistered(false),
+      mPrefObserverRegistered(false),
+      mUpdatedTimer(nullptr),
+      mTimeoutDisabled(false),
+      mUpdateInterval(15000) {
+  MOZ_ASSERT(mDocShell);
+}
+
+nsresult TabListener::Init() {
+  TabListener::UpdateSessionStore();
+  nsresult rv = mDocShell->AddWeakPrivacyTransitionObserver(this);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(mDocShell);
+  rv = webProgress->AddProgressListener(this,
+                                        nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+  NS_ENSURE_SUCCESS(rv, rv);
+  mProgressListenerRegistered = true;
+
+  nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  NS_WARNING_ASSERTION(prefBranch, "no prefservice");
+  if (prefBranch) {
+    prefBranch->AddObserver(kTimeOutDisable, this, true);
+    prefBranch->AddObserver(kPrefInterval, this, true);
+    mPrefObserverRegistered = true;
+  }
+
+  nsCOMPtr<EventTarget> eventTarget = nullptr;
+  if (mOwnerContent) {
+    eventTarget = mOwnerContent;
+  } else {
+    nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(mDocShell);
+    if (window) {
+      eventTarget = window->GetChromeEventHandler();
+    }
+  }
+
+  if (!eventTarget) {
+    return NS_OK;
+  }
+  eventTarget->AddSystemEventListener(NS_LITERAL_STRING("mozvisualscroll"),
+                                      this, false);
+  mEventListenerRegistered = true;
+  return NS_OK;
+}
+
+/* static */
+void TabListener::TimerCallback(nsITimer* aTimer, void* aClosure) {
+  auto listener = static_cast<TabListener*>(aClosure);
+  listener->UpdateSessionStore();
+  listener->StopTimerForUpdate();
+}
+
+void TabListener::StopTimerForUpdate() {
+  if (mUpdatedTimer) {
+    mUpdatedTimer->Cancel();
+    mUpdatedTimer = nullptr;
+  }
+}
+
+void TabListener::AddTimerForUpdate() {
+  if (mUpdatedTimer) {
+    return;
+  }
+
+  if (mTimeoutDisabled) {
+    UpdateSessionStore();
+    return;
+  }
+
+  NS_NewTimerWithFuncCallback(getter_AddRefs(mUpdatedTimer), TimerCallback,
+                              this, mUpdateInterval, nsITimer::TYPE_ONE_SHOT,
+                              "TabListener::TimerCallback");
+}
+
+NS_IMETHODIMP TabListener::PrivateModeChanged(bool aEnabled) {
+  mSessionStore->OnPrivateModeChanged(aEnabled);
+  AddTimerForUpdate();
+  return NS_OK;
+}
+
+NS_IMETHODIMP TabListener::OnStateChange(nsIWebProgress* aWebProgress,
+                                         nsIRequest* aRequest,
+                                         uint32_t aStateFlags,
+                                         nsresult aStatus) {
+  if (!mSessionStore) {
+    return NS_OK;
+  }
+
+  // Ignore state changes for subframes because we're only interested in the
+  // top-document starting or stopping its load.
+  bool isTopLevel = false;
+  nsresult rv = aWebProgress->GetIsTopLevel(&isTopLevel);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!isTopLevel) {
+    return NS_OK;
+  }
+
+  // The DOM Window ID getter here may throw if the inner or outer windows
+  // aren't created yet or are destroyed at the time we're making this call
+  // but that isn't fatal so ignore the exceptions here.
+  uint64_t DOMWindowID = 0;
+  Unused << aWebProgress->GetDOMWindowID(&DOMWindowID);
+  nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(mDocShell);
+  NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+  if (DOMWindowID != window->WindowID()) {
+    return NS_OK;
+  }
+
+  // onStateChange will be fired when loading the initial about:blank URI for
+  // a browser, which we don't actually care about. This is particularly for
+  // the case of unrestored background tabs, where the content has not yet
+  // been restored: we don't want to accidentally send any updates to the
+  // parent when the about:blank placeholder page has loaded.
+  if (!mDocShell->GetHasLoadedNonBlankURI()) {
+    return NS_OK;
+  }
+
+  if (aStateFlags & (nsIWebProgressListener::STATE_START)) {
+    mSessionStore->OnDocumentStart();
+  } else if (aStateFlags & (nsIWebProgressListener::STATE_STOP)) {
+    mSessionStore->OnDocumentEnd();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TabListener::HandleEvent(Event* aEvent) {
+  EventTarget* target = aEvent->GetTarget();
+  if (!target) {
+    return NS_OK;
+  }
+
+  nsPIDOMWindowOuter* outer = target->GetOwnerGlobalForBindingsInternal();
+  if (!outer) {
+    return NS_OK;
+  }
+
+  nsIDocShell* docShell = outer->GetDocShell();
+  if (!docShell) {
+    return NS_OK;
+  }
+
+  bool isDynamic = false;
+  nsresult rv = docShell->GetCreatedDynamically(&isDynamic);
+  if (NS_FAILED(rv)) {
+    return NS_OK;
+  }
+
+  if (isDynamic) {
+    return NS_OK;
+  }
+
+  nsAutoString eventType;
+  aEvent->GetType(eventType);
+  if (eventType.EqualsLiteral("mozvisualscroll")) {
+    mSessionStore->SetScrollPositionChanged();
+    AddTimerForUpdate();
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP TabListener::OnProgressChange(nsIWebProgress* aWebProgress,
+                                            nsIRequest* aRequest,
+                                            int32_t aCurSelfProgress,
+                                            int32_t aMaxSelfProgress,
+                                            int32_t aCurTotalProgress,
+                                            int32_t aMaxTotalProgress) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP TabListener::OnLocationChange(nsIWebProgress* aWebProgress,
+                                            nsIRequest* aRequest,
+                                            nsIURI* aLocation,
+                                            uint32_t aFlags) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP TabListener::OnStatusChange(nsIWebProgress* aWebProgress,
+                                          nsIRequest* aRequest,
+                                          nsresult aStatus,
+                                          const char16_t* aMessage) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP TabListener::OnSecurityChange(nsIWebProgress* aWebProgress,
+                                            nsIRequest* aRequest,
+                                            uint32_t aState) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP TabListener::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+                                                  nsIRequest* aRequest,
+                                                  uint32_t aEvent) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult TabListener::Observe(nsISupports* aSubject, const char* aTopic,
+                              const char16_t* aData) {
+  MOZ_ASSERT(!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID),
+             "unexpected topic!");
+
+  nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+
+  bool timeoutDisabled;
+  if (NS_SUCCEEDED(
+          prefBranch->GetBoolPref(kTimeOutDisable, &timeoutDisabled))) {
+    if (mTimeoutDisabled != timeoutDisabled) {
+      mTimeoutDisabled = timeoutDisabled;
+      if (mUpdatedTimer) {
+        StopTimerForUpdate();
+        AddTimerForUpdate();
+      }
+    }
+  }
+
+  int32_t interval = 0;
+  if (NS_SUCCEEDED(prefBranch->GetIntPref(kPrefInterval, &interval))) {
+    if (mUpdateInterval != interval) {
+      mUpdateInterval = interval;
+      if (mUpdatedTimer) {
+        StopTimerForUpdate();
+        AddTimerForUpdate();
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
+nsCString CollectPosition(Document& aDocument) {
+  PresShell* presShell = aDocument.GetPresShell();
+  if (!presShell) {
+    return EmptyCString();
+  }
+  nsPoint scrollPos = presShell->GetVisualViewportOffset();
+  int scrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x);
+  int scrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y);
+  if ((scrollX != 0) || (scrollY != 0)) {
+    return nsPrintfCString("%d,%d", scrollX, scrollY);
+  }
+
+  return EmptyCString();
+}
+
+int CollectPositions(BrowsingContext* aBrowsingContext,
+                     nsTArray<nsCString>& aPositions,
+                     nsTArray<int32_t>& aPositionDescendants) {
+  nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
+  if (!window) {
+    return 0;
+  }
+
+  nsIDocShell* docShell = window->GetDocShell();
+  if (!docShell || docShell->GetCreatedDynamically()) {
+    return 0;
+  }
+
+  Document* document = window->GetDoc();
+  if (!document) {
+    return 0;
+  }
+
+  /* Collect data from current frame */
+  aPositions.AppendElement(CollectPosition(*document));
+  aPositionDescendants.AppendElement(0);
+  int currentIdx = aPositions.Length() - 1;
+
+  /* Collect data from all child frame */
+  nsTArray<RefPtr<BrowsingContext>> children;
+  aBrowsingContext->GetChildren(children);
+  for (uint32_t i = 0; i < children.Length(); i++) {
+    aPositionDescendants[currentIdx] +=
+        CollectPositions(children[i], aPositions, aPositionDescendants);
+  }
+
+  return aPositionDescendants[currentIdx] + 1;
+}
+
+void ContentSessionStore::GetScrollPositions(
+    nsTArray<nsCString>& aPositions, nsTArray<int32_t>& aPositionDescendants) {
+  if (mScrollChanged == PAGELOADEDSTART) {
+    aPositionDescendants.AppendElement(0);
+    aPositions.AppendElement(EmptyCString());
+  } else {
+    CollectPositions(nsDocShell::Cast(mDocShell)->GetBrowsingContext(),
+                     aPositions, aPositionDescendants);
+  }
+  mScrollChanged = NO_CHANGE;
+}
+
+bool TabListener::ForceFlushFromParent(uint32_t aFlushId) {
+  if (!XRE_IsParentProcess()) {
+    return false;
+  }
+  if (!mSessionStore) {
+    return false;
+  }
+  return UpdateSessionStore(aFlushId);
+}
+
+bool TabListener::UpdateSessionStore(uint32_t aFlushId) {
+  if (!aFlushId) {
+    if (!mSessionStore || !mSessionStore->UpdateNeeded()) {
+      return false;
+    }
+  }
+
+  if (!XRE_IsParentProcess()) {
+    BrowserChild* browserChild = BrowserChild::GetFrom(mDocShell);
+    if (browserChild) {
+      StopTimerForUpdate();
+      return browserChild->UpdateSessionStore(aFlushId);
+    }
+    return false;
+  }
+
+  if (!mOwnerContent) {
+    return false;
+  }
+
+  uint32_t chromeFlags = 0;
+  nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+  mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
+  if (!treeOwner) {
+    return false;
+  }
+  nsCOMPtr<nsIXULWindow> window(do_GetInterface(treeOwner));
+  if (!window) {
+    return false;
+  }
+  if (window && NS_FAILED(window->GetChromeFlags(&chromeFlags))) {
+    return false;
+  }
+
+  nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow;
+  window->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
+  if (!xulBrowserWindow) {
+    return false;
+  }
+
+  if (mSessionStore->IsDocCapChanged()) {
+    xulBrowserWindow->UpdateDocShellCaps(mSessionStore->GetDocShellCaps());
+  }
+  if (mSessionStore->IsPrivateChanged()) {
+    xulBrowserWindow->UpdateIsPrivate(mSessionStore->GetPrivateModeEnabled());
+  }
+  if (mSessionStore->IsScrollPositionChanged()) {
+    nsTArray<nsCString> positions;
+    nsTArray<int> descendants;
+    mSessionStore->GetScrollPositions(positions, descendants);
+    xulBrowserWindow->UpdateScrollPositions(positions, descendants);
+  }
+  xulBrowserWindow->UpdateSessionStore(mOwnerContent, aFlushId);
+  StopTimerForUpdate();
+  return true;
+}
+
+void TabListener::RemoveListeners() {
+  if (mProgressListenerRegistered) {
+    nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(mDocShell);
+    if (webProgress) {
+      webProgress->RemoveProgressListener(this);
+      mProgressListenerRegistered = false;
+    }
+  }
+
+  if (mEventListenerRegistered) {
+    nsCOMPtr<EventTarget> eventTarget = nullptr;
+    if (mOwnerContent) {
+      eventTarget = mOwnerContent;
+    } else {
+      nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(mDocShell);
+      if (window) {
+        eventTarget = window->GetChromeEventHandler();
+      }
+    }
+
+    if (eventTarget) {
+      eventTarget->RemoveSystemEventListener(
+          NS_LITERAL_STRING("mozvisualscroll"), this, false);
+      mEventListenerRegistered = false;
+    }
+  }
+
+  if (mPrefObserverRegistered) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->RemoveObserver(this, kTimeOutDisable);
+      obs->RemoveObserver(this, kPrefInterval);
+      mPrefObserverRegistered = false;
+    }
+  }
+}
+
+TabListener::~TabListener() { RemoveListeners();}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreListener.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStoreListener_h
+#define mozilla_dom_SessionStoreListener_h
+
+#include "nsIDOMEventListener.h"
+#include "nsIPrivacyTransitionObserver.h"
+#include "nsIWebProgressListener.h"
+
+class nsITimer;
+
+namespace mozilla {
+namespace dom {
+
+class ContentSessionStore {
+ public:
+  explicit ContentSessionStore(nsIDocShell* aDocShell);
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ContentSessionStore)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ContentSessionStore)
+
+  void OnPrivateModeChanged(bool aEnabled);
+  bool IsDocCapChanged() { return mDocCapChanged; }
+  nsCString GetDocShellCaps();
+  bool IsPrivateChanged() { return mPrivateChanged; }
+  bool GetPrivateModeEnabled();
+  void SetScrollPositionChanged() { mScrollChanged = WITH_CHANGE; }
+  bool IsScrollPositionChanged() { return mScrollChanged != NO_CHANGE; }
+  void GetScrollPositions(nsTArray<nsCString>& aPositions,
+                          nsTArray<int32_t>& aPositionDescendants);
+  void OnDocumentStart();
+  void OnDocumentEnd();
+  bool UpdateNeeded() {
+    return mPrivateChanged || mDocCapChanged || IsScrollPositionChanged();
+  }
+
+ private:
+  virtual ~ContentSessionStore() = default;
+  nsCString CollectDocShellCapabilities();
+
+  nsCOMPtr<nsIDocShell> mDocShell;
+  bool mPrivateChanged;
+  bool mIsPrivate;
+  enum {
+    NO_CHANGE,
+    PAGELOADEDSTART,  // set when the state of document is STATE_START
+    WITH_CHANGE,      // set when the change event is observed
+  } mScrollChanged;
+  bool mDocCapChanged;
+  nsCString mDocCaps;
+};
+
+class TabListener : public nsIDOMEventListener,
+                    public nsIObserver,
+                    public nsIPrivacyTransitionObserver,
+                    public nsIWebProgressListener,
+                    public nsSupportsWeakReference {
+ public:
+  explicit TabListener(nsIDocShell* aDocShell, Element* aElement);
+  nsresult Init();
+  ContentSessionStore* GetSessionStore() { return mSessionStore; }
+  // the function is called only when TabListener is in parent process
+  bool ForceFlushFromParent(uint32_t aFlushId);
+  void RemoveListeners();
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TabListener, nsIDOMEventListener)
+
+  NS_DECL_NSIDOMEVENTLISTENER
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSIPRIVACYTRANSITIONOBSERVER
+  NS_DECL_NSIWEBPROGRESSLISTENER
+
+ private:
+  static void TimerCallback(nsITimer* aTimer, void* aClosure);
+  void AddTimerForUpdate();
+  void StopTimerForUpdate();
+  bool UpdateSessionStore(uint32_t aFlushId = 0);
+  virtual ~TabListener();
+
+  nsCOMPtr<nsIDocShell> mDocShell;
+  RefPtr<ContentSessionStore> mSessionStore;
+  RefPtr<mozilla::dom::Element> mOwnerContent;
+  bool mProgressListenerRegistered;
+  bool mEventListenerRegistered;
+  bool mPrefObserverRegistered;
+  // Timer used to update data
+  nsCOMPtr<nsITimer> mUpdatedTimer;
+  bool mTimeoutDisabled;
+  int32_t mUpdateInterval;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_SessionStoreListener_h
--- a/toolkit/components/sessionstore/moz.build
+++ b/toolkit/components/sessionstore/moz.build
@@ -1,18 +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/.
 
 EXPORTS.mozilla.dom += [
+    'SessionStoreListener.h',
     'SessionStoreUtils.h',
 ]
 
 UNIFIED_SOURCES += [
+    'SessionStoreListener.cpp',
     'SessionStoreUtils.cpp',
 ]
 
+include('/ipc/chromium/chromium-config.mozbuild')
+
 FINAL_LIBRARY = 'xul'
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Session Restore')
--- a/xpfe/appshell/nsIXULBrowserWindow.idl
+++ b/xpfe/appshell/nsIXULBrowserWindow.idl
@@ -87,10 +87,15 @@ interface nsIXULBrowserWindow : nsISuppo
   /**
    * Navigate the browser to the given history index after restoring the full history
    * from SessionStore. If the browser is currently in GroupedSHistory mode, it will
    * be reverted to a non-grouped history mode. If a process change is required to
    * perform the load, this will also occur.
    */
   void navigateAndRestoreByIndex(in nsIBrowser aBrowser, in long aIndex);
 
+  // update sessionStore from the tabListener implemented by C++
+  void updateDocShellCaps(in ACString aDisCaps);
+  void updateIsPrivate(in boolean aIsPrivate);
+  void updateScrollPositions(in Array<ACString> aPositions, in Array<long> aChildren);
+  void updateSessionStore(in Element aBrowser, in uint32_t aFlushId);
 };