summaryrefslogtreecommitdiffstats
path: root/toolkit/components/sessionstore
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/sessionstore
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/sessionstore')
-rw-r--r--toolkit/components/sessionstore/BrowserSessionStore.cpp292
-rw-r--r--toolkit/components/sessionstore/BrowserSessionStore.h57
-rw-r--r--toolkit/components/sessionstore/PSessionStore.ipdl63
-rw-r--r--toolkit/components/sessionstore/RestoreTabContentObserver.cpp112
-rw-r--r--toolkit/components/sessionstore/RestoreTabContentObserver.h34
-rw-r--r--toolkit/components/sessionstore/SessionStoreChangeListener.cpp393
-rw-r--r--toolkit/components/sessionstore/SessionStoreChangeListener.h103
-rw-r--r--toolkit/components/sessionstore/SessionStoreChild.cpp267
-rw-r--r--toolkit/components/sessionstore/SessionStoreChild.h73
-rw-r--r--toolkit/components/sessionstore/SessionStoreData.h61
-rw-r--r--toolkit/components/sessionstore/SessionStoreFormData.cpp172
-rw-r--r--toolkit/components/sessionstore/SessionStoreFormData.h83
-rw-r--r--toolkit/components/sessionstore/SessionStoreFunctions.idl23
-rw-r--r--toolkit/components/sessionstore/SessionStoreFunctions.sys.mjs87
-rw-r--r--toolkit/components/sessionstore/SessionStoreListener.cpp493
-rw-r--r--toolkit/components/sessionstore/SessionStoreListener.h114
-rw-r--r--toolkit/components/sessionstore/SessionStoreMessageUtils.h134
-rw-r--r--toolkit/components/sessionstore/SessionStoreParent.cpp318
-rw-r--r--toolkit/components/sessionstore/SessionStoreParent.h79
-rw-r--r--toolkit/components/sessionstore/SessionStoreRestoreData.cpp210
-rw-r--r--toolkit/components/sessionstore/SessionStoreRestoreData.h59
-rw-r--r--toolkit/components/sessionstore/SessionStoreScrollData.cpp123
-rw-r--r--toolkit/components/sessionstore/SessionStoreScrollData.h69
-rw-r--r--toolkit/components/sessionstore/SessionStoreTypes.ipdlh68
-rw-r--r--toolkit/components/sessionstore/SessionStoreUtils.cpp1948
-rw-r--r--toolkit/components/sessionstore/SessionStoreUtils.h182
-rw-r--r--toolkit/components/sessionstore/moz.build55
-rw-r--r--toolkit/components/sessionstore/nsISessionStoreRestoreData.idl35
28 files changed, 5707 insertions, 0 deletions
diff --git a/toolkit/components/sessionstore/BrowserSessionStore.cpp b/toolkit/components/sessionstore/BrowserSessionStore.cpp
new file mode 100644
index 0000000000..f4da9646b2
--- /dev/null
+++ b/toolkit/components/sessionstore/BrowserSessionStore.cpp
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BrowserSessionStore.h"
+#include <algorithm>
+#include <cstdint>
+#include <functional>
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPtr.h"
+
+#include "mozilla/dom/BrowserSessionStoreBinding.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/SessionStoreFormData.h"
+#include "mozilla/dom/SessionStoreScrollData.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+
+#include "nsTHashMap.h"
+#include "nsHashtablesFwd.h"
+
+#include "js/RootingAPI.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static StaticAutoPtr<nsTHashMap<nsUint64HashKey, BrowserSessionStore*>>
+ sSessionStore;
+
+NS_IMPL_CYCLE_COLLECTION(BrowserSessionStore, mBrowsingContext, mFormData,
+ mScrollData)
+
+/* static */
+already_AddRefed<BrowserSessionStore> BrowserSessionStore::GetOrCreate(
+ CanonicalBrowsingContext* aBrowsingContext) {
+ if (!aBrowsingContext->IsTop()) {
+ return nullptr;
+ }
+
+ if (!sSessionStore) {
+ sSessionStore = new nsTHashMap<nsUint64HashKey, BrowserSessionStore*>();
+ ClearOnShutdown(&sSessionStore);
+ }
+
+ return do_AddRef(sSessionStore->LookupOrInsertWith(
+ aBrowsingContext->Id(),
+ [&] { return new BrowserSessionStore(aBrowsingContext); }));
+}
+
+BrowserSessionStore::BrowserSessionStore(
+ CanonicalBrowsingContext* aBrowsingContext)
+ : mBrowsingContext(aBrowsingContext) {}
+
+SessionStoreFormData* BrowserSessionStore::GetFormdata() { return mFormData; }
+
+SessionStoreScrollData* BrowserSessionStore::GetScroll() { return mScrollData; }
+
+static bool ShouldUpdateSessionStore(CanonicalBrowsingContext* aBrowsingContext,
+ uint32_t aEpoch) {
+ if (!aBrowsingContext) {
+ return false;
+ }
+
+ if (aBrowsingContext->Top()->GetSessionStoreEpoch() != aEpoch) {
+ return false;
+ }
+
+ if (aBrowsingContext->IsReplaced()) {
+ return false;
+ }
+
+ if (aBrowsingContext->IsDynamic()) {
+ return false;
+ }
+
+ return true;
+}
+
+// With GetOrCreate we can create either of the weak fields:
+// WeakPtr<SessionStoreFormData> mFormdata;
+// WeakPtr<SessionStoreScrollData> mScroll;
+// in CanonicalBrowsingContext. If one already exists, then we return that.
+template <typename T, WeakPtr<T>& (CanonicalBrowsingContext::*GetWeakRef)()>
+static already_AddRefed<T> GetOrCreateEntry(
+ CanonicalBrowsingContext* aBrowsingContext) {
+ typename T::LocationType& location = (aBrowsingContext->*GetWeakRef)();
+ RefPtr<T> entry = location.get();
+ if (!entry) {
+ entry = MakeRefPtr<T>();
+ location = entry;
+ }
+
+ return entry.forget();
+}
+
+// With InsertEntry we can insert an entry in the session store data tree in
+// either of the weak fields:
+// WeakPtr<SessionStoreFormData> mFormdata;
+// WeakPtr<SessionStoreScrollData> mScroll;
+// in CanonicalBrowsingContext. If an entry is inserted where there is no parent
+// entry, a spine of entries will be created until one is found, or we reach the
+// top browsing context.
+template <typename T>
+void InsertEntry(BrowsingContext* aBrowsingContext, T* aParent, T* aUpdate) {
+ int32_t offset = aBrowsingContext->ChildOffset();
+ if (offset < 0) {
+ return;
+ }
+
+ aParent->ClearCachedChildren();
+
+ auto& children = aParent->Children();
+
+ children.EnsureLengthAtLeast(offset + 1);
+ if (children[offset] && !aBrowsingContext->Children().IsEmpty()) {
+ children[offset]->ClearCachedChildren();
+ aUpdate->ClearCachedChildren();
+ }
+
+ children[offset] = aUpdate;
+}
+
+// With RemoveEntry we can remove an entry in the session store data tree in
+// either of the weak fields:
+// WeakPtr<SessionStoreFormData> mFormdata;
+// WeakPtr<SessionStoreScrollData> mScroll;
+// in CanonicalBrowsingContext. If an entry is removed, where its parent doesn't
+// contain data, we'll remove the parent and repeat until we either find an
+// entry with data or reach the top browsing context.
+template <typename T>
+void RemoveEntry(BrowsingContext* aBrowsingContext, T* aParent) {
+ int32_t offset = aBrowsingContext->ChildOffset();
+ if (offset < 0) {
+ return;
+ }
+
+ if (!aParent) {
+ return;
+ }
+
+ aParent->ClearCachedChildren();
+
+ auto& children = aParent->Children();
+ size_t length = children.Length();
+ if (children.Length() <= static_cast<size_t>(offset)) {
+ // The children array doesn't extend to offset.
+ return;
+ }
+
+ if (static_cast<size_t>(offset) < length - 1) {
+ // offset is before the last item in the children array.
+ children[offset] = nullptr;
+ return;
+ }
+
+ // offset is the last item, find the first non-null item before it
+ // and remove anything after that item.
+ while (offset > 0 && !children[offset - 1]) {
+ --offset;
+ }
+
+ children.TruncateLength(offset);
+}
+
+// With UpdateSessionStoreField we can update an entry in the session store
+// data tree in either of the weak fields:
+// WeakPtr<SessionStoreFormData> mFormdata;
+// WeakPtr<SessionStoreScrollData> mScroll;
+// in CanonicalBrowsingContext. UpdateSessionStoreField uses the above
+// functions, `GetOrCreateEntry`, `InsertEntry` and `RemoveEntry` to operate on
+// the weak fields. We return the top-level entry attached to the top browsing
+// context through `aEntry`. If the entire browsing context tree contains no
+// session store data this will be set to nullptr.
+template <typename T, WeakPtr<T>& (CanonicalBrowsingContext::*GetWeakRef)()>
+void UpdateSessionStoreField(CanonicalBrowsingContext* aBrowsingContext,
+ const typename T::CollectedType& aUpdate,
+ T** aEntry) {
+ RefPtr<T> currentEntry;
+
+ if (T::HasData(aUpdate)) {
+ currentEntry = GetOrCreateEntry<T, GetWeakRef>(aBrowsingContext);
+ currentEntry->Update(aUpdate);
+
+ CanonicalBrowsingContext* currentBrowsingContext = aBrowsingContext;
+ while (CanonicalBrowsingContext* parent =
+ currentBrowsingContext->GetParent()) {
+ WeakPtr<T>& parentEntry = (parent->*GetWeakRef)();
+ if (parentEntry) {
+ InsertEntry(aBrowsingContext, parentEntry.get(), currentEntry.get());
+ break;
+ }
+
+ RefPtr<T> entry = GetOrCreateEntry<T, GetWeakRef>(parent);
+ InsertEntry(currentBrowsingContext, entry.get(), currentEntry.get());
+
+ currentEntry = entry;
+ currentBrowsingContext = parent;
+ }
+
+ currentEntry = (aBrowsingContext->Top()->*GetWeakRef)().get();
+ } else if ((currentEntry = (aBrowsingContext->*GetWeakRef)())) {
+ currentEntry->Update(aUpdate);
+
+ CanonicalBrowsingContext* currentBrowsingContext = aBrowsingContext;
+ while (CanonicalBrowsingContext* parent =
+ currentBrowsingContext->GetParent()) {
+ if (!currentEntry || !currentEntry->IsEmpty()) {
+ break;
+ }
+
+ T* parentEntry = (parent->*GetWeakRef)().get();
+ RemoveEntry(currentBrowsingContext, parentEntry);
+
+ currentEntry = parentEntry;
+ currentBrowsingContext = parent;
+ }
+
+ if (currentEntry && currentEntry->IsEmpty()) {
+ currentEntry = nullptr;
+ } else {
+ currentEntry = (aBrowsingContext->Top()->*GetWeakRef)().get();
+ }
+ } else {
+ currentEntry = (aBrowsingContext->Top()->*GetWeakRef)().get();
+ }
+
+ *aEntry = currentEntry.forget().take();
+}
+
+void BrowserSessionStore::UpdateSessionStore(
+ CanonicalBrowsingContext* aBrowsingContext,
+ const Maybe<sessionstore::FormData>& aFormData,
+ const Maybe<nsPoint>& aScrollPosition, uint32_t aEpoch) {
+ if (!aFormData && !aScrollPosition) {
+ return;
+ }
+
+ if (!ShouldUpdateSessionStore(aBrowsingContext, aEpoch)) {
+ return;
+ }
+
+ if (aFormData) {
+ UpdateSessionStoreField<
+ SessionStoreFormData,
+ &CanonicalBrowsingContext::GetSessionStoreFormDataRef>(
+ aBrowsingContext, *aFormData, getter_AddRefs(mFormData));
+ }
+
+ if (aScrollPosition) {
+ UpdateSessionStoreField<
+ SessionStoreScrollData,
+ &CanonicalBrowsingContext::GetSessionStoreScrollDataRef>(
+ aBrowsingContext, *aScrollPosition, getter_AddRefs(mScrollData));
+ }
+}
+
+void BrowserSessionStore::RemoveSessionStore(
+ CanonicalBrowsingContext* aBrowsingContext) {
+ if (!aBrowsingContext) {
+ return;
+ }
+
+ CanonicalBrowsingContext* parentContext = aBrowsingContext->GetParent();
+
+ if (parentContext) {
+ RemoveEntry(aBrowsingContext,
+ parentContext->GetSessionStoreFormDataRef().get());
+
+ RemoveEntry(aBrowsingContext,
+ parentContext->GetSessionStoreScrollDataRef().get());
+
+ return;
+ }
+
+ if (aBrowsingContext->IsTop()) {
+ mFormData = nullptr;
+ mScrollData = nullptr;
+ }
+}
+
+BrowserSessionStore::~BrowserSessionStore() {
+ if (sSessionStore) {
+ sSessionStore->Remove(mBrowsingContext->Id());
+ }
+}
diff --git a/toolkit/components/sessionstore/BrowserSessionStore.h b/toolkit/components/sessionstore/BrowserSessionStore.h
new file mode 100644
index 0000000000..011e7673ec
--- /dev/null
+++ b/toolkit/components/sessionstore/BrowserSessionStore.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStore_h
+#define mozilla_dom_SessionStore_h
+
+#include "nsWrapperCache.h"
+
+#include "mozilla/Maybe.h"
+
+struct nsPoint;
+
+namespace mozilla::dom {
+
+class CanonicalBrowsingContext;
+class GlobalObject;
+class SessionStoreFormData;
+class SessionStoreScrollData;
+class WindowGlobalParent;
+
+namespace sessionstore {
+class FormData;
+}
+
+class BrowserSessionStore final {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowserSessionStore)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(BrowserSessionStore)
+
+ static already_AddRefed<BrowserSessionStore> GetOrCreate(
+ CanonicalBrowsingContext* aBrowsingContext);
+
+ SessionStoreFormData* GetFormdata();
+ SessionStoreScrollData* GetScroll();
+
+ void UpdateSessionStore(CanonicalBrowsingContext* aBrowsingContext,
+ const Maybe<sessionstore::FormData>& aFormData,
+ const Maybe<nsPoint>& aScrollPosition,
+ uint32_t aEpoch);
+
+ void RemoveSessionStore(CanonicalBrowsingContext* aBrowsingContext);
+
+ private:
+ explicit BrowserSessionStore(CanonicalBrowsingContext* aBrowsingContext);
+ virtual ~BrowserSessionStore();
+
+ RefPtr<CanonicalBrowsingContext> mBrowsingContext;
+ RefPtr<SessionStoreFormData> mFormData;
+ RefPtr<SessionStoreScrollData> mScrollData;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStore_h
diff --git a/toolkit/components/sessionstore/PSessionStore.ipdl b/toolkit/components/sessionstore/PSessionStore.ipdl
new file mode 100644
index 0000000000..4b7bb48849
--- /dev/null
+++ b/toolkit/components/sessionstore/PSessionStore.ipdl
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 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/GfxMessageUtils.h";
+
+include protocol PBrowser;
+include protocol PInProcess;
+include SessionStoreTypes;
+
+using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h";
+using mozilla::dom::MaybeSessionStoreZoom from "mozilla/dom/SessionStoreScrollData.h";
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * The PSessionStore actor handles collection of session store data from content
+ * type documents. It can be used both in a content process and in the parent
+ * process. In particular it solves the problem of handling incremental updates
+ * to the session store, since we're collecting from potentially several content
+ * processes.
+ */
+async protocol PSessionStore
+{
+ manager PBrowser or PInProcess;
+
+parent:
+ /**
+ * Sends data to be stored and instructions to the session store to
+ * potentially collect data in the parent. This is data that is not
+ * collected incrementally.
+ */
+ async SessionStoreUpdate(
+ nsCString? aDocShellCaps, bool? aPrivateMode, MaybeSessionStoreZoom aZoom,
+ bool aNeedCollectSHistory, uint32_t aEpoch);
+
+ /**
+ * Sends data to be stored to the session store. The collected data
+ * is all the collected changed data from all the in-process documents
+ * in the process in which the SessionStoreChild actor lives.
+ */
+ async IncrementalSessionStoreUpdate(
+ MaybeDiscardedBrowsingContext aBrowsingContext, FormData? aFormData,
+ nsPoint? aScrollPosition, uint32_t aEpoch);
+
+ /**
+ * Drop all the collected data associated with the provided browsing
+ * context.
+ */
+ async ResetSessionStore(
+ MaybeDiscardedBrowsingContext aBrowsingContext, uint32_t aEpoch);
+
+child:
+ async FlushTabState() returns(bool aHadContext);
+
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/toolkit/components/sessionstore/RestoreTabContentObserver.cpp b/toolkit/components/sessionstore/RestoreTabContentObserver.cpp
new file mode 100644
index 0000000000..b0dda66ff0
--- /dev/null
+++ b/toolkit/components/sessionstore/RestoreTabContentObserver.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsReadableUtils.h"
+#include "nsThreadUtils.h"
+#include "RestoreTabContentObserver.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(RestoreTabContentObserver, nsIObserver)
+
+const char* const kAboutReaderTopic = "AboutReader:Ready";
+const char* const kContentDocumentLoaded = "content-document-loaded";
+const char* const kChromeDocumentLoaded = "chrome-document-loaded";
+
+/* static */
+void RestoreTabContentObserver::Initialize() {
+ MOZ_ASSERT(!gRestoreTabContentObserver);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<RestoreTabContentObserver> observer = new RestoreTabContentObserver();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return;
+ }
+
+ obs->AddObserver(observer, kAboutReaderTopic, false);
+ obs->AddObserver(observer, kContentDocumentLoaded, false);
+ obs->AddObserver(observer, kChromeDocumentLoaded, false);
+
+ gRestoreTabContentObserver = observer;
+}
+
+/* static */
+void RestoreTabContentObserver::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gRestoreTabContentObserver) {
+ return;
+ }
+
+ RefPtr<RestoreTabContentObserver> observer = gRestoreTabContentObserver;
+ gRestoreTabContentObserver = nullptr;
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return;
+ }
+
+ obs->RemoveObserver(observer, kAboutReaderTopic);
+ obs->RemoveObserver(observer, kContentDocumentLoaded);
+ obs->RemoveObserver(observer, kChromeDocumentLoaded);
+}
+
+NS_IMETHODIMP
+RestoreTabContentObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ nsCOMPtr<nsPIDOMWindowInner> inner;
+ if (!strcmp(aTopic, kAboutReaderTopic)) {
+ inner = do_QueryInterface(aSubject);
+ } else if (!strcmp(aTopic, kContentDocumentLoaded) ||
+ !strcmp(aTopic, kChromeDocumentLoaded)) {
+ nsCOMPtr<Document> doc = do_QueryInterface(aSubject);
+ inner = doc ? doc->GetInnerWindow() : nullptr;
+ }
+ if (!inner) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri = inner->GetDocumentURI();
+ if (!uri) {
+ return NS_OK;
+ }
+
+ // We'll handle loading about:reader with "AboutReader:Ready"
+ // rather than "content-document-loaded".
+ if (uri->SchemeIs("about") &&
+ StringBeginsWith(uri->GetSpecOrDefault(), "about:reader"_ns) &&
+ strcmp(aTopic, kAboutReaderTopic) != 0) {
+ return NS_OK;
+ }
+
+ RefPtr<BrowsingContext> bc = inner->GetBrowsingContext();
+ if (!bc || !bc->Top()->GetHasRestoreData()) {
+ return NS_OK;
+ }
+ if (XRE_IsParentProcess()) {
+ if (WindowGlobalParent* wgp = bc->Canonical()->GetCurrentWindowGlobal()) {
+ bc->Canonical()->Top()->RequestRestoreTabContent(wgp);
+ }
+ } else if (WindowContext* windowContext = bc->GetCurrentWindowContext()) {
+ if (WindowGlobalChild* wgc = windowContext->GetWindowGlobalChild()) {
+ wgc->SendRequestRestoreTabContent();
+ }
+ }
+ return NS_OK;
+}
+
+mozilla::StaticRefPtr<RestoreTabContentObserver>
+ RestoreTabContentObserver::gRestoreTabContentObserver;
diff --git a/toolkit/components/sessionstore/RestoreTabContentObserver.h b/toolkit/components/sessionstore/RestoreTabContentObserver.h
new file mode 100644
index 0000000000..edab379a55
--- /dev/null
+++ b/toolkit/components/sessionstore/RestoreTabContentObserver.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_RestoreTabContentObserver_h
+#define mozilla_dom_RestoreTabContentObserver_h
+
+#include "mozilla/StaticPtr.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace dom {
+
+// An observer for restoring session store data at a correct time.
+class RestoreTabContentObserver final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static void Initialize();
+ static void Shutdown();
+
+ private:
+ RestoreTabContentObserver() = default;
+
+ virtual ~RestoreTabContentObserver() = default;
+ static mozilla::StaticRefPtr<RestoreTabContentObserver>
+ gRestoreTabContentObserver;
+};
+} // namespace dom
+} // namespace mozilla
+#endif // mozilla_dom_RestoreTabContentObserver_h
diff --git a/toolkit/components/sessionstore/SessionStoreChangeListener.cpp b/toolkit/components/sessionstore/SessionStoreChangeListener.cpp
new file mode 100644
index 0000000000..f9de5ec9a0
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreChangeListener.cpp
@@ -0,0 +1,393 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/SessionStoreChangeListener.h"
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/SessionStoreChild.h"
+#include "mozilla/dom/SessionStoreUtils.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_browser.h"
+
+#include "nsBaseHashtable.h"
+#include "nsDocShell.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIXULRuntime.h"
+#include "nsPIDOMWindow.h"
+#include "nsTHashMap.h"
+#include "nsTHashtable.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace {
+constexpr auto kInput = u"input"_ns;
+constexpr auto kScroll = u"mozvisualscroll"_ns;
+constexpr auto kResize = u"mozvisualresize"_ns;
+
+static constexpr char kNoAutoUpdates[] =
+ "browser.sessionstore.debug.no_auto_updates";
+static constexpr char kInterval[] = "browser.sessionstore.interval";
+static const char* kObservedPrefs[] = {kNoAutoUpdates, kInterval, nullptr};
+} // namespace
+
+inline void ImplCycleCollectionUnlink(
+ SessionStoreChangeListener::SessionStoreChangeTable& aField) {
+ aField.Clear();
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ const SessionStoreChangeListener::SessionStoreChangeTable& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) {
+ CycleCollectionNoteChild(aCallback, iter.Key(), aName, aFlags);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION(SessionStoreChangeListener, mBrowsingContext,
+ mCurrentEventTarget, mSessionStoreChild,
+ mSessionStoreChanges)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStoreChangeListener)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStoreChangeListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStoreChangeListener)
+
+NS_IMETHODIMP
+SessionStoreChangeListener::GetName(nsACString& aName) {
+ aName.AssignLiteral("SessionStoreChangeListener");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreChangeListener::Notify(nsITimer* aTimer) {
+ FlushSessionStore();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreChangeListener::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ FlushSessionStore();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreChangeListener::HandleEvent(dom::Event* aEvent) {
+ EventTarget* target = aEvent->GetTarget();
+ if (!target) {
+ return NS_OK;
+ }
+
+ nsIGlobalObject* global = target->GetOwnerGlobal();
+ if (!global) {
+ return NS_OK;
+ }
+
+ nsPIDOMWindowInner* inner = global->GetAsInnerWindow();
+ if (!inner) {
+ return NS_OK;
+ }
+
+ WindowContext* windowContext = inner->GetWindowContext();
+ if (!windowContext) {
+ return NS_OK;
+ }
+
+ BrowsingContext* browsingContext = windowContext->GetBrowsingContext();
+ if (!browsingContext) {
+ return NS_OK;
+ }
+
+ if (browsingContext->IsDynamic()) {
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (eventType == kInput) {
+ RecordChange(windowContext, Change::Input);
+ } else if (eventType == kScroll) {
+ RecordChange(windowContext, Change::Scroll);
+ } else if (eventType == kResize && browsingContext->IsTop()) {
+ RecordChange(windowContext, Change::Resize);
+ }
+ return NS_OK;
+}
+
+/* static */ already_AddRefed<SessionStoreChangeListener>
+SessionStoreChangeListener::Create(BrowsingContext* aBrowsingContext) {
+ MOZ_RELEASE_ASSERT(
+ StaticPrefs::browser_sessionstore_platform_collection_AtStartup());
+ if (!aBrowsingContext) {
+ return nullptr;
+ }
+
+ RefPtr<SessionStoreChangeListener> listener =
+ new SessionStoreChangeListener(aBrowsingContext);
+ listener->Init();
+
+ return listener.forget();
+}
+
+void SessionStoreChangeListener::Stop() {
+ RemoveEventListeners();
+ Preferences::RemoveObservers(this, kObservedPrefs);
+}
+
+void SessionStoreChangeListener::UpdateEventTargets() {
+ RemoveEventListeners();
+ AddEventListeners();
+}
+
+static void CollectFormData(Document* aDocument,
+ Maybe<sessionstore::FormData>& aFormData) {
+ aFormData.emplace();
+ auto& formData = aFormData.ref();
+ uint32_t size = SessionStoreUtils::CollectFormData(aDocument, formData);
+
+ Element* body = aDocument->GetBody();
+ if (aDocument->HasFlag(NODE_IS_EDITABLE) && body) {
+ IgnoredErrorResult result;
+ body->GetInnerHTML(formData.innerHTML(), result);
+ size += formData.innerHTML().Length();
+ if (!result.Failed()) {
+ formData.hasData() = true;
+ }
+ }
+
+ if (!formData.hasData()) {
+ return;
+ }
+
+ nsIURI* documentURI = aDocument->GetDocumentURI();
+ if (!documentURI) {
+ return;
+ }
+
+ documentURI->GetSpecIgnoringRef(formData.uri());
+
+ if (size > StaticPrefs::browser_sessionstore_dom_form_max_limit()) {
+ aFormData = Nothing();
+ }
+}
+
+static void GetZoom(BrowsingContext* aBrowsingContext,
+ Maybe<SessionStoreZoom>& aZoom) {
+ nsIDocShell* docShell = aBrowsingContext->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ PresShell* presShell = docShell->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ LayoutDeviceIntSize displaySize;
+
+ if (!nsLayoutUtils::GetDocumentViewerSize(presShell->GetPresContext(),
+ displaySize)) {
+ return;
+ }
+
+ aZoom.emplace(presShell->GetResolution(), displaySize.width,
+ displaySize.height);
+}
+
+void SessionStoreChangeListener::FlushSessionStore() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ bool collectSessionHistory = false;
+ bool collectWireFrame = false;
+ bool didResize = false;
+
+ for (auto& iter : mSessionStoreChanges) {
+ WindowContext* windowContext = iter.GetKey();
+ if (!windowContext) {
+ continue;
+ }
+
+ BrowsingContext* browsingContext = windowContext->GetBrowsingContext();
+
+ // This is a bit unfortunate, but is needed when a window context with a
+ // recorded change has become non-current before its data has been
+ // collected. This can happen either due to navigation or destruction, and
+ // in the previous case we don't want to collect, but in the latter we do.
+ // This could be cleaned up if we change. See bug 1770773.
+ if (!windowContext->IsCurrent() && !browsingContext->IsDiscarded()) {
+ continue;
+ }
+
+ RefPtr<Document> document = windowContext->GetDocument();
+ if (!document) {
+ continue;
+ }
+
+ EnumSet<Change> changes = iter.GetData();
+ Maybe<sessionstore::FormData> maybeFormData;
+ if (changes.contains(Change::Input)) {
+ CollectFormData(document, maybeFormData);
+ }
+
+ Maybe<nsPoint> maybeScroll;
+ PresShell* presShell = document->GetPresShell();
+
+ if (presShell && changes.contains(Change::Scroll)) {
+ maybeScroll = Some(presShell->GetVisualViewportOffset());
+ }
+
+ collectWireFrame = collectWireFrame || changes.contains(Change::WireFrame);
+
+ collectSessionHistory =
+ collectSessionHistory || changes.contains(Change::SessionHistory);
+
+ if (presShell && changes.contains(Change::Resize)) {
+ didResize = true;
+ }
+
+ mSessionStoreChild->IncrementalSessionStoreUpdate(
+ browsingContext, maybeFormData, maybeScroll, mEpoch);
+ }
+
+ if (collectWireFrame) {
+ collectSessionHistory = CollectWireframe() || collectSessionHistory;
+ }
+
+ mSessionStoreChanges.Clear();
+
+ Maybe<SessionStoreZoom> zoom;
+ if (didResize) {
+ GetZoom(mBrowsingContext->Top(), zoom);
+ }
+
+ mSessionStoreChild->UpdateSessionStore(collectSessionHistory, zoom);
+}
+
+/* static */
+SessionStoreChangeListener* SessionStoreChangeListener::CollectSessionStoreData(
+ WindowContext* aWindowContext, const EnumSet<Change>& aChanges) {
+ SessionStoreChild* sessionStoreChild =
+ SessionStoreChild::From(aWindowContext->GetWindowGlobalChild());
+ if (!sessionStoreChild) {
+ return nullptr;
+ }
+
+ SessionStoreChangeListener* sessionStoreChangeListener =
+ sessionStoreChild->GetSessionStoreChangeListener();
+
+ if (!sessionStoreChangeListener) {
+ return nullptr;
+ }
+
+ sessionStoreChangeListener->RecordChange(aWindowContext, aChanges);
+
+ return sessionStoreChangeListener;
+}
+
+void SessionStoreChangeListener::SetActor(
+ SessionStoreChild* aSessionStoreChild) {
+ mSessionStoreChild = aSessionStoreChild;
+}
+
+bool SessionStoreChangeListener::CollectWireframe() {
+ if (auto* docShell = nsDocShell::Cast(mBrowsingContext->GetDocShell())) {
+ return docShell->CollectWireframe();
+ }
+
+ return false;
+}
+
+void SessionStoreChangeListener::RecordChange(WindowContext* aWindowContext,
+ EnumSet<Change> aChange) {
+ EnsureTimer();
+
+ Unused << mSessionStoreChanges.WithEntryHandle(
+ aWindowContext, [&](auto entryHandle) -> EnumSet<Change>& {
+ if (entryHandle) {
+ *entryHandle += aChange;
+ return *entryHandle;
+ }
+
+ return entryHandle.Insert(aChange);
+ });
+}
+
+SessionStoreChangeListener::SessionStoreChangeListener(
+ BrowsingContext* aBrowsingContext)
+ : mBrowsingContext(aBrowsingContext),
+ mEpoch(aBrowsingContext->GetSessionStoreEpoch()) {}
+
+void SessionStoreChangeListener::Init() {
+ AddEventListeners();
+
+ Preferences::AddStrongObservers(this, kObservedPrefs);
+}
+
+EventTarget* SessionStoreChangeListener::GetEventTarget() {
+ if (mBrowsingContext->GetDOMWindow()) {
+ return mBrowsingContext->GetDOMWindow()->GetChromeEventHandler();
+ }
+
+ return nullptr;
+}
+
+void SessionStoreChangeListener::AddEventListeners() {
+ if (EventTarget* target = GetEventTarget()) {
+ target->AddSystemEventListener(kInput, this, false);
+ target->AddSystemEventListener(kScroll, this, false);
+ if (StaticPrefs::browser_sessionstore_collect_zoom_AtStartup()) {
+ target->AddSystemEventListener(kResize, this, false);
+ }
+ mCurrentEventTarget = target;
+ }
+}
+
+void SessionStoreChangeListener::RemoveEventListeners() {
+ if (mCurrentEventTarget) {
+ mCurrentEventTarget->RemoveSystemEventListener(kInput, this, false);
+ mCurrentEventTarget->RemoveSystemEventListener(kScroll, this, false);
+ if (StaticPrefs::browser_sessionstore_collect_zoom_AtStartup()) {
+ mCurrentEventTarget->RemoveSystemEventListener(kResize, this, false);
+ }
+ }
+
+ mCurrentEventTarget = nullptr;
+}
+
+void SessionStoreChangeListener::EnsureTimer() {
+ if (mTimer) {
+ return;
+ }
+
+ if (!StaticPrefs::browser_sessionstore_debug_no_auto_updates()) {
+ auto result = NS_NewTimerWithCallback(
+ this, StaticPrefs::browser_sessionstore_interval(),
+ nsITimer::TYPE_ONE_SHOT);
+ if (result.isErr()) {
+ return;
+ }
+
+ mTimer = result.unwrap();
+ }
+}
diff --git a/toolkit/components/sessionstore/SessionStoreChangeListener.h b/toolkit/components/sessionstore/SessionStoreChangeListener.h
new file mode 100644
index 0000000000..803a2137bd
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreChangeListener.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStoreChangeListener_h
+#define mozilla_dom_SessionStoreChangeListener_h
+
+#include "ErrorList.h"
+
+#include "PLDHashTable.h"
+#include "mozilla/EnumSet.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsHashKeys.h"
+#include "nsIDOMEventListener.h"
+#include "nsIObserver.h"
+#include "nsISupports.h"
+#include "nsITimer.h"
+#include "nsINamed.h"
+#include "nsHashtablesFwd.h"
+
+#include "mozilla/EnumSet.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+
+#include "mozilla/dom/WindowGlobalChild.h"
+
+namespace mozilla::dom {
+
+class BrowsingContext;
+class Element;
+class EventTarget;
+class SessionStoreChild;
+class WindowContext;
+
+class SessionStoreChangeListener final : public nsINamed,
+ public nsIObserver,
+ public nsITimerCallback,
+ public nsIDOMEventListener {
+ public:
+ NS_DECL_NSINAMED
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SessionStoreChangeListener,
+ nsIObserver)
+
+ static already_AddRefed<SessionStoreChangeListener> Create(
+ BrowsingContext* aBrowsingContext);
+
+ void Stop();
+
+ void UpdateEventTargets();
+
+ void FlushSessionStore();
+
+ enum class Change { Input, Scroll, SessionHistory, WireFrame, Resize };
+
+ static SessionStoreChangeListener* CollectSessionStoreData(
+ WindowContext* aWindowContext, const EnumSet<Change>& aChanges);
+
+ void SetActor(SessionStoreChild* aSessionStoreChild);
+
+ void SetEpoch(uint32_t aEpoch) { mEpoch = aEpoch; }
+
+ uint32_t GetEpoch() const { return mEpoch; }
+
+ bool CollectWireframe();
+
+ private:
+ void RecordChange(WindowContext* aWindowContext, EnumSet<Change> aChanges);
+
+ public:
+ using SessionStoreChangeTable =
+ nsTHashMap<RefPtr<WindowContext>, EnumSet<Change>>;
+
+ private:
+ explicit SessionStoreChangeListener(BrowsingContext* aBrowsingContext);
+ ~SessionStoreChangeListener() = default;
+
+ void Init();
+
+ EventTarget* GetEventTarget();
+
+ void AddEventListeners();
+ void RemoveEventListeners();
+
+ void EnsureTimer();
+
+ RefPtr<BrowsingContext> mBrowsingContext;
+ RefPtr<EventTarget> mCurrentEventTarget;
+
+ uint32_t mEpoch;
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<SessionStoreChild> mSessionStoreChild;
+ SessionStoreChangeTable mSessionStoreChanges;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStoreChangeListener_h
diff --git a/toolkit/components/sessionstore/SessionStoreChild.cpp b/toolkit/components/sessionstore/SessionStoreChild.cpp
new file mode 100644
index 0000000000..6ffd74d9bd
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreChild.cpp
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/SessionStoreChild.h"
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/InProcessChild.h"
+#include "mozilla/dom/InProcessParent.h"
+#include "mozilla/dom/BrowserSessionStore.h"
+#include "mozilla/dom/SessionStoreChangeListener.h"
+#include "mozilla/dom/SessionStoreParent.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "nsCOMPtr.h"
+#include "nsFrameLoader.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class nsIDocShell;
+
+static already_AddRefed<TabListener> CreateTabListener(nsIDocShell* aDocShell) {
+ RefPtr<TabListener> tabListener =
+ mozilla::MakeRefPtr<TabListener>(aDocShell, nullptr);
+ nsresult rv = tabListener->Init();
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return tabListener.forget();
+}
+
+already_AddRefed<SessionStoreChild> SessionStoreChild::GetOrCreate(
+ BrowsingContext* aBrowsingContext, Element* aOwnerElement) {
+ RefPtr<TabListener> tabListener =
+ CreateTabListener(aBrowsingContext->GetDocShell());
+ if (!tabListener) {
+ return nullptr;
+ }
+
+ RefPtr<SessionStoreChangeListener> sessionStoreChangeListener =
+ SessionStoreChangeListener::Create(aBrowsingContext);
+ if (!sessionStoreChangeListener) {
+ return nullptr;
+ }
+
+ RefPtr<SessionStoreChild> sessionStoreChild =
+ new SessionStoreChild(tabListener, sessionStoreChangeListener);
+
+ sessionStoreChangeListener->SetActor(sessionStoreChild);
+
+ if (XRE_IsParentProcess()) {
+ MOZ_DIAGNOSTIC_ASSERT(aOwnerElement);
+ InProcessChild* inProcessChild = InProcessChild::Singleton();
+ InProcessParent* inProcessParent = InProcessParent::Singleton();
+ if (!inProcessChild || !inProcessParent) {
+ return nullptr;
+ }
+
+ RefPtr<BrowserSessionStore> sessionStore =
+ BrowserSessionStore::GetOrCreate(aBrowsingContext->Canonical()->Top());
+ if (!sessionStore) {
+ return nullptr;
+ }
+
+ CanonicalBrowsingContext* browsingContext = aBrowsingContext->Canonical();
+ RefPtr<SessionStoreParent> sessionStoreParent =
+ new SessionStoreParent(browsingContext, sessionStore);
+ ManagedEndpoint<PSessionStoreParent> endpoint =
+ inProcessChild->OpenPSessionStoreEndpoint(sessionStoreChild);
+ inProcessParent->BindPSessionStoreEndpoint(std::move(endpoint),
+ sessionStoreParent);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(!aOwnerElement);
+ RefPtr<BrowserChild> browserChild =
+ BrowserChild::GetFrom(aBrowsingContext->GetDOMWindow());
+
+ MOZ_DIAGNOSTIC_ASSERT(browserChild);
+ MOZ_DIAGNOSTIC_ASSERT(aBrowsingContext->IsInProcess());
+ sessionStoreChild = static_cast<SessionStoreChild*>(
+ browserChild->SendPSessionStoreConstructor(sessionStoreChild));
+ }
+
+ return sessionStoreChild.forget();
+}
+
+/* static */
+SessionStoreChild* SessionStoreChild::From(WindowGlobalChild* aWindowChild) {
+ if (!aWindowChild) {
+ return nullptr;
+ }
+
+ // If `aWindowChild` is inprocess
+ if (RefPtr<BrowserChild> browserChild = aWindowChild->GetBrowserChild()) {
+ return browserChild->GetSessionStoreChild();
+ }
+
+ if (XRE_IsContentProcess()) {
+ return nullptr;
+ }
+
+ WindowGlobalParent* windowParent = aWindowChild->WindowContext()->Canonical();
+ if (!windowParent) {
+ return nullptr;
+ }
+
+ RefPtr<nsFrameLoader> frameLoader = windowParent->GetRootFrameLoader();
+ if (!frameLoader) {
+ return nullptr;
+ }
+
+ return frameLoader->GetSessionStoreChild();
+}
+
+SessionStoreChild::SessionStoreChild(
+ TabListener* aSessionStoreListener,
+ SessionStoreChangeListener* aSessionStoreChangeListener)
+ : mSessionStoreListener(aSessionStoreListener),
+ mSessionStoreChangeListener(aSessionStoreChangeListener) {}
+
+void SessionStoreChild::SetEpoch(uint32_t aEpoch) {
+ if (mSessionStoreListener) {
+ mSessionStoreListener->SetEpoch(aEpoch);
+ }
+
+ if (mSessionStoreChangeListener) {
+ mSessionStoreChangeListener->SetEpoch(aEpoch);
+ }
+}
+
+void SessionStoreChild::SetOwnerContent(Element* aElement) {
+ if (mSessionStoreChangeListener) {
+ mSessionStoreChangeListener->FlushSessionStore();
+ }
+
+ if (!aElement) {
+ return;
+ }
+
+ if (mSessionStoreListener) {
+ mSessionStoreListener->SetOwnerContent(aElement);
+ }
+}
+
+void SessionStoreChild::Stop() {
+ if (mSessionStoreListener) {
+ mSessionStoreListener->RemoveListeners();
+ mSessionStoreListener = nullptr;
+ }
+
+ if (mSessionStoreChangeListener) {
+ mSessionStoreChangeListener->Stop();
+ }
+}
+
+void SessionStoreChild::UpdateEventTargets() {
+ if (mSessionStoreChangeListener) {
+ mSessionStoreChangeListener->UpdateEventTargets();
+ }
+}
+
+void SessionStoreChild::UpdateSessionStore(bool aSessionHistoryUpdate,
+ const MaybeSessionStoreZoom& aZoom) {
+ if (!mSessionStoreListener) {
+ // This is the case when we're shutting down, and expect a final update.
+ SessionStoreUpdate(Nothing(), Nothing(), Nothing(), aSessionHistoryUpdate,
+ 0);
+ return;
+ }
+
+ 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());
+ }
+
+ SessionStoreUpdate(
+ docShellCaps, privatedMode, aZoom,
+ store->GetAndClearSHistoryChanged() || aSessionHistoryUpdate,
+ mSessionStoreListener->GetEpoch());
+}
+
+void SessionStoreChild::FlushSessionStore() {
+ if (mSessionStoreChangeListener) {
+ mSessionStoreChangeListener->FlushSessionStore();
+ }
+}
+
+void SessionStoreChild::UpdateSHistoryChanges() {
+ if (mSessionStoreListener) {
+ mSessionStoreListener->UpdateSHistoryChanges();
+ }
+}
+
+mozilla::ipc::IPCResult SessionStoreChild::RecvFlushTabState(
+ FlushTabStateResolver&& aResolver) {
+ if (mSessionStoreChangeListener) {
+ mSessionStoreChangeListener->FlushSessionStore();
+ }
+ aResolver(true);
+
+ return IPC_OK();
+}
+
+void SessionStoreChild::SessionStoreUpdate(
+ const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
+ const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory,
+ const uint32_t& aEpoch) {
+ // Skipping an update here is acceptable, since it will only happen
+ // during actor teardown, and we're most likely in a final flush
+ // which expects that not all content processes manage to respond.
+ if (XRE_IsContentProcess() && CanSend()) {
+ Unused << SendSessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom,
+ aNeedCollectSHistory, aEpoch);
+ } else if (SessionStoreParent* sessionStoreParent =
+ static_cast<SessionStoreParent*>(
+ InProcessChild::ParentActorFor(this))) {
+ sessionStoreParent->SessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom,
+ aNeedCollectSHistory, aEpoch);
+ }
+}
+
+void SessionStoreChild::IncrementalSessionStoreUpdate(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext,
+ const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
+ uint32_t aEpoch) {
+ // Skipping an update here is acceptable, since it will only happen
+ // during actor teardown, and we're most likely in a final flush
+ // which expects that not all content processes manage to respond.
+ if (XRE_IsContentProcess() && CanSend()) {
+ Unused << SendIncrementalSessionStoreUpdate(aBrowsingContext, aFormData,
+ aScrollPosition, aEpoch);
+ } else if (SessionStoreParent* sessionStoreParent =
+ static_cast<SessionStoreParent*>(
+ InProcessChild::ParentActorFor(this))) {
+ sessionStoreParent->IncrementalSessionStoreUpdate(
+ aBrowsingContext, aFormData, aScrollPosition, aEpoch);
+ }
+}
+
+void SessionStoreChild::ResetSessionStore(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext, uint32_t aEpoch) {
+ if (XRE_IsContentProcess()) {
+ Unused << SendResetSessionStore(aBrowsingContext, aEpoch);
+ } else if (SessionStoreParent* sessionStoreParent =
+ static_cast<SessionStoreParent*>(
+ InProcessChild::ParentActorFor(this))) {
+ sessionStoreParent->ResetSessionStore(aBrowsingContext, aEpoch);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION(SessionStoreChild, mSessionStoreListener,
+ mSessionStoreChangeListener)
diff --git a/toolkit/components/sessionstore/SessionStoreChild.h b/toolkit/components/sessionstore/SessionStoreChild.h
new file mode 100644
index 0000000000..606aa0f441
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreChild.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStoreChild_h
+#define mozilla_dom_SessionStoreChild_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/PSessionStoreChild.h"
+#include "mozilla/dom/SessionStoreScrollData.h"
+#include "mozilla/dom/SessionStoreChangeListener.h"
+#include "mozilla/dom/SessionStoreListener.h"
+#include "mozilla/RefPtr.h"
+
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla::dom {
+class BrowsingContext;
+class SessionStoreChangeListener;
+class TabListener;
+
+class SessionStoreChild final : public PSessionStoreChild {
+ public:
+ static already_AddRefed<SessionStoreChild> GetOrCreate(
+ BrowsingContext* aBrowsingContext, Element* aOwnerElement = nullptr);
+
+ static SessionStoreChild* From(WindowGlobalChild* aWindowChild);
+
+ void SetEpoch(uint32_t aEpoch);
+ void SetOwnerContent(Element* aElement);
+ void Stop();
+ void UpdateEventTargets();
+ void UpdateSessionStore(bool aSessionHistoryUpdate = false,
+ const MaybeSessionStoreZoom& aZoom = Nothing());
+ void FlushSessionStore();
+ void UpdateSHistoryChanges();
+
+ void SessionStoreUpdate(const Maybe<nsCString>& aDocShellCaps,
+ const Maybe<bool>& aPrivatedMode,
+ const MaybeSessionStoreZoom& aZoom,
+ const bool aNeedCollectSHistory,
+ const uint32_t& aEpoch);
+
+ void IncrementalSessionStoreUpdate(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext,
+ const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
+ uint32_t aEpoch);
+
+ void ResetSessionStore(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext, uint32_t aEpoch);
+
+ SessionStoreChangeListener* GetSessionStoreChangeListener() const {
+ return mSessionStoreChangeListener;
+ }
+
+ mozilla::ipc::IPCResult RecvFlushTabState(FlushTabStateResolver&& aResolver);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SessionStoreChild)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(SessionStoreChild)
+
+ private:
+ SessionStoreChild(TabListener* aSessionStoreListener,
+ SessionStoreChangeListener* aSessionStoreChangeListener);
+ ~SessionStoreChild() = default;
+
+ RefPtr<TabListener> mSessionStoreListener;
+ RefPtr<SessionStoreChangeListener> mSessionStoreChangeListener;
+};
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStoreChild_h
diff --git a/toolkit/components/sessionstore/SessionStoreData.h b/toolkit/components/sessionstore/SessionStoreData.h
new file mode 100644
index 0000000000..0df1ea5bf5
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreData.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStoreData_h
+#define mozilla_dom_SessionStoreData_h
+
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/dom/SessionStoreUtilsBinding.h"
+#include "mozilla/Variant.h"
+
+typedef mozilla::Variant<nsString, bool,
+ mozilla::dom::CollectedNonMultipleSelectValue,
+ CopyableTArray<nsString>>
+ InputDataValue;
+
+/*
+ * Need two arrays based on this struct.
+ * One is for elements with id one is for XPath.
+ *
+ * id: id or XPath
+ * type: type of this input element
+ * bool: value is boolean
+ * string: value is nsString
+ * file: value is "arrayVal"
+ * singleSelect: value is "singleSelect"
+ * multipleSelect: value is "arrayVal"
+ *
+ * There are four value types:
+ * strVal: nsString
+ * boolVal: boolean
+ * singleSelect: single select value
+ * arrayVal: nsString array
+ */
+struct CollectedInputDataValue {
+ nsString id;
+ nsString type;
+ InputDataValue value{false};
+
+ CollectedInputDataValue() = default;
+};
+
+/*
+ * Each index of the following array is corresponging to each frame.
+ * descendants: number of child frames of this frame
+ * innerHTML: innerHTML of this frame
+ * url: url of this frame
+ * numId: number of containing elements with id for this frame
+ * numXPath: number of containing elements with XPath for this frame
+ */
+struct InputFormData {
+ int32_t descendants;
+ nsString innerHTML;
+ nsCString url;
+ int32_t numId;
+ int32_t numXPath;
+};
+
+#endif /* mozilla_dom_SessionStoreData_h */
diff --git a/toolkit/components/sessionstore/SessionStoreFormData.cpp b/toolkit/components/sessionstore/SessionStoreFormData.cpp
new file mode 100644
index 0000000000..c792b3f2f0
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreFormData.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/SessionStoreFormData.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BrowserSessionStoreBinding.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/sessionstore/SessionStoreTypes.h"
+
+#include "nsContentUtils.h"
+#include "js/JSON.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStoreFormData)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStoreFormData)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(SessionStoreFormData, mChildren)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStoreFormData)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsISupports* SessionStoreFormData::GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+JSObject* SessionStoreFormData::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return SessionStoreFormData_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void SessionStoreFormData::GetUrl(nsACString& aUrl) const {
+ if (mUrl.IsEmpty()) {
+ aUrl.SetIsVoid(true);
+ } else {
+ aUrl = mUrl;
+ }
+}
+
+void SessionStoreFormData::GetId(
+ JSContext* aCx,
+ Nullable<Record<nsString, OwningStringOrBooleanOrObject>>& aId) {
+ if (mId.IsEmpty() ||
+ NS_FAILED(SessionStoreUtils::ConstructFormDataValues(
+ aCx, mId, aId.SetValue().Entries(), mParseSessionData))) {
+ aId.SetNull();
+ }
+
+ // SessionStoreFormData.id is now stored in a slot, so we can free our
+ // internal state.
+ mId.Clear();
+}
+
+void SessionStoreFormData::GetXpath(
+ JSContext* aCx,
+ Nullable<Record<nsString, OwningStringOrBooleanOrObject>>& aXpath) {
+ if (mXpath.IsEmpty() ||
+ NS_FAILED(SessionStoreUtils::ConstructFormDataValues(
+ aCx, mXpath, aXpath.SetValue().Entries(), mParseSessionData))) {
+ aXpath.SetNull();
+ }
+
+ // SessionStoreFormData.xpath is now stored in a slot, so we can free our
+ // internal state.
+ mXpath.Clear();
+}
+
+void SessionStoreFormData::GetInnerHTML(nsAString& aInnerHTML) {
+ if (mInnerHTML.IsEmpty()) {
+ SetDOMStringToNull(aInnerHTML);
+ } else {
+ aInnerHTML = mInnerHTML;
+ }
+
+ // SessionStoreFormData.innerHTML is now stored in a slot, so we can free our
+ // internal state.
+ mInnerHTML.SetIsVoid(true);
+}
+
+SessionStoreFormData::ChildrenArray& SessionStoreFormData::Children() {
+ return mChildren;
+}
+
+void SessionStoreFormData::GetChildren(
+ Nullable<ChildrenArray>& aChildren) const {
+ if (!mChildren.IsEmpty()) {
+ aChildren.SetValue() = mChildren.Clone();
+ } else {
+ aChildren.SetNull();
+ }
+}
+
+void SessionStoreFormData::ToJSON(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval) {
+ JS::Rooted<JSObject*> self(aCx);
+ {
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, this, &value)) {
+ return;
+ }
+
+ self.set(value.toObjectOrNull());
+ }
+
+ JS::Rooted<JSObject*> result(aCx, JS_NewPlainObject(aCx));
+
+ if (!IsEmpty()) {
+ for (const auto& name :
+ {u"url"_ns, u"id"_ns, u"xpath"_ns, u"innerHTML"_ns}) {
+ if (!SessionStoreUtils::CopyProperty(aCx, result, self, name)) {
+ return;
+ }
+ }
+
+ SessionStoreUtils::CopyChildren(aCx, result, mChildren);
+ }
+
+ aRetval.set(result);
+}
+
+void SessionStoreFormData::Update(const CollectedType& aFormData) {
+ SessionStoreFormData_Binding::ClearCachedUrlValue(this);
+ SessionStoreFormData_Binding::ClearCachedIdValue(this);
+ SessionStoreFormData_Binding::ClearCachedXpathValue(this);
+ SessionStoreFormData_Binding::ClearCachedInnerHTMLValue(this);
+
+ if (!aFormData.hasData()) {
+ mParseSessionData = false;
+ mHasData = false;
+ mUrl = ""_ns;
+ mId.Clear();
+ mXpath.Clear();
+ mInnerHTML = u""_ns;
+
+ return;
+ }
+
+ mHasData = true;
+
+ mUrl = aFormData.uri();
+ // We want to avoid saving data for about:sessionrestore as a string.
+ // Since it's stored in the form as stringified JSON, stringifying
+ // further causes an explosion of escape characters. cf. bug 467409
+ mParseSessionData =
+ mUrl == "about:sessionrestore"_ns || mUrl == "about:welcomeback"_ns;
+
+ mInnerHTML = aFormData.innerHTML();
+
+ mId.Assign(aFormData.id());
+ mXpath.Assign(aFormData.xpath());
+}
+
+void SessionStoreFormData::ClearCachedChildren() {
+ SessionStoreFormData_Binding::ClearCachedChildrenValue(this);
+}
+
+/* static */ bool SessionStoreFormData::HasData(
+ const CollectedType& aFormData) {
+ return aFormData.hasData();
+}
+
+bool SessionStoreFormData::IsEmpty() const {
+ return !mHasData && mChildren.IsEmpty();
+}
diff --git a/toolkit/components/sessionstore/SessionStoreFormData.h b/toolkit/components/sessionstore/SessionStoreFormData.h
new file mode 100644
index 0000000000..0466d98441
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreFormData.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStoreFormData_h
+#define mozilla_dom_SessionStoreFormData_h
+
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+
+#include "nsTArrayForwardDeclare.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla::dom {
+
+namespace sessionstore {
+class FormData;
+}
+
+class BrowsingContext;
+class OwningByteStringOrObjectOrNull;
+class OwningStringOrObjectOrNull;
+class WindowGlobalParent;
+
+class SessionStoreFormData final : public nsISupports,
+ public nsWrapperCache,
+ public SupportsWeakPtr {
+ public:
+ using CollectedType = sessionstore::FormData;
+ using LocationType = WeakPtr<SessionStoreFormData>;
+ using ChildrenArray = nsTArray<RefPtr<SessionStoreFormData>>;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SessionStoreFormData)
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetUrl(nsACString& aUrl) const;
+
+ void GetId(JSContext* aCx,
+ Nullable<Record<nsString, OwningStringOrBooleanOrObject>>& aId);
+
+ void GetXpath(
+ JSContext* aCx,
+ Nullable<Record<nsString, OwningStringOrBooleanOrObject>>& aXpath);
+
+ void GetInnerHTML(nsAString& aInnerHTML);
+
+ ChildrenArray& Children();
+
+ void GetChildren(Nullable<ChildrenArray>& aChildren) const;
+
+ void ToJSON(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval);
+
+ void Update(const CollectedType& aFormData);
+
+ void ClearCachedChildren();
+
+ static bool HasData(const CollectedType& aFormData);
+
+ bool IsEmpty() const;
+
+ private:
+ ~SessionStoreFormData() = default;
+
+ bool mParseSessionData = false;
+ bool mHasData = false;
+ nsCString mUrl;
+ nsTArray<sessionstore::FormEntry> mId;
+ nsTArray<sessionstore::FormEntry> mXpath;
+ nsString mInnerHTML;
+ ChildrenArray mChildren;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStoreFormData_h
diff --git a/toolkit/components/sessionstore/SessionStoreFunctions.idl b/toolkit/components/sessionstore/SessionStoreFunctions.idl
new file mode 100644
index 0000000000..3c17015437
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreFunctions.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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"
+
+webidl BrowsingContext;
+webidl Element;
+
+[scriptable, uuid(1A060FBA-A19D-11E9-B7EB-580D0EDD8E6F)]
+interface nsISessionStoreFunctions : nsISupports {
+ // update sessionStore from the tabListener implemented by C++
+ // aData is a UpdateSessionStoreData dictionary (From SessionStoreUtils.webidl)
+ void UpdateSessionStore(
+ in Element aBrowser, in BrowsingContext aBrowsingContext,
+ in jsval aPermanentKey, in uint32_t aEpoch, in boolean aCollectSHistory,
+ in jsval aData);
+
+ void UpdateSessionStoreForStorage(
+ in Element aBrowser, in BrowsingContext aBrowsingContext,
+ in jsval aPermanentKey, in uint32_t aEpoch, in jsval aData);
+};
diff --git a/toolkit/components/sessionstore/SessionStoreFunctions.sys.mjs b/toolkit/components/sessionstore/SessionStoreFunctions.sys.mjs
new file mode 100644
index 0000000000..f42c371264
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreFunctions.sys.mjs
@@ -0,0 +1,87 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+import { SessionStore } from "resource:///modules/sessionstore/SessionStore.sys.mjs";
+
+export function UpdateSessionStore(
+ aBrowser,
+ aBrowsingContext,
+ aPermanentKey,
+ aEpoch,
+ aCollectSHistory,
+ aData
+) {
+ return SessionStoreFuncInternal.updateSessionStore(
+ aBrowser,
+ aBrowsingContext,
+ aPermanentKey,
+ aEpoch,
+ aCollectSHistory,
+ aData
+ );
+}
+
+export function UpdateSessionStoreForStorage(
+ aBrowser,
+ aBrowsingContext,
+ aPermanentKey,
+ aEpoch,
+ aData
+) {
+ return SessionStoreFuncInternal.updateSessionStoreForStorage(
+ aBrowser,
+ aBrowsingContext,
+ aPermanentKey,
+ aEpoch,
+ aData
+ );
+}
+
+var SessionStoreFuncInternal = {
+ updateSessionStore: function SSF_updateSessionStore(
+ aBrowser,
+ aBrowsingContext,
+ aPermanentKey,
+ aEpoch,
+ aCollectSHistory,
+ aData
+ ) {
+ let { formdata, scroll } = aData;
+
+ if (formdata) {
+ aData.formdata = formdata.toJSON();
+ }
+
+ if (scroll) {
+ aData.scroll = scroll.toJSON();
+ }
+
+ SessionStore.updateSessionStoreFromTablistener(
+ aBrowser,
+ aBrowsingContext,
+ aPermanentKey,
+ {
+ data: aData,
+ epoch: aEpoch,
+ sHistoryNeeded: aCollectSHistory,
+ }
+ );
+ },
+
+ updateSessionStoreForStorage: function SSF_updateSessionStoreForStorage(
+ aBrowser,
+ aBrowsingContext,
+ aPermanentKey,
+ aEpoch,
+ aData
+ ) {
+ SessionStore.updateSessionStoreFromTablistener(
+ aBrowser,
+ aBrowsingContext,
+ aPermanentKey,
+ { data: { storage: aData }, epoch: aEpoch },
+ true
+ );
+ },
+};
diff --git a/toolkit/components/sessionstore/SessionStoreListener.cpp b/toolkit/components/sessionstore/SessionStoreListener.cpp
new file mode 100644
index 0000000000..11b9966954
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreListener.cpp
@@ -0,0 +1,493 @@
+/* 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/BrowserSessionStoreBinding.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/SessionStoreListener.h"
+#include "mozilla/dom/SessionStoreUtils.h"
+#include "mozilla/dom/SessionStoreUtilsBinding.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "nsGenericHTMLElement.h"
+#include "nsDocShell.h"
+#include "nsIAppWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsImportModule.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsITimer.h"
+#include "nsIWebProgress.h"
+#include "nsIXPConnect.h"
+#include "nsIXULRuntime.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "SessionStoreFunctions.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)
+
+ContentSessionStore::ContentSessionStore(nsIDocShell* aDocShell)
+ : mDocShell(aDocShell),
+ mPrivateChanged(false),
+ mIsPrivate(false),
+ mDocCapChanged(false),
+ mSHistoryChanged(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
+
+ // 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::SetSHistoryChanged() {
+ mSHistoryChanged = mozilla::SessionHistoryInParent();
+}
+
+void ContentSessionStore::OnDocumentStart() {
+ nsCString caps = CollectDocShellCapabilities();
+ if (!mDocCaps.Equals(caps)) {
+ mDocCaps = caps;
+ mDocCapChanged = true;
+ }
+
+ if (mozilla::SessionHistoryInParent()) {
+ mSHistoryChanged = true;
+ }
+}
+
+void ContentSessionStore::OnDocumentEnd() {
+ if (mozilla::SessionHistoryInParent()) {
+ mSHistoryChanged = true;
+ }
+}
+
+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_WEAK(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),
+ mEpoch(0) {
+ MOZ_ASSERT(mDocShell);
+}
+
+EventTarget* TabListener::GetEventTarget() {
+ if (mOwnerContent) {
+ return mOwnerContent;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(mDocShell);
+ if (window) {
+ return window->GetChromeEventHandler();
+ }
+
+ return nullptr;
+}
+
+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;
+ }
+
+ AddEventListeners();
+ return NS_OK;
+}
+
+void TabListener::AddEventListeners() {
+ if (nsCOMPtr<EventTarget> eventTarget = GetEventTarget()) {
+ if (mozilla::SessionHistoryInParent()) {
+ eventTarget->AddSystemEventListener(u"DOMTitleChanged"_ns, this, false);
+ }
+ mEventListenerRegistered = true;
+ }
+}
+
+void TabListener::RemoveEventListeners() {
+ if (nsCOMPtr<EventTarget> eventTarget = GetEventTarget()) {
+ if (mEventListenerRegistered) {
+ if (mozilla::SessionHistoryInParent()) {
+ eventTarget->RemoveSystemEventListener(u"DOMTitleChanged"_ns, this,
+ false);
+ }
+ mEventListenerRegistered = false;
+ }
+ }
+}
+
+void TabListener::SetOwnerContent(Element* aElement) {
+ MOZ_DIAGNOSTIC_ASSERT(aElement);
+ RemoveEventListeners();
+ mOwnerContent = aElement;
+ AddEventListeners();
+}
+
+/* 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;
+ }
+
+ nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(mDocShell);
+ if (webProgress != aWebProgress) {
+ 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 || !outer->GetDocShell()) {
+ return NS_OK;
+ }
+
+ RefPtr<BrowsingContext> context = outer->GetBrowsingContext();
+ if (!context || context->CreatedDynamically()) {
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("DOMTitleChanged")) {
+ mSessionStore->SetSHistoryChanged();
+ 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) {
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ 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;
+ }
+
+ NS_ERROR("Unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+}
+
+void TabListener::ForceFlushFromParent() {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+ if (!mSessionStore) {
+ return;
+ }
+
+ UpdateSessionStore(true);
+}
+
+void TabListener::UpdateSessionStore(bool aIsFlush) {
+ if (!aIsFlush) {
+ if (!mSessionStore || !mSessionStore->UpdateNeeded()) {
+ return;
+ }
+ }
+
+ if (!XRE_IsParentProcess()) {
+ BrowserChild* browserChild = BrowserChild::GetFrom(mDocShell);
+ if (browserChild) {
+ StopTimerForUpdate();
+ browserChild->UpdateSessionStore();
+ }
+ return;
+ }
+
+ BrowsingContext* context = mDocShell->GetBrowsingContext();
+ if (!context) {
+ return;
+ }
+
+ uint32_t chromeFlags = 0;
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (!treeOwner) {
+ return;
+ }
+ nsCOMPtr<nsIAppWindow> window(do_GetInterface(treeOwner));
+ if (!window) {
+ return;
+ }
+ if (window && NS_FAILED(window->GetChromeFlags(&chromeFlags))) {
+ return;
+ }
+
+ UpdateSessionStoreData data;
+ if (mSessionStore->IsDocCapChanged()) {
+ data.mDisallow.Construct() = mSessionStore->GetDocShellCaps();
+ }
+ if (mSessionStore->IsPrivateChanged()) {
+ data.mIsPrivate.Construct() = mSessionStore->GetPrivateModeEnabled();
+ }
+
+ nsCOMPtr<nsISessionStoreFunctions> funcs = do_ImportESModule(
+ "resource://gre/modules/SessionStoreFunctions.sys.mjs", fallible);
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
+ if (!wrapped) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> update(jsapi.cx());
+ if (!ToJSValue(jsapi.cx(), data, &update)) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> key(jsapi.cx(),
+ context->Canonical()->Top()->PermanentKey());
+
+ nsresult rv = funcs->UpdateSessionStore(
+ mOwnerContent, context, key, mEpoch,
+ mSessionStore->GetAndClearSHistoryChanged(), update);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ StopTimerForUpdate();
+}
+
+void TabListener::RemoveListeners() {
+ if (mProgressListenerRegistered) {
+ nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(mDocShell);
+ if (webProgress) {
+ webProgress->RemoveProgressListener(this);
+ mProgressListenerRegistered = false;
+ }
+ }
+
+ RemoveEventListeners();
+
+ if (mPrefObserverRegistered) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return;
+ }
+ if (mPrefObserverRegistered) {
+ obs->RemoveObserver(this, kTimeOutDisable);
+ obs->RemoveObserver(this, kPrefInterval);
+ mPrefObserverRegistered = false;
+ }
+ }
+}
+
+TabListener::~TabListener() { RemoveListeners(); }
diff --git a/toolkit/components/sessionstore/SessionStoreListener.h b/toolkit/components/sessionstore/SessionStoreListener.h
new file mode 100644
index 0000000000..2cd35a00f5
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreListener.h
@@ -0,0 +1,114 @@
+/* -*- 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 "SessionStoreData.h"
+#include "nsIDOMEventListener.h"
+#include "nsIObserver.h"
+#include "nsIPrivacyTransitionObserver.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+
+class nsITimer;
+
+namespace mozilla::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 SetSHistoryChanged();
+ // request "collect sessionHistory" which is happened in the parent process
+ bool GetAndClearSHistoryChanged() {
+ bool ret = mSHistoryChanged;
+ mSHistoryChanged = false;
+ return ret;
+ }
+
+ void OnDocumentStart();
+ void OnDocumentEnd();
+ bool UpdateNeeded() {
+ return mPrivateChanged || mDocCapChanged || mSHistoryChanged;
+ }
+
+ private:
+ virtual ~ContentSessionStore() = default;
+ nsCString CollectDocShellCapabilities();
+
+ nsCOMPtr<nsIDocShell> mDocShell;
+ bool mPrivateChanged;
+ bool mIsPrivate;
+ bool mDocCapChanged;
+ nsCString mDocCaps;
+ // mSHistoryChanged means there are history changes which are found
+ // in the child process. The flag is set when
+ // 1. webProgress changes to STATE_START
+ // 2. webProgress changes to STATE_STOP
+ // 3. receiving "DOMTitleChanged" event
+ bool mSHistoryChanged;
+};
+
+class TabListener : public nsIDOMEventListener,
+ public nsIObserver,
+ public nsIPrivacyTransitionObserver,
+ public nsIWebProgressListener,
+ public nsSupportsWeakReference {
+ public:
+ explicit TabListener(nsIDocShell* aDocShell, Element* aElement);
+ EventTarget* GetEventTarget();
+ nsresult Init();
+ ContentSessionStore* GetSessionStore() { return mSessionStore; }
+ // the function is called only when TabListener is in parent process
+ void ForceFlushFromParent();
+ void RemoveListeners();
+ void SetEpoch(uint32_t aEpoch) { mEpoch = aEpoch; }
+ uint32_t GetEpoch() { return mEpoch; }
+ void UpdateSHistoryChanges() { AddTimerForUpdate(); }
+ void SetOwnerContent(Element* aElement);
+
+ 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();
+ void AddEventListeners();
+ void RemoveEventListeners();
+ void UpdateSessionStore(bool aIsFlush = false);
+ 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;
+ uint32_t mEpoch;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStoreListener_h
diff --git a/toolkit/components/sessionstore/SessionStoreMessageUtils.h b/toolkit/components/sessionstore/SessionStoreMessageUtils.h
new file mode 100644
index 0000000000..8475955f87
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreMessageUtils.h
@@ -0,0 +1,134 @@
+/* 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_SessionStoreMessageUtils_h
+#define mozilla_dom_SessionStoreMessageUtils_h
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SessionStoreData.h"
+#include "SessionStoreUtils.h"
+#include "SessionStoreRestoreData.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::CollectedNonMultipleSelectValue> {
+ typedef mozilla::dom::CollectedNonMultipleSelectValue paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mSelectedIndex);
+ WriteParam(aWriter, aParam.mValue);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mSelectedIndex) &&
+ ReadParam(aReader, &aResult->mValue);
+ }
+};
+
+template <>
+struct ParamTraits<CollectedInputDataValue> {
+ typedef CollectedInputDataValue paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.id);
+ WriteParam(aWriter, aParam.type);
+ WriteParam(aWriter, aParam.value);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->id) &&
+ ReadParam(aReader, &aResult->type) &&
+ ReadParam(aReader, &aResult->value);
+ }
+};
+
+template <>
+struct ParamTraits<InputFormData> {
+ typedef InputFormData paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.descendants);
+ WriteParam(aWriter, aParam.innerHTML);
+ WriteParam(aWriter, aParam.url);
+ WriteParam(aWriter, aParam.numId);
+ WriteParam(aWriter, aParam.numXPath);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->descendants) &&
+ ReadParam(aReader, &aResult->innerHTML) &&
+ ReadParam(aReader, &aResult->url) &&
+ ReadParam(aReader, &aResult->numId) &&
+ ReadParam(aReader, &aResult->numXPath);
+ }
+};
+
+} // namespace IPC
+
+namespace mozilla {
+namespace ipc {
+
+template <>
+struct IPDLParamTraits<mozilla::dom::SessionStoreRestoreData*> {
+ // Note that we intentionally don't de/serialize mChildren here. The receiver
+ // won't be doing anything with the children lists, and it avoids sending form
+ // data for subframes to the content processes of their embedders.
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ mozilla::dom::SessionStoreRestoreData* aParam) {
+ bool isNull = !aParam;
+ WriteIPDLParam(aWriter, aActor, isNull);
+ if (isNull) {
+ return;
+ }
+ WriteIPDLParam(aWriter, aActor, aParam->mURI);
+ WriteIPDLParam(aWriter, aActor, aParam->mInnerHTML);
+ WriteIPDLParam(aWriter, aActor, aParam->mScroll);
+ WriteIPDLParam(aWriter, aActor, aParam->mEntries);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ RefPtr<mozilla::dom::SessionStoreRestoreData>* aResult) {
+ *aResult = nullptr;
+ bool isNull;
+ if (!ReadIPDLParam(aReader, aActor, &isNull)) {
+ return false;
+ }
+ if (isNull) {
+ return true;
+ }
+ auto data = MakeRefPtr<mozilla::dom::SessionStoreRestoreData>();
+ if (!ReadIPDLParam(aReader, aActor, &data->mURI) ||
+ !ReadIPDLParam(aReader, aActor, &data->mInnerHTML) ||
+ !ReadIPDLParam(aReader, aActor, &data->mScroll) ||
+ !ReadIPDLParam(aReader, aActor, &data->mEntries)) {
+ return false;
+ }
+ *aResult = std::move(data);
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::dom::SessionStoreRestoreData::Entry> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ mozilla::dom::SessionStoreRestoreData::Entry aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mData);
+ WriteIPDLParam(aWriter, aActor, aParam.mIsXPath);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ mozilla::dom::SessionStoreRestoreData::Entry* aResult) {
+ return ReadIPDLParam(aReader, aActor, &aResult->mData) &&
+ ReadIPDLParam(aReader, aActor, &aResult->mIsXPath);
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_dom_SessionStoreMessageUtils_h
diff --git a/toolkit/components/sessionstore/SessionStoreParent.cpp b/toolkit/components/sessionstore/SessionStoreParent.cpp
new file mode 100644
index 0000000000..58be84d4d9
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreParent.cpp
@@ -0,0 +1,318 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/SessionStoreParent.h"
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowserSessionStore.h"
+#include "mozilla/dom/BrowserSessionStoreBinding.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/InProcessChild.h"
+#include "mozilla/dom/InProcessParent.h"
+#include "mozilla/dom/SessionStoreChild.h"
+#include "mozilla/dom/SessionStoreUtilsBinding.h"
+#include "SessionStoreFunctions.h"
+#include "nsISupports.h"
+#include "nsIXULRuntime.h"
+#include "nsImportModule.h"
+#include "nsIXPConnect.h"
+
+#ifdef ANDROID
+# include "mozilla/widget/nsWindow.h"
+# include "mozilla/jni/GeckoBundleUtils.h"
+# include "JavaBuiltins.h"
+#endif /* ANDROID */
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+SessionStoreParent::SessionStoreParent(
+ CanonicalBrowsingContext* aBrowsingContext,
+ BrowserSessionStore* aSessionStore)
+ : mBrowsingContext(aBrowsingContext), mSessionStore(aSessionStore) {}
+
+#ifdef ANDROID
+static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext,
+ const Maybe<nsCString>& aDocShellCaps,
+ const Maybe<bool>& aPrivatedMode,
+ SessionStoreFormData* aFormData,
+ SessionStoreScrollData* aScroll,
+ const MaybeSessionStoreZoom& aZoom,
+ bool aNeedCollectSHistory, uint32_t aEpoch) {
+ RefPtr<BrowserSessionStore> sessionStore =
+ BrowserSessionStore::GetOrCreate(aBrowsingContext->Top());
+
+ nsCOMPtr<nsIWidget> widget =
+ aBrowsingContext->GetParentProcessWidgetContaining();
+ if (RefPtr<nsWindow> window = nsWindow::From(widget)) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::PrivilegedJunkScope())) {
+ return;
+ }
+ jni::Object::LocalRef formDataBundle(jni::GetGeckoThreadEnv());
+ jni::Object::LocalRef scrollBundle(jni::GetGeckoThreadEnv());
+
+ if (aFormData) {
+ JS::Rooted<JSObject*> object(jsapi.cx());
+ ErrorResult rv;
+ aFormData->ToJSON(jsapi.cx(), &object);
+
+ JS::Rooted<JS::Value> value(jsapi.cx(), JS::ObjectValue(*object));
+
+ if (NS_FAILED(jni::BoxData(jsapi.cx(), value, formDataBundle, true))) {
+ JS_ClearPendingException(jsapi.cx());
+ return;
+ }
+ }
+
+ if (aScroll) {
+ JS::Rooted<JSObject*> object(jsapi.cx());
+ ErrorResult rv;
+ aScroll->ToJSON(jsapi.cx(), &object);
+ JS::Rooted<JS::Value> value(jsapi.cx(), JS::ObjectValue(*object));
+
+ if (NS_FAILED(jni::BoxData(jsapi.cx(), value, scrollBundle, true))) {
+ JS_ClearPendingException(jsapi.cx());
+ return;
+ }
+ }
+
+ GECKOBUNDLE_START(update);
+ GECKOBUNDLE_PUT(update, "formdata", formDataBundle);
+ GECKOBUNDLE_PUT(update, "scroll", scrollBundle);
+ if (aZoom) {
+ GECKOBUNDLE_START(zoomBundle);
+ GECKOBUNDLE_PUT(zoomBundle, "resolution",
+ java::sdk::Double::New(std::get<0>(*aZoom)));
+ GECKOBUNDLE_START(displaySizeBundle);
+ GECKOBUNDLE_PUT(displaySizeBundle, "width",
+ java::sdk::Integer::ValueOf(std::get<1>(*aZoom)));
+ GECKOBUNDLE_PUT(displaySizeBundle, "height",
+ java::sdk::Integer::ValueOf(std::get<2>(*aZoom)));
+ GECKOBUNDLE_FINISH(displaySizeBundle);
+ GECKOBUNDLE_PUT(zoomBundle, "displaySize", displaySizeBundle);
+ GECKOBUNDLE_FINISH(zoomBundle);
+ GECKOBUNDLE_PUT(update, "zoom", zoomBundle);
+ }
+ GECKOBUNDLE_FINISH(update);
+
+ window->OnUpdateSessionStore(update);
+ }
+}
+#else
+static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext,
+ const Maybe<nsCString>& aDocShellCaps,
+ const Maybe<bool>& aPrivatedMode,
+ SessionStoreFormData* aFormData,
+ SessionStoreScrollData* aScroll,
+ const MaybeSessionStoreZoom& aZoom,
+ bool aNeedCollectSHistory, uint32_t aEpoch) {
+ UpdateSessionStoreData data;
+ if (aDocShellCaps.isSome()) {
+ auto& disallow = data.mDisallow.Construct();
+ if (!aDocShellCaps->IsEmpty()) {
+ disallow = aDocShellCaps.value();
+ } else {
+ disallow.SetIsVoid(true);
+ }
+ }
+
+ if (aPrivatedMode.isSome()) {
+ data.mIsPrivate.Construct() = aPrivatedMode.value();
+ }
+
+ RefPtr<BrowserSessionStore> sessionStore =
+ BrowserSessionStore::GetOrCreate(aBrowsingContext->Top());
+
+ if (!aFormData) {
+ SessionStoreFormData* formData = sessionStore->GetFormdata();
+ data.mFormdata.Construct(formData);
+ } else {
+ data.mFormdata.Construct(aFormData);
+ }
+
+ if (!aScroll) {
+ SessionStoreScrollData* scroll = sessionStore->GetScroll();
+ data.mScroll.Construct(scroll);
+ } else {
+ data.mScroll.Construct(aScroll);
+ }
+
+ nsCOMPtr<nsISessionStoreFunctions> funcs = do_ImportESModule(
+ "resource://gre/modules/SessionStoreFunctions.sys.mjs", fallible);
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
+ if (!wrapped) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> update(jsapi.cx());
+ if (!ToJSValue(jsapi.cx(), data, &update)) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> key(jsapi.cx(),
+ aBrowsingContext->Top()->PermanentKey());
+
+ Unused << funcs->UpdateSessionStore(nullptr, aBrowsingContext, key, aEpoch,
+ aNeedCollectSHistory, update);
+}
+#endif
+
+void SessionStoreParent::FlushAllSessionStoreChildren(
+ const std::function<void()>& aDone) {
+ if (!mBrowsingContext) {
+ aDone();
+ return;
+ }
+
+ nsTArray<RefPtr<SessionStoreParent::FlushTabStatePromise>> flushPromises;
+
+ // We're special casing this for when the SessionStore{Child, Parent} have
+ // been created in the same process. This is only ever true for the parent
+ // process session store actor, and is needed because
+ // nsFrameLoader::RequestTabStateFlush expect flushes to happen faster than we
+ // can manage by using the common path of sending a message the
+ // SessionStoreChild. Ideally we should be able to do just that, but not
+ // without more work.
+ if (InProcessParent::ChildActorFor(this)) {
+ // Here we assume that the session store data collection only collect for in
+ // (parent-)process content type browsing contexts, which we only flush one
+ // session store actor.
+ flushPromises.AppendElement(FlushSessionStore());
+ } else {
+ // While here we flush all participating actors.
+ BrowserParent* browserParent = static_cast<BrowserParent*>(Manager());
+ browserParent->VisitAll([&flushPromises](BrowserParent* aBrowser) {
+ if (PSessionStoreParent* sessionStoreParent =
+ SingleManagedOrNull(aBrowser->ManagedPSessionStoreParent())) {
+ flushPromises.AppendElement(
+ static_cast<SessionStoreParent*>(sessionStoreParent)
+ ->FlushSessionStore());
+ }
+ });
+ }
+
+ RefPtr<SessionStoreParent::FlushTabStatePromise::AllPromiseType>
+ flushPromise = SessionStoreParent::FlushTabStatePromise::All(
+ GetMainThreadSerialEventTarget(), flushPromises);
+
+ mBrowsingContext->UpdateSessionStoreSessionStorage([aDone, flushPromise]() {
+ flushPromise->Then(GetCurrentSerialEventTarget(), __func__,
+ [aDone]() { aDone(); });
+ });
+}
+
+already_AddRefed<SessionStoreParent::FlushTabStatePromise>
+SessionStoreParent::FlushSessionStore() {
+ if (!mBrowsingContext) {
+ return nullptr;
+ }
+
+ RefPtr<SessionStoreParent::FlushTabStatePromise> promise =
+ SendFlushTabState();
+ return promise.forget();
+}
+
+void SessionStoreParent::FinalFlushAllSessionStoreChildren(
+ const std::function<void()>& aDone) {
+ if (!mBrowsingContext) {
+ aDone();
+ return;
+ }
+
+ SessionStoreChild* sessionStoreChild =
+ static_cast<SessionStoreChild*>(InProcessParent::ChildActorFor(this));
+ if (!sessionStoreChild || mozilla::SessionHistoryInParent()) {
+ return FlushAllSessionStoreChildren(aDone);
+ }
+
+ sessionStoreChild->FlushSessionStore();
+ mBrowsingContext->UpdateSessionStoreSessionStorage(aDone);
+}
+
+mozilla::ipc::IPCResult SessionStoreParent::RecvSessionStoreUpdate(
+ const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
+ const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory,
+ const uint32_t& aEpoch) {
+ if (!mBrowsingContext) {
+ return IPC_OK();
+ }
+
+ RefPtr<SessionStoreFormData> formData =
+ mHasNewFormData ? mSessionStore->GetFormdata() : nullptr;
+ RefPtr<SessionStoreScrollData> scroll =
+ mHasNewScrollPosition ? mSessionStore->GetScroll() : nullptr;
+
+ DoSessionStoreUpdate(mBrowsingContext, aDocShellCaps, aPrivatedMode, formData,
+ scroll, aZoom, aNeedCollectSHistory, aEpoch);
+
+ mHasNewFormData = false;
+ mHasNewScrollPosition = false;
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SessionStoreParent::RecvIncrementalSessionStoreUpdate(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext,
+ const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
+ uint32_t aEpoch) {
+ if (!aBrowsingContext.IsNull()) {
+ if (aFormData.isSome()) {
+ mHasNewFormData = true;
+ }
+ if (aScrollPosition.isSome()) {
+ mHasNewScrollPosition = true;
+ }
+
+ mSessionStore->UpdateSessionStore(
+ aBrowsingContext.GetMaybeDiscarded()->Canonical(), aFormData,
+ aScrollPosition, aEpoch);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SessionStoreParent::RecvResetSessionStore(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext, uint32_t aEpoch) {
+ if (!aBrowsingContext.IsNull()) {
+ mSessionStore->RemoveSessionStore(
+ aBrowsingContext.GetMaybeDiscarded()->Canonical());
+ }
+ return IPC_OK();
+}
+
+void SessionStoreParent::SessionStoreUpdate(
+ const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
+ const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory,
+ const uint32_t& aEpoch) {
+ Unused << RecvSessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom,
+ aNeedCollectSHistory, aEpoch);
+}
+
+void SessionStoreParent::IncrementalSessionStoreUpdate(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext,
+ const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
+ uint32_t aEpoch) {
+ Unused << RecvIncrementalSessionStoreUpdate(aBrowsingContext, aFormData,
+ aScrollPosition, aEpoch);
+}
+
+void SessionStoreParent::ResetSessionStore(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext, uint32_t aEpoch) {
+ Unused << RecvResetSessionStore(aBrowsingContext, aEpoch);
+}
+
+NS_IMPL_CYCLE_COLLECTION(SessionStoreParent, mBrowsingContext, mSessionStore)
diff --git a/toolkit/components/sessionstore/SessionStoreParent.h b/toolkit/components/sessionstore/SessionStoreParent.h
new file mode 100644
index 0000000000..0cf723985e
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreParent.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStoreParent_h
+#define mozilla_dom_SessionStoreParent_h
+
+#include "mozilla/dom/BrowserSessionStore.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/PSessionStoreParent.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/SessionStoreScrollData.h"
+
+namespace mozilla::dom {
+class BrowserParent;
+
+class SessionStoreParent final : public PSessionStoreParent {
+ public:
+ SessionStoreParent(CanonicalBrowsingContext* aBrowsingContext,
+ BrowserSessionStore* aSessionStore);
+
+ void FlushAllSessionStoreChildren(const std::function<void()>& aDone);
+
+ void FinalFlushAllSessionStoreChildren(const std::function<void()>& aDone);
+
+ /**
+ * Sends data to be stored and instructions to the session store to
+ * potentially collect data in the parent.
+ */
+ mozilla::ipc::IPCResult RecvSessionStoreUpdate(
+ const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
+ const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory,
+ const uint32_t& aEpoch);
+
+ mozilla::ipc::IPCResult RecvIncrementalSessionStoreUpdate(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext,
+ const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
+ uint32_t aEpoch);
+
+ mozilla::ipc::IPCResult RecvResetSessionStore(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext, uint32_t aEpoch);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SessionStoreParent)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(SessionStoreParent)
+
+ protected:
+ friend class SessionStoreChild;
+ void SessionStoreUpdate(const Maybe<nsCString>& aDocShellCaps,
+ const Maybe<bool>& aPrivatedMode,
+ const MaybeSessionStoreZoom& aZoom,
+ const bool aNeedCollectSHistory,
+ const uint32_t& aEpoch);
+
+ void IncrementalSessionStoreUpdate(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext,
+ const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
+ uint32_t aEpoch);
+
+ void ResetSessionStore(
+ const MaybeDiscarded<BrowsingContext>& aBrowsingContext, uint32_t aEpoch);
+
+ private:
+ ~SessionStoreParent() = default;
+
+ already_AddRefed<SessionStoreParent::FlushTabStatePromise>
+ FlushSessionStore();
+
+ bool mHasNewFormData = false;
+ bool mHasNewScrollPosition = false;
+
+ RefPtr<CanonicalBrowsingContext> mBrowsingContext;
+ RefPtr<BrowserSessionStore> mSessionStore;
+};
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStoreParent_h
diff --git a/toolkit/components/sessionstore/SessionStoreRestoreData.cpp b/toolkit/components/sessionstore/SessionStoreRestoreData.cpp
new file mode 100644
index 0000000000..fbb96e1b87
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreRestoreData.cpp
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CustomElementTypes.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/SessionStoreUtils.h"
+#include "mozilla/dom/sessionstore/SessionStoreTypes.h"
+#include "mozilla/dom/WindowContext.h"
+#include "nsISessionStoreRestoreData.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace dom {
+
+bool SessionStoreRestoreData::IsEmpty() {
+ return (!mURI && mScroll.IsEmpty() && mInnerHTML.IsEmpty() &&
+ mEntries.IsEmpty() && mChildren.IsEmpty());
+}
+
+SessionStoreRestoreData* SessionStoreRestoreData::FindDataForChild(
+ BrowsingContext* aContext) {
+ nsTArray<uint32_t> path;
+ if (!aContext->GetOffsetPath(path)) {
+ return nullptr;
+ }
+
+ SessionStoreRestoreData* data = this;
+
+ for (uint32_t offset : Reversed(path)) {
+ if (!data || data->mChildren.Length() <= offset ||
+ !data->mChildren[offset] || data->mChildren[offset]->IsEmpty()) {
+ return nullptr;
+ }
+ data = data->mChildren[offset];
+ }
+
+ return data;
+}
+
+bool SessionStoreRestoreData::CanRestoreInto(nsIURI* aDocumentURI) {
+ if (!mURI) {
+ // This should mean that we don't have form data. It's fine to restore this
+ // data into any document — the worst that will happen is that we restore an
+ // incorrect scroll position.
+ MOZ_ASSERT(mEntries.IsEmpty());
+ MOZ_ASSERT(mInnerHTML.IsEmpty());
+ return true;
+ }
+ bool equalsExceptRef = false;
+ return (aDocumentURI &&
+ NS_SUCCEEDED(mURI->EqualsExceptRef(aDocumentURI, &equalsExceptRef)) &&
+ equalsExceptRef);
+}
+
+bool SessionStoreRestoreData::RestoreInto(RefPtr<BrowsingContext> aContext) {
+ if (!aContext->IsInProcess()) {
+ return false;
+ }
+
+ if (WindowContext* window = aContext->GetCurrentWindowContext()) {
+ if (!mScroll.IsEmpty()) {
+ if (nsGlobalWindowInner* inner = window->GetInnerWindow()) {
+ SessionStoreUtils::RestoreScrollPosition(*inner, mScroll);
+ }
+ }
+ if (mURI) {
+ if (nsCOMPtr<Document> doc = window->GetExtantDoc()) {
+ if (!CanRestoreInto(doc->GetDocumentURI())) {
+ return false;
+ }
+ SessionStoreUtils::RestoreFormData(*doc, mInnerHTML, mEntries);
+ }
+ }
+ }
+
+ return true;
+}
+
+void SessionStoreRestoreData::AddFormEntry(
+ bool aIsXPath, const nsAString& aIdOrXPath,
+ sessionstore::FormEntryValue aValue) {
+ mEntries.AppendElement(
+ Entry{sessionstore::FormEntry{nsString(aIdOrXPath), aValue}, aIsXPath});
+}
+
+NS_IMPL_ISUPPORTS(SessionStoreRestoreData, nsISessionStoreRestoreData,
+ SessionStoreRestoreData)
+
+NS_IMETHODIMP
+SessionStoreRestoreData::GetUrl(nsACString& aURL) {
+ if (mURI) {
+ nsresult rv = mURI->GetSpec(aURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::SetUrl(const nsACString& aURL) {
+ nsresult rv = NS_NewURI(getter_AddRefs(mURI), aURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::GetInnerHTML(nsAString& aInnerHTML) {
+ aInnerHTML = mInnerHTML;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::SetInnerHTML(const nsAString& aInnerHTML) {
+ mInnerHTML = aInnerHTML;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::GetScroll(nsACString& aScroll) {
+ aScroll = mScroll;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::SetScroll(const nsACString& aScroll) {
+ mScroll = aScroll;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::AddTextField(bool aIsXPath,
+ const nsAString& aIdOrXPath,
+ const nsAString& aValue) {
+ AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::TextField{nsString(aValue)});
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::AddCheckbox(bool aIsXPath, const nsAString& aIdOrXPath,
+ const bool aValue) {
+ AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::Checkbox{aValue});
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::AddFileList(bool aIsXPath, const nsAString& aIdOrXPath,
+ const nsAString& aType,
+ const nsTArray<nsString>& aFileList) {
+ AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::FileList{aFileList});
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::AddSingleSelect(bool aIsXPath,
+ const nsAString& aIdOrXPath,
+ uint32_t aSelectedIndex,
+ const nsAString& aValue) {
+ AddFormEntry(aIsXPath, aIdOrXPath,
+ sessionstore::SingleSelect{aSelectedIndex, nsString(aValue)});
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::AddMultipleSelect(bool aIsXPath,
+ const nsAString& aIdOrXPath,
+ const nsTArray<nsString>& aValues) {
+ AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::MultipleSelect{aValues});
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::AddCustomElement(bool aIsXPath,
+ const nsAString& aIdOrXPath,
+ JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aState) {
+ AutoJSContext cx;
+ Nullable<OwningFileOrUSVStringOrFormData> value;
+ if (!aValue.isNull() && !value.SetValue().Init(cx, aValue)) {
+ return NS_ERROR_FAILURE;
+ }
+ Nullable<OwningFileOrUSVStringOrFormData> state;
+ if (!aState.isNull() && !state.SetValue().Init(cx, aState)) {
+ return NS_ERROR_FAILURE;
+ }
+ AddFormEntry(aIsXPath, aIdOrXPath,
+ CustomElementTuple(
+ nsContentUtils::ConvertToCustomElementFormValue(value),
+ nsContentUtils::ConvertToCustomElementFormValue(state)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStoreRestoreData::AddChild(nsISessionStoreRestoreData* aChild,
+ uint32_t aIndex) {
+ if (nsCOMPtr<SessionStoreRestoreData> child = do_QueryInterface(aChild)) {
+ if (aIndex > mChildren.Length()) {
+ mChildren.SetLength(aIndex);
+ }
+ mChildren.InsertElementAt(aIndex, child);
+ }
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/toolkit/components/sessionstore/SessionStoreRestoreData.h b/toolkit/components/sessionstore/SessionStoreRestoreData.h
new file mode 100644
index 0000000000..4952045e9d
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreRestoreData.h
@@ -0,0 +1,59 @@
+/* -*- 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_SessionStoreRestoreData_h
+#define mozilla_dom_SessionStoreRestoreData_h
+
+#include "mozilla/dom/sessionstore/SessionStoreTypes.h"
+#include "mozilla/dom/SessionStoreData.h"
+#include "nsISessionStoreRestoreData.h"
+
+namespace mozilla::dom {
+
+#define NS_SESSIONSTORERESTOREDATA_IID \
+ { \
+ 0x88800c5b, 0xe142, 0x4ac6, { \
+ 0x91, 0x52, 0xca, 0xbd, 0x3b, 0x74, 0xb9, 0xf8 \
+ } \
+ }
+
+class SessionStoreRestoreData final : public nsISessionStoreRestoreData {
+ public:
+ SessionStoreRestoreData() = default;
+ bool IsEmpty();
+ SessionStoreRestoreData* FindDataForChild(BrowsingContext* aContext);
+ bool CanRestoreInto(nsIURI* aDocumentURI);
+ MOZ_CAN_RUN_SCRIPT bool RestoreInto(RefPtr<BrowsingContext> aContext);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISESSIONSTORERESTOREDATA
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_SESSIONSTORERESTOREDATA_IID)
+
+ struct Entry {
+ sessionstore::FormEntry mData;
+ bool mIsXPath;
+ };
+
+ private:
+ virtual ~SessionStoreRestoreData() = default;
+ void AddFormEntry(bool aIsXPath, const nsAString& aIdOrXPath,
+ sessionstore::FormEntryValue aValue);
+
+ nsCString mScroll;
+ nsCOMPtr<nsIURI> mURI;
+ nsString mInnerHTML;
+ nsTArray<Entry> mEntries;
+ nsTArray<RefPtr<SessionStoreRestoreData>> mChildren;
+
+ friend struct mozilla::ipc::IPDLParamTraits<SessionStoreRestoreData*>;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SessionStoreRestoreData,
+ NS_SESSIONSTORERESTOREDATA_IID)
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/toolkit/components/sessionstore/SessionStoreScrollData.cpp b/toolkit/components/sessionstore/SessionStoreScrollData.cpp
new file mode 100644
index 0000000000..29d58befaa
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreScrollData.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/SessionStoreScrollData.h"
+#include <utility>
+
+#include "js/PropertyAndElement.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BrowserSessionStoreBinding.h"
+#include "mozilla/dom/BrowsingContext.h"
+
+#include "nsPresContext.h"
+
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
+#include "js/PropertyDescriptor.h"
+
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/GeneratedAtomList.h"
+#include "js/StructuredClone.h"
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "nsContentUtils.h"
+#include "js/Array.h"
+#include "js/JSON.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStoreScrollData)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStoreScrollData)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(SessionStoreScrollData,
+ mChildren)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStoreScrollData)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsISupports* SessionStoreScrollData::GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+ ;
+}
+
+JSObject* SessionStoreScrollData::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return SessionStoreScrollData_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void SessionStoreScrollData::GetScroll(nsACString& aScroll) const {
+ int scrollX = nsPresContext::AppUnitsToIntCSSPixels(mScroll.x);
+ int scrollY = nsPresContext::AppUnitsToIntCSSPixels(mScroll.y);
+
+ if ((scrollX != 0) || (scrollY != 0)) {
+ aScroll = nsPrintfCString("%d,%d", scrollX, scrollY);
+ } else {
+ aScroll.SetIsVoid(true);
+ }
+}
+
+SessionStoreScrollData::ChildrenArray& SessionStoreScrollData::Children() {
+ return mChildren;
+}
+
+void SessionStoreScrollData::GetChildren(
+ Nullable<ChildrenArray>& aChildren) const {
+ if (!mChildren.IsEmpty()) {
+ aChildren.SetValue() = mChildren.Clone();
+ } else {
+ aChildren.SetNull();
+ }
+}
+
+void SessionStoreScrollData::ToJSON(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval) {
+ JS::Rooted<JSObject*> self(aCx);
+ {
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, this, &value)) {
+ return;
+ }
+
+ self.set(value.toObjectOrNull());
+ }
+
+ JS::Rooted<JSObject*> result(aCx, JS_NewPlainObject(aCx));
+
+ if (!IsEmpty()) {
+ if (HasData(mScroll)) {
+ if (!SessionStoreUtils::CopyProperty(aCx, result, self, u"scroll"_ns)) {
+ return;
+ }
+ }
+
+ SessionStoreUtils::CopyChildren(aCx, result, mChildren);
+ }
+
+ aRetval.set(result);
+}
+
+void SessionStoreScrollData::Update(const CollectedType& aUpdate) {
+ SessionStoreScrollData_Binding::ClearCachedScrollValue(this);
+ mScroll = aUpdate;
+}
+
+void SessionStoreScrollData::ClearCachedChildren() {
+ SessionStoreScrollData_Binding::ClearCachedChildrenValue(this);
+}
+
+/* static */
+bool SessionStoreScrollData::HasData(const CollectedType& aPoint) {
+ int scrollX = nsPresContext::AppUnitsToIntCSSPixels(aPoint.x);
+ int scrollY = nsPresContext::AppUnitsToIntCSSPixels(aPoint.y);
+ return scrollX != 0 || scrollY != 0;
+}
+
+bool SessionStoreScrollData::IsEmpty() const {
+ return !HasData(mScroll) && mChildren.IsEmpty();
+}
diff --git a/toolkit/components/sessionstore/SessionStoreScrollData.h b/toolkit/components/sessionstore/SessionStoreScrollData.h
new file mode 100644
index 0000000000..b2f5d4bd32
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreScrollData.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStoreScrollData_h
+#define mozilla_dom_SessionStoreScrollData_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/WeakPtr.h"
+#include "nsPoint.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/SessionStoreChangeListener.h"
+
+namespace mozilla::dom {
+
+class BrowsingContext;
+class WindowGlobalParent;
+class OwningByteStringOrObjectOrNull;
+struct SessionStoreZoomData;
+
+using SessionStoreZoom = std::tuple<float, uint32_t, uint32_t>;
+using MaybeSessionStoreZoom =
+ mozilla::Maybe<std::tuple<float, uint32_t, uint32_t>>;
+
+class SessionStoreScrollData final : public nsISupports,
+ public nsWrapperCache,
+ public SupportsWeakPtr {
+ public:
+ using CollectedType = nsPoint;
+ using LocationType = WeakPtr<SessionStoreScrollData>;
+ using ChildrenArray = nsTArray<RefPtr<SessionStoreScrollData>>;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SessionStoreScrollData)
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetScroll(nsACString& aScroll) const;
+
+ ChildrenArray& Children();
+
+ void GetChildren(Nullable<ChildrenArray>& aChildren) const;
+
+ void ToJSON(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval);
+
+ void Update(const CollectedType& aUpdate);
+
+ void ClearCachedChildren();
+
+ static bool HasData(const CollectedType& aPoint);
+
+ bool IsEmpty() const;
+
+ private:
+ ~SessionStoreScrollData() = default;
+
+ nsPoint mScroll;
+ MaybeSessionStoreZoom mZoom;
+ nsTArray<RefPtr<SessionStoreScrollData>> mChildren;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStoreScrollData_h
diff --git a/toolkit/components/sessionstore/SessionStoreTypes.ipdlh b/toolkit/components/sessionstore/SessionStoreTypes.ipdlh
new file mode 100644
index 0000000000..331005b11a
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreTypes.ipdlh
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+using struct CollectedInputDataValue from "mozilla/dom/SessionStoreMessageUtils.h";
+using struct nsPoint from "nsPoint.h";
+
+include CustomElementTypes;
+include DOMTypes;
+
+namespace mozilla {
+namespace dom {
+
+namespace sessionstore {
+struct Checkbox {
+ bool value;
+};
+
+struct TextField {
+ nsString value;
+};
+
+struct FileList {
+ nsString[] valueList;
+};
+
+struct SingleSelect {
+ uint32_t index;
+ nsString value;
+};
+
+struct MultipleSelect {
+ nsString[] valueList;
+};
+
+union FormEntryValue {
+ Checkbox;
+ TextField;
+ FileList;
+ SingleSelect;
+ MultipleSelect;
+ CustomElementTuple;
+};
+
+struct FormEntry {
+ nsString id;
+ FormEntryValue value;
+};
+
+struct FormData {
+ bool hasData;
+ FormEntry[] id;
+ FormEntry[] xpath;
+ nsString innerHTML;
+ nsCString uri;
+};
+
+struct DocShellRestoreState {
+ nullable nsIURI URI;
+ nsCString docShellCaps;
+};
+
+} // sessionstore
+
+} // dom
+} // mozilla
diff --git a/toolkit/components/sessionstore/SessionStoreUtils.cpp b/toolkit/components/sessionstore/SessionStoreUtils.cpp
new file mode 100644
index 0000000000..3c94a54261
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp
@@ -0,0 +1,1948 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
+#include "js/JSON.h"
+#include "js/Object.h"
+#include "js/PropertyAndElement.h" // JS_GetElement
+#include "js/TypeDecls.h"
+#include "jsapi.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/AutocompleteInfoBinding.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/CustomElementTypes.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/FragmentOrElement.h"
+#include "mozilla/dom/HTMLElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "mozilla/dom/HTMLTextAreaElement.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/dom/PBackgroundSessionStorageCache.h"
+#include "mozilla/dom/SessionStoreChangeListener.h"
+#include "mozilla/dom/SessionStoreChild.h"
+#include "mozilla/dom/SessionStoreUtils.h"
+#include "mozilla/dom/SessionStoreUtilsBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/sessionstore/SessionStoreTypes.h"
+#include "mozilla/dom/txIXPathContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/XPathResult.h"
+#include "mozilla/dom/XPathEvaluator.h"
+#include "mozilla/dom/XPathExpression.h"
+#include "mozilla/dom/PBackgroundSessionStorageCache.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ReverseIterator.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentList.h"
+#include "nsContentUtils.h"
+#include "nsFocusManager.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIContentInlines.h"
+#include "nsIDocShell.h"
+#include "nsIFormControl.h"
+#include "nsIScrollableFrame.h"
+#include "nsISHistory.h"
+#include "nsIXULRuntime.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "nsDocShell.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::sessionstore;
+
+namespace {
+
+class DynamicFrameEventFilter final : public nsIDOMEventListener {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(DynamicFrameEventFilter)
+
+ explicit DynamicFrameEventFilter(EventListener* aListener)
+ : mListener(aListener) {}
+
+ NS_IMETHODIMP HandleEvent(Event* aEvent) override {
+ if (mListener && TargetInNonDynamicDocShell(aEvent)) {
+ mListener->HandleEvent(*aEvent);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ ~DynamicFrameEventFilter() = default;
+
+ bool TargetInNonDynamicDocShell(Event* aEvent) {
+ EventTarget* target = aEvent->GetTarget();
+ if (!target) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* outer = target->GetOwnerGlobalForBindingsInternal();
+ if (!outer || !outer->GetDocShell()) {
+ return false;
+ }
+
+ RefPtr<BrowsingContext> context = outer->GetBrowsingContext();
+ return context && !context->CreatedDynamically();
+ }
+
+ RefPtr<EventListener> mListener;
+};
+
+NS_IMPL_CYCLE_COLLECTION(DynamicFrameEventFilter, mListener)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DynamicFrameEventFilter)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DynamicFrameEventFilter)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DynamicFrameEventFilter)
+
+} // anonymous namespace
+
+/* static */
+void SessionStoreUtils::ForEachNonDynamicChildFrame(
+ const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
+ SessionStoreUtilsFrameCallback& aCallback, ErrorResult& aRv) {
+ if (!aWindow.get()) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow.get()->GetDocShell();
+ if (!docShell) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ int32_t length;
+ aRv = docShell->GetInProcessChildCount(&length);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ for (int32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ docShell->GetInProcessChildAt(i, getter_AddRefs(item));
+ if (!item) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<BrowsingContext> context = item->GetBrowsingContext();
+ if (!context) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (!context->CreatedDynamically()) {
+ int32_t childOffset = context->ChildOffset();
+ aCallback.Call(WindowProxyHolder(context.forget()), childOffset);
+ }
+ }
+}
+
+/* static */
+already_AddRefed<nsISupports>
+SessionStoreUtils::AddDynamicFrameFilteredListener(
+ const GlobalObject& aGlobal, EventTarget& aTarget, const nsAString& aType,
+ JS::Handle<JS::Value> aListener, bool aUseCapture, bool aMozSystemGroup,
+ ErrorResult& aRv) {
+ if (NS_WARN_IF(!aListener.isObject())) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> obj(cx, &aListener.toObject());
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ RefPtr<EventListener> listener =
+ new EventListener(cx, obj, global, GetIncumbentGlobal());
+
+ nsCOMPtr<nsIDOMEventListener> filter(new DynamicFrameEventFilter(listener));
+ if (aMozSystemGroup) {
+ aRv = aTarget.AddSystemEventListener(aType, filter, aUseCapture);
+ } else {
+ aRv = aTarget.AddEventListener(aType, filter, aUseCapture);
+ }
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return filter.forget();
+}
+
+/* static */
+void SessionStoreUtils::RemoveDynamicFrameFilteredListener(
+ const GlobalObject& global, EventTarget& aTarget, const nsAString& aType,
+ nsISupports* aListener, bool aUseCapture, bool aMozSystemGroup,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIDOMEventListener> listener = do_QueryInterface(aListener);
+ if (!listener) {
+ aRv.Throw(NS_ERROR_NO_INTERFACE);
+ return;
+ }
+
+ if (aMozSystemGroup) {
+ aTarget.RemoveSystemEventListener(aType, listener, aUseCapture);
+ } else {
+ aTarget.RemoveEventListener(aType, listener, aUseCapture);
+ }
+}
+
+/* static */
+void SessionStoreUtils::CollectDocShellCapabilities(const GlobalObject& aGlobal,
+ nsIDocShell* aDocShell,
+ nsCString& aRetVal) {
+ bool allow;
+
+#define TRY_ALLOWPROP(y) \
+ PR_BEGIN_MACRO \
+ aDocShell->GetAllow##y(&allow); \
+ if (!allow) { \
+ if (!aRetVal.IsEmpty()) { \
+ aRetVal.Append(','); \
+ } \
+ aRetVal.Append(#y); \
+ } \
+ PR_END_MACRO
+
+ // 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
+}
+
+/* static */
+void SessionStoreUtils::RestoreDocShellCapabilities(
+ nsIDocShell* aDocShell, const nsCString& aDisallowCapabilities) {
+ aDocShell->SetAllowMetaRedirects(true);
+ aDocShell->SetAllowSubframes(true);
+ aDocShell->SetAllowImages(true);
+ aDocShell->SetAllowMedia(true);
+ aDocShell->SetAllowDNSPrefetch(true);
+ aDocShell->SetAllowWindowControl(true);
+ aDocShell->SetAllowContentRetargeting(true);
+ aDocShell->SetAllowContentRetargetingOnChildren(true);
+
+ bool allowJavascript = true;
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(aDisallowCapabilities, ',').ToRange()) {
+ if (token.EqualsLiteral("Javascript")) {
+ allowJavascript = false;
+ } else if (token.EqualsLiteral("MetaRedirects")) {
+ aDocShell->SetAllowMetaRedirects(false);
+ } else if (token.EqualsLiteral("Subframes")) {
+ aDocShell->SetAllowSubframes(false);
+ } else if (token.EqualsLiteral("Images")) {
+ aDocShell->SetAllowImages(false);
+ } else if (token.EqualsLiteral("Media")) {
+ aDocShell->SetAllowMedia(false);
+ } else if (token.EqualsLiteral("DNSPrefetch")) {
+ aDocShell->SetAllowDNSPrefetch(false);
+ } else if (token.EqualsLiteral("WindowControl")) {
+ aDocShell->SetAllowWindowControl(false);
+ } else if (token.EqualsLiteral("ContentRetargeting")) {
+ bool allow;
+ aDocShell->GetAllowContentRetargetingOnChildren(&allow);
+ aDocShell->SetAllowContentRetargeting(
+ false); // will also set AllowContentRetargetingOnChildren
+ aDocShell->SetAllowContentRetargetingOnChildren(
+ allow); // restore the allowProp to original
+ } else if (token.EqualsLiteral("ContentRetargetingOnChildren")) {
+ aDocShell->SetAllowContentRetargetingOnChildren(false);
+ }
+ }
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // With SessionHistoryInParent, this is set from the parent process.
+ BrowsingContext* bc = aDocShell->GetBrowsingContext();
+ Unused << bc->SetAllowJavascript(allowJavascript);
+ }
+}
+
+static void CollectCurrentScrollPosition(JSContext* aCx, Document& aDocument,
+ Nullable<CollectedData>& aRetVal) {
+ PresShell* presShell = aDocument.GetPresShell();
+ if (!presShell) {
+ return;
+ }
+ nsPoint scrollPos = presShell->GetVisualViewportOffset();
+ int scrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x);
+ int scrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y);
+
+ if ((scrollX != 0) || (scrollY != 0)) {
+ aRetVal.SetValue().mScroll.Construct() =
+ nsPrintfCString("%d,%d", scrollX, scrollY);
+ }
+}
+
+/* static */
+void SessionStoreUtils::RestoreScrollPosition(const GlobalObject& aGlobal,
+ nsGlobalWindowInner& aWindow,
+ const CollectedData& aData) {
+ if (aData.mScroll.WasPassed()) {
+ RestoreScrollPosition(aWindow, aData.mScroll.Value());
+ }
+}
+
+/* static */
+void SessionStoreUtils::RestoreScrollPosition(
+ nsGlobalWindowInner& aWindow, const nsCString& aScrollPosition) {
+ using Change = mozilla::dom::SessionStoreChangeListener::Change;
+ SessionStoreChangeListener::CollectSessionStoreData(
+ aWindow.GetWindowContext(), EnumSet<Change>(Change::Scroll));
+
+ nsCCharSeparatedTokenizer tokenizer(aScrollPosition, ',');
+ nsAutoCString token(tokenizer.nextToken());
+ int pos_X = atoi(token.get());
+ token = tokenizer.nextToken();
+ int pos_Y = atoi(token.get());
+
+ aWindow.ScrollTo(pos_X, pos_Y);
+
+ if (nsCOMPtr<Document> doc = aWindow.GetExtantDoc()) {
+ if (nsPresContext* presContext = doc->GetPresContext()) {
+ if (presContext->IsRootContentDocumentCrossProcess()) {
+ // Use eMainThread so this takes precedence over session history
+ // (ScrollFrameHelper::ScrollToRestoredPosition()).
+ presContext->PresShell()->ScrollToVisual(
+ CSSPoint::ToAppUnits(CSSPoint(pos_X, pos_Y)),
+ layers::FrameMetrics::eMainThread, ScrollMode::Instant);
+ }
+ }
+ }
+}
+
+// Implements the Luhn checksum algorithm as described at
+// http://wikipedia.org/wiki/Luhn_algorithm
+// Number digit lengths vary with network, but should fall within 12-19 range.
+// [2] More details at https://en.wikipedia.org/wiki/Payment_card_number
+static bool IsValidCCNumber(nsAString& aValue) {
+ uint32_t total = 0;
+ uint32_t numLength = 0;
+ uint32_t strLen = aValue.Length();
+ for (uint32_t i = 0; i < strLen; ++i) {
+ uint32_t idx = strLen - i - 1;
+ // ignore whitespace and dashes)
+ char16_t chr = aValue[idx];
+ if (IsSpaceCharacter(chr) || chr == '-') {
+ continue;
+ }
+ // If our number is too long, note that fact
+ ++numLength;
+ if (numLength > 19) {
+ return false;
+ }
+ // Try to parse the character as a base-10 integer.
+ nsresult rv = NS_OK;
+ uint32_t val = Substring(aValue, idx, 1).ToInteger(&rv, 10);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ if (i % 2 == 1) {
+ val *= 2;
+ if (val > 9) {
+ val -= 9;
+ }
+ }
+ total += val;
+ }
+
+ return numLength >= 12 && total % 10 == 0;
+}
+
+// Limit the number of XPath expressions for performance reasons. See bug
+// 477564.
+static const uint16_t kMaxTraversedXPaths = 100;
+
+// A helper function to append a element into mId or mXpath of CollectedData
+static Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
+AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId,
+ uint16_t& aGeneratedCount,
+ Nullable<CollectedData>& aRetVal) {
+ Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry;
+ if (!aId.IsEmpty()) {
+ if (!aRetVal.SetValue().mId.WasPassed()) {
+ aRetVal.SetValue().mId.Construct();
+ }
+ auto& recordEntries = aRetVal.SetValue().mId.Value().Entries();
+ entry = recordEntries.AppendElement();
+ entry->mKey = aId;
+ } else {
+ if (!aRetVal.SetValue().mXpath.WasPassed()) {
+ aRetVal.SetValue().mXpath.Construct();
+ }
+ auto& recordEntries = aRetVal.SetValue().mXpath.Value().Entries();
+ entry = recordEntries.AppendElement();
+ nsAutoString xpath;
+ aNode->GenerateXPath(xpath);
+ aGeneratedCount++;
+ entry->mKey = xpath;
+ }
+ return entry;
+}
+
+/* for bool value */
+static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
+ const bool& aValue,
+ uint16_t& aGeneratedCount,
+ JSContext* aCx,
+ Nullable<CollectedData>& aRetVal) {
+ Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+ AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
+ entry->mValue.SetAsBoolean() = aValue;
+}
+
+/* for nsString value */
+static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
+ const nsString& aValue,
+ uint16_t& aGeneratedCount,
+ Nullable<CollectedData>& aRetVal) {
+ Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+ AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
+ entry->mValue.SetAsString() = aValue;
+}
+
+/* for single select value */
+static void AppendValueToCollectedData(
+ nsINode* aNode, const nsAString& aId,
+ const CollectedNonMultipleSelectValue& aValue, uint16_t& aGeneratedCount,
+ JSContext* aCx, Nullable<CollectedData>& aRetVal) {
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, aValue, &jsval)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+ AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
+ entry->mValue.SetAsObject() = &jsval.toObject();
+}
+
+/* special handing for input element with string type */
+static void AppendValueToCollectedData(Document& aDocument, nsINode* aNode,
+ const nsAString& aId,
+ const nsString& aValue,
+ uint16_t& aGeneratedCount,
+ JSContext* aCx,
+ Nullable<CollectedData>& aRetVal) {
+ if (!aId.IsEmpty()) {
+ // We want to avoid saving data for about:sessionrestore as a string.
+ // Since it's stored in the form as stringified JSON, stringifying
+ // further causes an explosion of escape characters. cf. bug 467409
+ if (aId.EqualsLiteral("sessionData")) {
+ nsAutoCString url;
+ Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
+ if (url.EqualsLiteral("about:sessionrestore") ||
+ url.EqualsLiteral("about:welcomeback")) {
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (JS_ParseJSON(aCx, aValue.get(), aValue.Length(), &jsval) &&
+ jsval.isObject()) {
+ Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+ AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ return;
+ }
+ }
+ }
+ AppendValueToCollectedData(aNode, aId, aValue, aGeneratedCount, aRetVal);
+}
+
+/* for nsTArray<nsString>: file and multipleSelect */
+static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
+ const nsAString& aValueType,
+ nsTArray<nsString>& aValue,
+ uint16_t& aGeneratedCount,
+ JSContext* aCx,
+ Nullable<CollectedData>& aRetVal) {
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (aValueType.EqualsLiteral("file")) {
+ CollectedFileListValue val;
+ val.mType = aValueType;
+ val.mFileList = std::move(aValue);
+ if (!ToJSValue(aCx, val, &jsval)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ } else {
+ if (!ToJSValue(aCx, aValue, &jsval)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ }
+ Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+ AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
+ entry->mValue.SetAsObject() = &jsval.toObject();
+}
+
+/* For form-associated custom element state */
+static void AppendValueToCollectedData(
+ nsINode* aNode, const nsAString& aId,
+ const Nullable<OwningFileOrUSVStringOrFormData>& aValue,
+ const Nullable<OwningFileOrUSVStringOrFormData>& aState,
+ uint16_t& aGeneratedCount, JSContext* aCx,
+ Nullable<CollectedData>& aRetVal) {
+ Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+ AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal);
+
+ CollectedCustomElementValue val;
+ val.mValue = aValue;
+ val.mState = aState;
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, val, &jsval)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+}
+
+// This isn't size as in binary size, just a heuristic to not store too large
+// fields in session store. See StaticPrefs::browser_sessionstore_dom_form_limit
+static uint32_t SizeOfFormEntry(const FormEntryValue& aValue) {
+ uint32_t size = 0;
+ switch (aValue.type()) {
+ case FormEntryValue::TCheckbox:
+ size = aValue.get_Checkbox().value() ? 4 : 5;
+ break;
+ case FormEntryValue::TTextField:
+ size = aValue.get_TextField().value().Length();
+ break;
+ case FormEntryValue::TFileList: {
+ for (const auto& value : aValue.get_FileList().valueList()) {
+ size += value.Length();
+ }
+ break;
+ }
+ case FormEntryValue::TSingleSelect:
+ size = aValue.get_SingleSelect().value().Length();
+ break;
+ case FormEntryValue::TMultipleSelect: {
+ for (const auto& value : aValue.get_MultipleSelect().valueList()) {
+ size += value.Length();
+ }
+ break;
+ }
+ case FormEntryValue::TCustomElementTuple: {
+ auto customElementTupleSize =
+ [](const CustomElementFormValue& value) -> uint32_t {
+ switch (value.type()) {
+ case CustomElementFormValue::TBlobImpl:
+ return value.get_BlobImpl()->GetAllocationSize();
+ case CustomElementFormValue::TnsString:
+ return value.get_nsString().Length();
+ case CustomElementFormValue::TArrayOfFormDataTuple: {
+ uint32_t formDataSize = 0;
+ for (const auto& entry : value.get_ArrayOfFormDataTuple()) {
+ formDataSize += entry.name().Length();
+ const auto& entryValue = entry.value();
+ switch (entryValue.type()) {
+ case FormDataValue::TBlobImpl:
+ formDataSize +=
+ entryValue.get_BlobImpl()->GetAllocationSize();
+ break;
+ case FormDataValue::TnsString:
+ formDataSize += entryValue.get_nsString().Length();
+ break;
+ default:
+ break;
+ }
+ }
+ return formDataSize;
+ }
+ default:
+ return 0;
+ }
+ };
+
+ auto ceTuple = aValue.get_CustomElementTuple();
+ size += customElementTupleSize(ceTuple.value());
+ size += customElementTupleSize(ceTuple.state());
+ break;
+ }
+ default:
+ break;
+ }
+ return size;
+}
+
+static uint32_t AppendEntry(nsINode* aNode, const nsString& aId,
+ const FormEntryValue& aValue,
+ sessionstore::FormData& aFormData) {
+ uint32_t size = SizeOfFormEntry(aValue);
+ if (size > StaticPrefs::browser_sessionstore_dom_form_limit()) {
+ return 0;
+ }
+
+ if (aId.IsEmpty()) {
+ FormEntry* entry = aFormData.xpath().AppendElement();
+ entry->value() = aValue;
+ aNode->GenerateXPath(entry->id());
+ size += entry->id().Length();
+ } else {
+ aFormData.id().AppendElement(FormEntry{aId, aValue});
+ size += aId.Length();
+ }
+
+ return size;
+}
+
+static uint32_t CollectTextAreaElement(Document* aDocument,
+ sessionstore::FormData& aFormData) {
+ uint32_t size = 0;
+ RefPtr<nsContentList> textlist =
+ NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"textarea"_ns);
+ uint32_t length = textlist->Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ MOZ_ASSERT(textlist->Item(i), "null item in node list!");
+
+ auto* textArea = HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i));
+ if (!textArea) {
+ continue;
+ }
+ DOMString autocomplete;
+ textArea->GetAutocomplete(autocomplete);
+ if (autocomplete.AsAString().EqualsLiteral("off")) {
+ continue;
+ }
+ nsAutoString id;
+ textArea->GetId(id);
+ if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
+ continue;
+ }
+ nsString value;
+ textArea->GetValue(value);
+ // In order to reduce XPath generation (which is slow), we only save data
+ // for form fields that have been changed. (cf. bug 537289)
+ if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
+ eCaseMatters)) {
+ continue;
+ }
+
+ size += AppendEntry(textArea, id, TextField{value}, aFormData);
+ }
+
+ return size;
+}
+
+static uint32_t CollectInputElement(Document* aDocument,
+ sessionstore::FormData& aFormData) {
+ uint32_t size = 0;
+ RefPtr<nsContentList> inputlist =
+ NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"input"_ns);
+ uint32_t length = inputlist->Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ MOZ_ASSERT(inputlist->Item(i), "null item in node list!");
+ nsCOMPtr<nsIFormControl> formControl =
+ do_QueryInterface(inputlist->Item(i));
+ if (formControl) {
+ auto controlType = formControl->ControlType();
+ if (controlType == FormControlType::InputPassword ||
+ controlType == FormControlType::InputHidden ||
+ controlType == FormControlType::InputButton ||
+ controlType == FormControlType::InputImage ||
+ controlType == FormControlType::InputSubmit ||
+ controlType == FormControlType::InputReset) {
+ continue;
+ }
+ }
+ RefPtr<HTMLInputElement> input =
+ HTMLInputElement::FromNodeOrNull(inputlist->Item(i));
+ if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) {
+ continue;
+ }
+ nsAutoString id;
+ input->GetId(id);
+ if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
+ continue;
+ }
+ Nullable<AutocompleteInfo> aInfo;
+ input->GetAutocompleteInfo(aInfo);
+ if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) {
+ continue;
+ }
+
+ FormEntryValue value;
+ if (input->ControlType() == FormControlType::InputCheckbox ||
+ input->ControlType() == FormControlType::InputRadio) {
+ bool checked = input->Checked();
+ if (checked == input->DefaultChecked()) {
+ continue;
+ }
+ size += AppendEntry(input, id, Checkbox{checked}, aFormData);
+ } else if (input->ControlType() == FormControlType::InputFile) {
+ IgnoredErrorResult rv;
+ sessionstore::FileList file;
+ input->MozGetFileNameArray(file.valueList(), rv);
+ if (rv.Failed() || file.valueList().IsEmpty()) {
+ continue;
+ }
+ size += AppendEntry(input, id, file, aFormData);
+ } else {
+ TextField field;
+ input->GetValue(field.value(), CallerType::System);
+ auto& value = field.value();
+ // In order to reduce XPath generation (which is slow), we only save data
+ // for form fields that have been changed. (cf. bug 537289)
+ // Also, don't want to collect credit card number.
+ if (value.IsEmpty() || IsValidCCNumber(value) ||
+ input->HasBeenTypePassword() ||
+ input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
+ eCaseMatters)) {
+ continue;
+ }
+ size += AppendEntry(input, id, field, aFormData);
+ }
+ }
+
+ return size;
+}
+
+static uint32_t CollectSelectElement(Document* aDocument,
+ sessionstore::FormData& aFormData) {
+ uint32_t size = 0;
+ RefPtr<nsContentList> selectlist =
+ NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"select"_ns);
+ uint32_t length = selectlist->Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ MOZ_ASSERT(selectlist->Item(i), "null item in node list!");
+ RefPtr<HTMLSelectElement> select =
+ HTMLSelectElement::FromNodeOrNull(selectlist->Item(i));
+ if (!select) {
+ continue;
+ }
+ nsAutoString id;
+ select->GetId(id);
+ if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
+ continue;
+ }
+ AutocompleteInfo aInfo;
+ select->GetAutocompleteInfo(aInfo);
+ if (!aInfo.mCanAutomaticallyPersist) {
+ continue;
+ }
+
+ if (!select->Multiple()) {
+ HTMLOptionsCollection* options = select->GetOptions();
+ if (!options) {
+ continue;
+ }
+
+ uint32_t numOptions = options->Length();
+ int32_t defaultIndex = 0;
+ for (uint32_t idx = 0; idx < numOptions; idx++) {
+ HTMLOptionElement* option = options->ItemAsOption(idx);
+ if (option->DefaultSelected()) {
+ defaultIndex = option->Index();
+ }
+ }
+
+ int32_t selectedIndex = select->SelectedIndex();
+ if (selectedIndex == defaultIndex || selectedIndex < 0) {
+ continue;
+ }
+
+ DOMString selectVal;
+ select->GetValue(selectVal);
+ size += AppendEntry(select, id,
+ SingleSelect{static_cast<uint32_t>(selectedIndex),
+ selectVal.AsAString()},
+ aFormData);
+ } else {
+ HTMLOptionsCollection* options = select->GetOptions();
+ if (!options) {
+ continue;
+ }
+ bool hasDefaultValue = true;
+ nsTArray<nsString> selectslist;
+ uint32_t numOptions = options->Length();
+ for (uint32_t idx = 0; idx < numOptions; idx++) {
+ HTMLOptionElement* option = options->ItemAsOption(idx);
+ bool selected = option->Selected();
+
+ hasDefaultValue =
+ hasDefaultValue && (selected == option->DefaultSelected());
+
+ if (!selected) {
+ continue;
+ }
+ option->GetValue(*selectslist.AppendElement());
+ }
+ // In order to reduce XPath generation (which is slow), we only save data
+ // for form fields that have been changed. (cf. bug 537289)
+ if (hasDefaultValue) {
+ continue;
+ }
+
+ size += AppendEntry(select, id, MultipleSelect{selectslist}, aFormData);
+ }
+ }
+
+ return size;
+}
+
+static already_AddRefed<nsContentList> GetFormAssociatedCustomElements(
+ nsINode* aRootNode) {
+ MOZ_ASSERT(aRootNode, "Content list has to have a root");
+
+ auto matchFunc = [](Element* aElement, int32_t aNamespace, nsAtom* aAtom,
+ void* aData) -> bool {
+ return aElement->HasCustomElementData() &&
+ aElement->GetCustomElementData()->IsFormAssociated();
+ };
+ RefPtr<nsContentList> list =
+ new nsContentList(aRootNode, matchFunc, nullptr, nullptr);
+ return list.forget();
+}
+
+static uint32_t CollectFormAssociatedCustomElement(
+ Document* aDocument, sessionstore::FormData& aFormData) {
+ uint32_t size = 0;
+ RefPtr<nsContentList> faceList = GetFormAssociatedCustomElements(aDocument);
+ uint32_t length = faceList->Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ MOZ_ASSERT(faceList->Item(i), "null item in node list!");
+ RefPtr<Element> element = Element::FromNode(faceList->Item(i));
+
+ nsAutoString id;
+ element->GetId(id);
+ if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
+ continue;
+ }
+
+ const auto* internals =
+ element->GetCustomElementData()->GetElementInternals();
+ auto formState = internals->GetFormState();
+ auto formValue = internals->GetFormSubmissionValue();
+ if (formState.IsNull() && formValue.IsNull()) {
+ continue;
+ }
+
+ CustomElementTuple entry;
+ entry.value() = nsContentUtils::ConvertToCustomElementFormValue(formValue);
+ entry.state() = nsContentUtils::ConvertToCustomElementFormValue(formState);
+ size += AppendEntry(element, id, entry, aFormData);
+ }
+ return size;
+}
+
+/* static */
+uint32_t SessionStoreUtils::CollectFormData(Document* aDocument,
+ sessionstore::FormData& aFormData) {
+ MOZ_DIAGNOSTIC_ASSERT(aDocument);
+ uint32_t size = 0;
+ size += CollectTextAreaElement(aDocument, aFormData);
+ size += CollectInputElement(aDocument, aFormData);
+ size += CollectSelectElement(aDocument, aFormData);
+ size += CollectFormAssociatedCustomElement(aDocument, aFormData);
+
+ aFormData.hasData() =
+ !aFormData.id().IsEmpty() || !aFormData.xpath().IsEmpty();
+
+ return size;
+}
+
+/* static */
+template <typename... ArgsT>
+void SessionStoreUtils::CollectFromTextAreaElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args) {
+ RefPtr<nsContentList> textlist =
+ NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"textarea"_ns);
+ uint32_t length = textlist->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ MOZ_ASSERT(textlist->Item(i), "null item in node list!");
+
+ HTMLTextAreaElement* textArea =
+ HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i));
+ if (!textArea) {
+ continue;
+ }
+ DOMString autocomplete;
+ textArea->GetAutocomplete(autocomplete);
+ if (autocomplete.AsAString().EqualsLiteral("off")) {
+ continue;
+ }
+ nsAutoString id;
+ textArea->GetId(id);
+ if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
+ continue;
+ }
+ nsString value;
+ textArea->GetValue(value);
+ // In order to reduce XPath generation (which is slow), we only save data
+ // for form fields that have been changed. (cf. bug 537289)
+ if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
+ eCaseMatters)) {
+ continue;
+ }
+ AppendValueToCollectedData(textArea, id, value, aGeneratedCount,
+ std::forward<ArgsT>(args)...);
+ }
+}
+
+/* static */
+template <typename... ArgsT>
+void SessionStoreUtils::CollectFromInputElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args) {
+ RefPtr<nsContentList> inputlist =
+ NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"input"_ns);
+ uint32_t length = inputlist->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ MOZ_ASSERT(inputlist->Item(i), "null item in node list!");
+ nsCOMPtr<nsIFormControl> formControl =
+ do_QueryInterface(inputlist->Item(i));
+ if (formControl) {
+ auto controlType = formControl->ControlType();
+ if (controlType == FormControlType::InputPassword ||
+ controlType == FormControlType::InputHidden ||
+ controlType == FormControlType::InputButton ||
+ controlType == FormControlType::InputImage ||
+ controlType == FormControlType::InputSubmit ||
+ controlType == FormControlType::InputReset) {
+ continue;
+ }
+ }
+ RefPtr<HTMLInputElement> input =
+ HTMLInputElement::FromNodeOrNull(inputlist->Item(i));
+ if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) {
+ continue;
+ }
+ nsAutoString id;
+ input->GetId(id);
+ if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
+ continue;
+ }
+ Nullable<AutocompleteInfo> aInfo;
+ input->GetAutocompleteInfo(aInfo);
+ if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) {
+ continue;
+ }
+
+ if (input->ControlType() == FormControlType::InputCheckbox ||
+ input->ControlType() == FormControlType::InputRadio) {
+ bool checked = input->Checked();
+ if (checked == input->DefaultChecked()) {
+ continue;
+ }
+ AppendValueToCollectedData(input, id, checked, aGeneratedCount,
+ std::forward<ArgsT>(args)...);
+ } else if (input->ControlType() == FormControlType::InputFile) {
+ IgnoredErrorResult rv;
+ nsTArray<nsString> result;
+ input->MozGetFileNameArray(result, rv);
+ if (rv.Failed() || result.Length() == 0) {
+ continue;
+ }
+ AppendValueToCollectedData(input, id, u"file"_ns, result, aGeneratedCount,
+ std::forward<ArgsT>(args)...);
+ } else {
+ nsString value;
+ input->GetValue(value, CallerType::System);
+ // In order to reduce XPath generation (which is slow), we only save data
+ // for form fields that have been changed. (cf. bug 537289)
+ // Also, don't want to collect credit card number.
+ if (value.IsEmpty() || IsValidCCNumber(value) ||
+ input->HasBeenTypePassword() ||
+ input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
+ eCaseMatters)) {
+ continue;
+ }
+ AppendValueToCollectedData(aDocument, input, id, value, aGeneratedCount,
+ std::forward<ArgsT>(args)...);
+ }
+ }
+}
+
+/* static */
+template <typename... ArgsT>
+void SessionStoreUtils::CollectFromSelectElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args) {
+ RefPtr<nsContentList> selectlist =
+ NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"select"_ns);
+ uint32_t length = selectlist->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ MOZ_ASSERT(selectlist->Item(i), "null item in node list!");
+ RefPtr<HTMLSelectElement> select =
+ HTMLSelectElement::FromNodeOrNull(selectlist->Item(i));
+ if (!select) {
+ continue;
+ }
+ nsAutoString id;
+ select->GetId(id);
+ if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
+ continue;
+ }
+ AutocompleteInfo aInfo;
+ select->GetAutocompleteInfo(aInfo);
+ if (!aInfo.mCanAutomaticallyPersist) {
+ continue;
+ }
+ nsAutoCString value;
+ if (!select->Multiple()) {
+ // <select>s without the multiple attribute are hard to determine the
+ // default value, so assume we don't have the default.
+ DOMString selectVal;
+ select->GetValue(selectVal);
+ CollectedNonMultipleSelectValue val;
+ val.mSelectedIndex = select->SelectedIndex();
+ val.mValue = selectVal.AsAString();
+ AppendValueToCollectedData(select, id, val, aGeneratedCount,
+ std::forward<ArgsT>(args)...);
+ } else {
+ // <select>s with the multiple attribute are easier to determine the
+ // default value since each <option> has a defaultSelected property
+ HTMLOptionsCollection* options = select->GetOptions();
+ if (!options) {
+ continue;
+ }
+ bool hasDefaultValue = true;
+ nsTArray<nsString> selectslist;
+ uint32_t numOptions = options->Length();
+ for (uint32_t idx = 0; idx < numOptions; idx++) {
+ HTMLOptionElement* option = options->ItemAsOption(idx);
+ bool selected = option->Selected();
+ if (!selected) {
+ continue;
+ }
+ option->GetValue(*selectslist.AppendElement());
+ hasDefaultValue =
+ hasDefaultValue && (selected == option->DefaultSelected());
+ }
+ // In order to reduce XPath generation (which is slow), we only save data
+ // for form fields that have been changed. (cf. bug 537289)
+ if (hasDefaultValue) {
+ continue;
+ }
+
+ AppendValueToCollectedData(select, id, u"multipleSelect"_ns, selectslist,
+ aGeneratedCount, std::forward<ArgsT>(args)...);
+ }
+ }
+}
+
+/* static */
+template <typename... ArgsT>
+void SessionStoreUtils::CollectFromFormAssociatedCustomElement(
+ Document& aDocument, uint16_t& aGeneratedCount, ArgsT&&... args) {
+ RefPtr<nsContentList> faceList = GetFormAssociatedCustomElements(&aDocument);
+ uint32_t length = faceList->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ MOZ_ASSERT(faceList->Item(i), "null item in node list!");
+ RefPtr<Element> element = Element::FromNode(faceList->Item(i));
+
+ nsAutoString id;
+ element->GetId(id);
+ if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) {
+ continue;
+ }
+
+ auto* internals = element->GetCustomElementData()->GetElementInternals();
+ const auto& state = internals->GetFormState();
+ const auto& value = internals->GetFormSubmissionValue();
+ if (state.IsNull() && value.IsNull()) {
+ continue;
+ }
+
+ AppendValueToCollectedData(element, id, value, state, aGeneratedCount,
+ std::forward<ArgsT>(args)...);
+ }
+}
+
+static void CollectCurrentFormData(JSContext* aCx, Document& aDocument,
+ Nullable<CollectedData>& aRetVal) {
+ uint16_t generatedCount = 0;
+ /* textarea element */
+ SessionStoreUtils::CollectFromTextAreaElement(aDocument, generatedCount,
+ aRetVal);
+ /* input element */
+ SessionStoreUtils::CollectFromInputElement(aDocument, generatedCount, aCx,
+ aRetVal);
+ /* select element */
+ SessionStoreUtils::CollectFromSelectElement(aDocument, generatedCount, aCx,
+ aRetVal);
+ /* form-associated custom element */
+ SessionStoreUtils::CollectFromFormAssociatedCustomElement(
+ aDocument, generatedCount, aCx, aRetVal);
+
+ Element* bodyElement = aDocument.GetBody();
+ if (bodyElement && bodyElement->IsInDesignMode()) {
+ bodyElement->GetInnerHTML(aRetVal.SetValue().mInnerHTML.Construct(),
+ IgnoreErrors());
+ }
+
+ if (aRetVal.IsNull()) {
+ return;
+ }
+
+ // Store the frame's current URL with its form data so that we can compare
+ // it when restoring data to not inject form data into the wrong document.
+ nsIURI* uri = aDocument.GetDocumentURI();
+ if (uri) {
+ uri->GetSpecIgnoringRef(aRetVal.SetValue().mUrl.Construct());
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsString(Element* aElement, const nsAString& aValue) {
+ IgnoredErrorResult rv;
+ if (auto* textArea = HTMLTextAreaElement::FromNode(aElement)) {
+ // Known live because `aElement` is known live.
+ MOZ_KnownLive(textArea)->SetValue(aValue, rv);
+ if (!rv.Failed()) {
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ return;
+ }
+ if (auto* input = HTMLInputElement::FromNode(aElement)) {
+ input->SetValue(aValue, CallerType::NonSystem, rv);
+ if (!rv.Failed()) {
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ return;
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsBool(Element* aElement, bool aValue) {
+ HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
+ if (input) {
+ bool checked = input->Checked();
+ if (aValue != checked) {
+ input->SetChecked(aValue);
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsFiles(HTMLInputElement* aElement,
+ const CollectedFileListValue& aValue) {
+ IgnoredErrorResult rv;
+ aElement->MozSetFileNameArray(aValue.mFileList, rv);
+ if (rv.Failed()) {
+ return;
+ }
+ nsContentUtils::DispatchInputEvent(aElement);
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsSelect(HTMLSelectElement* aElement,
+ const CollectedNonMultipleSelectValue& aValue) {
+ HTMLOptionsCollection* options = aElement->GetOptions();
+ if (!options) {
+ return;
+ }
+ int32_t selectIdx = options->SelectedIndex();
+ if (selectIdx >= 0) {
+ nsAutoString selectOptionVal;
+ options->ItemAsOption(selectIdx)->GetValue(selectOptionVal);
+ if (aValue.mValue.Equals(selectOptionVal)) {
+ return;
+ }
+ }
+ uint32_t numOptions = options->Length();
+ for (uint32_t idx = 0; idx < numOptions; idx++) {
+ HTMLOptionElement* option = options->ItemAsOption(idx);
+ nsAutoString optionValue;
+ option->GetValue(optionValue);
+ if (aValue.mValue.Equals(optionValue)) {
+ aElement->SetSelectedIndex(idx);
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsMultiSelect(HTMLSelectElement* aElement,
+ const nsTArray<nsString>& aValueArray) {
+ bool fireEvent = false;
+ HTMLOptionsCollection* options = aElement->GetOptions();
+ if (!options) {
+ return;
+ }
+ uint32_t numOptions = options->Length();
+ for (uint32_t idx = 0; idx < numOptions; idx++) {
+ HTMLOptionElement* option = options->ItemAsOption(idx);
+ nsAutoString optionValue;
+ option->GetValue(optionValue);
+ for (uint32_t i = 0, l = aValueArray.Length(); i < l; ++i) {
+ if (optionValue.Equals(aValueArray[i])) {
+ option->SetSelected(true);
+ if (!option->DefaultSelected()) {
+ fireEvent = true;
+ }
+ }
+ }
+ }
+ if (fireEvent) {
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetElementAsObject(JSContext* aCx, Element* aElement,
+ JS::Handle<JS::Value> aObject) {
+ RefPtr<HTMLInputElement> input = HTMLInputElement::FromNode(aElement);
+ if (input) {
+ if (input->ControlType() == FormControlType::InputFile) {
+ CollectedFileListValue value;
+ if (value.Init(aCx, aObject)) {
+ SetElementAsFiles(input, value);
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ }
+ return;
+ }
+ RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aElement);
+ if (select) {
+ // For Single Select Element
+ if (!select->Multiple()) {
+ CollectedNonMultipleSelectValue value;
+ if (value.Init(aCx, aObject)) {
+ SetElementAsSelect(select, value);
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ return;
+ }
+
+ // For Multiple Selects Element
+ bool isArray = false;
+ JS::IsArrayObject(aCx, aObject, &isArray);
+ if (!isArray) {
+ return;
+ }
+ JS::Rooted<JSObject*> arrayObj(aCx, &aObject.toObject());
+ uint32_t arrayLength = 0;
+ if (!JS::GetArrayLength(aCx, arrayObj, &arrayLength)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ nsTArray<nsString> array(arrayLength);
+ for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) {
+ JS::Rooted<JS::Value> element(aCx);
+ if (!JS_GetElement(aCx, arrayObj, arrayIdx, &element)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ if (!element.isString()) {
+ return;
+ }
+ nsAutoJSString value;
+ if (!value.init(aCx, element)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ array.AppendElement(value);
+ }
+ SetElementAsMultiSelect(select, array);
+ }
+
+ // For Form-Associated Custom Element:
+ if (!aObject.isObject()) {
+ // Don't restore null values.
+ return;
+ }
+
+ auto* data = aElement->GetCustomElementData();
+ if (!data || !data->IsFormAssociated()) {
+ return;
+ }
+ auto* internals = data->GetElementInternals();
+
+ CollectedCustomElementValue value;
+ if (!value.Init(aCx, aObject)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ internals->RestoreFormValue(std::move(value.mValue), std::move(value.mState));
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetSessionData(JSContext* aCx, Element* aElement,
+ JS::MutableHandle<JS::Value> aObject) {
+ nsAutoString data;
+ if (nsContentUtils::StringifyJSON(aCx, aObject, data,
+ UndefinedIsNullStringLiteral)) {
+ SetElementAsString(aElement, data);
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetInnerHTML(Document& aDocument, const nsString& aInnerHTML) {
+ RefPtr<Element> bodyElement = aDocument.GetBody();
+ if (bodyElement && bodyElement->IsInDesignMode()) {
+ IgnoredErrorResult rv;
+ bodyElement->SetInnerHTML(aInnerHTML, aDocument.NodePrincipal(), rv);
+ if (!rv.Failed()) {
+ nsContentUtils::DispatchInputEvent(bodyElement);
+ }
+ }
+}
+
+class FormDataParseContext : public txIParseContext {
+ public:
+ explicit FormDataParseContext(bool aCaseInsensitive)
+ : mIsCaseInsensitive(aCaseInsensitive) {}
+
+ nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override {
+ if (aPrefix == nsGkAtoms::xul) {
+ aID = kNameSpaceID_XUL;
+ } else {
+ MOZ_ASSERT(nsDependentAtomString(aPrefix).EqualsLiteral("xhtml"));
+ aID = kNameSpaceID_XHTML;
+ }
+ return NS_OK;
+ }
+
+ nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
+ FunctionCall** aFunction) override {
+ return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
+ }
+
+ bool caseInsensitiveNameTests() override { return mIsCaseInsensitive; }
+
+ void SetErrorOffset(uint32_t aOffset) override {}
+
+ private:
+ bool mIsCaseInsensitive;
+};
+
+static Element* FindNodeByXPath(Document& aDocument,
+ const nsAString& aExpression) {
+ FormDataParseContext parsingContext(aDocument.IsHTMLDocument());
+ IgnoredErrorResult rv;
+ UniquePtr<XPathExpression> expression(
+ aDocument.XPathEvaluator()->CreateExpression(aExpression, &parsingContext,
+ &aDocument, rv));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<XPathResult> result = expression->Evaluate(
+ aDocument, XPathResult::FIRST_ORDERED_NODE_TYPE, nullptr, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return Element::FromNodeOrNull(result->GetSingleNodeValue(rv));
+}
+
+/* static */
+bool SessionStoreUtils::RestoreFormData(const GlobalObject& aGlobal,
+ Document& aDocument,
+ const CollectedData& aData) {
+ if (!aData.mUrl.WasPassed()) {
+ return true;
+ }
+
+ // Don't restore any data for the given frame if the URL
+ // stored in the form data doesn't match its current URL.
+ nsAutoCString url;
+ Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
+ if (!aData.mUrl.Value().Equals(url)) {
+ return false;
+ }
+
+ using Change = SessionStoreChangeListener::Change;
+ SessionStoreChangeListener::CollectSessionStoreData(
+ aDocument.GetWindowContext(), EnumSet<Change>(Change::Input));
+
+ if (aData.mInnerHTML.WasPassed()) {
+ SetInnerHTML(aDocument, aData.mInnerHTML.Value());
+ }
+ if (aData.mId.WasPassed()) {
+ for (auto& entry : aData.mId.Value().Entries()) {
+ RefPtr<Element> node = aDocument.GetElementById(entry.mKey);
+ if (node == nullptr) {
+ continue;
+ }
+ if (entry.mValue.IsString()) {
+ SetElementAsString(node, entry.mValue.GetAsString());
+ } else if (entry.mValue.IsBoolean()) {
+ SetElementAsBool(node, entry.mValue.GetAsBoolean());
+ } else {
+ // For about:{sessionrestore,welcomeback} we saved the field as JSON to
+ // avoid nested instances causing humongous sessionstore.js files.
+ // cf. bug 467409
+ JSContext* cx = aGlobal.Context();
+ if (entry.mKey.EqualsLiteral("sessionData")) {
+ if (url.EqualsLiteral("about:sessionrestore") ||
+ url.EqualsLiteral("about:welcomeback")) {
+ JS::Rooted<JS::Value> object(
+ cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
+ SetSessionData(cx, node, &object);
+ continue;
+ }
+ }
+ JS::Rooted<JS::Value> object(
+ cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
+ SetElementAsObject(cx, node, object);
+ }
+ }
+ }
+
+ if (aData.mXpath.WasPassed()) {
+ for (auto& entry : aData.mXpath.Value().Entries()) {
+ RefPtr<Element> node = FindNodeByXPath(aDocument, entry.mKey);
+ if (node == nullptr) {
+ continue;
+ }
+ if (entry.mValue.IsString()) {
+ SetElementAsString(node, entry.mValue.GetAsString());
+ } else if (entry.mValue.IsBoolean()) {
+ SetElementAsBool(node, entry.mValue.GetAsBoolean());
+ } else {
+ JS::Rooted<JS::Value> object(
+ aGlobal.Context(), JS::ObjectValue(*entry.mValue.GetAsObject()));
+ SetElementAsObject(aGlobal.Context(), node, object);
+ }
+ }
+ }
+
+ return true;
+}
+
+MOZ_CAN_RUN_SCRIPT
+void RestoreFormEntry(Element* aNode, const FormEntryValue& aValue) {
+ using Type = sessionstore::FormEntryValue::Type;
+ switch (aValue.type()) {
+ case Type::TCheckbox:
+ SetElementAsBool(aNode, aValue.get_Checkbox().value());
+ break;
+ case Type::TTextField:
+ SetElementAsString(aNode, aValue.get_TextField().value());
+ break;
+ case Type::TFileList: {
+ if (RefPtr<HTMLInputElement> input = HTMLInputElement::FromNode(aNode);
+ input && input->ControlType() == FormControlType::InputFile) {
+ CollectedFileListValue value;
+ value.mFileList = aValue.get_FileList().valueList().Clone();
+ SetElementAsFiles(input, value);
+ }
+ break;
+ }
+ case Type::TSingleSelect: {
+ if (RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aNode);
+ select && !select->Multiple()) {
+ CollectedNonMultipleSelectValue value;
+ value.mSelectedIndex = aValue.get_SingleSelect().index();
+ value.mValue = aValue.get_SingleSelect().value();
+ SetElementAsSelect(select, value);
+ }
+ break;
+ }
+ case Type::TMultipleSelect: {
+ if (RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNode(aNode);
+ select && select->Multiple()) {
+ SetElementAsMultiSelect(select,
+ aValue.get_MultipleSelect().valueList());
+ }
+ break;
+ }
+ case Type::TCustomElementTuple: {
+ const auto* data = aNode->GetCustomElementData();
+ if (!data || !data->IsFormAssociated()) {
+ return;
+ }
+ auto* internals = data->GetElementInternals();
+ nsCOMPtr<nsIGlobalObject> global = aNode->GetOwnerGlobal();
+ internals->RestoreFormValue(
+ nsContentUtils::ExtractFormAssociatedCustomElementValue(
+ global, aValue.get_CustomElementTuple().value()),
+ nsContentUtils::ExtractFormAssociatedCustomElementValue(
+ global, aValue.get_CustomElementTuple().state()));
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+}
+
+/* static */
+void SessionStoreUtils::RestoreFormData(
+ Document& aDocument, const nsString& aInnerHTML,
+ const nsTArray<SessionStoreRestoreData::Entry>& aEntries) {
+ using Change = SessionStoreChangeListener::Change;
+ SessionStoreChangeListener::CollectSessionStoreData(
+ aDocument.GetWindowContext(), EnumSet<Change>(Change::Input));
+
+ if (!aInnerHTML.IsEmpty()) {
+ SetInnerHTML(aDocument, aInnerHTML);
+ }
+
+ for (const auto& entry : aEntries) {
+ RefPtr<Element> node = entry.mIsXPath
+ ? FindNodeByXPath(aDocument, entry.mData.id())
+ : aDocument.GetElementById(entry.mData.id());
+ if (node) {
+ RestoreFormEntry(node, entry.mData.value());
+ }
+ }
+}
+
+typedef void (*CollectorFunc)(JSContext* aCx, Document& aDocument,
+ Nullable<CollectedData>& aRetVal);
+
+/**
+ * A function that will recursively call |CollectorFunc| to collect data for all
+ * non-dynamic frames in the current frame/docShell tree.
+ */
+static void CollectFrameTreeData(JSContext* aCx,
+ BrowsingContext* aBrowsingContext,
+ Nullable<CollectedData>& aRetVal,
+ CollectorFunc aFunc) {
+ if (aBrowsingContext->CreatedDynamically()) {
+ return;
+ }
+
+ nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
+ if (!window || !window->GetDocShell()) {
+ return;
+ }
+
+ Document* document = window->GetExtantDoc();
+ if (!document) {
+ return;
+ }
+
+ /* Collect data from current frame */
+ aFunc(aCx, *document, aRetVal);
+
+ /* Collect data from all child frame */
+ nsTArray<JSObject*> childrenData;
+ SequenceRooter<JSObject*> rooter(aCx, &childrenData);
+ uint32_t trailingNullCounter = 0;
+
+ // This is not going to work for fission. Bug 1572084 for tracking it.
+ for (auto& child : aBrowsingContext->Children()) {
+ NullableRootedDictionary<CollectedData> data(aCx);
+ CollectFrameTreeData(aCx, child, data, aFunc);
+ if (data.IsNull()) {
+ childrenData.AppendElement(nullptr);
+ trailingNullCounter++;
+ continue;
+ }
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, data.SetValue(), &jsval)) {
+ JS_ClearPendingException(aCx);
+ continue;
+ }
+ childrenData.AppendElement(&jsval.toObject());
+ trailingNullCounter = 0;
+ }
+
+ if (trailingNullCounter != childrenData.Length()) {
+ childrenData.TruncateLength(childrenData.Length() - trailingNullCounter);
+ aRetVal.SetValue().mChildren.Construct() = std::move(childrenData);
+ }
+}
+
+/* static */ void SessionStoreUtils::CollectScrollPosition(
+ const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
+ Nullable<CollectedData>& aRetVal) {
+ CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
+ CollectCurrentScrollPosition);
+}
+
+/* static */ void SessionStoreUtils::CollectFormData(
+ const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
+ Nullable<CollectedData>& aRetVal) {
+ CollectFrameTreeData(aGlobal.Context(), aWindow.get(), aRetVal,
+ CollectCurrentFormData);
+}
+
+/* static */ void SessionStoreUtils::ComposeInputData(
+ const nsTArray<CollectedInputDataValue>& aData, InputElementData& ret) {
+ nsTArray<int> selectedIndex, valueIdx;
+ nsTArray<nsString> id, selectVal, strVal, type;
+ nsTArray<bool> boolVal;
+
+ for (const CollectedInputDataValue& data : aData) {
+ id.AppendElement(data.id);
+ type.AppendElement(data.type);
+
+ if (data.value.is<mozilla::dom::CollectedNonMultipleSelectValue>()) {
+ valueIdx.AppendElement(selectVal.Length());
+ selectedIndex.AppendElement(
+ data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
+ .mSelectedIndex);
+ selectVal.AppendElement(
+ data.value.as<mozilla::dom::CollectedNonMultipleSelectValue>()
+ .mValue);
+ } else if (data.value.is<CopyableTArray<nsString>>()) {
+ // The first valueIdx is "index of the first string value"
+ valueIdx.AppendElement(strVal.Length());
+ strVal.AppendElements(data.value.as<CopyableTArray<nsString>>());
+ // The second valueIdx is "index of the last string value" + 1
+ id.AppendElement(data.id);
+ type.AppendElement(data.type);
+ valueIdx.AppendElement(strVal.Length());
+ } else if (data.value.is<nsString>()) {
+ valueIdx.AppendElement(strVal.Length());
+ strVal.AppendElement(data.value.as<nsString>());
+ } else if (data.type.EqualsLiteral("bool")) {
+ valueIdx.AppendElement(boolVal.Length());
+ boolVal.AppendElement(data.value.as<bool>());
+ }
+ }
+
+ if (selectedIndex.Length() != 0) {
+ ret.mSelectedIndex.Construct(std::move(selectedIndex));
+ }
+ if (valueIdx.Length() != 0) {
+ ret.mValueIdx.Construct(std::move(valueIdx));
+ }
+ if (id.Length() != 0) {
+ ret.mId.Construct(std::move(id));
+ }
+ if (selectVal.Length() != 0) {
+ ret.mSelectVal.Construct(std::move(selectVal));
+ }
+ if (strVal.Length() != 0) {
+ ret.mStrVal.Construct(std::move(strVal));
+ }
+ if (type.Length() != 0) {
+ ret.mType.Construct(std::move(type));
+ }
+ if (boolVal.Length() != 0) {
+ ret.mBoolVal.Construct(std::move(boolVal));
+ }
+}
+
+already_AddRefed<nsISessionStoreRestoreData>
+SessionStoreUtils::ConstructSessionStoreRestoreData(
+ const GlobalObject& aGlobal) {
+ nsCOMPtr<nsISessionStoreRestoreData> data = new SessionStoreRestoreData();
+ return data.forget();
+}
+
+/* static */
+already_AddRefed<Promise> SessionStoreUtils::InitializeRestore(
+ const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
+ nsISessionStoreRestoreData* aData, ErrorResult& aError) {
+ if (!mozilla::SessionHistoryInParent()) {
+ MOZ_CRASH("why were we called?");
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aContext.IsTop());
+
+ MOZ_DIAGNOSTIC_ASSERT(aData);
+ nsCOMPtr<SessionStoreRestoreData> data = do_QueryInterface(aData);
+ aContext.SetRestoreData(data, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISHistory> shistory = aContext.GetSessionHistory();
+ MOZ_DIAGNOSTIC_ASSERT(shistory);
+ shistory->ReloadCurrentEntry();
+
+ return aContext.GetRestorePromise();
+}
+
+/* static */
+void SessionStoreUtils::RestoreDocShellState(
+ nsIDocShell* aDocShell, const DocShellRestoreState& aState) {
+ if (aDocShell) {
+ nsCOMPtr<nsIURI> currentUri;
+ nsDocShell::Cast(aDocShell)->GetCurrentURI(getter_AddRefs(currentUri));
+ if (aState.URI() &&
+ (!currentUri || mozilla::net::SchemeIsAbout(currentUri))) {
+ aDocShell->SetCurrentURIForSessionStore(aState.URI());
+ }
+ RestoreDocShellCapabilities(aDocShell, aState.docShellCaps());
+ }
+}
+
+/* static */
+already_AddRefed<Promise> SessionStoreUtils::RestoreDocShellState(
+ const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
+ const nsACString& aURL, const nsCString& aDocShellCaps,
+ ErrorResult& aError) {
+ MOZ_RELEASE_ASSERT(mozilla::SessionHistoryInParent());
+ MOZ_RELEASE_ASSERT(aContext.IsTop());
+
+ WindowGlobalParent* wgp = aContext.GetCurrentWindowGlobal();
+ if (!wgp) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_DIAGNOSTIC_ASSERT(global);
+
+ RefPtr<Promise> promise = Promise::Create(global, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (!aURL.IsEmpty()) {
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aURL))) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ bool allowJavascript = true;
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(aDocShellCaps, ',').ToRange()) {
+ if (token.EqualsLiteral("Javascript")) {
+ allowJavascript = false;
+ }
+ }
+
+ Unused << aContext.SetAllowJavascript(allowJavascript);
+
+ DocShellRestoreState state = {uri, aDocShellCaps};
+
+ // TODO (anny): Investigate removing this roundtrip.
+ wgp->SendRestoreDocShellState(state)->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [promise](void) { promise->MaybeResolveWithUndefined(); },
+ [promise](void) { promise->MaybeRejectWithUndefined(); });
+
+ return promise.forget();
+}
+
+/* static */
+void SessionStoreUtils::RestoreSessionStorageFromParent(
+ const GlobalObject& aGlobal, const CanonicalBrowsingContext& aContext,
+ const Record<nsCString, Record<nsString, nsString>>& aSessionStorage) {
+ nsTArray<SSCacheCopy> cacheInitList;
+ for (const auto& originEntry : aSessionStorage.Entries()) {
+ nsCOMPtr<nsIPrincipal> storagePrincipal =
+ BasePrincipal::CreateContentPrincipal(originEntry.mKey);
+
+ nsCString originKey;
+ nsresult rv = storagePrincipal->GetStorageOriginKey(originKey);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ SSCacheCopy& cacheInit = *cacheInitList.AppendElement();
+
+ cacheInit.originKey() = originKey;
+ PrincipalToPrincipalInfo(storagePrincipal, &cacheInit.principalInfo());
+
+ for (const auto& entry : originEntry.mValue.Entries()) {
+ SSSetItemInfo& setItemInfo = *cacheInit.data().AppendElement();
+ setItemInfo.key() = entry.mKey;
+ setItemInfo.value() = entry.mValue;
+ }
+ }
+
+ BackgroundSessionStorageManager::LoadData(aContext.Id(), cacheInitList);
+}
+
+/* static */
+nsresult SessionStoreUtils::ConstructFormDataValues(
+ JSContext* aCx, const nsTArray<sessionstore::FormEntry>& aValues,
+ nsTArray<Record<nsString, OwningStringOrBooleanOrObject>::EntryType>&
+ aEntries,
+ bool aParseSessionData) {
+ using EntryType = Record<nsString, OwningStringOrBooleanOrObject>::EntryType;
+
+ if (!aEntries.SetCapacity(aValues.Length(), fallible)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const auto& value : aValues) {
+ EntryType* entry = aEntries.AppendElement();
+
+ using Type = sessionstore::FormEntryValue::Type;
+ switch (value.value().type()) {
+ case Type::TCheckbox:
+ entry->mValue.SetAsBoolean() = value.value().get_Checkbox().value();
+ break;
+ case Type::TTextField: {
+ if (aParseSessionData && value.id() == u"sessionData"_ns) {
+ JS::Rooted<JS::Value> jsval(aCx);
+ const auto& fieldValue = value.value().get_TextField().value();
+ if (!JS_ParseJSON(aCx, fieldValue.get(), fieldValue.Length(),
+ &jsval) ||
+ !jsval.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ } else {
+ entry->mValue.SetAsString() = value.value().get_TextField().value();
+ }
+ break;
+ }
+ case Type::TFileList: {
+ CollectedFileListValue file;
+ file.mFileList = value.value().get_FileList().valueList().Clone();
+
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, file, &jsval) || !jsval.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ break;
+ }
+ case Type::TSingleSelect: {
+ CollectedNonMultipleSelectValue select;
+ select.mSelectedIndex = value.value().get_SingleSelect().index();
+ select.mValue = value.value().get_SingleSelect().value();
+
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, select, &jsval) || !jsval.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ break;
+ }
+ case Type::TMultipleSelect: {
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, value.value().get_MultipleSelect().valueList(),
+ &jsval) ||
+ !jsval.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ break;
+ }
+ case Type::TCustomElementTuple: {
+ nsCOMPtr<nsIGlobalObject> global;
+ JS::Rooted<JSObject*> globalObject(aCx, JS::CurrentGlobalOrNull(aCx));
+ if (NS_WARN_IF(!globalObject)) {
+ break;
+ }
+ global = xpc::NativeGlobal(globalObject);
+ if (NS_WARN_IF(!global)) {
+ break;
+ }
+
+ auto formState =
+ nsContentUtils::ExtractFormAssociatedCustomElementValue(
+ global, value.value().get_CustomElementTuple().state());
+ auto formValue =
+ nsContentUtils::ExtractFormAssociatedCustomElementValue(
+ global, value.value().get_CustomElementTuple().value());
+ MOZ_ASSERT(!formValue.IsNull() || !formState.IsNull(),
+ "Shouldn't be storing null values!");
+
+ CollectedCustomElementValue val;
+ val.mValue = formValue;
+ val.mState = formState;
+ JS::Rooted<JS::Value> jsval(aCx);
+ if (!ToJSValue(aCx, val, &jsval)) {
+ return NS_ERROR_FAILURE;
+ }
+ entry->mValue.SetAsObject() = &jsval.toObject();
+ break;
+ }
+ default:
+ break;
+ }
+
+ entry->mKey = value.id();
+ }
+
+ return NS_OK;
+}
+
+static nsresult ConstructSessionStorageValue(
+ const nsTArray<SSSetItemInfo>& aValues,
+ Record<nsString, nsString>& aRecord) {
+ auto& entries = aRecord.Entries();
+ for (const auto& value : aValues) {
+ auto entry = entries.AppendElement();
+ entry->mKey = value.key();
+ entry->mValue = value.value();
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult SessionStoreUtils::ConstructSessionStorageValues(
+ CanonicalBrowsingContext* aBrowsingContext,
+ const nsTArray<SSCacheCopy>& aValues,
+ Record<nsCString, Record<nsString, nsString>>& aRecord) {
+ if (!aRecord.Entries().SetCapacity(aValues.Length(), fallible)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const auto& value : aValues) {
+ auto storagePrincipal = PrincipalInfoToPrincipal(value.principalInfo());
+ if (storagePrincipal.isErr()) {
+ continue;
+ }
+
+ auto entry = aRecord.Entries().AppendElement();
+
+ if (!entry->mValue.Entries().SetCapacity(value.data().Length(), fallible)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(storagePrincipal.inspect()->GetOrigin(entry->mKey))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ConstructSessionStorageValue(value.data(), entry->mValue);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+bool SessionStoreUtils::CopyProperty(JSContext* aCx, JS::Handle<JSObject*> aDst,
+ JS::Handle<JSObject*> aSrc,
+ const nsAString& aName) {
+ JS::Rooted<JS::PropertyKey> name(aCx);
+ const char16_t* data;
+ size_t length = aName.GetData(&data);
+
+ if (!JS_CharsToId(aCx, JS::TwoByteChars(data, length), &name)) {
+ return false;
+ }
+
+ bool found = false;
+ if (!JS_HasPropertyById(aCx, aSrc, name, &found) || !found) {
+ return true;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_GetPropertyById(aCx, aSrc, name, &value)) {
+ return false;
+ }
+
+ if (value.isNullOrUndefined()) {
+ return true;
+ }
+
+ return JS_DefinePropertyById(aCx, aDst, name, value, JSPROP_ENUMERATE);
+}
diff --git a/toolkit/components/sessionstore/SessionStoreUtils.h b/toolkit/components/sessionstore/SessionStoreUtils.h
new file mode 100644
index 0000000000..e3028b0f29
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreUtils.h
@@ -0,0 +1,182 @@
+/* -*- 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_SessionStoreUtils_h
+#define mozilla_dom_SessionStoreUtils_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/SessionStoreUtilsBinding.h"
+#include "SessionStoreData.h"
+#include "SessionStoreRestoreData.h"
+
+class nsIDocument;
+class nsGlobalWindowInner;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class CanonicalBrowsingContext;
+class GlobalObject;
+struct SSScrollPositionDict;
+class SSCacheCopy;
+class SSSetItemInfo;
+
+namespace sessionstore {
+class DocShellRestoreState;
+class FormData;
+class FormEntry;
+class StorageEntry;
+} // namespace sessionstore
+
+class SessionStoreUtils {
+ public:
+ MOZ_CAN_RUN_SCRIPT
+ static void ForEachNonDynamicChildFrame(
+ const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
+ SessionStoreUtilsFrameCallback& aCallback, ErrorResult& aRv);
+
+ static already_AddRefed<nsISupports> AddDynamicFrameFilteredListener(
+ const GlobalObject& aGlobal, EventTarget& aTarget, const nsAString& aType,
+ JS::Handle<JS::Value> aListener, bool aUseCapture, bool aMozSystemGroup,
+ ErrorResult& aRv);
+
+ static void RemoveDynamicFrameFilteredListener(
+ const GlobalObject& aGlobal, EventTarget& aTarget, const nsAString& aType,
+ nsISupports* aListener, bool aUseCapture, bool aMozSystemGroup,
+ ErrorResult& aRv);
+
+ static void CollectDocShellCapabilities(const GlobalObject& aGlobal,
+ nsIDocShell* aDocShell,
+ nsCString& aRetVal);
+
+ static void RestoreDocShellCapabilities(
+ nsIDocShell* aDocShell, const nsCString& aDisallowCapabilities);
+ static void RestoreDocShellCapabilities(
+ const GlobalObject& aGlobal, nsIDocShell* aDocShell,
+ const nsCString& aDisallowCapabilities) {
+ return RestoreDocShellCapabilities(aDocShell, aDisallowCapabilities);
+ }
+
+ static void CollectScrollPosition(const GlobalObject& aGlobal,
+ WindowProxyHolder& aWindow,
+ Nullable<CollectedData>& aRetVal);
+
+ static void RestoreScrollPosition(const GlobalObject& aGlobal,
+ nsGlobalWindowInner& aWindow,
+ const CollectedData& data);
+ static void RestoreScrollPosition(nsGlobalWindowInner& aWindow,
+ const nsCString& aScrollPosition);
+
+ static uint32_t CollectFormData(Document* aDocument,
+ sessionstore::FormData& aFormData);
+
+ /*
+ @param aDocument: DOMDocument instance to obtain form data for.
+ @param aGeneratedCount: the current number of XPath expressions in the
+ returned object.
+ */
+ template <typename... ArgsT>
+ static void CollectFromTextAreaElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args);
+ template <typename... ArgsT>
+ static void CollectFromInputElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args);
+ template <typename... ArgsT>
+ static void CollectFromSelectElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args);
+ template <typename... ArgsT>
+ static void CollectFromFormAssociatedCustomElement(Document& aDocument,
+ uint16_t& aGeneratedCount,
+ ArgsT&&... args);
+
+ static void CollectFormData(const GlobalObject& aGlobal,
+ WindowProxyHolder& aWindow,
+ Nullable<CollectedData>& aRetVal);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static bool RestoreFormData(const GlobalObject& aGlobal, Document& aDocument,
+ const CollectedData& aData);
+ MOZ_CAN_RUN_SCRIPT static void RestoreFormData(
+ Document& aDocument, const nsString& aInnerHTML,
+ const nsTArray<SessionStoreRestoreData::Entry>& aEntries);
+
+ static void ComposeInputData(const nsTArray<CollectedInputDataValue>& aData,
+ InputElementData& ret);
+
+ MOZ_CAN_RUN_SCRIPT static already_AddRefed<nsISessionStoreRestoreData>
+ ConstructSessionStoreRestoreData(const GlobalObject& aGlobal);
+
+ MOZ_CAN_RUN_SCRIPT static already_AddRefed<Promise> InitializeRestore(
+ const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
+ nsISessionStoreRestoreData* aData, ErrorResult& aError);
+
+ static void RestoreDocShellState(
+ nsIDocShell* aDocShell, const sessionstore::DocShellRestoreState& aState);
+
+ static already_AddRefed<Promise> RestoreDocShellState(
+ const GlobalObject& aGlobal, CanonicalBrowsingContext& aContext,
+ const nsACString& aURL, const nsCString& aDocShellCaps,
+ ErrorResult& aError);
+
+ static void RestoreSessionStorageFromParent(
+ const GlobalObject& aGlobal, const CanonicalBrowsingContext& aContext,
+ const Record<nsCString, Record<nsString, nsString>>& aSessionStorage);
+
+ static nsresult ConstructFormDataValues(
+ JSContext* aCx, const nsTArray<sessionstore::FormEntry>& aValues,
+ nsTArray<Record<nsString, OwningStringOrBooleanOrObject>::EntryType>&
+ aEntries,
+ bool aParseSessionData = false);
+
+ static nsresult ConstructSessionStorageValues(
+ CanonicalBrowsingContext* aBrowsingContext,
+ const nsTArray<SSCacheCopy>& aValues,
+ Record<nsCString, Record<nsString, nsString>>& aStorage);
+
+ static bool CopyProperty(JSContext* aCx, JS::Handle<JSObject*> aDst,
+ JS::Handle<JSObject*> aSrc, const nsAString& aName);
+
+ template <typename T>
+ static bool CopyChildren(JSContext* aCx, JS::Handle<JSObject*> aDst,
+ const nsTArray<RefPtr<T>>& aChildren) {
+ if (!aChildren.IsEmpty()) {
+ JS::Rooted<JSObject*> children(
+ aCx, JS::NewArrayObject(aCx, aChildren.Length()));
+
+ for (const auto index : IntegerRange(aChildren.Length())) {
+ if (!aChildren[index]) {
+ continue;
+ }
+
+ JS::Rooted<JSObject*> object(aCx);
+ aChildren[index]->ToJSON(aCx, &object);
+
+ if (!JS_DefineElement(aCx, children, index, object, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ if (!JS_DefineProperty(aCx, aDst, "children", children,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SessionStoreUtils_h
diff --git a/toolkit/components/sessionstore/moz.build b/toolkit/components/sessionstore/moz.build
new file mode 100644
index 0000000000..9e8d2eb45e
--- /dev/null
+++ b/toolkit/components/sessionstore/moz.build
@@ -0,0 +1,55 @@
+# -*- 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 += [
+ "BrowserSessionStore.h",
+ "SessionStoreChangeListener.h",
+ "SessionStoreChild.h",
+ "SessionStoreData.h",
+ "SessionStoreFormData.h",
+ "SessionStoreListener.h",
+ "SessionStoreMessageUtils.h",
+ "SessionStoreParent.h",
+ "SessionStoreRestoreData.h",
+ "SessionStoreScrollData.h",
+ "SessionStoreUtils.h",
+]
+
+UNIFIED_SOURCES += [
+ "BrowserSessionStore.cpp",
+ "RestoreTabContentObserver.cpp",
+ "SessionStoreChangeListener.cpp",
+ "SessionStoreChild.cpp",
+ "SessionStoreFormData.cpp",
+ "SessionStoreListener.cpp",
+ "SessionStoreParent.cpp",
+ "SessionStoreRestoreData.cpp",
+ "SessionStoreScrollData.cpp",
+ "SessionStoreUtils.cpp",
+]
+
+EXTRA_JS_MODULES += [
+ "SessionStoreFunctions.sys.mjs",
+]
+
+XPIDL_MODULE = "sessionstore"
+
+XPIDL_SOURCES += [
+ "nsISessionStoreRestoreData.idl",
+ "SessionStoreFunctions.idl",
+]
+
+IPDL_SOURCES += [
+ "PSessionStore.ipdl",
+ "SessionStoreTypes.ipdlh",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Session Restore")
diff --git a/toolkit/components/sessionstore/nsISessionStoreRestoreData.idl b/toolkit/components/sessionstore/nsISessionStoreRestoreData.idl
new file mode 100644
index 0000000000..eff070ef6a
--- /dev/null
+++ b/toolkit/components/sessionstore/nsISessionStoreRestoreData.idl
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(cd9f33c5-460d-4bbf-a459-f375ca9566d8)]
+interface nsISessionStoreRestoreData : nsISupports {
+ // Setters for form data.
+ attribute AUTF8String url;
+ attribute AString innerHTML;
+
+ // Setters for scroll data.
+ attribute ACString scroll;
+
+ // Methods for adding individual form fields which are called as the JS code
+ // finds them.
+ void addTextField(in boolean aIsXPath, in AString aIdOrXPath,
+ in AString aValue);
+ void addCheckbox(in boolean aIsXPath, in AString aIdOrXPath,
+ in boolean aValue);
+ void addFileList(in boolean aIsXPath, in AString aIdOrXPath, in AString aType,
+ in Array<AString> aFileList);
+ void addSingleSelect(in boolean aIsXPath, in AString aIdOrXPath,
+ in unsigned long aSelectedIndex, in AString aValue);
+ void addMultipleSelect(in boolean aIsXPath, in AString aIdOrXPath,
+ in Array<AString> aValues);
+ void addCustomElement(in boolean aIsXPath, in AString aIdOrXPath,
+ in jsval aValue, in jsval aState);
+
+ // Add a child data object to our children list.
+ void addChild(in nsISessionStoreRestoreData aChild, in unsigned long aIndex);
+};