Bug 1572084 - Part 2: Make Session Store data collection work with fission. r=nika

Instead of collecting data from the entire tree of documents, we
collect data per document. The collected data is sent to the
corresponding parent window context and is applied incrementally to
the tab state cache.

Differential Revision: https://phabricator.services.mozilla.com/D107814
This commit is contained in:
Andreas Farre 2021-03-25 15:36:38 +00:00
Родитель a0d3aba220
Коммит 728f061c16
38 изменённых файлов: 1511 добавлений и 828 удалений

Просмотреть файл

@ -906,9 +906,6 @@ pref("browser.sessionstore.resume_from_crash", true);
pref("browser.sessionstore.resume_session_once", false);
pref("browser.sessionstore.resuming_after_os_restart", false);
// Minimal interval between two save operations in milliseconds (while the user is active).
pref("browser.sessionstore.interval", 15000); // 15 seconds
// Minimal interval between two save operations in milliseconds (while the user is idle).
pref("browser.sessionstore.interval.idle", 3600000); // 1h
@ -949,10 +946,6 @@ pref("browser.sessionstore.upgradeBackup.latestBuildID", "");
pref("browser.sessionstore.upgradeBackup.maxUpgradeBackups", 3);
// End-users should not run sessionstore in debug mode
pref("browser.sessionstore.debug", false);
// Causes SessionStore to ignore non-final update messages from
// browser tabs that were not caused by a flush from the parent.
// This is a testing flag and should not be used by end-users.
pref("browser.sessionstore.debug.no_auto_updates", false);
// Forget closed windows/tabs after two weeks
pref("browser.sessionstore.cleanup.forget_closed_after", 1209600000);
// Amount of failed SessionFile writes until we restart the worker.

Просмотреть файл

@ -1294,13 +1294,6 @@ var SessionStoreInternal = {
TabState.update(aBrowser, aData);
let win = aBrowser.ownerGlobal;
this.saveStateDelayed(win);
if (aData.flushID) {
// This is an update kicked off by an async flush request. Notify the
// TabStateFlusher so that it can finish the request and notify its
// consumer that's waiting for the flush to be done.
TabStateFlusher.resolve(aBrowser, aData.flushID);
}
},
/**

Просмотреть файл

@ -199,19 +199,18 @@ var TabStateInternal = {
if (value.hasOwnProperty("requestedIndex")) {
tabData.requestedIndex = value.requestedIndex;
}
} else if (!value && (key == "scroll" || key == "formdata")) {
// [Bug 1554512]
// If scroll or formdata null it indicates that the update to
// be performed is to remove them, and not copy a null
// value. Scroll will be null when the position is at the top
// of the document, formdata will be null when there is only
// default data.
delete tabData[key];
} else {
tabData[key] = value;
}
}
// [Bug 1554512]
// If the latest scroll position is on the top, we will delete scroll entry.
// When scroll entry is deleted in TabStateCache, it cannot be updated.
// To prevent losing the scroll position, we need to add a handing here.
if (tabData.scroll) {
if (!data.scroll) {
delete tabData.scroll;
}
}
},
};

Просмотреть файл

@ -139,6 +139,77 @@ var TabStateCacheInternal = {
}
},
/**
* Helper function used by update (see below). To be fission compatible
* we need to be able to update scroll and formdata per entry in the
* cache. This is done by looking up the desired position and applying
* the update for that node only.
*
* @param data (object)
* The cached data where we want to update the changes.
* @param path (object)
* The path to the node to update specified by a list of indices
* to follow from the root downwards.
* @param includeChildren (booelan)
* Determines if the children of the changed node should be kept
* or not.
* @param change (object)
* Object containing the optional formdata and optional scroll
* position to be updated as well as information if the node
* should keep the data for its children.
*/
updatePartialWindowStateChange(data, path, includeChildren, change) {
if (!path.length) {
for (let key of Object.keys(change)) {
let children = includeChildren ? data[key]?.children : null;
if (!Object.keys(change[key]).length) {
data[key] = null;
} else {
data[key] = change[key];
}
if (children) {
data[key] = { ...data[key], children };
}
}
return data;
}
let index = path.pop();
let scroll = data?.scroll?.children?.[index];
let formdata = data?.formdata?.children?.[index];
change = this.updatePartialWindowStateChange(
{ scroll, formdata },
path,
includeChildren,
change
);
for (let key of Object.keys(change)) {
let value = change[key];
let children = data[key]?.children;
if (children) {
if (value) {
children[index] = value;
} else {
delete children[index];
}
if (!children.some(e => e)) {
data[key] = null;
}
} else if (value) {
children = new Array(index + 1);
children[index] = value;
data[key] = { ...data[key], children };
}
}
return data;
},
/**
* Updates cached data for a given |tab| or associated |browser|.
*
@ -162,6 +233,22 @@ var TabStateCacheInternal = {
continue;
}
if (key == "windowstatechange") {
let { path, hasChildren, ...change } = newData.windowstatechange;
this.updatePartialWindowStateChange(data, path, hasChildren, change);
for (key of Object.keys(change)) {
let value = data[key];
if (value === null) {
delete data[key];
} else {
data[key] = value;
}
}
continue;
}
let value = newData[key];
if (value === null) {
delete data[key];

Просмотреть файл

@ -77,11 +77,23 @@ var TabStateFlusherInternal = {
// Stores the last request ID.
_lastRequestID: 0,
// A map storing all active requests per browser.
// A map storing all active requests per browser. A request is a
// triple of a map containing all flush requests, a promise that
// resolve when a request for a browser is canceled, and the
// function to call to cancel a reqeust.
_requests: new WeakMap(),
// A map storing if there is requests to native listener per browser.
_requestsToNativeListener: new WeakMap(),
initEntry(entry) {
entry.perBrowserRequests = new Map();
entry.cancelPromise = new Promise(resolve => {
entry.cancel = resolve;
}).then(result => {
TabStateFlusherInternal.initEntry(entry);
return result;
});
return entry;
},
/**
* Requests an async flush for the given browser. Returns a promise that will
@ -90,13 +102,13 @@ var TabStateFlusherInternal = {
*/
flush(browser) {
let id = ++this._lastRequestID;
let requestNativeListener = false;
let nativePromise = Promise.resolve();
if (browser && browser.frameLoader) {
/*
Request native listener to flush the tabState.
True if the flush is involved async ipc call.
*/
requestNativeListener = browser.frameLoader.requestTabStateFlush(id);
Resolves when flush is complete.
*/
nativePromise = browser.frameLoader.requestTabStateFlush();
}
/*
In the event that we have to trigger a process switch and thus change
@ -113,22 +125,23 @@ var TabStateFlusherInternal = {
// Retrieve active requests for given browser.
let permanentKey = browser.permanentKey;
let perBrowserRequests = this._requests.get(permanentKey) || new Map();
let perBrowserRequestsToNative =
this._requestsToNativeListener.get(permanentKey) || new Map();
let request = this._requests.get(permanentKey);
if (!request) {
// If we don't have any requests for this browser, create a new
// entry for browser.
request = this.initEntry({});
this._requests.set(permanentKey, request);
}
return new Promise(resolve => {
let promise = new Promise(resolve => {
// Store resolve() so that we can resolve the promise later.
perBrowserRequests.set(id, resolve);
perBrowserRequestsToNative.set(id, requestNativeListener);
// Update the flush requests stored per browser.
this._requests.set(permanentKey, perBrowserRequests);
this._requestsToNativeListener.set(
permanentKey,
perBrowserRequestsToNative
);
request.perBrowserRequests.set(id, resolve);
});
return Promise.race([
nativePromise.then(_ => promise),
request.cancelPromise,
]);
},
/**
@ -164,25 +177,8 @@ var TabStateFlusherInternal = {
return;
}
// Check if there is request to native listener for given browser.
let perBrowserRequestsForNativeListener = this._requestsToNativeListener.get(
browser.permanentKey
);
if (!perBrowserRequestsForNativeListener.has(flushID)) {
return;
}
let waitForNextResolve = perBrowserRequestsForNativeListener.get(flushID);
if (waitForNextResolve) {
perBrowserRequestsForNativeListener.set(flushID, false);
this._requestsToNativeListener.set(
browser.permanentKey,
perBrowserRequestsForNativeListener
);
return;
}
// Retrieve active requests for given browser.
let perBrowserRequests = this._requests.get(browser.permanentKey);
let { perBrowserRequests } = this._requests.get(browser.permanentKey);
if (!perBrowserRequests.has(flushID)) {
return;
}
@ -217,19 +213,14 @@ var TabStateFlusherInternal = {
return;
}
// Retrieve active requests for given browser.
let perBrowserRequests = this._requests.get(browser.permanentKey);
// Retrieve the cancel function for a given browser.
let { cancel } = this._requests.get(browser.permanentKey);
if (!success) {
Cu.reportError("Failed to flush browser: " + message);
}
// Resolve all requests.
for (let resolve of perBrowserRequests.values()) {
resolve(success);
}
// Clear active requests.
perBrowserRequests.clear();
cancel(success);
},
};

Просмотреть файл

@ -20,6 +20,7 @@
# endif
#endif
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/BrowsingContextBinding.h"
@ -34,6 +35,7 @@
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/dom/SessionStoreDataCollector.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/UserActivationIPCUtils.h"
#include "mozilla/dom/WindowBinding.h"
@ -2140,6 +2142,44 @@ void BrowsingContext::IncrementHistoryEntryCountForBrowsingContext() {
Unused << SetHistoryEntryCount(GetHistoryEntryCount() + 1);
}
void BrowsingContext::FlushSessionStore() {
nsTArray<RefPtr<BrowserChild>> nestedBrowserChilds;
PreOrderWalk([&](BrowsingContext* aContext) {
BrowserChild* browserChild = BrowserChild::GetFrom(aContext->GetDocShell());
if (browserChild && browserChild->GetBrowsingContext() == aContext) {
nestedBrowserChilds.AppendElement(browserChild);
}
if (aContext->CreatedDynamically()) {
return WalkFlag::Skip;
}
WindowContext* windowContext = aContext->GetCurrentWindowContext();
if (!windowContext) {
return WalkFlag::Skip;
}
WindowGlobalChild* windowChild = windowContext->GetWindowGlobalChild();
if (!windowChild) {
return WalkFlag::Next;
}
RefPtr<SessionStoreDataCollector> collector =
windowChild->GetSessionStoreDataCollector();
if (!collector) {
return WalkFlag::Next;
}
collector->Flush();
return WalkFlag::Next;
});
for (auto& child : nestedBrowserChilds) {
child->UpdateSessionStore();
}
}
std::tuple<bool, bool> BrowsingContext::CanFocusCheck(CallerType aCallerType) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) {

Просмотреть файл

@ -198,7 +198,8 @@ enum class ExplicitActiveStatus : uint8_t {
* browsing context. */ \
FIELD(HistoryEntryCount, uint32_t) \
FIELD(IsInBFCache, bool) \
FIELD(HasRestoreData, bool)
FIELD(HasRestoreData, bool) \
FIELD(SessionStoreEpoch, uint32_t)
// BrowsingContext, in this context, is the cross process replicated
// environment in which information about documents is stored. In
@ -391,12 +392,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
bool IsInSubtreeOf(BrowsingContext* aContext);
bool IsContentSubframe() const { return IsContent() && IsFrame(); }
// non-zero
uint64_t Id() const { return mBrowsingContextId; }
BrowsingContext* GetParent() const;
BrowsingContext* Top();
const BrowsingContext* Top() const;
int32_t IndexOf(BrowsingContext* aChild);
// NOTE: Unlike `GetEmbedderWindowGlobal`, `GetParentWindowContext` does not
@ -844,6 +847,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
return GetPrefersColorSchemeOverride();
}
void FlushSessionStore();
protected:
virtual ~BrowsingContext();
BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup,
@ -922,6 +927,11 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
void SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn,
uint64_t aEpoch);
bool CanSet(FieldIndex<IDX_SessionStoreEpoch>, uint32_t aEpoch,
ContentParent* aSource) {
return IsTop() && !aSource;
}
using CanSetResult = syncedcontext::CanSetResult;
// Ensure that opener is in the same BrowsingContextGroup.

Просмотреть файл

@ -74,6 +74,9 @@
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/dom/SessionStoreChangeListener.h"
#include "mozilla/dom/SessionStoreDataCollector.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/UserActivation.h"
@ -5892,6 +5895,12 @@ nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest,
mainWidget->SetCursor(eCursor_spinning, nullptr, 0, 0);
}
}
if constexpr (SessionStoreUtils::NATIVE_LISTENER) {
if (IsForceReloadType(mLoadType)) {
SessionStoreUtils::ResetSessionStore(mBrowsingContext);
}
}
}
} else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) {
// Page is loading
@ -5909,6 +5918,7 @@ nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest,
}
}
}
if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) {
nsCOMPtr<nsIWebProgress> webProgress =
do_QueryInterface(GetAsSupports(this));
@ -6652,6 +6662,17 @@ nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
PredictorLearnRedirect(url, aChannel, loadInfo->GetOriginAttributes());
}
if constexpr (SessionStoreUtils::NATIVE_LISTENER) {
if (Document* document = GetDocument()) {
if (WindowGlobalChild* windowChild = document->GetWindowGlobalChild()) {
RefPtr<SessionStoreDataCollector> collector =
SessionStoreDataCollector::CollectSessionStoreData(windowChild);
collector->RecordInputChange();
collector->RecordScrollChange();
}
}
}
return NS_OK;
}

Просмотреть файл

@ -88,7 +88,9 @@
#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
#include "mozilla/dom/PBrowser.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "mozilla/dom/SessionStoreChangeListener.h"
#include "mozilla/dom/SessionStoreListener.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/XULFrameElement.h"
#include "mozilla/gfx/CrossProcessPaint.h"
@ -153,7 +155,8 @@ using PrintPreviewResolver = std::function<void(const PrintPreviewResultInfo&)>;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsFrameLoader, mPendingBrowsingContext,
mMessageManager, mChildMessageManager,
mRemoteBrowser)
mRemoteBrowser,
mSessionStoreChangeListener)
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader)
@ -1849,7 +1852,7 @@ void nsFrameLoader::StartDestroy(bool aForProcessSwitch) {
mDestroyCalled = true;
// request a tabStateFlush before tab is closed
RequestTabStateFlush(/*flushId*/ 0, /*isFinal*/ true);
RequestTabStateFlush();
// After this point, we return an error when trying to send a message using
// the message manager on the frame.
@ -2016,6 +2019,11 @@ void nsFrameLoader::DestroyDocShell() {
mSessionStoreListener = nullptr;
}
if (mSessionStoreChangeListener) {
mSessionStoreChangeListener->Stop();
mSessionStoreChangeListener = nullptr;
}
// Destroy the docshell.
if (GetDocShell()) {
GetDocShell()->Destroy();
@ -2109,6 +2117,7 @@ void nsFrameLoader::SetOwnerContent(Element* aContent) {
if (mSessionStoreListener && mOwnerContent) {
// mOwnerContent will only be null when the frame loader is being destroyed,
// so the session store listener will be destroyed along with it.
// XXX(farre): This probably needs to update the cache. See bug 1698497.
mSessionStoreListener->SetOwnerContent(mOwnerContent);
}
@ -2116,6 +2125,13 @@ void nsFrameLoader::SetOwnerContent(Element* aContent) {
browsingContext->SetEmbedderElement(mOwnerContent);
}
if (mSessionStoreChangeListener) {
// UpdateEventTargets will requery its browser contexts for event
// targets, so this call needs to happen after the call to
// SetEmbedderElement above.
mSessionStoreChangeListener->UpdateEventTargets();
}
AutoJSAPI jsapi;
jsapi.Init();
@ -2995,15 +3011,17 @@ nsresult nsFrameLoader::EnsureMessageManager() {
GetDocShell(), mOwnerContent, mMessageManager);
NS_ENSURE_TRUE(mChildMessageManager, NS_ERROR_UNEXPECTED);
#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_THUNDERBIRD) && \
!defined(MOZ_SUITE)
// Set up a TabListener for sessionStore
if (XRE_IsParentProcess()) {
mSessionStoreListener = new TabListener(GetDocShell(), mOwnerContent);
rv = mSessionStoreListener->Init();
NS_ENSURE_SUCCESS(rv, rv);
if constexpr (SessionStoreUtils::NATIVE_LISTENER) {
if (XRE_IsParentProcess()) {
mSessionStoreListener = new TabListener(GetDocShell(), mOwnerContent);
rv = mSessionStoreListener->Init();
NS_ENSURE_SUCCESS(rv, rv);
mSessionStoreChangeListener =
SessionStoreChangeListener::Create(GetExtantBrowsingContext());
}
}
#endif
}
return NS_OK;
}
@ -3141,23 +3159,78 @@ void nsFrameLoader::RequestUpdatePosition(ErrorResult& aRv) {
}
}
bool nsFrameLoader::RequestTabStateFlush(uint32_t aFlushId, bool aIsFinal) {
already_AddRefed<Promise> nsFrameLoader::RequestTabStateFlush(
ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
RefPtr<Promise> promise =
Promise::Create(GetOwnerDoc()->GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
BrowsingContext* context = GetExtantBrowsingContext();
if (!context) {
promise->MaybeResolveWithUndefined();
return promise.forget();
}
if (mSessionStoreListener) {
mSessionStoreListener->ForceFlushFromParent(aFlushId, aIsFinal);
context->FlushSessionStore();
mSessionStoreListener->ForceFlushFromParent(false);
// No async ipc call is involved in parent only case
return false;
promise->MaybeResolveWithUndefined();
return promise.forget();
}
// If remote browsing (e10s), handle this with the BrowserParent.
if (auto* browserParent = GetBrowserParent()) {
Unused << browserParent->SendFlushTabState(aFlushId, aIsFinal);
return true;
nsTArray<RefPtr<ContentParent::FlushTabStatePromise>> flushPromises;
context->Group()->EachParent([&](ContentParent* aParent) {
if (aParent->CanSend()) {
flushPromises.AppendElement(aParent->SendFlushTabState(context));
}
});
using ResultType =
ContentParent::FlushTabStatePromise::AllPromiseType::ResolveOrRejectValue;
ContentParent::FlushTabStatePromise::All(GetCurrentSerialEventTarget(),
flushPromises)
->Then(GetCurrentSerialEventTarget(), __func__,
[promise](const ResultType&) {
promise->MaybeResolveWithUndefined();
});
return promise.forget();
}
void nsFrameLoader::RequestTabStateFlush() {
BrowsingContext* context = GetExtantBrowsingContext();
if (!context || !context->IsTop()) {
return;
}
return false;
if (mSessionStoreListener) {
context->FlushSessionStore();
mSessionStoreListener->ForceFlushFromParent(true);
// No async ipc call is involved in parent only case
return;
}
context->Group()->EachParent([&](ContentParent* aParent) {
if (aParent->CanSend()) {
aParent->SendFlushTabState(
context, [](auto) {}, [](auto) {});
}
});
}
void nsFrameLoader::RequestEpochUpdate(uint32_t aEpoch) {
BrowsingContext* context = GetExtantBrowsingContext();
if (context) {
BrowsingContext* top = context->Top();
Unused << top->SetSessionStoreEpoch(aEpoch);
}
if (mSessionStoreListener) {
mSessionStoreListener->SetEpoch(aEpoch);
return;

Просмотреть файл

@ -73,6 +73,7 @@ class BrowserBridgeChild;
class RemoteBrowser;
struct RemotenessOptions;
struct RemotenessChangeOptions;
class SessionStoreChangeListener;
namespace ipc {
class StructuredCloneData;
@ -222,7 +223,7 @@ class nsFrameLoader final : public nsStubMutationObserver,
void RequestUpdatePosition(mozilla::ErrorResult& aRv);
bool RequestTabStateFlush(uint32_t aFlushId, bool aIsFinal = false);
already_AddRefed<Promise> RequestTabStateFlush(mozilla::ErrorResult& aRv);
void RequestEpochUpdate(uint32_t aEpoch);
@ -493,6 +494,8 @@ class nsFrameLoader final : public nsStubMutationObserver,
// browsing context for a newly opened tab/window is ready.
void InvokeBrowsingContextReadyCallback();
void RequestTabStateFlush();
RefPtr<mozilla::dom::BrowsingContext> mPendingBrowsingContext;
nsCOMPtr<nsIURI> mURIToLoad;
nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
@ -529,6 +532,8 @@ class nsFrameLoader final : public nsStubMutationObserver,
RefPtr<mozilla::dom::TabListener> mSessionStoreListener;
RefPtr<mozilla::dom::SessionStoreChangeListener> mSessionStoreChangeListener;
nsCString mRemoteType;
bool mDepthTooGreat : 1;

Просмотреть файл

@ -108,6 +108,7 @@ interface BrowsingContext {
readonly attribute DOMString embedderElementType;
readonly attribute boolean createdDynamically;
/**
* The sandbox flags on the browsing context. These reflect the value of the
* sandbox attribute of the associated IFRAME or CSP-protectable content, if

Просмотреть файл

@ -102,9 +102,11 @@ interface FrameLoader {
/**
* Force a TabStateFlush from native sessionStoreListeners.
* Return true if the flush requires async ipc call.
* Returns a promise that resolves when all session store data has been
* flushed.
*/
boolean requestTabStateFlush(unsigned long aFlushId);
[Throws]
Promise<void> requestTabStateFlush();
/**
* Force Epoch update in native sessionStoreListeners.

Просмотреть файл

@ -133,7 +133,7 @@ namespace SessionStoreUtils {
[GenerateConversionToJS, GenerateInit]
dictionary CollectedFileListValue
{
required DOMString type;
DOMString type = "file";
required sequence<DOMString> fileList;
};
@ -172,19 +172,28 @@ dictionary InputElementData {
dictionary UpdateSessionStoreData {
ByteString docShellCaps;
boolean isPrivate;
sequence<ByteString> positions;
sequence<long> positionDescendants;
// The following are for input data
InputElementData id;
InputElementData xpath;
sequence<long> inputDescendants;
sequence<long> numId;
sequence<long> numXPath;
sequence<DOMString> innerHTML;
sequence<ByteString> url;
// for sessionStorage
sequence<ByteString> storageOrigins;
sequence<DOMString> storageKeys;
sequence<DOMString> storageValues;
boolean isFullStorage;
};
[GenerateConversionToJS]
dictionary SessionStoreWindowStateChange {
SessionStoreFormData formdata;
SessionStoreScroll scroll;
boolean hasChildren;
required sequence<unsigned long> path;
};
dictionary SessionStoreFormData {
ByteString url;
record<DOMString, CollectedFormDataValue> id;
record<DOMString, CollectedFormDataValue> xpath;
DOMString innerHTML;
};
dictionary SessionStoreScroll {
ByteString scroll;
};

Просмотреть файл

@ -69,7 +69,10 @@
#include "mozilla/dom/PBrowser.h"
#include "mozilla/dom/PaymentRequestChild.h"
#include "mozilla/dom/PointerEventHandler.h"
#include "mozilla/dom/SessionStoreChangeListener.h"
#include "mozilla/dom/SessionStoreDataCollector.h"
#include "mozilla/dom/SessionStoreListener.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowProxyHolder.h"
#include "mozilla/gfx/CrossProcessPaint.h"
@ -543,12 +546,14 @@ nsresult BrowserChild::Init(mozIDOMWindowProxy* aParent,
mIPCOpen = true;
#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_THUNDERBIRD) && \
!defined(MOZ_SUITE)
mSessionStoreListener = new TabListener(docShell, nullptr);
rv = mSessionStoreListener->Init();
NS_ENSURE_SUCCESS(rv, rv);
#endif
if constexpr (SessionStoreUtils::NATIVE_LISTENER) {
mSessionStoreListener = new TabListener(docShell, nullptr);
rv = mSessionStoreListener->Init();
NS_ENSURE_SUCCESS(rv, rv);
mSessionStoreChangeListener =
SessionStoreChangeListener::Create(mBrowsingContext);
}
// We've all set up, make sure our visibility state is consistent. This is
// important for OOP iframes, which start off as hidden.
@ -566,6 +571,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BrowserChild)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusFilter)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebNav)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStoreListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStoreChangeListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -575,6 +582,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowserChild)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusFilter)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebNav)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStoreListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStoreChangeListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(BrowserChild)
@ -874,6 +883,11 @@ void BrowserChild::DestroyWindow() {
mSessionStoreListener = nullptr;
}
if (mSessionStoreChangeListener) {
mSessionStoreChangeListener->Stop();
mSessionStoreChangeListener = nullptr;
}
// In case we don't have chance to process all entries, clean all data in
// the queue.
while (mToBeDispatchedMouseData.GetSize() > 0) {
@ -1932,14 +1946,9 @@ mozilla::ipc::IPCResult BrowserChild::RecvNativeSynthesisResponse(
return IPC_OK();
}
mozilla::ipc::IPCResult BrowserChild::RecvFlushTabState(
const uint32_t& aFlushId, const bool& aIsFinal) {
UpdateSessionStore(aFlushId, aIsFinal);
return IPC_OK();
}
mozilla::ipc::IPCResult BrowserChild::RecvUpdateEpoch(const uint32_t& aEpoch) {
mSessionStoreListener->SetEpoch(aEpoch);
return IPC_OK();
}
@ -3801,7 +3810,7 @@ nsresult BrowserChild::PrepareProgressListenerData(
return PrepareRequestData(aRequest, aRequestData);
}
bool BrowserChild::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
bool BrowserChild::UpdateSessionStore(bool aIsFinal) {
if (!mSessionStoreListener) {
return false;
}
@ -3817,18 +3826,6 @@ bool BrowserChild::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
privatedMode.emplace(store->GetPrivateModeEnabled());
}
nsTArray<int32_t> positionDescendants;
nsTArray<nsCString> positions;
if (store->IsScrollPositionChanged()) {
store->GetScrollPositions(positions, positionDescendants);
}
nsTArray<InputFormData> inputs;
nsTArray<CollectedInputDataValue> idVals, xPathVals;
if (store->IsFormDataChanged()) {
inputs = store->GetInputs(idVals, xPathVals);
}
nsTArray<nsCString> origins;
nsTArray<nsString> keys, values;
bool isFullStorage = false;
@ -3836,11 +3833,10 @@ bool BrowserChild::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
isFullStorage = store->GetAndClearStorageChanges(origins, keys, values);
}
Unused << SendSessionStoreUpdate(
docShellCaps, privatedMode, positions, positionDescendants, inputs,
idVals, xPathVals, origins, keys, values, isFullStorage,
store->GetAndClearSHistoryChanged(), aFlushId, aIsFinal,
mSessionStoreListener->GetEpoch());
Unused << SendSessionStoreUpdate(docShellCaps, privatedMode, origins, keys,
values, isFullStorage,
store->GetAndClearSHistoryChanged(),
aIsFinal, mSessionStoreListener->GetEpoch());
return true;
}

Просмотреть файл

@ -84,6 +84,7 @@ class ClonedMessageData;
class CoalescedMouseData;
class CoalescedWheelData;
class ContentSessionStore;
class SessionStoreChangeListener;
class TabListener;
class RequestData;
class WebProgressData;
@ -387,9 +388,6 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
aApzResponse);
}
mozilla::ipc::IPCResult RecvFlushTabState(const uint32_t& aFlushId,
const bool& aIsFinal);
mozilla::ipc::IPCResult RecvUpdateEpoch(const uint32_t& aEpoch);
mozilla::ipc::IPCResult RecvUpdateSHistory(const bool& aImmediately);
@ -682,7 +680,7 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
mCancelContentJSEpoch = aEpoch;
}
bool UpdateSessionStore(uint32_t aFlushId, bool aIsFinal = false);
bool UpdateSessionStore(bool aIsFinal = false);
#ifdef XP_WIN
// Check if the window this BrowserChild is associated with supports
@ -878,6 +876,7 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
RefPtr<layers::IAPZCTreeManager> mApzcTreeManager;
RefPtr<TabListener> mSessionStoreListener;
RefPtr<SessionStoreChangeListener> mSessionStoreChangeListener;
// The most recently seen layer observer epoch in RecvSetDocShellIsActive.
layers::LayersObserverEpoch mLayersObserverEpoch;

Просмотреть файл

@ -2906,14 +2906,10 @@ bool BrowserParent::ReconstructWebProgressAndRequest(
mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
nsTArray<nsCString>&& aPositions, nsTArray<int32_t>&& aPositionDescendants,
const nsTArray<InputFormData>& aInputs,
const nsTArray<CollectedInputDataValue>& aIdVals,
const nsTArray<CollectedInputDataValue>& aXPathVals,
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
nsTArray<nsString>&& aValues, const bool aIsFullStorage,
const bool aNeedCollectSHistory, const uint32_t& aFlushId,
const bool& aIsFinal, const uint32_t& aEpoch) {
const bool aNeedCollectSHistory, const bool& aIsFinal,
const uint32_t& aEpoch) {
UpdateSessionStoreData data;
if (aDocShellCaps.isSome()) {
data.mDocShellCaps.Construct() = aDocShellCaps.value();
@ -2921,34 +2917,6 @@ mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
if (aPrivatedMode.isSome()) {
data.mIsPrivate.Construct() = aPrivatedMode.value();
}
if (aPositions.Length() != 0) {
data.mPositions.Construct(std::move(aPositions));
data.mPositionDescendants.Construct(std::move(aPositionDescendants));
}
if (aIdVals.Length() != 0) {
SessionStoreUtils::ComposeInputData(aIdVals, data.mId.Construct());
}
if (aXPathVals.Length() != 0) {
SessionStoreUtils::ComposeInputData(aXPathVals, data.mXpath.Construct());
}
if (aInputs.Length() != 0) {
nsTArray<int> descendants, numId, numXPath;
nsTArray<nsString> innerHTML;
nsTArray<nsCString> url;
for (const InputFormData& input : aInputs) {
descendants.AppendElement(input.descendants);
numId.AppendElement(input.numId);
numXPath.AppendElement(input.numXPath);
innerHTML.AppendElement(input.innerHTML);
url.AppendElement(input.url);
}
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));
}
// In normal case, we only update the storage when needed.
// However, we need to reset the session storage(aOrigins.Length() will be 0)
// if the usage is over the "browser_sessionstore_dom_storage_limit".
@ -2970,9 +2938,9 @@ mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate(
bool ok = ToJSValue(jsapi.cx(), data, &dataVal);
NS_ENSURE_TRUE(ok, IPC_OK());
nsresult rv = funcs->UpdateSessionStore(mFrameElement, mBrowsingContext,
aFlushId, aIsFinal, aEpoch, dataVal,
aNeedCollectSHistory);
nsresult rv = funcs->UpdateSessionStore(
mFrameElement, mBrowsingContext, aEpoch, dataVal, aNeedCollectSHistory);
NS_ENSURE_SUCCESS(rv, IPC_OK());
return IPC_OK();

Просмотреть файл

@ -314,15 +314,10 @@ class BrowserParent final : public PBrowserParent,
mozilla::ipc::IPCResult RecvSessionStoreUpdate(
const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
nsTArray<nsCString>&& aPositions,
nsTArray<int32_t>&& aPositionDescendants,
const nsTArray<InputFormData>& aInputs,
const nsTArray<CollectedInputDataValue>& aIdVals,
const nsTArray<CollectedInputDataValue>& aXPathVals,
nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys,
nsTArray<nsString>&& aValues, const bool aIsFullStorage,
const bool aNeedCollectSHistory, const uint32_t& aFlushId,
const bool& aIsFinal, const uint32_t& aEpoch);
const bool aNeedCollectSHistory, const bool& aIsFinal,
const uint32_t& aEpoch);
mozilla::ipc::IPCResult RecvIntrinsicSizeOrRatioChanged(
const Maybe<IntrinsicSize>& aIntrinsicSize,

Просмотреть файл

@ -4297,6 +4297,21 @@ mozilla::ipc::IPCResult ContentChild::RecvCanSavePresentation(
}
}
mozilla::ipc::IPCResult ContentChild::RecvFlushTabState(
const MaybeDiscarded<BrowsingContext>& aContext,
FlushTabStateResolver&& aResolver) {
if (aContext.IsNullOrDiscarded()) {
aResolver(false);
return IPC_OK();
}
aContext->FlushSessionStore();
aResolver(true);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentChild::RecvGoBack(
const MaybeDiscarded<BrowsingContext>& aContext,
const Maybe<int32_t>& aCancelContentJSEpoch, bool aRequireUserInteraction) {

Просмотреть файл

@ -821,6 +821,10 @@ class ContentChild final : public PContentChild,
Maybe<uint64_t> aDocumentChannelId,
CanSavePresentationResolver&& aResolve);
mozilla::ipc::IPCResult RecvFlushTabState(
const MaybeDiscarded<BrowsingContext>& aContext,
FlushTabStateResolver&& aResolver);
public:
static void DispatchBeforeUnloadToSubtree(
BrowsingContext* aStartingAt,

Просмотреть файл

@ -579,12 +579,9 @@ parent:
async NavigationFinished();
async SessionStoreUpdate(nsCString? aDocShellCaps, bool? aPrivatedMode,
nsCString[] aPositions, int32_t[] aPositionDescendants,
InputFormData[] aInputs, CollectedInputDataValue[] aIdVals,
CollectedInputDataValue[] aXPathVals,
nsCString[] aOrigins, nsString[] aKeys,
nsString[] aValues, bool aIsFullStorage,
bool aNeedCollectSHistory, uint32_t aFlushId,
bool aNeedCollectSHistory,
bool aIsFinal, uint32_t aEpoch);
async IntrinsicSizeOrRatioChanged(IntrinsicSize? aIntrinsicSize,
@ -604,7 +601,6 @@ parent:
child:
async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
async FlushTabState(uint32_t aFlushId, bool aIsFinal);
async UpdateEpoch(uint32_t aEpoch);
async UpdateSHistory(bool aImmediately);
async CloneDocumentTreeIntoSelf(MaybeDiscardedBrowsingContext aBc);

Просмотреть файл

@ -938,8 +938,12 @@ child:
// Update the cached list of codec supported in the given process.
async UpdateMediaCodecsSupported(RemoteDecodeIn aLocation, MediaCodecsSupported aSupported);
async FlushTabState(MaybeDiscardedBrowsingContext aBrowsingContext)
returns(bool aHadContext);
parent:
async SynchronizeLayoutHistoryState(MaybeDiscardedBrowsingContext aContext,
nsILayoutHistoryState aState);

Просмотреть файл

@ -18,6 +18,7 @@ include protocol PBrowserBridge;
include DOMTypes;
include ClientIPCTypes;
include NeckoChannelParams;
include SessionStoreTypes;
include "mozilla/layers/LayersMessageUtils.h";
@ -174,6 +175,11 @@ parent:
async AccumulatePageUseCounters(UseCounters aUseCounters);
async RequestRestoreTabContent();
async UpdateSessionStore(FormData? aFormData, nsPoint? aScrollPosition,
uint32_t aEpoch);
async ResetSessionStore(uint32_t aEpoch);
async Destroy();
};

Просмотреть файл

@ -18,6 +18,7 @@
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/SecurityPolicyViolationEvent.h"
#include "mozilla/dom/SessionStoreRestoreData.h"
#include "mozilla/dom/SessionStoreDataCollector.h"
#include "mozilla/dom/WindowGlobalActorsBinding.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WindowContext.h"
@ -339,6 +340,11 @@ void WindowGlobalChild::Destroy() {
if (!browserChild || !browserChild->IsDestroyed()) {
SendDestroy();
}
if (mSessionStoreDataCollector) {
mSessionStoreDataCollector->Cancel();
mSessionStoreDataCollector = nullptr;
}
}
mozilla::ipc::IPCResult WindowGlobalChild::RecvMakeFrameLocal(
@ -682,9 +688,20 @@ nsISupports* WindowGlobalChild::GetParentObject() {
return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
}
void WindowGlobalChild::SetSessionStoreDataCollector(
SessionStoreDataCollector* aCollector) {
mSessionStoreDataCollector = aCollector;
}
SessionStoreDataCollector* WindowGlobalChild::GetSessionStoreDataCollector()
const {
return mSessionStoreDataCollector;
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(WindowGlobalChild, mWindowGlobal,
mContainerFeaturePolicy,
mWindowContext)
mWindowContext,
mSessionStoreDataCollector)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowGlobalChild)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY

Просмотреть файл

@ -27,6 +27,7 @@ class WindowGlobalParent;
class JSWindowActorChild;
class JSActorMessageMeta;
class BrowserChild;
class SessionStoreDataCollector;
/**
* Actor for a single nsGlobalWindowInner. This actor is used to communicate
@ -128,6 +129,9 @@ class WindowGlobalChild final : public WindowGlobalActor,
return mContainerFeaturePolicy;
}
void SetSessionStoreDataCollector(SessionStoreDataCollector* aCollector);
SessionStoreDataCollector* GetSessionStoreDataCollector() const;
protected:
const nsACString& GetRemoteType() override;
@ -189,6 +193,7 @@ class WindowGlobalChild final : public WindowGlobalActor,
nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
RefPtr<dom::FeaturePolicy> mContainerFeaturePolicy;
nsCOMPtr<nsIURI> mDocumentURI;
RefPtr<SessionStoreDataCollector> mSessionStoreDataCollector;
int64_t mBeforeUnloadListeners = 0;
};

Просмотреть файл

@ -27,6 +27,9 @@
#include "mozilla/dom/ChromeUtils.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/dom/sessionstore/SessionStoreTypes.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/SessionStoreUtilsBinding.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ServoCSSParser.h"
#include "mozilla/ServoStyleSet.h"
@ -60,6 +63,10 @@
#include "mozilla/dom/JSWindowActorBinding.h"
#include "mozilla/dom/JSWindowActorParent.h"
#include "SessionStoreFunctions.h"
#include "nsIXPConnect.h"
#include "nsImportModule.h"
using namespace mozilla::ipc;
using namespace mozilla::dom::ipc;
@ -478,6 +485,16 @@ const nsACString& WindowGlobalParent::GetRemoteType() {
return NOT_REMOTE_TYPE;
}
static nsCString PointToString(const nsPoint& aPoint) {
int scrollX = nsPresContext::AppUnitsToIntCSSPixels(aPoint.x);
int scrollY = nsPresContext::AppUnitsToIntCSSPixels(aPoint.y);
if ((scrollX != 0) || (scrollY != 0)) {
return nsPrintfCString("%d,%d", scrollX, scrollY);
}
return ""_ns;
}
void WindowGlobalParent::NotifyContentBlockingEvent(
uint32_t aEvent, nsIRequest* aRequest, bool aBlocked,
const nsACString& aTrackingOrigin,
@ -1086,6 +1103,196 @@ void WindowGlobalParent::FinishAccumulatingPageUseCounters() {
mPageUseCounters = nullptr;
}
// Collect the path from aContext up to its parent. Returns true if any context
// in the chain isn't a child of its parent
static bool GetPath(BrowsingContext* aContext,
FallibleTArray<uint32_t>& aPath) {
bool missingContext = false;
BrowsingContext* current = aContext;
while (current) {
BrowsingContext* parent = current->GetParent();
if (parent) {
auto children = parent->Children();
auto result = std::find(children.cbegin(), children.cend(), current);
if (result == children.cend()) {
missingContext = true;
}
if (!aPath.AppendElement(std::distance(children.cbegin(), result),
fallible)) {
break;
}
}
current = parent;
}
return missingContext;
}
static void GetFormData(JSContext* aCx, const sessionstore::FormData& aFormData,
nsIURI* aDocumentURI, SessionStoreFormData& aUpdate) {
if (!aFormData.hasData()) {
return;
}
bool parseSessionData = false;
if (aDocumentURI) {
nsCString& url = aUpdate.mUrl.Construct();
aDocumentURI->GetSpecIgnoringRef(url);
// 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
parseSessionData =
url == "about:sessionrestore"_ns || url == "about:welcomeback"_ns;
}
if (!aFormData.innerHTML().IsEmpty()) {
aUpdate.mInnerHTML.Construct(aFormData.innerHTML());
}
if (!aFormData.id().IsEmpty()) {
auto& id = aUpdate.mId.Construct();
if (NS_FAILED(SessionStoreUtils::ConstructFormDataValues(
aCx, aFormData.id(), id.Entries(), parseSessionData))) {
return;
}
}
if (!aFormData.xpath().IsEmpty()) {
auto& xpath = aUpdate.mXpath.Construct();
if (NS_FAILED(SessionStoreUtils::ConstructFormDataValues(
aCx, aFormData.xpath(), xpath.Entries()))) {
return;
}
}
}
Element* WindowGlobalParent::GetRootOwnerElement() {
WindowGlobalParent* top = TopWindowContext();
if (IsInProcess()) {
return top->BrowsingContext()->GetEmbedderElement();
}
BrowserParent* parent = top->GetBrowserParent();
return parent->GetOwnerElement();
}
nsresult WindowGlobalParent::UpdateSessionStore(
const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
uint32_t aEpoch) {
if (!aFormData && !aScrollPosition) {
return NS_OK;
}
Element* frameElement = GetRootOwnerElement();
if (!frameElement) {
return NS_OK;
}
nsCOMPtr<nsISessionStoreFunctions> funcs =
do_ImportModule("resource://gre/modules/SessionStoreFunctions.jsm");
if (!funcs) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
AutoJSAPI jsapi;
if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
return NS_ERROR_FAILURE;
}
RootedDictionary<SessionStoreWindowStateChange> windowState(jsapi.cx());
if (GetPath(GetBrowsingContext(), windowState.mPath)) {
// If a context in the parent chain from the current context is
// missing, do nothing.
return NS_OK;
}
if (aFormData) {
GetFormData(jsapi.cx(), *aFormData, mDocumentURI,
windowState.mFormdata.Construct());
}
if (aScrollPosition) {
auto& update = windowState.mScroll.Construct();
if (*aScrollPosition != nsPoint(0, 0)) {
update.mScroll.Construct() = PointToString(*aScrollPosition);
}
}
windowState.mHasChildren.Construct() =
!GetBrowsingContext()->Children().IsEmpty();
JS::RootedValue update(jsapi.cx());
if (!ToJSValue(jsapi.cx(), windowState, &update)) {
return NS_ERROR_FAILURE;
}
return funcs->UpdateSessionStoreForWindow(frameElement, GetBrowsingContext(),
aEpoch, update);
}
nsresult WindowGlobalParent::ResetSessionStore(uint32_t aEpoch) {
Element* frameElement = GetRootOwnerElement();
if (!frameElement) {
return NS_OK;
}
nsCOMPtr<nsISessionStoreFunctions> funcs =
do_ImportModule("resource://gre/modules/SessionStoreFunctions.jsm");
if (!funcs) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
AutoJSAPI jsapi;
if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
return NS_ERROR_FAILURE;
}
RootedDictionary<SessionStoreWindowStateChange> windowState(jsapi.cx());
if (GetPath(GetBrowsingContext(), windowState.mPath)) {
// If a context in the parent chain from the current context is
// missing, do nothing.
return NS_OK;
}
windowState.mHasChildren.Construct() = false;
windowState.mFormdata.Construct();
windowState.mScroll.Construct();
JS::RootedValue update(jsapi.cx());
if (!ToJSValue(jsapi.cx(), windowState, &update)) {
return NS_ERROR_FAILURE;
}
return funcs->UpdateSessionStoreForWindow(frameElement, GetBrowsingContext(),
aEpoch, update);
}
mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateSessionStore(
const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
uint32_t aEpoch) {
if (NS_FAILED(UpdateSessionStore(aFormData, aScrollPosition, aEpoch))) {
MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Failed to update session store entry."));
}
return IPC_OK();
}
mozilla::ipc::IPCResult WindowGlobalParent::RecvResetSessionStore(
uint32_t aEpoch) {
if (NS_FAILED(ResetSessionStore(aEpoch))) {
MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Failed to reset session store entry."));
}
return IPC_OK();
}
mozilla::ipc::IPCResult WindowGlobalParent::RecvRequestRestoreTabContent() {
CanonicalBrowsingContext* bc = BrowsingContext()->Top();
if (bc && !bc->IsDiscarded()) {

Просмотреть файл

@ -11,6 +11,7 @@
#include "mozilla/ContentBlockingNotifier.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/ClientInfo.h"
#include "mozilla/dom/ClientIPCTypes.h"
#include "mozilla/dom/DOMRect.h"
@ -43,6 +44,8 @@ class WindowGlobalChild;
class JSWindowActorParent;
class JSActorMessageMeta;
struct PageUseCounters;
class WindowSessionStoreState;
struct WindowSessionStoreUpdate;
/**
* A handle in the parent process to a specific nsGlobalWindowInner object.
@ -80,6 +83,8 @@ class WindowGlobalParent final : public WindowContext,
return CanonicalBrowsingContext::Cast(WindowContext::GetBrowsingContext());
}
Element* GetRootOwnerElement();
// Has this actor been shut down
bool IsClosed() { return !CanSend(); }
@ -202,6 +207,10 @@ class WindowGlobalParent final : public WindowContext,
const nsACString& GetRemoteType() override;
nsresult UpdateSessionStore(const Maybe<FormData>& aFormData,
const Maybe<nsPoint>& aScrollPosition,
uint32_t aEpoch);
protected:
already_AddRefed<JSActor> InitJSActor(JS::HandleObject aMaybeActor,
const nsACString& aName,
@ -263,6 +272,12 @@ class WindowGlobalParent final : public WindowContext,
mozilla::ipc::IPCResult RecvRequestRestoreTabContent();
mozilla::ipc::IPCResult RecvUpdateSessionStore(
const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
uint32_t aEpoch);
mozilla::ipc::IPCResult RecvResetSessionStore(uint32_t aEpoch);
private:
WindowGlobalParent(CanonicalBrowsingContext* aBrowsingContext,
uint64_t aInnerWindowId, uint64_t aOuterWindowId,
@ -273,6 +288,8 @@ class WindowGlobalParent final : public WindowContext,
bool ShouldTrackSiteOriginTelemetry();
void FinishAccumulatingPageUseCounters();
nsresult ResetSessionStore(uint32_t aEpoch);
// NOTE: This document principal doesn't reflect possible |document.domain|
// mutations which may have been made in the actual document.
nsCOMPtr<nsIPrincipal> mDocumentPrincipal;

Просмотреть файл

@ -1137,13 +1137,27 @@
value: 512*1024
mirror: always
# ContentSessionStore prefs
# SessionStore prefs
# Maximum number of bytes of DOMSessionStorage data we collect per origin.
- name: browser.sessionstore.dom_storage_limit
type: uint32_t
value: 2048
mirror: always
# Minimal interval between two save operations in milliseconds (while the user is active).
- name: browser.sessionstore.interval
type: uint32_t
value: 15000
mirror: always
# Causes SessionStore to ignore non-final update messages from
# browser tabs that were not caused by a flush from the parent.
# This is a testing flag and should not be used by end-users.
- name: browser.sessionstore.debug.no_auto_updates
type: bool
value: false
mirror: always
# If set, use DocumentChannel to directly initiate loads entirely
# from parent-process BrowsingContexts
- name: browser.tabs.documentchannel.parent-controlled

Просмотреть файл

@ -0,0 +1,170 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/SessionStoreChangeListener.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/EventTarget.h"
#include "mozilla/dom/SessionStoreDataCollector.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/Preferences.h"
#include "nsDocShell.h"
#include "nsPIDOMWindow.h"
namespace {
constexpr auto kInput = u"input"_ns;
constexpr auto kScroll = u"mozvisualscroll"_ns;
static constexpr char kNoAutoUpdates[] =
"browser.sessionstore.debug.no_auto_updates";
static constexpr char kInterval[] = "browser.sessionstore.interval";
static const char* kObservedPrefs[] = {kNoAutoUpdates, kInterval, nullptr};
} // namespace
namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION(SessionStoreChangeListener, mBrowsingContext,
mCurrentEventTarget)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStoreChangeListener)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStoreChangeListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStoreChangeListener)
NS_IMETHODIMP
SessionStoreChangeListener::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
Flush();
return NS_OK;
}
NS_IMETHODIMP
SessionStoreChangeListener::HandleEvent(dom::Event* aEvent) {
EventTarget* target = aEvent->GetTarget();
if (!target) {
return NS_OK;
}
nsCOMPtr<nsPIDOMWindowInner> inner =
do_QueryInterface(target->GetOwnerGlobal());
if (!inner) {
return NS_OK;
}
WindowGlobalChild* windowChild = inner->GetWindowGlobalChild();
if (!windowChild) {
return NS_OK;
}
RefPtr<BrowsingContext> browsingContext = windowChild->BrowsingContext();
if (!browsingContext) {
return NS_OK;
}
bool dynamic = false;
BrowsingContext* current = browsingContext;
while (current) {
if ((dynamic = current->CreatedDynamically())) {
break;
}
current = current->GetParent();
}
if (dynamic) {
return NS_OK;
}
nsAutoString eventType;
aEvent->GetType(eventType);
RefPtr<SessionStoreDataCollector> collector =
SessionStoreDataCollector::CollectSessionStoreData(windowChild);
if (!collector) {
return NS_OK;
}
if (eventType == kInput) {
collector->RecordInputChange();
} else if (eventType == kScroll) {
collector->RecordScrollChange();
}
return NS_OK;
}
/* static */ already_AddRefed<SessionStoreChangeListener>
SessionStoreChangeListener::Create(BrowsingContext* aBrowsingContext) {
MOZ_RELEASE_ASSERT(SessionStoreUtils::NATIVE_LISTENER);
if (!aBrowsingContext) {
return nullptr;
}
RefPtr<SessionStoreChangeListener> listener =
new SessionStoreChangeListener(aBrowsingContext);
listener->Init();
return listener.forget();
}
void SessionStoreChangeListener::Stop() {
RemoveEventListeners();
Preferences::RemoveObservers(this, kObservedPrefs);
}
void SessionStoreChangeListener::UpdateEventTargets() {
RemoveEventListeners();
AddEventListeners();
}
void SessionStoreChangeListener::Flush() {
mBrowsingContext->FlushSessionStore();
}
SessionStoreChangeListener::SessionStoreChangeListener(
BrowsingContext* aBrowsingContext)
: mBrowsingContext(aBrowsingContext) {}
void SessionStoreChangeListener::Init() {
AddEventListeners();
Preferences::AddStrongObservers(this, kObservedPrefs);
}
EventTarget* SessionStoreChangeListener::GetEventTarget() {
if (mBrowsingContext->GetDOMWindow()) {
return mBrowsingContext->GetDOMWindow()->GetChromeEventHandler();
}
return nullptr;
}
void SessionStoreChangeListener::AddEventListeners() {
if (EventTarget* target = GetEventTarget()) {
target->AddSystemEventListener(kInput, this, false);
target->AddSystemEventListener(kScroll, this, false);
mCurrentEventTarget = target;
}
}
void SessionStoreChangeListener::RemoveEventListeners() {
if (mCurrentEventTarget) {
mCurrentEventTarget->RemoveSystemEventListener(kInput, this, false);
mCurrentEventTarget->RemoveSystemEventListener(kScroll, this, false);
}
mCurrentEventTarget = nullptr;
}
} // namespace mozilla::dom

Просмотреть файл

@ -0,0 +1,62 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_SessionStoreChangeListener_h
#define mozilla_dom_SessionStoreChangeListener_h
#include "ErrorList.h"
#include "nsIDOMEventListener.h"
#include "nsIObserver.h"
#include "nsCycleCollectionParticipant.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Result.h"
namespace mozilla::dom {
class Element;
class EventTarget;
class SessionStoreDataCollector;
class BrowsingContext;
class SessionStoreChangeListener final : public nsIObserver,
public nsIDOMEventListener {
public:
NS_DECL_NSIOBSERVER
NS_DECL_NSIDOMEVENTLISTENER
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SessionStoreChangeListener,
nsIObserver)
static already_AddRefed<SessionStoreChangeListener> Create(
BrowsingContext* aBrowsingContext);
void Stop();
void UpdateEventTargets();
void Flush();
private:
explicit SessionStoreChangeListener(BrowsingContext* aBrowsingContext);
~SessionStoreChangeListener() = default;
void Init();
EventTarget* GetEventTarget();
void AddEventListeners();
void RemoveEventListeners();
RefPtr<BrowsingContext> mBrowsingContext;
RefPtr<EventTarget> mCurrentEventTarget;
};
} // namespace mozilla::dom
#endif // mozilla_dom_SessionStoreChangeListener_h

Просмотреть файл

@ -0,0 +1,171 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/SessionStoreDataCollector.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/sessionstore/SessionStoreTypes.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "nsGenericHTMLElement.h"
namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION(SessionStoreDataCollector, mWindowChild, mTimer)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStoreDataCollector)
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStoreDataCollector)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStoreDataCollector)
NS_IMETHODIMP
SessionStoreDataCollector::Notify(nsITimer* aTimer) {
Collect();
return NS_OK;
}
/* static */ already_AddRefed<SessionStoreDataCollector>
SessionStoreDataCollector::CollectSessionStoreData(
WindowGlobalChild* aWindowChild) {
MOZ_RELEASE_ASSERT(SessionStoreUtils::NATIVE_LISTENER);
MOZ_DIAGNOSTIC_ASSERT(aWindowChild);
RefPtr<SessionStoreDataCollector> listener =
aWindowChild->GetSessionStoreDataCollector();
uint32_t epoch =
aWindowChild->BrowsingContext()->Top()->GetSessionStoreEpoch();
if (listener) {
MOZ_DIAGNOSTIC_ASSERT(listener->mTimer);
if (listener->mEpoch == epoch) {
return listener.forget();
}
listener->mTimer->Cancel();
}
listener = new SessionStoreDataCollector(aWindowChild, epoch);
auto result = NS_NewTimerWithCallback(
listener, StaticPrefs::browser_sessionstore_interval(),
nsITimer::TYPE_ONE_SHOT);
if (result.isErr()) {
return nullptr;
}
listener->mTimer = result.unwrap();
aWindowChild->SetSessionStoreDataCollector(listener);
return listener.forget();
}
void SessionStoreDataCollector::RecordInputChange() {
mInputChanged = true;
if (StaticPrefs::browser_sessionstore_debug_no_auto_updates()) {
Collect();
}
}
void SessionStoreDataCollector::RecordScrollChange() {
mScrollChanged = true;
if (StaticPrefs::browser_sessionstore_debug_no_auto_updates()) {
Collect();
}
}
void SessionStoreDataCollector::Flush() { Collect(); }
void SessionStoreDataCollector::Cancel() {
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
mWindowChild->SetSessionStoreDataCollector(nullptr);
}
void SessionStoreDataCollector::Collect() {
if (this != mWindowChild->GetSessionStoreDataCollector()) {
return;
}
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
nsGlobalWindowInner* inner = mWindowChild->GetWindowGlobal();
if (!inner) {
return;
}
Document* document = inner->GetDocument();
if (!document) {
return;
}
Maybe<sessionstore::FormData> maybeFormData;
if (mInputChanged) {
maybeFormData.emplace();
auto& formData = maybeFormData.ref();
SessionStoreUtils::CollectFormData(document, formData);
Element* body = document->GetBody();
if (document->HasFlag(NODE_IS_EDITABLE) && body) {
IgnoredErrorResult result;
body->GetInnerHTML(formData.innerHTML(), result);
if (!result.Failed()) {
formData.hasData() = true;
}
}
}
PresShell* presShell = document->GetPresShell();
Maybe<nsPoint> maybeScroll;
if (mScrollChanged && presShell) {
maybeScroll = Some(presShell->GetVisualViewportOffset());
}
if (!mWindowChild->CanSend()) {
return;
}
if (RefPtr<WindowGlobalParent> windowParent =
mWindowChild->GetParentActor()) {
windowParent->UpdateSessionStore(maybeFormData, maybeScroll, mEpoch);
} else {
mWindowChild->SendUpdateSessionStore(maybeFormData, maybeScroll, mEpoch);
}
mWindowChild->SetSessionStoreDataCollector(nullptr);
}
SessionStoreDataCollector::SessionStoreDataCollector(
WindowGlobalChild* aWindowChild, uint32_t aEpoch)
: mWindowChild(aWindowChild),
mTimer(nullptr),
mEpoch(aEpoch),
mInputChanged(false),
mScrollChanged(false) {}
SessionStoreDataCollector::~SessionStoreDataCollector() {
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
}
} // namespace mozilla::dom

Просмотреть файл

@ -0,0 +1,65 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_SessionStoreDataCollector_h
#define mozilla_dom_SessionStoreDataCollector_h
#include "ErrorList.h"
#include "nsITimer.h"
#include "nsCycleCollectionParticipant.h"
#include "mozilla/RefPtr.h"
namespace mozilla::dom {
class BrowserChild;
class EventTarget;
class WindowGlobalChild;
namespace sessionstore {
class FormData;
}
class SessionStoreDataCollector final : public nsITimerCallback {
public:
NS_DECL_NSITIMERCALLBACK
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(SessionStoreDataCollector)
enum class Change { Input, Scroll };
static already_AddRefed<SessionStoreDataCollector> CollectSessionStoreData(
WindowGlobalChild* aWindowChild);
void RecordInputChange();
void RecordScrollChange();
void Flush();
void Cancel();
private:
void Collect();
nsresult Apply(Maybe<sessionstore::FormData>&& aFormData,
Maybe<nsPoint>&& aScroll);
SessionStoreDataCollector(WindowGlobalChild* aWindowChild, uint32_t aEpoch);
~SessionStoreDataCollector();
RefPtr<WindowGlobalChild> mWindowChild;
nsCOMPtr<nsITimer> mTimer;
uint32_t mEpoch;
bool mInputChanged : 1;
bool mScrollChanged : 1;
};
} // namespace mozilla::dom
#endif // mozilla_dom_SessionStoreDataCollector_h

Просмотреть файл

@ -14,8 +14,12 @@ interface nsISessionStoreFunctions : nsISupports {
// 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 uint32_t aEpoch,
in jsval aData, in boolean aCollectSHistory);
void RestoreTabContentComplete(in Element aBrowser);
void UpdateSessionStoreForWindow(
in Element aBrowser, in BrowsingContext aBrowsingContext,
in uint32_t aEpoch, in jsval aData);
};

Просмотреть файл

@ -17,8 +17,6 @@ function RestoreTabContentComplete(aBrowser) {
function UpdateSessionStore(
aBrowser,
aBrowsingContext,
aFlushId,
aIsFinal,
aEpoch,
aData,
aCollectSHistory
@ -26,345 +24,33 @@ function UpdateSessionStore(
return SessionStoreFuncInternal.updateSessionStore(
aBrowser,
aBrowsingContext,
aFlushId,
aIsFinal,
aEpoch,
aData,
aCollectSHistory
);
}
var EXPORTED_SYMBOLS = ["RestoreTabContentComplete", "UpdateSessionStore"];
function UpdateSessionStoreForWindow(
aBrowser,
aBrowsingContext,
aEpoch,
aData
) {
return SessionStoreFuncInternal.updateSessionStoreForWindow(
aBrowser,
aBrowsingContext,
aEpoch,
aData
);
}
var EXPORTED_SYMBOLS = [
"RestoreTabContentComplete",
"UpdateSessionStore",
"UpdateSessionStoreForWindow",
];
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++) {
@ -396,8 +82,6 @@ var SessionStoreFuncInternal = {
updateSessionStore: function SSF_updateSessionStore(
aBrowser,
aBrowsingContext,
aFlushId,
aIsFinal,
aEpoch,
aData,
aCollectSHistory
@ -409,31 +93,7 @@ var SessionStoreFuncInternal = {
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,
@ -449,14 +109,21 @@ var SessionStoreFuncInternal = {
SessionStore.updateSessionStoreFromTablistener(aBrowser, aBrowsingContext, {
data: currentData,
flushID: aFlushId,
isFinal: aIsFinal,
epoch: aEpoch,
sHistoryNeeded: aCollectSHistory,
});
this._formDataId = [];
this._formDataIdValue = [];
this._formDataXPath = [];
this._formDataXPathValue = [];
},
updateSessionStoreForWindow: function SSF_updateSessionStoreForWindow(
aBrowser,
aBrowsingContext,
aEpoch,
aData
) {
let windowstatechange = aData;
SessionStore.updateSessionStoreFromTablistener(aBrowser, aBrowsingContext, {
data: { windowstatechange },
epoch: aEpoch,
});
},
};

Просмотреть файл

@ -44,8 +44,6 @@ ContentSessionStore::ContentSessionStore(nsIDocShell* aDocShell)
: mDocShell(aDocShell),
mPrivateChanged(false),
mIsPrivate(false),
mScrollChanged(NO_CHANGE),
mFormDataChanged(NO_CHANGE),
mStorageStatus(NO_STORAGE),
mDocCapChanged(false),
mSHistoryChanged(false),
@ -128,8 +126,6 @@ void ContentSessionStore::SetSHistoryFromParentChanged() {
}
void ContentSessionStore::OnDocumentStart() {
mScrollChanged = PAGELOADEDSTART;
mFormDataChanged = PAGELOADEDSTART;
nsCString caps = CollectDocShellCapabilities();
if (!mDocCaps.Equals(caps)) {
mDocCaps = caps;
@ -144,9 +140,7 @@ void ContentSessionStore::OnDocumentStart() {
}
void ContentSessionStore::OnDocumentEnd() {
mScrollChanged = WITH_CHANGE;
SetFullStorageNeeded();
if (mozilla::SessionHistoryInParent()) {
mSHistoryChanged = true;
}
@ -227,8 +221,6 @@ nsresult TabListener::Init() {
void TabListener::AddEventListeners() {
if (nsCOMPtr<EventTarget> eventTarget = GetEventTarget()) {
eventTarget->AddSystemEventListener(u"mozvisualscroll"_ns, this, false);
eventTarget->AddSystemEventListener(u"input"_ns, this, false);
if (mozilla::SessionHistoryInParent()) {
eventTarget->AddSystemEventListener(u"DOMTitleChanged"_ns, this, false);
}
@ -243,9 +235,6 @@ void TabListener::AddEventListeners() {
void TabListener::RemoveEventListeners() {
if (nsCOMPtr<EventTarget> eventTarget = GetEventTarget()) {
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);
@ -362,13 +351,7 @@ TabListener::HandleEvent(Event* aEvent) {
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")) {
if (eventType.EqualsLiteral("MozSessionStorageChanged")) {
auto event = static_cast<StorageEvent*>(aEvent);
RefPtr<Storage> changingStorage = event->GetStorageArea();
if (!changingStorage) {
@ -513,115 +496,6 @@ int CollectPositions(BrowsingContext* aBrowsingContext,
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.
@ -677,18 +551,17 @@ bool ContentSessionStore::GetAndClearStorageChanges(
return isFullStorage;
}
bool TabListener::ForceFlushFromParent(uint32_t aFlushId, bool aIsFinal) {
bool TabListener::ForceFlushFromParent(bool aIsFinal) {
if (!XRE_IsParentProcess()) {
return false;
}
if (!mSessionStore) {
return false;
}
return UpdateSessionStore(aFlushId, aIsFinal);
return UpdateSessionStore(true, aIsFinal);
}
void TabListener::UpdateSHistoryChanges(bool aImmediately) {
mSessionStore->SetSHistoryFromParentChanged();
if (aImmediately) {
UpdateSessionStore();
} else {
@ -696,8 +569,8 @@ void TabListener::UpdateSHistoryChanges(bool aImmediately) {
}
}
bool TabListener::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
if (!aFlushId) {
bool TabListener::UpdateSessionStore(bool aIsFlush, bool aIsFinal) {
if (!aIsFlush) {
if (!mSessionStore || !mSessionStore->UpdateNeeded()) {
return false;
}
@ -707,7 +580,7 @@ bool TabListener::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
BrowserChild* browserChild = BrowserChild::GetFrom(mDocShell);
if (browserChild) {
StopTimerForUpdate();
return browserChild->UpdateSessionStore(aFlushId);
return browserChild->UpdateSessionStore(aIsFinal);
}
return false;
}
@ -737,44 +610,6 @@ bool TabListener::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
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;
@ -796,8 +631,8 @@ bool TabListener::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) {
NS_ENSURE_TRUE(ok, false);
nsresult rv = funcs->UpdateSessionStore(
mOwnerContent, mDocShell->GetBrowsingContext(), aFlushId, aIsFinal,
mEpoch, dataVal, mSessionStore->GetAndClearSHistoryChanged());
mOwnerContent, mDocShell->GetBrowsingContext(), mEpoch, dataVal,
mSessionStore->GetAndClearSHistoryChanged());
NS_ENSURE_SUCCESS(rv, false);
StopTimerForUpdate();
return true;

Просмотреть файл

@ -32,15 +32,6 @@ class ContentSessionStore {
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; }
@ -80,9 +71,8 @@ class ContentSessionStore {
void OnDocumentStart();
void OnDocumentEnd();
bool UpdateNeeded() {
return mPrivateChanged || mDocCapChanged || IsScrollPositionChanged() ||
IsFormDataChanged() || IsStorageUpdated() || mSHistoryChanged ||
mSHistoryChangedFromParent;
return mPrivateChanged || mDocCapChanged || IsStorageUpdated() ||
mSHistoryChanged || mSHistoryChangedFromParent;
}
private:
@ -92,12 +82,6 @@ class ContentSessionStore {
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,
@ -132,7 +116,7 @@ class TabListener : public nsIDOMEventListener,
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);
bool ForceFlushFromParent(bool aIsFinal);
void RemoveListeners();
void SetEpoch(uint32_t aEpoch) { mEpoch = aEpoch; }
uint32_t GetEpoch() { return mEpoch; }
@ -153,7 +137,7 @@ class TabListener : public nsIDOMEventListener,
void StopTimerForUpdate();
void AddEventListeners();
void RemoveEventListeners();
bool UpdateSessionStore(uint32_t aFlushId = 0, bool aIsFinal = false);
bool UpdateSessionStore(bool aIsFlush = false, bool aIsFinal = false);
void ResetStorageChangeListener();
void RemoveStorageChangeListener();
virtual ~TabListener();

Просмотреть файл

@ -1,3 +1,5 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -7,6 +9,7 @@
#include "jsapi.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/AutocompleteInfoBinding.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/HTMLInputElement.h"
@ -16,10 +19,12 @@
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/txIXPathContext.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WindowProxyHolder.h"
#include "mozilla/dom/XPathResult.h"
#include "mozilla/dom/XPathEvaluator.h"
#include "mozilla/dom/XPathExpression.h"
#include "mozilla/ReverseIterator.h"
#include "mozilla/UniquePtr.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsContentList.h"
@ -374,25 +379,6 @@ AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId,
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,
@ -404,19 +390,6 @@ static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
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,
@ -427,19 +400,6 @@ static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
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,
@ -455,19 +415,6 @@ static void AppendValueToCollectedData(
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,
@ -500,18 +447,6 @@ static void AppendValueToCollectedData(Document& aDocument, nsINode* aNode,
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,
@ -539,17 +474,214 @@ static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId,
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 void AppendEntry(nsINode* aNode, const nsString& aId,
const FormEntryValue& aValue,
sessionstore::FormData& aFormData) {
if (aId.IsEmpty()) {
FormEntry* entry = aFormData.xpath().AppendElement();
entry->value() = aValue;
aNode->GenerateXPath(entry->id());
} else {
aFormData.id().AppendElement(FormEntry{aId, aValue});
}
}
static void CollectTextAreaElement(Document* aDocument,
sessionstore::FormData& aFormData) {
RefPtr<nsContentList> textlist =
NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"textarea"_ns);
uint32_t length = textlist->Length();
for (uint32_t i = 0; i < length; ++i) {
MOZ_ASSERT(textlist->Item(i), "null item in node list!");
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() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
continue;
}
nsString value;
textArea->GetValue(value);
// In order to reduce XPath generation (which is slow), we only save data
// for form fields that have been changed. (cf. bug 537289)
if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
eCaseMatters)) {
continue;
}
AppendEntry(textArea, id, TextField{value}, aFormData);
}
}
static void CollectInputElement(Document* aDocument,
sessionstore::FormData& aFormData) {
RefPtr<nsContentList> inputlist =
NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"input"_ns);
uint32_t length = inputlist->Length();
for (uint32_t i = 0; i < length; ++i) {
MOZ_ASSERT(inputlist->Item(i), "null item in node list!");
nsCOMPtr<nsIFormControl> formControl =
do_QueryInterface(inputlist->Item(i));
if (formControl) {
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() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
continue;
}
Nullable<AutocompleteInfo> aInfo;
input->GetAutocompleteInfo(aInfo);
if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) {
continue;
}
FormEntryValue value;
if (input->ControlType() == NS_FORM_INPUT_CHECKBOX ||
input->ControlType() == NS_FORM_INPUT_RADIO) {
bool checked = input->Checked();
if (checked == input->DefaultChecked()) {
continue;
}
AppendEntry(input, id, Checkbox{checked}, aFormData);
} else if (input->ControlType() == NS_FORM_INPUT_FILE) {
IgnoredErrorResult rv;
sessionstore::FileList file;
input->MozGetFileNameArray(file.valueList(), rv);
if (rv.Failed() || file.valueList().IsEmpty()) {
continue;
}
AppendEntry(input, id, file, aFormData);
} else {
TextField field;
input->GetValue(field.value(), CallerType::System);
auto& value = field.value();
// In order to reduce XPath generation (which is slow), we only save data
// for form fields that have been changed. (cf. bug 537289)
// Also, don't want to collect credit card number.
if (value.IsEmpty() || IsValidCCNumber(value) ||
input->HasBeenTypePassword() ||
input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
eCaseMatters)) {
continue;
}
AppendEntry(input, id, field, aFormData);
}
}
}
static void CollectSelectElement(Document* aDocument,
sessionstore::FormData& aFormData) {
RefPtr<nsContentList> selectlist =
NS_GetContentList(aDocument, kNameSpaceID_XHTML, u"select"_ns);
uint32_t length = selectlist->Length();
for (uint32_t i = 0; i < length; ++i) {
MOZ_ASSERT(selectlist->Item(i), "null item in node list!");
RefPtr<HTMLSelectElement> select =
HTMLSelectElement::FromNodeOrNull(selectlist->Item(i));
if (!select) {
continue;
}
nsAutoString id;
select->GetId(id);
if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) {
continue;
}
AutocompleteInfo aInfo;
select->GetAutocompleteInfo(aInfo);
if (!aInfo.mCanAutomaticallyPersist) {
continue;
}
if (!select->Multiple()) {
HTMLOptionsCollection* options = select->GetOptions();
if (!options) {
continue;
}
uint32_t numOptions = options->Length();
int32_t defaultIndex = 0;
for (uint32_t idx = 0; idx < numOptions; idx++) {
HTMLOptionElement* option = options->ItemAsOption(idx);
if (option->DefaultSelected()) {
defaultIndex = option->Index();
}
}
int32_t selectedIndex = select->SelectedIndex();
if (selectedIndex == defaultIndex || selectedIndex < 0) {
continue;
}
DOMString selectVal;
select->GetValue(selectVal);
AppendEntry(select, id,
SingleSelect{static_cast<uint32_t>(selectedIndex),
selectVal.AsAString()},
aFormData);
} else {
HTMLOptionsCollection* options = select->GetOptions();
if (!options) {
continue;
}
bool hasDefaultValue = true;
nsTArray<nsString> selectslist;
uint32_t numOptions = options->Length();
for (uint32_t idx = 0; idx < numOptions; idx++) {
HTMLOptionElement* option = options->ItemAsOption(idx);
bool selected = option->Selected();
hasDefaultValue =
hasDefaultValue && (selected == option->DefaultSelected());
if (!selected) {
continue;
}
option->GetValue(*selectslist.AppendElement());
}
// In order to reduce XPath generation (which is slow), we only save data
// for form fields that have been changed. (cf. bug 537289)
if (hasDefaultValue) {
continue;
}
AppendEntry(select, id, MultipleSelect{selectslist}, aFormData);
}
}
}
/* static */
void SessionStoreUtils::CollectFormData(Document* aDocument,
sessionstore::FormData& aFormData) {
MOZ_DIAGNOSTIC_ASSERT(aDocument);
CollectTextAreaElement(aDocument, aFormData);
CollectInputElement(aDocument, aFormData);
CollectSelectElement(aDocument, aFormData);
aFormData.hasData() =
!aFormData.id().IsEmpty() || !aFormData.xpath().IsEmpty();
}
/* static */
@ -1048,6 +1180,7 @@ bool SessionStoreUtils::RestoreFormData(const GlobalObject& aGlobal,
}
}
}
if (aData.mXpath.WasPassed()) {
for (auto& entry : aData.mXpath.Value().Entries()) {
RefPtr<Element> node = FindNodeByXPath(aDocument, entry.mKey);
@ -1065,6 +1198,7 @@ bool SessionStoreUtils::RestoreFormData(const GlobalObject& aGlobal,
}
}
}
return true;
}
@ -1456,3 +1590,99 @@ nsresult SessionStoreUtils::CallRestoreTabContentComplete(Element* aBrowser) {
NS_ENSURE_TRUE(funcs, NS_ERROR_FAILURE);
return funcs->RestoreTabContentComplete(aBrowser);
}
/* static */
nsresult SessionStoreUtils::ConstructFormDataValues(
JSContext* aCx, const nsTArray<sessionstore::FormEntry>& aValues,
nsTArray<Record<nsString, OwningStringOrBooleanOrObject>::EntryType>&
aEntries,
bool aParseSessionData) {
using EntryType = Record<nsString, OwningStringOrBooleanOrObject>::EntryType;
if (!aEntries.SetCapacity(aValues.Length(), fallible)) {
return NS_ERROR_FAILURE;
}
for (const auto& value : aValues) {
EntryType* entry = aEntries.AppendElement();
using Type = sessionstore::FormEntryValue::Type;
switch (value.value().type()) {
case Type::TCheckbox:
entry->mValue.SetAsBoolean() = value.value().get_Checkbox().value();
break;
case Type::TTextField: {
if (aParseSessionData && value.id() == u"sessionData"_ns) {
JS::Rooted<JS::Value> jsval(aCx);
const auto& fieldValue = value.value().get_TextField().value();
if (!JS_ParseJSON(aCx, fieldValue.get(), fieldValue.Length(),
&jsval) ||
!jsval.isObject()) {
return NS_ERROR_FAILURE;
}
entry->mValue.SetAsObject() = &jsval.toObject();
} else {
entry->mValue.SetAsString() = value.value().get_TextField().value();
}
break;
}
case Type::TFileList: {
CollectedFileListValue file;
file.mFileList = value.value().get_FileList().valueList().Clone();
JS::Rooted<JS::Value> jsval(aCx);
if (!ToJSValue(aCx, file, &jsval) || !jsval.isObject()) {
return NS_ERROR_FAILURE;
}
entry->mValue.SetAsObject() = &jsval.toObject();
break;
}
case Type::TSingleSelect: {
CollectedNonMultipleSelectValue select;
select.mSelectedIndex = value.value().get_SingleSelect().index();
select.mValue = value.value().get_SingleSelect().value();
JS::Rooted<JS::Value> jsval(aCx);
if (!ToJSValue(aCx, select, &jsval) || !jsval.isObject()) {
return NS_ERROR_FAILURE;
}
entry->mValue.SetAsObject() = &jsval.toObject();
break;
}
case Type::TMultipleSelect: {
JS::Rooted<JS::Value> jsval(aCx);
if (!ToJSValue(aCx, value.value().get_MultipleSelect().valueList(),
&jsval) ||
!jsval.isObject()) {
return NS_ERROR_FAILURE;
}
entry->mValue.SetAsObject() = &jsval.toObject();
break;
}
default:
break;
}
entry->mKey = value.id();
}
return NS_OK;
}
/* static */ void SessionStoreUtils::ResetSessionStore(
BrowsingContext* aContext) {
MOZ_RELEASE_ASSERT(NATIVE_LISTENER);
WindowContext* windowContext = aContext->GetCurrentWindowContext();
if (!windowContext) {
return;
}
WindowGlobalChild* windowChild = windowContext->GetWindowGlobalChild();
if (!windowChild || !windowChild->CanSend()) {
return;
}
uint32_t epoch = aContext->GetSessionStoreEpoch();
Unused << windowChild->SendResetSessionStore(epoch);
}

Просмотреть файл

@ -22,9 +22,15 @@ class ErrorResult;
namespace dom {
class CanonicalBrowsingContext;
class GlobalObject;
struct SSScrollPositionDict;
namespace sessionstore {
class FormData;
class FormEntry;
} // namespace sessionstore
class SessionStoreUtils {
public:
MOZ_CAN_RUN_SCRIPT
@ -60,6 +66,9 @@ class SessionStoreUtils {
static void RestoreScrollPosition(nsGlobalWindowInner& aWindow,
const nsCString& aScrollPosition);
static void CollectFormData(Document* aDocument,
sessionstore::FormData& aFormData);
/*
@param aDocument: DOMDocument instance to obtain form data for.
@param aGeneratedCount: the current number of XPath expressions in the
@ -109,6 +118,21 @@ class SessionStoreUtils {
nsISessionStoreRestoreData* aData);
static nsresult CallRestoreTabContentComplete(Element* aBrowser);
static nsresult ConstructFormDataValues(
JSContext* aCx, const nsTArray<sessionstore::FormEntry>& aValues,
nsTArray<Record<nsString, OwningStringOrBooleanOrObject>::EntryType>&
aEntries,
bool aParseSessionData = false);
static void ResetSessionStore(BrowsingContext* aContext);
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_THUNDERBIRD) || \
defined(MOZ_SUITE)
static constexpr bool NATIVE_LISTENER = false;
#else
static constexpr bool NATIVE_LISTENER = true;
#endif
};
} // namespace dom

Просмотреть файл

@ -5,7 +5,9 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.mozilla.dom += [
"SessionStoreChangeListener.h",
"SessionStoreData.h",
"SessionStoreDataCollector.h",
"SessionStoreListener.h",
"SessionStoreMessageUtils.h",
"SessionStoreRestoreData.h",
@ -14,6 +16,8 @@ EXPORTS.mozilla.dom += [
UNIFIED_SOURCES += [
"RestoreTabContentObserver.cpp",
"SessionStoreChangeListener.cpp",
"SessionStoreDataCollector.cpp",
"SessionStoreListener.cpp",
"SessionStoreRestoreData.cpp",
"SessionStoreUtils.cpp",