summaryrefslogtreecommitdiffstats
path: root/toolkit/components/sessionstore
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/sessionstore')
-rw-r--r--toolkit/components/sessionstore/SessionStoreData.h61
-rw-r--r--toolkit/components/sessionstore/SessionStoreFunctions.idl19
-rw-r--r--toolkit/components/sessionstore/SessionStoreFunctions.jsm456
-rw-r--r--toolkit/components/sessionstore/SessionStoreListener.cpp852
-rw-r--r--toolkit/components/sessionstore/SessionStoreListener.h174
-rw-r--r--toolkit/components/sessionstore/SessionStoreMessageUtils.h71
-rw-r--r--toolkit/components/sessionstore/SessionStoreUtils.cpp1366
-rw-r--r--toolkit/components/sessionstore/SessionStoreUtils.h101
-rw-r--r--toolkit/components/sessionstore/moz.build34
9 files changed, 3134 insertions, 0 deletions
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/SessionStoreFunctions.idl b/toolkit/components/sessionstore/SessionStoreFunctions.idl
new file mode 100644
index 0000000000..1fd9bfb22d
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreFunctions.idl
@@ -0,0 +1,19 @@
+/* -*- 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 uint32_t aFlushId, in boolean aIsFinal, in uint32_t aEpoch,
+ in jsval aData, in boolean aCollectSHistory);
+};
diff --git a/toolkit/components/sessionstore/SessionStoreFunctions.jsm b/toolkit/components/sessionstore/SessionStoreFunctions.jsm
new file mode 100644
index 0000000000..fc5f73651a
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreFunctions.jsm
@@ -0,0 +1,456 @@
+/* -*- 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/. */
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
+});
+
+function UpdateSessionStore(
+ aBrowser,
+ aBrowsingContext,
+ aFlushId,
+ aIsFinal,
+ aEpoch,
+ aData,
+ aCollectSHistory
+) {
+ return SessionStoreFuncInternal.updateSessionStore(
+ aBrowser,
+ aBrowsingContext,
+ aFlushId,
+ aIsFinal,
+ aEpoch,
+ aData,
+ aCollectSHistory
+ );
+}
+
+var EXPORTED_SYMBOLS = ["UpdateSessionStore"];
+
+var SessionStoreFuncInternal = {
+ // form data which is waiting to be updated
+ _formDataId: [],
+ _formDataIdValue: [],
+ _formDataXPath: [],
+ _formDataXPathValue: [],
+
+ /**
+ * The data will be stored in the arrays:
+ * "_formDataId, _formDataIdValue" for the elements with id.
+ * "_formDataXPath, _formDataXPathValue" for the elements with XPath.
+ */
+ updateFormData: function SSF_updateFormData(aType, aData) {
+ let idArray = this._formDataId;
+ let valueArray = this._formDataIdValue;
+
+ if (aType == "XPath") {
+ idArray = this._formDataXPath;
+ valueArray = this._formDataXPathValue;
+ }
+
+ let valueIdx = aData.valueIdx;
+ for (let i = 0; i < aData.id.length; i++) {
+ idArray.push(aData.id[i]);
+
+ if (aData.type[i] == "singleSelect") {
+ valueArray.push({
+ selectedIndex: aData.selectedIndex[valueIdx[i]],
+ value: aData.selectVal[valueIdx[i]],
+ });
+ } else if (aData.type[i] == "file") {
+ valueArray.push({
+ type: "file",
+ fileList: aData.strVal.slice(valueIdx[i], valueIdx[++i]),
+ });
+ } else if (aData.type[i] == "multipleSelect") {
+ valueArray.push(aData.strVal.slice(valueIdx[i], valueIdx[++i]));
+ } else if (aData.type[i] == "string") {
+ valueArray.push(aData.strVal[valueIdx[i]]);
+ } else if (aData.type[i] == "bool") {
+ valueArray.push(aData.boolVal[valueIdx[i]]);
+ }
+ }
+ },
+
+ /**
+ * Return the array of formdata for this._sessionData.formdata.children
+ *
+ * aStartIndex: Current index for aInnerHTML/aUrl/aNumId/aNumXPath/aDescendants.
+ * (aStartIndex means the index of current root frame)
+ * aInnerHTML: Array for innerHTML.
+ * aUrl: Array for url.
+ * aNumId: Array for number of containing elements with id
+ * aNumXPath: Array for number of containing elements with XPath
+ * aDescendants: Array for number of descendants.
+ *
+ * aCurrentIdIdx: Current index for this._formDataId and this._formDataIdValue
+ * aCurrentXPathIdx: Current index for this._formDataXPath and this._formDataXPathValue
+ * aNumberOfDescendants: The number of descendants for current frame
+ *
+ * The returned array includes "aNumberOfDescendants" formdata objects.
+ */
+ composeInputChildren: function SSF_composeInputChildren(
+ aInnerHTML,
+ aUrl,
+ aCurrentIdIdx,
+ aNumId,
+ aCurrentXpathIdx,
+ aNumXPath,
+ aDescendants,
+ aStartIndex,
+ aNumberOfDescendants
+ ) {
+ let children = [];
+ let lastIndexOfNonNullbject = -1;
+ for (let i = 0; i < aNumberOfDescendants; i++) {
+ let currentIndex = aStartIndex + i;
+ let obj = {};
+ let objWithData = false;
+
+ // set url/id/xpath
+ if (aUrl[currentIndex]) {
+ obj.url = aUrl[currentIndex];
+ objWithData = true;
+
+ if (aInnerHTML[currentIndex]) {
+ // eslint-disable-next-line no-unsanitized/property
+ obj.innerHTML = aInnerHTML[currentIndex];
+ }
+ if (aNumId[currentIndex]) {
+ let idObj = {};
+ for (let idx = 0; idx < aNumId[currentIndex]; idx++) {
+ idObj[
+ this._formDataId[aCurrentIdIdx + idx]
+ ] = this._formDataIdValue[aCurrentIdIdx + idx];
+ }
+ obj.id = idObj;
+ }
+
+ // 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 (
+ obj.url == "about:sessionrestore" ||
+ obj.url == "about:welcomeback"
+ ) {
+ obj.id.sessionData = JSON.parse(obj.id.sessionData);
+ }
+
+ if (aNumXPath[currentIndex]) {
+ let xpathObj = {};
+ for (let idx = 0; idx < aNumXPath[currentIndex]; idx++) {
+ xpathObj[
+ this._formDataXPath[aCurrentXpathIdx + idx]
+ ] = this._formDataXPathValue[aCurrentXpathIdx + idx];
+ }
+ obj.xpath = xpathObj;
+ }
+ }
+
+ // compose the descendantsTree which will be pushed into children array
+ if (aDescendants[currentIndex]) {
+ let descendantsTree = this.composeInputChildren(
+ aInnerHTML,
+ aUrl,
+ aCurrentIdIdx + aNumId[currentIndex],
+ aNumId,
+ aCurrentXpathIdx + aNumXPath[currentIndex],
+ aNumXPath,
+ aDescendants,
+ currentIndex + 1,
+ aDescendants[currentIndex]
+ );
+ i += aDescendants[currentIndex];
+ if (descendantsTree) {
+ obj.children = descendantsTree;
+ }
+ }
+
+ if (objWithData) {
+ lastIndexOfNonNullbject = children.length;
+ children.push(obj);
+ } else {
+ children.push(null);
+ }
+ }
+
+ if (lastIndexOfNonNullbject == -1) {
+ return null;
+ }
+
+ return children.slice(0, lastIndexOfNonNullbject + 1);
+ },
+
+ /**
+ * Update the object for this._sessionData.formdata.
+ * The object contains the formdata for all reachable frames.
+ *
+ * "object.children" is an array with one entry per frame,
+ * containing formdata as a nested data structure according
+ * to the layout of the frame tree, or null if no formdata.
+ *
+ * Example:
+ * {
+ * url: "http://mozilla.org/",
+ * id: {input_id: "input value"},
+ * xpath: {input_xpath: "input value"},
+ * children: [
+ * null,
+ * {url: "http://sub.mozilla.org/", id: {input_id: "input value 2"}}
+ * ]
+ * }
+ *
+ * Each index of the following array is corresponging to each frame.
+ * aDescendants: Array for number of descendants
+ * aInnerHTML: Array for innerHTML
+ * aUrl: Array for url
+ * aNumId: Array for number of containing elements with id
+ * aNumXPath: Array for number of containing elements with XPath
+ *
+ * Here we use [index 0] to compose the formdata object of root frame.
+ * Besides, we use composeInputChildren() to get array of "object.children".
+ */
+ updateInput: function SSF_updateInput(
+ aSessionData,
+ aDescendants,
+ aInnerHTML,
+ aUrl,
+ aNumId,
+ aNumXPath
+ ) {
+ let obj = {};
+ let objWithData = false;
+
+ if (aUrl[0]) {
+ obj.url = aUrl[0];
+
+ if (aInnerHTML[0]) {
+ // eslint-disable-next-line no-unsanitized/property
+ obj.innerHTML = aInnerHTML[0];
+ objWithData = true;
+ }
+
+ if (aNumId[0]) {
+ let idObj = {};
+ for (let i = 0; i < aNumId[0]; i++) {
+ idObj[this._formDataId[i]] = this._formDataIdValue[i];
+ }
+ obj.id = idObj;
+ objWithData = true;
+ }
+
+ // 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 (obj.url == "about:sessionrestore" || obj.url == "about:welcomeback") {
+ obj.id.sessionData = JSON.parse(obj.id.sessionData);
+ }
+
+ if (aNumXPath[0]) {
+ let xpathObj = {};
+ for (let i = 0; i < aNumXPath[0]; i++) {
+ xpathObj[this._formDataXPath[i]] = this._formDataXPathValue[i];
+ }
+ obj.xpath = xpathObj;
+ objWithData = true;
+ }
+ }
+
+ if (aDescendants.length > 1) {
+ let descendantsTree = this.composeInputChildren(
+ aInnerHTML,
+ aUrl,
+ aNumId[0],
+ aNumId,
+ aNumXPath[0],
+ aNumXPath,
+ aDescendants,
+ 1,
+ aDescendants[0]
+ );
+ if (descendantsTree) {
+ obj.children = descendantsTree;
+ objWithData = true;
+ }
+ }
+
+ if (objWithData) {
+ aSessionData.formdata = obj;
+ } else {
+ aSessionData.formdata = null;
+ }
+ },
+
+ composeChildren: function SSF_composeScrollPositionsData(
+ aPositions,
+ aDescendants,
+ aStartIndex,
+ aNumberOfDescendants
+ ) {
+ let children = [];
+ let lastIndexOfNonNullbject = -1;
+ for (let i = 0; i < aNumberOfDescendants; i++) {
+ let currentIndex = aStartIndex + i;
+ let obj = {};
+ let objWithData = false;
+ if (aPositions[currentIndex]) {
+ obj.scroll = aPositions[currentIndex];
+ objWithData = true;
+ }
+ if (aDescendants[currentIndex]) {
+ let descendantsTree = this.composeChildren(
+ aPositions,
+ aDescendants,
+ currentIndex + 1,
+ aDescendants[currentIndex]
+ );
+ i += aDescendants[currentIndex];
+ if (descendantsTree) {
+ obj.children = descendantsTree;
+ objWithData = true;
+ }
+ }
+
+ if (objWithData) {
+ lastIndexOfNonNullbject = children.length;
+ children.push(obj);
+ } else {
+ children.push(null);
+ }
+ }
+
+ if (lastIndexOfNonNullbject == -1) {
+ return null;
+ }
+
+ return children.slice(0, lastIndexOfNonNullbject + 1);
+ },
+
+ updateScrollPositions: function SSF_updateScrollPositions(
+ aPositions,
+ aDescendants
+ ) {
+ let obj = {};
+ let objWithData = false;
+
+ if (aPositions[0]) {
+ obj.scroll = aPositions[0];
+ objWithData = true;
+ }
+
+ if (aPositions.length > 1) {
+ let children = this.composeChildren(
+ aPositions,
+ aDescendants,
+ 1,
+ aDescendants[0]
+ );
+ if (children) {
+ obj.children = children;
+ objWithData = true;
+ }
+ }
+ if (objWithData) {
+ return obj;
+ }
+ return null;
+ },
+
+ updateStorage: function SSF_updateStorage(aOrigins, aKeys, aValues) {
+ let data = {};
+ for (let i = 0; i < aOrigins.length; i++) {
+ // If the key isn't defined, then .clear() was called, and we send
+ // up null for this domain to indicate that storage has been cleared
+ // for it.
+ if (aKeys[i] == "") {
+ while (aOrigins[i + 1] == aOrigins[i]) {
+ i++;
+ }
+ data[aOrigins[i]] = null;
+ } else {
+ let hostData = {};
+ hostData[aKeys[i]] = aValues[i];
+ while (aOrigins[i + 1] == aOrigins[i]) {
+ i++;
+ hostData[aKeys[i]] = aValues[i];
+ }
+ data[aOrigins[i]] = hostData;
+ }
+ }
+ if (aOrigins.length) {
+ return data;
+ }
+
+ return null;
+ },
+
+ updateSessionStore: function SSF_updateSessionStore(
+ aBrowser,
+ aBrowsingContext,
+ aFlushId,
+ aIsFinal,
+ aEpoch,
+ aData,
+ aCollectSHistory
+ ) {
+ let currentData = {};
+ if (aData.docShellCaps != undefined) {
+ currentData.disallow = aData.docShellCaps ? aData.docShellCaps : null;
+ }
+ if (aData.isPrivate != undefined) {
+ currentData.isPrivate = aData.isPrivate;
+ }
+ if (
+ aData.positions != undefined &&
+ aData.positionDescendants != undefined
+ ) {
+ currentData.scroll = this.updateScrollPositions(
+ aData.positions,
+ aData.positionDescendants
+ );
+ }
+ if (aData.id != undefined) {
+ this.updateFormData("id", aData.id);
+ }
+ if (aData.xpath != undefined) {
+ this.updateFormData("XPath", aData.xpath);
+ }
+ if (aData.inputDescendants != undefined) {
+ this.updateInput(
+ currentData,
+ aData.inputDescendants,
+ aData.innerHTML,
+ aData.url,
+ aData.numId,
+ aData.numXPath
+ );
+ }
+ if (aData.isFullStorage != undefined) {
+ let storage = this.updateStorage(
+ aData.storageOrigins,
+ aData.storageKeys,
+ aData.storageValues
+ );
+ if (aData.isFullStorage) {
+ currentData.storage = storage;
+ } else {
+ currentData.storagechange = storage;
+ }
+ }
+
+ SessionStore.updateSessionStoreFromTablistener(aBrowser, aBrowsingContext, {
+ data: currentData,
+ flushID: aFlushId,
+ isFinal: aIsFinal,
+ epoch: aEpoch,
+ sHistoryNeeded: aCollectSHistory,
+ });
+ this._formDataId = [];
+ this._formDataIdValue = [];
+ this._formDataXPath = [];
+ this._formDataXPathValue = [];
+ },
+};
diff --git a/toolkit/components/sessionstore/SessionStoreListener.cpp b/toolkit/components/sessionstore/SessionStoreListener.cpp
new file mode 100644
index 0000000000..b3dfddf1d8
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreListener.cpp
@@ -0,0 +1,852 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/SessionStoreListener.h"
+#include "mozilla/dom/SessionStoreUtils.h"
+#include "mozilla/dom/SessionStoreUtilsBinding.h"
+#include "mozilla/dom/StorageEvent.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)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ContentSessionStore, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ContentSessionStore, Release)
+
+ContentSessionStore::ContentSessionStore(nsIDocShell* aDocShell)
+ : mDocShell(aDocShell),
+ mPrivateChanged(false),
+ mIsPrivate(false),
+ mScrollChanged(NO_CHANGE),
+ mFormDataChanged(NO_CHANGE),
+ mStorageStatus(NO_STORAGE),
+ mDocCapChanged(false),
+ mSHistoryChanged(false),
+ mSHistoryChangedFromParent(false) {
+ MOZ_ASSERT(mDocShell);
+ // Check that value at startup as it might have
+ // been set before the frame script was loaded.
+ if (NS_SUCCEEDED(nsDocShell::Cast(mDocShell)->GetUsePrivateBrowsing(
+ &mPrivateChanged)) &&
+ mPrivateChanged) {
+ mIsPrivate = true;
+ }
+}
+
+nsCString ContentSessionStore::CollectDocShellCapabilities() {
+ bool allow;
+ nsCString aRetVal;
+
+#define TRY_ALLOWPROP(y) \
+ PR_BEGIN_MACRO \
+ nsresult rv = mDocShell->GetAllow##y(&allow); \
+ if (NS_SUCCEEDED(rv) && !allow) { \
+ if (!aRetVal.IsEmpty()) { \
+ aRetVal.Append(','); \
+ } \
+ aRetVal.Append(#y); \
+ } \
+ PR_END_MACRO
+
+ TRY_ALLOWPROP(Plugins);
+ // Bug 1328013 : Don't collect "AllowJavascript" property
+ // TRY_ALLOWPROP(Javascript);
+ TRY_ALLOWPROP(MetaRedirects);
+ TRY_ALLOWPROP(Subframes);
+ TRY_ALLOWPROP(Images);
+ TRY_ALLOWPROP(Media);
+ TRY_ALLOWPROP(DNSPrefetch);
+ TRY_ALLOWPROP(WindowControl);
+ TRY_ALLOWPROP(Auth);
+ TRY_ALLOWPROP(ContentRetargeting);
+ TRY_ALLOWPROP(ContentRetargetingOnChildren);
+#undef TRY_ALLOWPROP
+ return aRetVal;
+}
+
+void ContentSessionStore::OnPrivateModeChanged(bool aEnabled) {
+ mPrivateChanged = true;
+ mIsPrivate = aEnabled;
+}
+
+nsCString ContentSessionStore::GetDocShellCaps() {
+ mDocCapChanged = false;
+ return mDocCaps;
+}
+
+bool ContentSessionStore::GetPrivateModeEnabled() {
+ mPrivateChanged = false;
+ return mIsPrivate;
+}
+
+void ContentSessionStore::SetFullStorageNeeded() {
+ // We need the entire session storage, reset the pending individual change
+ ResetStorageChanges();
+ mStorageStatus = FULLSTORAGE;
+}
+
+void ContentSessionStore::ResetStorageChanges() {
+ mOrigins.Clear();
+ mKeys.Clear();
+ mValues.Clear();
+}
+
+void ContentSessionStore::SetSHistoryChanged() {
+ mSHistoryChanged = mozilla::SessionHistoryInParent();
+}
+
+// Request "collect sessionHistory" from the parent process
+void ContentSessionStore::SetSHistoryFromParentChanged() {
+ mSHistoryChangedFromParent = mozilla::SessionHistoryInParent();
+}
+
+void ContentSessionStore::OnDocumentStart() {
+ mScrollChanged = PAGELOADEDSTART;
+ mFormDataChanged = PAGELOADEDSTART;
+ nsCString caps = CollectDocShellCapabilities();
+ if (!mDocCaps.Equals(caps)) {
+ mDocCaps = caps;
+ mDocCapChanged = true;
+ }
+
+ SetFullStorageNeeded();
+
+ if (mozilla::SessionHistoryInParent()) {
+ mSHistoryChanged = true;
+ }
+}
+
+void ContentSessionStore::OnDocumentEnd() {
+ mScrollChanged = WITH_CHANGE;
+ SetFullStorageNeeded();
+
+ 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),
+ mStorageObserverRegistered(false),
+ mStorageChangeListenerRegistered(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;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ NS_WARNING_ASSERTION(obs, "no observer service");
+ if (obs) {
+ obs->AddObserver(this, "browser:purge-sessionStorage", true);
+ mStorageObserverRegistered = true;
+ }
+
+ nsCOMPtr<EventTarget> eventTarget = GetEventTarget();
+ if (!eventTarget) {
+ return NS_OK;
+ }
+ eventTarget->AddSystemEventListener(u"mozvisualscroll"_ns, this, false);
+ eventTarget->AddSystemEventListener(u"input"_ns, this, false);
+
+ if (mozilla::SessionHistoryInParent()) {
+ eventTarget->AddSystemEventListener(u"DOMTitleChanged"_ns, this, false);
+ }
+
+ mEventListenerRegistered = true;
+ eventTarget->AddSystemEventListener(u"MozSessionStorageChanged"_ns, this,
+ false);
+ mStorageChangeListenerRegistered = true;
+ return NS_OK;
+}
+
+/* static */
+void TabListener::TimerCallback(nsITimer* aTimer, void* aClosure) {
+ auto listener = static_cast<TabListener*>(aClosure);
+ listener->UpdateSessionStore();
+ listener->StopTimerForUpdate();
+}
+
+void TabListener::StopTimerForUpdate() {
+ if (mUpdatedTimer) {
+ mUpdatedTimer->Cancel();
+ mUpdatedTimer = nullptr;
+ }
+}
+
+void TabListener::AddTimerForUpdate() {
+ if (mUpdatedTimer) {
+ return;
+ }
+
+ if (mTimeoutDisabled) {
+ UpdateSessionStore();
+ return;
+ }
+
+ NS_NewTimerWithFuncCallback(getter_AddRefs(mUpdatedTimer), TimerCallback,
+ this, mUpdateInterval, nsITimer::TYPE_ONE_SHOT,
+ "TabListener::TimerCallback");
+}
+
+NS_IMETHODIMP TabListener::PrivateModeChanged(bool aEnabled) {
+ mSessionStore->OnPrivateModeChanged(aEnabled);
+ AddTimerForUpdate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TabListener::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus) {
+ if (!mSessionStore) {
+ return NS_OK;
+ }
+
+ // Ignore state changes for subframes because we're only interested in the
+ // top-document starting or stopping its load.
+ bool isTopLevel = false;
+ nsresult rv = aWebProgress->GetIsTopLevel(&isTopLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isTopLevel) {
+ return NS_OK;
+ }
+
+ 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();
+ ResetStorageChangeListener();
+ } 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("mozvisualscroll")) {
+ mSessionStore->SetScrollPositionChanged();
+ AddTimerForUpdate();
+ } else if (eventType.EqualsLiteral("input")) {
+ mSessionStore->SetFormDataChanged();
+ AddTimerForUpdate();
+ } else if (eventType.EqualsLiteral("MozSessionStorageChanged")) {
+ auto event = static_cast<StorageEvent*>(aEvent);
+ RefPtr<Storage> changingStorage = event->GetStorageArea();
+ if (!changingStorage) {
+ return NS_OK;
+ }
+ // How much data does DOMSessionStorage contain?
+ int64_t storageUsage = changingStorage->GetOriginQuotaUsage();
+ if (storageUsage > StaticPrefs::browser_sessionstore_dom_storage_limit()) {
+ RemoveStorageChangeListener();
+ mSessionStore->ResetStorageChanges();
+ mSessionStore->ResetStorage();
+ return NS_OK;
+ }
+ if (mSessionStore->AppendSessionStorageChange(event)) {
+ AddTimerForUpdate();
+ }
+ } else 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, "browser:purge-sessionStorage")) {
+ mSessionStore->SetFullStorageNeeded();
+ AddTimerForUpdate();
+ return NS_OK;
+ }
+
+ 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;
+}
+
+nsCString CollectPosition(Document& aDocument) {
+ PresShell* presShell = aDocument.GetPresShell();
+ if (!presShell) {
+ return ""_ns;
+ }
+ nsPoint scrollPos = presShell->GetVisualViewportOffset();
+ int scrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x);
+ int scrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y);
+ if ((scrollX != 0) || (scrollY != 0)) {
+ return nsPrintfCString("%d,%d", scrollX, scrollY);
+ }
+
+ return ""_ns;
+}
+
+int CollectPositions(BrowsingContext* aBrowsingContext,
+ nsTArray<nsCString>& aPositions,
+ nsTArray<int32_t>& aPositionDescendants) {
+ if (aBrowsingContext->CreatedDynamically()) {
+ return 0;
+ }
+
+ nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
+ if (!window) {
+ return 0;
+ }
+
+ Document* document = window->GetDoc();
+ if (!document) {
+ return 0;
+ }
+
+ /* Collect data from current frame */
+ aPositions.AppendElement(CollectPosition(*document));
+ aPositionDescendants.AppendElement(0);
+ unsigned long currentIdx = aPositions.Length() - 1;
+
+ /* Collect data from all child frame */
+ // This is not going to work for fission. Bug 1572084 for tracking it.
+ for (auto& child : aBrowsingContext->Children()) {
+ aPositionDescendants[currentIdx] +=
+ CollectPositions(child, aPositions, aPositionDescendants);
+ }
+
+ return aPositionDescendants[currentIdx] + 1;
+}
+
+void ContentSessionStore::GetScrollPositions(
+ nsTArray<nsCString>& aPositions, nsTArray<int32_t>& aPositionDescendants) {
+ if (mScrollChanged == PAGELOADEDSTART) {
+ aPositionDescendants.AppendElement(0);
+ aPositions.AppendElement(""_ns);
+ } else {
+ CollectPositions(mDocShell->GetBrowsingContext(), aPositions,
+ aPositionDescendants);
+ }
+ mScrollChanged = NO_CHANGE;
+}
+
+void CollectInput(Document& aDocument, InputFormData& aInput,
+ nsTArray<CollectedInputDataValue>& aIdVals,
+ nsTArray<CollectedInputDataValue>& aXPathVals) {
+ PresShell* presShell = aDocument.GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ uint16_t numXPath = 0;
+ uint16_t numId = 0;
+
+ // textarea element
+ SessionStoreUtils::CollectFromTextAreaElement(aDocument, numXPath, numId,
+ aXPathVals, aIdVals);
+ // input element
+ SessionStoreUtils::CollectFromInputElement(aDocument, numXPath, numId,
+ aXPathVals, aIdVals);
+ // select element
+ SessionStoreUtils::CollectFromSelectElement(aDocument, numXPath, numId,
+ aXPathVals, aIdVals);
+
+ Element* bodyElement = aDocument.GetBody();
+ if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
+ bodyElement->GetInnerHTML(aInput.innerHTML, IgnoreErrors());
+ }
+ if (aInput.innerHTML.IsEmpty() && numXPath == 0 && numId == 0) {
+ 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(aInput.url);
+ }
+ aInput.numId = numId;
+ aInput.numXPath = numXPath;
+}
+
+int CollectInputs(BrowsingContext* aBrowsingContext,
+ nsTArray<InputFormData>& aInputs,
+ nsTArray<CollectedInputDataValue>& aIdVals,
+ nsTArray<CollectedInputDataValue>& aXPathVals) {
+ if (aBrowsingContext->CreatedDynamically()) {
+ return 0;
+ }
+
+ nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
+ if (!window || !window->GetDocShell()) {
+ return 0;
+ }
+
+ Document* document = window->GetDoc();
+ if (!document) {
+ return 0;
+ }
+
+ /* Collect data from current frame */
+ InputFormData input;
+ input.descendants = 0;
+ input.numId = 0;
+ input.numXPath = 0;
+ CollectInput(*document, input, aIdVals, aXPathVals);
+ aInputs.AppendElement(input);
+ unsigned long currentIdx = aInputs.Length() - 1;
+
+ /* Collect data from all child frame */
+ // This is not going to work for fission. Bug 1572084 for tracking it.
+ for (auto& child : aBrowsingContext->Children()) {
+ aInputs[currentIdx].descendants +=
+ CollectInputs(child, aInputs, aIdVals, aXPathVals);
+ }
+
+ return aInputs[currentIdx].descendants + 1;
+}
+
+nsTArray<InputFormData> ContentSessionStore::GetInputs(
+ nsTArray<CollectedInputDataValue>& aIdVals,
+ nsTArray<CollectedInputDataValue>& aXPathVals) {
+ nsTArray<InputFormData> inputs;
+ if (mFormDataChanged == PAGELOADEDSTART) {
+ mFormDataChanged = NO_CHANGE;
+ InputFormData input;
+ input.descendants = 0;
+ input.innerHTML.Truncate();
+ input.url.Truncate();
+ input.numId = 0;
+ input.numXPath = 0;
+ inputs.AppendElement(input);
+ } else {
+ mFormDataChanged = NO_CHANGE;
+ CollectInputs(nsDocShell::Cast(mDocShell)->GetBrowsingContext(), inputs,
+ aIdVals, aXPathVals);
+ }
+ return inputs;
+}
+
+bool ContentSessionStore::AppendSessionStorageChange(StorageEvent* aEvent) {
+ // We will collect the full SessionStore if mStorageStatus is FULLSTORAGE.
+ // These partial changes can be skipped in this case.
+ if (mStorageStatus == FULLSTORAGE) {
+ return false;
+ }
+
+ RefPtr<Storage> changingStorage = aEvent->GetStorageArea();
+ if (!changingStorage) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> storagePrincipal = changingStorage->StoragePrincipal();
+ if (!storagePrincipal) {
+ return false;
+ }
+
+ nsAutoCString origin;
+ nsresult rv = storagePrincipal->GetOrigin(origin);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ mOrigins.AppendElement(origin);
+ aEvent->GetKey(*mKeys.AppendElement());
+ aEvent->GetNewValue(*mValues.AppendElement());
+ mStorageStatus = STORAGECHANGE;
+ return true;
+}
+
+bool ContentSessionStore::GetAndClearStorageChanges(
+ nsTArray<nsCString>& aOrigins, nsTArray<nsString>& aKeys,
+ nsTArray<nsString>& aValues) {
+ MOZ_ASSERT(IsStorageUpdated());
+ bool isFullStorage = false;
+
+ if (mStorageStatus == RESET) {
+ isFullStorage = true;
+ } else if (mStorageStatus == FULLSTORAGE) {
+ MOZ_ASSERT(mDocShell);
+ SessionStoreUtils::CollectedSessionStorage(
+ nsDocShell::Cast(mDocShell)->GetBrowsingContext(), aOrigins, aKeys,
+ aValues);
+ isFullStorage = true;
+ } else if (mStorageStatus == STORAGECHANGE) {
+ aOrigins.SwapElements(mOrigins);
+ aKeys.SwapElements(mKeys);
+ aValues.SwapElements(mValues);
+ }
+
+ ResetStorageChanges();
+ mStorageStatus = NO_STORAGE;
+ return isFullStorage;
+}
+
+bool TabListener::ForceFlushFromParent(uint32_t aFlushId, bool aIsFinal) {
+ if (!XRE_IsParentProcess()) {
+ return false;
+ }
+ if (!mSessionStore) {
+ return false;
+ }
+ return UpdateSessionStore(aFlushId, aIsFinal);
+}
+
+void TabListener::UpdateSHistoryChanges(bool aImmediately) {
+ mSessionStore->SetSHistoryFromParentChanged();
+ if (aImmediately) {
+ UpdateSessionStore();
+ } else {
+ AddTimerForUpdate();
+ }
+}
+
+bool TabListener::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
+ if (!aFlushId) {
+ if (!mSessionStore || !mSessionStore->UpdateNeeded()) {
+ return false;
+ }
+ }
+
+ if (!XRE_IsParentProcess()) {
+ BrowserChild* browserChild = BrowserChild::GetFrom(mDocShell);
+ if (browserChild) {
+ StopTimerForUpdate();
+ return browserChild->UpdateSessionStore(aFlushId);
+ }
+ return false;
+ }
+
+ if (!mOwnerContent) {
+ return false;
+ }
+
+ uint32_t chromeFlags = 0;
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (!treeOwner) {
+ return false;
+ }
+ nsCOMPtr<nsIAppWindow> window(do_GetInterface(treeOwner));
+ if (!window) {
+ return false;
+ }
+ if (window && NS_FAILED(window->GetChromeFlags(&chromeFlags))) {
+ return false;
+ }
+
+ UpdateSessionStoreData data;
+ if (mSessionStore->IsDocCapChanged()) {
+ data.mDocShellCaps.Construct() = mSessionStore->GetDocShellCaps();
+ }
+ if (mSessionStore->IsPrivateChanged()) {
+ data.mIsPrivate.Construct() = mSessionStore->GetPrivateModeEnabled();
+ }
+ if (mSessionStore->IsScrollPositionChanged()) {
+ nsTArray<nsCString> positions;
+ nsTArray<int> descendants;
+ mSessionStore->GetScrollPositions(positions, descendants);
+ data.mPositions.Construct(std::move(positions));
+ data.mPositionDescendants.Construct(std::move(descendants));
+ }
+ if (mSessionStore->IsFormDataChanged()) {
+ nsTArray<CollectedInputDataValue> dataWithId, dataWithXpath;
+ nsTArray<InputFormData> inputs =
+ mSessionStore->GetInputs(dataWithId, dataWithXpath);
+ nsTArray<int> descendants, numId, numXPath;
+ nsTArray<nsString> innerHTML;
+ nsTArray<nsCString> url;
+
+ if (dataWithId.Length() != 0) {
+ SessionStoreUtils::ComposeInputData(dataWithId, data.mId.Construct());
+ }
+ if (dataWithXpath.Length() != 0) {
+ SessionStoreUtils::ComposeInputData(dataWithXpath,
+ data.mXpath.Construct());
+ }
+
+ for (const InputFormData& input : inputs) {
+ descendants.AppendElement(input.descendants);
+ numId.AppendElement(input.numId);
+ numXPath.AppendElement(input.numXPath);
+ innerHTML.AppendElement(input.innerHTML);
+ url.AppendElement(input.url);
+ }
+ if (descendants.Length() != 0) {
+ data.mInputDescendants.Construct(std::move(descendants));
+ data.mNumId.Construct(std::move(numId));
+ data.mNumXPath.Construct(std::move(numXPath));
+ data.mInnerHTML.Construct(std::move(innerHTML));
+ data.mUrl.Construct(std::move(url));
+ }
+ }
+ if (mSessionStore->IsStorageUpdated()) {
+ nsTArray<nsCString> origins;
+ nsTArray<nsString> keys, values;
+ data.mIsFullStorage.Construct() =
+ mSessionStore->GetAndClearStorageChanges(origins, keys, values);
+ data.mStorageOrigins.Construct(std::move(origins));
+ data.mStorageKeys.Construct(std::move(keys));
+ data.mStorageValues.Construct(std::move(values));
+ }
+
+ nsCOMPtr<nsISessionStoreFunctions> funcs =
+ do_ImportModule("resource://gre/modules/SessionStoreFunctions.jsm");
+ NS_ENSURE_TRUE(funcs, false);
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(wrapped->GetJSObjectGlobal()));
+ JS::Rooted<JS::Value> dataVal(jsapi.cx());
+ bool ok = ToJSValue(jsapi.cx(), data, &dataVal);
+ NS_ENSURE_TRUE(ok, false);
+
+ nsresult rv = funcs->UpdateSessionStore(
+ mOwnerContent, mDocShell->GetBrowsingContext(), aFlushId, aIsFinal,
+ mEpoch, dataVal, mSessionStore->GetAndClearSHistoryChanged());
+ NS_ENSURE_SUCCESS(rv, false);
+ StopTimerForUpdate();
+ return true;
+}
+
+void TabListener::ResetStorageChangeListener() {
+ if (mStorageChangeListenerRegistered) {
+ return;
+ }
+
+ nsCOMPtr<EventTarget> eventTarget = GetEventTarget();
+ if (!eventTarget) {
+ return;
+ }
+ eventTarget->AddSystemEventListener(u"MozSessionStorageChanged"_ns, this,
+ false);
+ mStorageChangeListenerRegistered = true;
+}
+
+void TabListener::RemoveStorageChangeListener() {
+ if (!mStorageChangeListenerRegistered) {
+ return;
+ }
+
+ nsCOMPtr<EventTarget> eventTarget = GetEventTarget();
+ if (eventTarget) {
+ eventTarget->RemoveSystemEventListener(u"MozSessionStorageChanged"_ns, this,
+ false);
+ mStorageChangeListenerRegistered = false;
+ }
+}
+
+void TabListener::RemoveListeners() {
+ if (mProgressListenerRegistered) {
+ nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(mDocShell);
+ if (webProgress) {
+ webProgress->RemoveProgressListener(this);
+ mProgressListenerRegistered = false;
+ }
+ }
+
+ if (mEventListenerRegistered || mStorageChangeListenerRegistered) {
+ nsCOMPtr<EventTarget> eventTarget = GetEventTarget();
+ if (eventTarget) {
+ if (mEventListenerRegistered) {
+ eventTarget->RemoveSystemEventListener(u"mozvisualscroll"_ns, this,
+ false);
+ eventTarget->RemoveSystemEventListener(u"input"_ns, this, false);
+ if (mozilla::SessionHistoryInParent()) {
+ eventTarget->RemoveSystemEventListener(u"DOMTitleChanged"_ns, this,
+ false);
+ }
+ mEventListenerRegistered = false;
+ }
+ if (mStorageChangeListenerRegistered) {
+ eventTarget->RemoveSystemEventListener(u"MozSessionStorageChanged"_ns,
+ this, false);
+ mStorageChangeListenerRegistered = false;
+ }
+ }
+ }
+
+ if (mPrefObserverRegistered || mStorageObserverRegistered) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return;
+ }
+ if (mPrefObserverRegistered) {
+ obs->RemoveObserver(this, kTimeOutDisable);
+ obs->RemoveObserver(this, kPrefInterval);
+ mPrefObserverRegistered = false;
+ }
+ if (mStorageObserverRegistered) {
+ obs->RemoveObserver(this, "browser:purge-sessionStorage");
+ mStorageObserverRegistered = false;
+ }
+ }
+}
+
+TabListener::~TabListener() { RemoveListeners(); }
diff --git a/toolkit/components/sessionstore/SessionStoreListener.h b/toolkit/components/sessionstore/SessionStoreListener.h
new file mode 100644
index 0000000000..e2f79cb5e6
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreListener.h
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStoreListener_h
+#define mozilla_dom_SessionStoreListener_h
+
+#include "nsIDOMEventListener.h"
+#include "nsIPrivacyTransitionObserver.h"
+#include "nsIWebProgressListener.h"
+#include "SessionStoreData.h"
+
+class nsITimer;
+
+namespace mozilla {
+namespace dom {
+
+class StorageEvent;
+
+class ContentSessionStore {
+ public:
+ explicit ContentSessionStore(nsIDocShell* aDocShell);
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ContentSessionStore)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ContentSessionStore)
+
+ void OnPrivateModeChanged(bool aEnabled);
+ bool IsDocCapChanged() { return mDocCapChanged; }
+ nsCString GetDocShellCaps();
+ bool IsPrivateChanged() { return mPrivateChanged; }
+ bool GetPrivateModeEnabled();
+ void SetScrollPositionChanged() { mScrollChanged = WITH_CHANGE; }
+ bool IsScrollPositionChanged() { return mScrollChanged != NO_CHANGE; }
+ void GetScrollPositions(nsTArray<nsCString>& aPositions,
+ nsTArray<int32_t>& aPositionDescendants);
+ void SetFormDataChanged() { mFormDataChanged = WITH_CHANGE; }
+ bool IsFormDataChanged() { return mFormDataChanged != NO_CHANGE; }
+ nsTArray<InputFormData> GetInputs(
+ nsTArray<CollectedInputDataValue>& aIdVals,
+ nsTArray<CollectedInputDataValue>& aXPathVals);
+
+ // Use "mStorageStatus" to manage the status of storageChanges
+ bool IsStorageUpdated() { return mStorageStatus != NO_STORAGE; }
+ void ResetStorage() { mStorageStatus = RESET; }
+ /*
+ There are three situations we need entire session storage:
+ 1. OnDocumentStart: PageLoad started
+ 2. OnDocumentEnd: PageLoad completed
+ 3. receive "browser:purge-sessionStorage" event
+ Use SetFullStorageNeeded() to set correct "mStorageStatus" and
+ reset the pending individual change.
+ */
+ void SetFullStorageNeeded();
+ void ResetStorageChanges();
+ // GetAndClearStorageChanges() is used for getting storageChanges.
+ // It clears the stored storage changes before returning.
+ // It will return true if it is a entire session storage.
+ // Otherwise, it will return false.
+ bool GetAndClearStorageChanges(nsTArray<nsCString>& aOrigins,
+ nsTArray<nsString>& aKeys,
+ nsTArray<nsString>& aValues);
+ // Using AppendSessionStorageChange() to append session storage change when
+ // receiving "MozSessionStorageChanged".
+ // Return true if there is a new storage change which is appended.
+ bool AppendSessionStorageChange(StorageEvent* aEvent);
+
+ void SetSHistoryChanged();
+ // request "collect sessionHistory" which is happened in the parent process
+ void SetSHistoryFromParentChanged();
+ bool GetAndClearSHistoryChanged() {
+ bool ret = mSHistoryChanged;
+ mSHistoryChanged = false;
+ mSHistoryChangedFromParent = false;
+ return ret;
+ }
+
+ void OnDocumentStart();
+ void OnDocumentEnd();
+ bool UpdateNeeded() {
+ return mPrivateChanged || mDocCapChanged || IsScrollPositionChanged() ||
+ IsFormDataChanged() || IsStorageUpdated() || mSHistoryChanged ||
+ mSHistoryChangedFromParent;
+ }
+
+ private:
+ virtual ~ContentSessionStore() = default;
+ nsCString CollectDocShellCapabilities();
+
+ nsCOMPtr<nsIDocShell> mDocShell;
+ bool mPrivateChanged;
+ bool mIsPrivate;
+ enum {
+ NO_CHANGE,
+ PAGELOADEDSTART, // set when the state of document is STATE_START
+ WITH_CHANGE, // set when the change event is observed
+ } mScrollChanged,
+ mFormDataChanged;
+ enum {
+ NO_STORAGE,
+ RESET,
+ FULLSTORAGE,
+ STORAGECHANGE,
+ } mStorageStatus;
+ bool mDocCapChanged;
+ nsCString mDocCaps;
+ // mOrigins, mKeys, mValues are for sessionStorage partial changes
+ nsTArray<nsCString> mOrigins;
+ nsTArray<nsString> mKeys;
+ nsTArray<nsString> mValues;
+ // 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;
+ // mSHistoryChangedFromParent means there are history changes which
+ // are found by session history listener in the parent process.
+ bool mSHistoryChangedFromParent;
+};
+
+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
+ bool ForceFlushFromParent(uint32_t aFlushId, bool aIsFinal = false);
+ void RemoveListeners();
+ void SetEpoch(uint32_t aEpoch) { mEpoch = aEpoch; }
+ uint32_t GetEpoch() { return mEpoch; }
+ void UpdateSHistoryChanges(bool aImmediately);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TabListener, nsIDOMEventListener)
+
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIPRIVACYTRANSITIONOBSERVER
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ private:
+ static void TimerCallback(nsITimer* aTimer, void* aClosure);
+ void AddTimerForUpdate();
+ void StopTimerForUpdate();
+ bool UpdateSessionStore(uint32_t aFlushId = 0, bool aIsFinal = false);
+ void ResetStorageChangeListener();
+ void RemoveStorageChangeListener();
+ virtual ~TabListener();
+
+ nsCOMPtr<nsIDocShell> mDocShell;
+ RefPtr<ContentSessionStore> mSessionStore;
+ RefPtr<mozilla::dom::Element> mOwnerContent;
+ bool mProgressListenerRegistered;
+ bool mEventListenerRegistered;
+ bool mPrefObserverRegistered;
+ bool mStorageObserverRegistered;
+ bool mStorageChangeListenerRegistered;
+ // Timer used to update data
+ nsCOMPtr<nsITimer> mUpdatedTimer;
+ bool mTimeoutDisabled;
+ int32_t mUpdateInterval;
+ uint32_t mEpoch;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#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..26f82273ca
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreMessageUtils.h
@@ -0,0 +1,71 @@
+/* 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 "SessionStoreData.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::CollectedNonMultipleSelectValue> {
+ typedef mozilla::dom::CollectedNonMultipleSelectValue paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mSelectedIndex);
+ WriteParam(aMsg, aParam.mValue);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mSelectedIndex) &&
+ ReadParam(aMsg, aIter, &aResult->mValue);
+ }
+};
+
+template <>
+struct ParamTraits<CollectedInputDataValue> {
+ typedef CollectedInputDataValue paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.id);
+ WriteParam(aMsg, aParam.type);
+ WriteParam(aMsg, aParam.value);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->id) &&
+ ReadParam(aMsg, aIter, &aResult->type) &&
+ ReadParam(aMsg, aIter, &aResult->value);
+ }
+};
+
+template <>
+struct ParamTraits<InputFormData> {
+ typedef InputFormData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.descendants);
+ WriteParam(aMsg, aParam.innerHTML);
+ WriteParam(aMsg, aParam.url);
+ WriteParam(aMsg, aParam.numId);
+ WriteParam(aMsg, aParam.numXPath);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->descendants) &&
+ ReadParam(aMsg, aIter, &aResult->innerHTML) &&
+ ReadParam(aMsg, aIter, &aResult->url) &&
+ ReadParam(aMsg, aIter, &aResult->numId) &&
+ ReadParam(aMsg, aIter, &aResult->numXPath);
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_dom_SessionStoreMessageUtils_h
diff --git a/toolkit/components/sessionstore/SessionStoreUtils.cpp b/toolkit/components/sessionstore/SessionStoreUtils.cpp
new file mode 100644
index 0000000000..4bdc191ccb
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp
@@ -0,0 +1,1366 @@
+/* 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 "jsapi.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/AutocompleteInfoBinding.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.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/SessionStoreUtils.h"
+#include "mozilla/dom/txIXPathContext.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/XPathResult.h"
+#include "mozilla/dom/XPathEvaluator.h"
+#include "mozilla/dom/XPathExpression.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentList.h"
+#include "nsContentUtils.h"
+#include "nsFocusManager.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIDocShell.h"
+#include "nsIFormControl.h"
+#include "nsIScrollableFrame.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+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()) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocShell> childDocShell(do_QueryInterface(item));
+ if (!childDocShell) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ int32_t childOffset = childDocShell->GetChildOffset();
+ 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
+
+ TRY_ALLOWPROP(Plugins);
+ // Bug 1328013 : Don't collect "AllowJavascript" property
+ // TRY_ALLOWPROP(Javascript);
+ TRY_ALLOWPROP(MetaRedirects);
+ TRY_ALLOWPROP(Subframes);
+ TRY_ALLOWPROP(Images);
+ TRY_ALLOWPROP(Media);
+ TRY_ALLOWPROP(DNSPrefetch);
+ TRY_ALLOWPROP(WindowControl);
+ TRY_ALLOWPROP(Auth);
+ TRY_ALLOWPROP(ContentRetargeting);
+ TRY_ALLOWPROP(ContentRetargetingOnChildren);
+#undef TRY_ALLOWPROP
+}
+
+/* static */
+void SessionStoreUtils::RestoreDocShellCapabilities(
+ const GlobalObject& aGlobal, nsIDocShell* aDocShell,
+ const nsCString& aDisallowCapabilities) {
+ aDocShell->SetAllowPlugins(true);
+ aDocShell->SetAllowJavascript(true);
+ aDocShell->SetAllowMetaRedirects(true);
+ aDocShell->SetAllowSubframes(true);
+ aDocShell->SetAllowImages(true);
+ aDocShell->SetAllowMedia(true);
+ aDocShell->SetAllowDNSPrefetch(true);
+ aDocShell->SetAllowWindowControl(true);
+ aDocShell->SetAllowContentRetargeting(true);
+ aDocShell->SetAllowContentRetargetingOnChildren(true);
+
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(aDisallowCapabilities, ',').ToRange()) {
+ if (token.EqualsLiteral("Plugins")) {
+ aDocShell->SetAllowPlugins(false);
+ } else if (token.EqualsLiteral("Javascript")) {
+ aDocShell->SetAllowJavascript(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);
+ }
+ }
+}
+
+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()) {
+ return;
+ }
+
+ nsCCharSeparatedTokenizer tokenizer(aData.mScroll.Value(), ',');
+ 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->IsRootContentDocument()) {
+ // 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;
+}
+
+// A helper function to append a element into aXPathVals or aIdVals
+static void AppendEntryToCollectedData(
+ nsINode* aNode, const nsAString& aId, CollectedInputDataValue& aEntry,
+ uint16_t& aNumXPath, uint16_t& aNumId,
+ nsTArray<CollectedInputDataValue>& aXPathVals,
+ nsTArray<CollectedInputDataValue>& aIdVals) {
+ if (!aId.IsEmpty()) {
+ aEntry.id = aId;
+ aIdVals.AppendElement(aEntry);
+ aNumId++;
+ } else {
+ nsAutoString xpath;
+ aNode->GenerateXPath(xpath);
+ aEntry.id = xpath;
+ aXPathVals.AppendElement(aEntry);
+ aNumXPath++;
+ }
+}
+
+/* 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 bool value */
+static void AppendValueToCollectedData(
+ nsINode* aNode, const nsAString& aId, const bool& aValue,
+ uint16_t& aNumXPath, uint16_t& aNumId,
+ nsTArray<CollectedInputDataValue>& aXPathVals,
+ nsTArray<CollectedInputDataValue>& aIdVals) {
+ CollectedInputDataValue entry;
+ entry.type = u"bool"_ns;
+ entry.value = AsVariant(aValue);
+ AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
+ aIdVals);
+}
+
+/* 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 nsString value */
+static void AppendValueToCollectedData(
+ nsINode* aNode, const nsAString& aId, const nsString& aValue,
+ uint16_t& aNumXPath, uint16_t& aNumId,
+ nsTArray<CollectedInputDataValue>& aXPathVals,
+ nsTArray<CollectedInputDataValue>& aIdVals) {
+ CollectedInputDataValue entry;
+ entry.type = u"string"_ns;
+ entry.value = AsVariant(aValue);
+ AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
+ aIdVals);
+}
+
+/* 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();
+}
+
+/* for single select value */
+static void AppendValueToCollectedData(
+ nsINode* aNode, const nsAString& aId,
+ const CollectedNonMultipleSelectValue& aValue, uint16_t& aNumXPath,
+ uint16_t& aNumId, nsTArray<CollectedInputDataValue>& aXPathVals,
+ nsTArray<CollectedInputDataValue>& aIdVals) {
+ CollectedInputDataValue entry;
+ entry.type = u"singleSelect"_ns;
+ entry.value = AsVariant(aValue);
+ AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
+ aIdVals);
+}
+
+/* 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);
+}
+
+static void AppendValueToCollectedData(
+ Document& aDocument, nsINode* aNode, const nsAString& aId,
+ const nsString& aValue, uint16_t& aNumXPath, uint16_t& aNumId,
+ nsTArray<CollectedInputDataValue>& aXPathVals,
+ nsTArray<CollectedInputDataValue>& aIdVals) {
+ CollectedInputDataValue entry;
+ entry.type = u"string"_ns;
+ entry.value = AsVariant(aValue);
+ AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
+ aIdVals);
+}
+
+/* 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 nsTArray<nsString>: file and multipleSelect */
+static void AppendValueToCollectedData(
+ nsINode* aNode, const nsAString& aId, const nsAString& aValueType,
+ const nsTArray<nsString>& aValue, uint16_t& aNumXPath, uint16_t& aNumId,
+ nsTArray<CollectedInputDataValue>& aXPathVals,
+ nsTArray<CollectedInputDataValue>& aIdVals) {
+ CollectedInputDataValue entry;
+ entry.type = aValueType;
+ entry.value = AsVariant(CopyableTArray(aValue.Clone()));
+ AppendEntryToCollectedData(aNode, aId, entry, aNumXPath, aNumId, aXPathVals,
+ aIdVals);
+}
+
+/* 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) {
+ uint8_t controlType = formControl->ControlType();
+ if (controlType == NS_FORM_INPUT_PASSWORD ||
+ controlType == NS_FORM_INPUT_HIDDEN ||
+ controlType == NS_FORM_INPUT_BUTTON ||
+ controlType == NS_FORM_INPUT_IMAGE ||
+ controlType == NS_FORM_INPUT_SUBMIT ||
+ controlType == NS_FORM_INPUT_RESET) {
+ 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() == NS_FORM_INPUT_CHECKBOX ||
+ input->ControlType() == NS_FORM_INPUT_RADIO) {
+ bool checked = input->Checked();
+ if (checked == input->DefaultChecked()) {
+ continue;
+ }
+ AppendValueToCollectedData(input, id, checked, aGeneratedCount,
+ std::forward<ArgsT>(args)...);
+ } else if (input->ControlType() == NS_FORM_INPUT_FILE) {
+ 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 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);
+
+ Element* bodyElement = aDocument.GetBody();
+ if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
+ 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;
+ HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(aElement);
+ if (textArea) {
+ textArea->SetValue(aValue, rv);
+ if (!rv.Failed()) {
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ return;
+ }
+ HTMLInputElement* input = HTMLInputElement::FromNode(aElement);
+ if (input) {
+ input->SetValue(aValue, CallerType::NonSystem, rv);
+ if (!rv.Failed()) {
+ nsContentUtils::DispatchInputEvent(aElement);
+ return;
+ }
+ }
+ input = HTMLInputElement::FromNodeOrNull(
+ nsFocusManager::GetRedirectedFocus(aElement));
+ if (input) {
+ input->SetValue(aValue, CallerType::NonSystem, rv);
+ if (!rv.Failed()) {
+ nsContentUtils::DispatchInputEvent(aElement);
+ }
+ }
+}
+
+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) {
+ nsTArray<nsString> fileList;
+ 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() == NS_FORM_INPUT_FILE) {
+ 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);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetRestoreData(JSContext* aCx, Element* aElement,
+ JS::MutableHandle<JS::Value> aObject) {
+ nsAutoString data;
+ if (nsContentUtils::StringifyJSON(aCx, aObject, data)) {
+ SetElementAsString(aElement, data);
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+static void SetInnerHTML(Document& aDocument, const CollectedData& aData) {
+ RefPtr<Element> bodyElement = aDocument.GetBody();
+ if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
+ IgnoredErrorResult rv;
+ bodyElement->SetInnerHTML(aData.mInnerHTML.Value(),
+ 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(JSContext* aCx, 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(
+ aCx, aDocument, XPathResult::FIRST_ORDERED_NODE_TYPE, nullptr, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return Element::FromNodeOrNull(result->GetSingleNodeValue(rv));
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+/* 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;
+ }
+ if (aData.mInnerHTML.WasPassed()) {
+ SetInnerHTML(aDocument, aData);
+ }
+ 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")) {
+ nsAutoCString url;
+ Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
+ if (url.EqualsLiteral("about:sessionrestore") ||
+ url.EqualsLiteral("about:welcomeback")) {
+ JS::Rooted<JS::Value> object(
+ cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
+ SetRestoreData(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(aGlobal.Context(), 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;
+}
+
+/* Read entries in the session storage data contained in a tab's history. */
+static void ReadAllEntriesFromStorage(nsPIDOMWindowOuter* aWindow,
+ nsTArray<nsCString>& aOrigins,
+ nsTArray<nsString>& aKeys,
+ nsTArray<nsString>& aValues) {
+ BrowsingContext* const browsingContext = aWindow->GetBrowsingContext();
+ if (!browsingContext) {
+ return;
+ }
+
+ Document* doc = aWindow->GetDoc();
+ if (!doc) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ if (!principal) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> storagePrincipal = doc->EffectiveStoragePrincipal();
+ if (!storagePrincipal) {
+ return;
+ }
+
+ nsAutoCString origin;
+ nsresult rv = storagePrincipal->GetOrigin(origin);
+ if (NS_FAILED(rv) || aOrigins.Contains(origin)) {
+ // Don't read a host twice.
+ return;
+ }
+
+ /* Completed checking for recursion and is about to read storage*/
+ const RefPtr<SessionStorageManager> storageManager =
+ browsingContext->GetSessionStorageManager();
+ if (!storageManager) {
+ return;
+ }
+ RefPtr<Storage> storage;
+ storageManager->GetStorage(aWindow->GetCurrentInnerWindow(), principal,
+ storagePrincipal, false, getter_AddRefs(storage));
+ if (!storage) {
+ return;
+ }
+ mozilla::IgnoredErrorResult result;
+ uint32_t len = storage->GetLength(*principal, result);
+ if (result.Failed() || len == 0) {
+ return;
+ }
+ int64_t storageUsage = storage->GetOriginQuotaUsage();
+ if (storageUsage > StaticPrefs::browser_sessionstore_dom_storage_limit()) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < len; i++) {
+ nsString key, value;
+ mozilla::IgnoredErrorResult res;
+ storage->Key(i, key, *principal, res);
+ if (res.Failed()) {
+ continue;
+ }
+
+ storage->GetItem(key, value, *principal, res);
+ if (res.Failed()) {
+ continue;
+ }
+
+ aKeys.AppendElement(key);
+ aValues.AppendElement(value);
+ aOrigins.AppendElement(origin);
+ }
+}
+
+/* Collect Collect session storage from current frame and all child frame */
+/* static */
+void SessionStoreUtils::CollectedSessionStorage(
+ BrowsingContext* aBrowsingContext, nsTArray<nsCString>& aOrigins,
+ nsTArray<nsString>& aKeys, nsTArray<nsString>& aValues) {
+ /* Collect session store from current frame */
+ nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
+ if (!window) {
+ return;
+ }
+ ReadAllEntriesFromStorage(window, aOrigins, aKeys, aValues);
+
+ /* Collect session storage from all child frame */
+ if (!window->GetDocShell()) {
+ return;
+ }
+
+ // This is not going to work for fission. Bug 1572084 for tracking it.
+ for (BrowsingContext* child : aBrowsingContext->Children()) {
+ if (!child->CreatedDynamically()) {
+ SessionStoreUtils::CollectedSessionStorage(child, aOrigins, aKeys,
+ aValues);
+ }
+ }
+}
+
+/* static */
+void SessionStoreUtils::RestoreSessionStorage(
+ const GlobalObject& aGlobal, nsIDocShell* aDocShell,
+ const Record<nsString, Record<nsString, nsString>>& aData) {
+ for (auto& entry : aData.Entries()) {
+ // NOTE: In capture() we record the full origin for the URI which the
+ // sessionStorage is being captured for. As of bug 1235657 this code
+ // stopped parsing any origins which have originattributes correctly, as
+ // it decided to use the origin attributes from the docshell, and try to
+ // interpret the origin as a URI. Since bug 1353844 this code now correctly
+ // parses the full origin, and then discards the origin attributes, to
+ // make the behavior line up with the original intentions in bug 1235657
+ // while preserving the ability to read all session storage from
+ // previous versions. In the future, if this behavior is desired, we may
+ // want to use the spec instead of the origin as the key, and avoid
+ // transmitting origin attribute information which we then discard when
+ // restoring.
+ //
+ // If changing this logic, make sure to also change the principal
+ // computation logic in SessionStore::_sendRestoreHistory.
+
+ // OriginAttributes are always after a '^' character
+ int32_t pos = entry.mKey.RFindChar('^');
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
+ NS_ConvertUTF16toUTF8(Substring(entry.mKey, 0, pos)));
+ BrowsingContext* const browsingContext =
+ nsDocShell::Cast(aDocShell)->GetBrowsingContext();
+ if (!browsingContext) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> storagePrincipal =
+ BasePrincipal::CreateContentPrincipal(
+ NS_ConvertUTF16toUTF8(entry.mKey));
+
+ const RefPtr<SessionStorageManager> storageManager =
+ browsingContext->GetSessionStorageManager();
+ if (!storageManager) {
+ return;
+ }
+ RefPtr<Storage> storage;
+ // There is no need to pass documentURI, it's only used to fill documentURI
+ // property of domstorage event, which in this case has no consumer.
+ // Prevention of events in case of missing documentURI will be solved in a
+ // followup bug to bug 600307.
+ // Null window because the current window doesn't match the principal yet
+ // and loads about:blank.
+ storageManager->CreateStorage(nullptr, principal, storagePrincipal, u""_ns,
+ false, getter_AddRefs(storage));
+ if (!storage) {
+ continue;
+ }
+ for (auto& InnerEntry : entry.mValue.Entries()) {
+ IgnoredErrorResult result;
+ storage->SetItem(InnerEntry.mKey, InnerEntry.mValue, *principal, result);
+ if (result.Failed()) {
+ NS_WARNING("storage set item failed!");
+ }
+ }
+ }
+}
+
+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->GetDoc();
+ 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));
+ }
+}
diff --git a/toolkit/components/sessionstore/SessionStoreUtils.h b/toolkit/components/sessionstore/SessionStoreUtils.h
new file mode 100644
index 0000000000..b52ab4f483
--- /dev/null
+++ b/toolkit/components/sessionstore/SessionStoreUtils.h
@@ -0,0 +1,101 @@
+/* -*- 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/dom/BindingDeclarations.h"
+#include "mozilla/dom/SessionStoreUtilsBinding.h"
+#include "SessionStoreData.h"
+
+class nsIDocument;
+class nsGlobalWindowInner;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+struct SSScrollPositionDict;
+
+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(
+ const GlobalObject& aGlobal, nsIDocShell* aDocShell,
+ const nsCString& aDisallowCapabilities);
+
+ static void CollectScrollPosition(const GlobalObject& aGlobal,
+ WindowProxyHolder& aWindow,
+ Nullable<CollectedData>& aRetVal);
+
+ static void RestoreScrollPosition(const GlobalObject& aGlobal,
+ nsGlobalWindowInner& aWindow,
+ const CollectedData& data);
+
+ /*
+ @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);
+
+ 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);
+
+ static void CollectedSessionStorage(BrowsingContext* aBrowsingContext,
+ nsTArray<nsCString>& aOrigins,
+ nsTArray<nsString>& aKeys,
+ nsTArray<nsString>& aValues);
+
+ static void RestoreSessionStorage(
+ const GlobalObject& aGlobal, nsIDocShell* aDocShell,
+ const Record<nsString, Record<nsString, nsString>>& aData);
+
+ static void ComposeInputData(const nsTArray<CollectedInputDataValue>& aData,
+ InputElementData& ret);
+};
+
+} // 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..dc2b0aec46
--- /dev/null
+++ b/toolkit/components/sessionstore/moz.build
@@ -0,0 +1,34 @@
+# -*- 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 += [
+ "SessionStoreData.h",
+ "SessionStoreListener.h",
+ "SessionStoreMessageUtils.h",
+ "SessionStoreUtils.h",
+]
+
+UNIFIED_SOURCES += [
+ "SessionStoreListener.cpp",
+ "SessionStoreUtils.cpp",
+]
+
+EXTRA_JS_MODULES += [
+ "SessionStoreFunctions.jsm",
+]
+
+XPIDL_MODULE = "sessionStore_funcs"
+
+XPIDL_SOURCES += [
+ "SessionStoreFunctions.idl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Session Restore")