From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- .../sessionstore/BrowserSessionStore.cpp | 292 ++++ .../components/sessionstore/BrowserSessionStore.h | 57 + toolkit/components/sessionstore/PSessionStore.ipdl | 63 + .../sessionstore/RestoreTabContentObserver.cpp | 112 ++ .../sessionstore/RestoreTabContentObserver.h | 34 + .../sessionstore/SessionStoreChangeListener.cpp | 393 +++++ .../sessionstore/SessionStoreChangeListener.h | 103 ++ .../components/sessionstore/SessionStoreChild.cpp | 261 +++ .../components/sessionstore/SessionStoreChild.h | 73 + toolkit/components/sessionstore/SessionStoreData.h | 61 + .../sessionstore/SessionStoreFormData.cpp | 172 ++ .../components/sessionstore/SessionStoreFormData.h | 83 + .../sessionstore/SessionStoreFunctions.idl | 23 + .../sessionstore/SessionStoreFunctions.sys.mjs | 87 + .../sessionstore/SessionStoreListener.cpp | 494 ++++++ .../components/sessionstore/SessionStoreListener.h | 114 ++ .../sessionstore/SessionStoreMessageUtils.h | 134 ++ .../components/sessionstore/SessionStoreParent.cpp | 318 ++++ .../components/sessionstore/SessionStoreParent.h | 79 + .../sessionstore/SessionStoreRestoreData.cpp | 186 +++ .../sessionstore/SessionStoreRestoreData.h | 59 + .../sessionstore/SessionStoreScrollData.cpp | 123 ++ .../sessionstore/SessionStoreScrollData.h | 69 + .../sessionstore/SessionStoreTypes.ipdlh | 66 + .../components/sessionstore/SessionStoreUtils.cpp | 1738 ++++++++++++++++++++ .../components/sessionstore/SessionStoreUtils.h | 178 ++ toolkit/components/sessionstore/moz.build | 55 + .../sessionstore/nsISessionStoreRestoreData.idl | 33 + 28 files changed, 5460 insertions(+) create mode 100644 toolkit/components/sessionstore/BrowserSessionStore.cpp create mode 100644 toolkit/components/sessionstore/BrowserSessionStore.h create mode 100644 toolkit/components/sessionstore/PSessionStore.ipdl create mode 100644 toolkit/components/sessionstore/RestoreTabContentObserver.cpp create mode 100644 toolkit/components/sessionstore/RestoreTabContentObserver.h create mode 100644 toolkit/components/sessionstore/SessionStoreChangeListener.cpp create mode 100644 toolkit/components/sessionstore/SessionStoreChangeListener.h create mode 100644 toolkit/components/sessionstore/SessionStoreChild.cpp create mode 100644 toolkit/components/sessionstore/SessionStoreChild.h create mode 100644 toolkit/components/sessionstore/SessionStoreData.h create mode 100644 toolkit/components/sessionstore/SessionStoreFormData.cpp create mode 100644 toolkit/components/sessionstore/SessionStoreFormData.h create mode 100644 toolkit/components/sessionstore/SessionStoreFunctions.idl create mode 100644 toolkit/components/sessionstore/SessionStoreFunctions.sys.mjs create mode 100644 toolkit/components/sessionstore/SessionStoreListener.cpp create mode 100644 toolkit/components/sessionstore/SessionStoreListener.h create mode 100644 toolkit/components/sessionstore/SessionStoreMessageUtils.h create mode 100644 toolkit/components/sessionstore/SessionStoreParent.cpp create mode 100644 toolkit/components/sessionstore/SessionStoreParent.h create mode 100644 toolkit/components/sessionstore/SessionStoreRestoreData.cpp create mode 100644 toolkit/components/sessionstore/SessionStoreRestoreData.h create mode 100644 toolkit/components/sessionstore/SessionStoreScrollData.cpp create mode 100644 toolkit/components/sessionstore/SessionStoreScrollData.h create mode 100644 toolkit/components/sessionstore/SessionStoreTypes.ipdlh create mode 100644 toolkit/components/sessionstore/SessionStoreUtils.cpp create mode 100644 toolkit/components/sessionstore/SessionStoreUtils.h create mode 100644 toolkit/components/sessionstore/moz.build create mode 100644 toolkit/components/sessionstore/nsISessionStoreRestoreData.idl (limited to 'toolkit/components/sessionstore') diff --git a/toolkit/components/sessionstore/BrowserSessionStore.cpp b/toolkit/components/sessionstore/BrowserSessionStore.cpp new file mode 100644 index 0000000000..f4da9646b2 --- /dev/null +++ b/toolkit/components/sessionstore/BrowserSessionStore.cpp @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BrowserSessionStore.h" +#include +#include +#include + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPtr.h" + +#include "mozilla/dom/BrowserSessionStoreBinding.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/SessionStoreFormData.h" +#include "mozilla/dom/SessionStoreScrollData.h" +#include "mozilla/dom/WindowGlobalParent.h" + +#include "nsTHashMap.h" +#include "nsHashtablesFwd.h" + +#include "js/RootingAPI.h" + +using namespace mozilla; +using namespace mozilla::dom; + +static StaticAutoPtr> + sSessionStore; + +NS_IMPL_CYCLE_COLLECTION(BrowserSessionStore, mBrowsingContext, mFormData, + mScrollData) + +/* static */ +already_AddRefed BrowserSessionStore::GetOrCreate( + CanonicalBrowsingContext* aBrowsingContext) { + if (!aBrowsingContext->IsTop()) { + return nullptr; + } + + if (!sSessionStore) { + sSessionStore = new nsTHashMap(); + ClearOnShutdown(&sSessionStore); + } + + return do_AddRef(sSessionStore->LookupOrInsertWith( + aBrowsingContext->Id(), + [&] { return new BrowserSessionStore(aBrowsingContext); })); +} + +BrowserSessionStore::BrowserSessionStore( + CanonicalBrowsingContext* aBrowsingContext) + : mBrowsingContext(aBrowsingContext) {} + +SessionStoreFormData* BrowserSessionStore::GetFormdata() { return mFormData; } + +SessionStoreScrollData* BrowserSessionStore::GetScroll() { return mScrollData; } + +static bool ShouldUpdateSessionStore(CanonicalBrowsingContext* aBrowsingContext, + uint32_t aEpoch) { + if (!aBrowsingContext) { + return false; + } + + if (aBrowsingContext->Top()->GetSessionStoreEpoch() != aEpoch) { + return false; + } + + if (aBrowsingContext->IsReplaced()) { + return false; + } + + if (aBrowsingContext->IsDynamic()) { + return false; + } + + return true; +} + +// With GetOrCreate we can create either of the weak fields: +// WeakPtr mFormdata; +// WeakPtr mScroll; +// in CanonicalBrowsingContext. If one already exists, then we return that. +template & (CanonicalBrowsingContext::*GetWeakRef)()> +static already_AddRefed GetOrCreateEntry( + CanonicalBrowsingContext* aBrowsingContext) { + typename T::LocationType& location = (aBrowsingContext->*GetWeakRef)(); + RefPtr entry = location.get(); + if (!entry) { + entry = MakeRefPtr(); + location = entry; + } + + return entry.forget(); +} + +// With InsertEntry we can insert an entry in the session store data tree in +// either of the weak fields: +// WeakPtr mFormdata; +// WeakPtr mScroll; +// in CanonicalBrowsingContext. If an entry is inserted where there is no parent +// entry, a spine of entries will be created until one is found, or we reach the +// top browsing context. +template +void InsertEntry(BrowsingContext* aBrowsingContext, T* aParent, T* aUpdate) { + int32_t offset = aBrowsingContext->ChildOffset(); + if (offset < 0) { + return; + } + + aParent->ClearCachedChildren(); + + auto& children = aParent->Children(); + + children.EnsureLengthAtLeast(offset + 1); + if (children[offset] && !aBrowsingContext->Children().IsEmpty()) { + children[offset]->ClearCachedChildren(); + aUpdate->ClearCachedChildren(); + } + + children[offset] = aUpdate; +} + +// With RemoveEntry we can remove an entry in the session store data tree in +// either of the weak fields: +// WeakPtr mFormdata; +// WeakPtr mScroll; +// in CanonicalBrowsingContext. If an entry is removed, where its parent doesn't +// contain data, we'll remove the parent and repeat until we either find an +// entry with data or reach the top browsing context. +template +void RemoveEntry(BrowsingContext* aBrowsingContext, T* aParent) { + int32_t offset = aBrowsingContext->ChildOffset(); + if (offset < 0) { + return; + } + + if (!aParent) { + return; + } + + aParent->ClearCachedChildren(); + + auto& children = aParent->Children(); + size_t length = children.Length(); + if (children.Length() <= static_cast(offset)) { + // The children array doesn't extend to offset. + return; + } + + if (static_cast(offset) < length - 1) { + // offset is before the last item in the children array. + children[offset] = nullptr; + return; + } + + // offset is the last item, find the first non-null item before it + // and remove anything after that item. + while (offset > 0 && !children[offset - 1]) { + --offset; + } + + children.TruncateLength(offset); +} + +// With UpdateSessionStoreField we can update an entry in the session store +// data tree in either of the weak fields: +// WeakPtr mFormdata; +// WeakPtr mScroll; +// in CanonicalBrowsingContext. UpdateSessionStoreField uses the above +// functions, `GetOrCreateEntry`, `InsertEntry` and `RemoveEntry` to operate on +// the weak fields. We return the top-level entry attached to the top browsing +// context through `aEntry`. If the entire browsing context tree contains no +// session store data this will be set to nullptr. +template & (CanonicalBrowsingContext::*GetWeakRef)()> +void UpdateSessionStoreField(CanonicalBrowsingContext* aBrowsingContext, + const typename T::CollectedType& aUpdate, + T** aEntry) { + RefPtr currentEntry; + + if (T::HasData(aUpdate)) { + currentEntry = GetOrCreateEntry(aBrowsingContext); + currentEntry->Update(aUpdate); + + CanonicalBrowsingContext* currentBrowsingContext = aBrowsingContext; + while (CanonicalBrowsingContext* parent = + currentBrowsingContext->GetParent()) { + WeakPtr& parentEntry = (parent->*GetWeakRef)(); + if (parentEntry) { + InsertEntry(aBrowsingContext, parentEntry.get(), currentEntry.get()); + break; + } + + RefPtr entry = GetOrCreateEntry(parent); + InsertEntry(currentBrowsingContext, entry.get(), currentEntry.get()); + + currentEntry = entry; + currentBrowsingContext = parent; + } + + currentEntry = (aBrowsingContext->Top()->*GetWeakRef)().get(); + } else if ((currentEntry = (aBrowsingContext->*GetWeakRef)())) { + currentEntry->Update(aUpdate); + + CanonicalBrowsingContext* currentBrowsingContext = aBrowsingContext; + while (CanonicalBrowsingContext* parent = + currentBrowsingContext->GetParent()) { + if (!currentEntry || !currentEntry->IsEmpty()) { + break; + } + + T* parentEntry = (parent->*GetWeakRef)().get(); + RemoveEntry(currentBrowsingContext, parentEntry); + + currentEntry = parentEntry; + currentBrowsingContext = parent; + } + + if (currentEntry && currentEntry->IsEmpty()) { + currentEntry = nullptr; + } else { + currentEntry = (aBrowsingContext->Top()->*GetWeakRef)().get(); + } + } else { + currentEntry = (aBrowsingContext->Top()->*GetWeakRef)().get(); + } + + *aEntry = currentEntry.forget().take(); +} + +void BrowserSessionStore::UpdateSessionStore( + CanonicalBrowsingContext* aBrowsingContext, + const Maybe& aFormData, + const Maybe& aScrollPosition, uint32_t aEpoch) { + if (!aFormData && !aScrollPosition) { + return; + } + + if (!ShouldUpdateSessionStore(aBrowsingContext, aEpoch)) { + return; + } + + if (aFormData) { + UpdateSessionStoreField< + SessionStoreFormData, + &CanonicalBrowsingContext::GetSessionStoreFormDataRef>( + aBrowsingContext, *aFormData, getter_AddRefs(mFormData)); + } + + if (aScrollPosition) { + UpdateSessionStoreField< + SessionStoreScrollData, + &CanonicalBrowsingContext::GetSessionStoreScrollDataRef>( + aBrowsingContext, *aScrollPosition, getter_AddRefs(mScrollData)); + } +} + +void BrowserSessionStore::RemoveSessionStore( + CanonicalBrowsingContext* aBrowsingContext) { + if (!aBrowsingContext) { + return; + } + + CanonicalBrowsingContext* parentContext = aBrowsingContext->GetParent(); + + if (parentContext) { + RemoveEntry(aBrowsingContext, + parentContext->GetSessionStoreFormDataRef().get()); + + RemoveEntry(aBrowsingContext, + parentContext->GetSessionStoreScrollDataRef().get()); + + return; + } + + if (aBrowsingContext->IsTop()) { + mFormData = nullptr; + mScrollData = nullptr; + } +} + +BrowserSessionStore::~BrowserSessionStore() { + if (sSessionStore) { + sSessionStore->Remove(mBrowsingContext->Id()); + } +} diff --git a/toolkit/components/sessionstore/BrowserSessionStore.h b/toolkit/components/sessionstore/BrowserSessionStore.h new file mode 100644 index 0000000000..011e7673ec --- /dev/null +++ b/toolkit/components/sessionstore/BrowserSessionStore.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStore_h +#define mozilla_dom_SessionStore_h + +#include "nsWrapperCache.h" + +#include "mozilla/Maybe.h" + +struct nsPoint; + +namespace mozilla::dom { + +class CanonicalBrowsingContext; +class GlobalObject; +class SessionStoreFormData; +class SessionStoreScrollData; +class WindowGlobalParent; + +namespace sessionstore { +class FormData; +} + +class BrowserSessionStore final { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowserSessionStore) + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(BrowserSessionStore) + + static already_AddRefed GetOrCreate( + CanonicalBrowsingContext* aBrowsingContext); + + SessionStoreFormData* GetFormdata(); + SessionStoreScrollData* GetScroll(); + + void UpdateSessionStore(CanonicalBrowsingContext* aBrowsingContext, + const Maybe& aFormData, + const Maybe& aScrollPosition, + uint32_t aEpoch); + + void RemoveSessionStore(CanonicalBrowsingContext* aBrowsingContext); + + private: + explicit BrowserSessionStore(CanonicalBrowsingContext* aBrowsingContext); + virtual ~BrowserSessionStore(); + + RefPtr mBrowsingContext; + RefPtr mFormData; + RefPtr mScrollData; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_SessionStore_h diff --git a/toolkit/components/sessionstore/PSessionStore.ipdl b/toolkit/components/sessionstore/PSessionStore.ipdl new file mode 100644 index 0000000000..4b7bb48849 --- /dev/null +++ b/toolkit/components/sessionstore/PSessionStore.ipdl @@ -0,0 +1,63 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include "mozilla/GfxMessageUtils.h"; + +include protocol PBrowser; +include protocol PInProcess; +include SessionStoreTypes; + +using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h"; +using mozilla::dom::MaybeSessionStoreZoom from "mozilla/dom/SessionStoreScrollData.h"; + +namespace mozilla { +namespace dom { + +/** + * The PSessionStore actor handles collection of session store data from content + * type documents. It can be used both in a content process and in the parent + * process. In particular it solves the problem of handling incremental updates + * to the session store, since we're collecting from potentially several content + * processes. + */ +async protocol PSessionStore +{ + manager PBrowser or PInProcess; + +parent: + /** + * Sends data to be stored and instructions to the session store to + * potentially collect data in the parent. This is data that is not + * collected incrementally. + */ + async SessionStoreUpdate( + nsCString? aDocShellCaps, bool? aPrivateMode, MaybeSessionStoreZoom aZoom, + bool aNeedCollectSHistory, uint32_t aEpoch); + + /** + * Sends data to be stored to the session store. The collected data + * is all the collected changed data from all the in-process documents + * in the process in which the SessionStoreChild actor lives. + */ + async IncrementalSessionStoreUpdate( + MaybeDiscardedBrowsingContext aBrowsingContext, FormData? aFormData, + nsPoint? aScrollPosition, uint32_t aEpoch); + + /** + * Drop all the collected data associated with the provided browsing + * context. + */ + async ResetSessionStore( + MaybeDiscardedBrowsingContext aBrowsingContext, uint32_t aEpoch); + +child: + async FlushTabState() returns(bool aHadContext); + + async __delete__(); +}; + +} // namespace dom +} // namespace mozilla diff --git a/toolkit/components/sessionstore/RestoreTabContentObserver.cpp b/toolkit/components/sessionstore/RestoreTabContentObserver.cpp new file mode 100644 index 0000000000..b0dda66ff0 --- /dev/null +++ b/toolkit/components/sessionstore/RestoreTabContentObserver.cpp @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsPIDOMWindow.h" +#include "nsReadableUtils.h" +#include "nsThreadUtils.h" +#include "RestoreTabContentObserver.h" + +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(RestoreTabContentObserver, nsIObserver) + +const char* const kAboutReaderTopic = "AboutReader:Ready"; +const char* const kContentDocumentLoaded = "content-document-loaded"; +const char* const kChromeDocumentLoaded = "chrome-document-loaded"; + +/* static */ +void RestoreTabContentObserver::Initialize() { + MOZ_ASSERT(!gRestoreTabContentObserver); + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr observer = new RestoreTabContentObserver(); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return; + } + + obs->AddObserver(observer, kAboutReaderTopic, false); + obs->AddObserver(observer, kContentDocumentLoaded, false); + obs->AddObserver(observer, kChromeDocumentLoaded, false); + + gRestoreTabContentObserver = observer; +} + +/* static */ +void RestoreTabContentObserver::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!gRestoreTabContentObserver) { + return; + } + + RefPtr observer = gRestoreTabContentObserver; + gRestoreTabContentObserver = nullptr; + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) { + return; + } + + obs->RemoveObserver(observer, kAboutReaderTopic); + obs->RemoveObserver(observer, kContentDocumentLoaded); + obs->RemoveObserver(observer, kChromeDocumentLoaded); +} + +NS_IMETHODIMP +RestoreTabContentObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + nsCOMPtr inner; + if (!strcmp(aTopic, kAboutReaderTopic)) { + inner = do_QueryInterface(aSubject); + } else if (!strcmp(aTopic, kContentDocumentLoaded) || + !strcmp(aTopic, kChromeDocumentLoaded)) { + nsCOMPtr doc = do_QueryInterface(aSubject); + inner = doc ? doc->GetInnerWindow() : nullptr; + } + if (!inner) { + return NS_OK; + } + + nsCOMPtr uri = inner->GetDocumentURI(); + if (!uri) { + return NS_OK; + } + + // We'll handle loading about:reader with "AboutReader:Ready" + // rather than "content-document-loaded". + if (uri->SchemeIs("about") && + StringBeginsWith(uri->GetSpecOrDefault(), "about:reader"_ns) && + strcmp(aTopic, kAboutReaderTopic) != 0) { + return NS_OK; + } + + RefPtr bc = inner->GetBrowsingContext(); + if (!bc || !bc->Top()->GetHasRestoreData()) { + return NS_OK; + } + if (XRE_IsParentProcess()) { + if (WindowGlobalParent* wgp = bc->Canonical()->GetCurrentWindowGlobal()) { + bc->Canonical()->Top()->RequestRestoreTabContent(wgp); + } + } else if (WindowContext* windowContext = bc->GetCurrentWindowContext()) { + if (WindowGlobalChild* wgc = windowContext->GetWindowGlobalChild()) { + wgc->SendRequestRestoreTabContent(); + } + } + return NS_OK; +} + +mozilla::StaticRefPtr + RestoreTabContentObserver::gRestoreTabContentObserver; diff --git a/toolkit/components/sessionstore/RestoreTabContentObserver.h b/toolkit/components/sessionstore/RestoreTabContentObserver.h new file mode 100644 index 0000000000..edab379a55 --- /dev/null +++ b/toolkit/components/sessionstore/RestoreTabContentObserver.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_RestoreTabContentObserver_h +#define mozilla_dom_RestoreTabContentObserver_h + +#include "mozilla/StaticPtr.h" +#include "nsIObserver.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace dom { + +// An observer for restoring session store data at a correct time. +class RestoreTabContentObserver final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static void Initialize(); + static void Shutdown(); + + private: + RestoreTabContentObserver() = default; + + virtual ~RestoreTabContentObserver() = default; + static mozilla::StaticRefPtr + gRestoreTabContentObserver; +}; +} // namespace dom +} // namespace mozilla +#endif // mozilla_dom_RestoreTabContentObserver_h diff --git a/toolkit/components/sessionstore/SessionStoreChangeListener.cpp b/toolkit/components/sessionstore/SessionStoreChangeListener.cpp new file mode 100644 index 0000000000..1418774a37 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreChangeListener.cpp @@ -0,0 +1,393 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SessionStoreChangeListener.h" + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/SessionStoreChild.h" +#include "mozilla/dom/SessionStoreUtils.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_browser.h" + +#include "nsBaseHashtable.h" +#include "nsDocShell.h" +#include "nsGenericHTMLElement.h" +#include "nsIXULRuntime.h" +#include "nsPIDOMWindow.h" +#include "nsTHashMap.h" +#include "nsTHashtable.h" +#include "nsLayoutUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace { +constexpr auto kInput = u"input"_ns; +constexpr auto kScroll = u"mozvisualscroll"_ns; +constexpr auto kResize = u"mozvisualresize"_ns; + +static constexpr char kNoAutoUpdates[] = + "browser.sessionstore.debug.no_auto_updates"; +static constexpr char kInterval[] = "browser.sessionstore.interval"; +static const char* kObservedPrefs[] = {kNoAutoUpdates, kInterval, nullptr}; +} // namespace + +inline void ImplCycleCollectionUnlink( + SessionStoreChangeListener::SessionStoreChangeTable& aField) { + aField.Clear(); +} + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + const SessionStoreChangeListener::SessionStoreChangeTable& aField, + const char* aName, uint32_t aFlags = 0) { + for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) { + CycleCollectionNoteChild(aCallback, iter.Key(), aName, aFlags); + } +} + +NS_IMPL_CYCLE_COLLECTION(SessionStoreChangeListener, mBrowsingContext, + mCurrentEventTarget, mSessionStoreChild, + mSessionStoreChanges) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStoreChangeListener) + NS_INTERFACE_MAP_ENTRY(nsINamed) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStoreChangeListener) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStoreChangeListener) + +NS_IMETHODIMP +SessionStoreChangeListener::GetName(nsACString& aName) { + aName.AssignLiteral("SessionStoreChangeListener"); + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreChangeListener::Notify(nsITimer* aTimer) { + FlushSessionStore(); + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreChangeListener::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + FlushSessionStore(); + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreChangeListener::HandleEvent(dom::Event* aEvent) { + EventTarget* target = aEvent->GetTarget(); + if (!target) { + return NS_OK; + } + + nsIGlobalObject* global = target->GetOwnerGlobal(); + if (!global) { + return NS_OK; + } + + nsPIDOMWindowInner* inner = global->AsInnerWindow(); + if (!inner) { + return NS_OK; + } + + WindowContext* windowContext = inner->GetWindowContext(); + if (!windowContext) { + return NS_OK; + } + + BrowsingContext* browsingContext = windowContext->GetBrowsingContext(); + if (!browsingContext) { + return NS_OK; + } + + if (browsingContext->IsDynamic()) { + return NS_OK; + } + + nsAutoString eventType; + aEvent->GetType(eventType); + + if (eventType == kInput) { + RecordChange(windowContext, Change::Input); + } else if (eventType == kScroll) { + RecordChange(windowContext, Change::Scroll); + } else if (eventType == kResize && browsingContext->IsTop()) { + RecordChange(windowContext, Change::Resize); + } + return NS_OK; +} + +/* static */ already_AddRefed +SessionStoreChangeListener::Create(BrowsingContext* aBrowsingContext) { + MOZ_RELEASE_ASSERT( + StaticPrefs::browser_sessionstore_platform_collection_AtStartup()); + if (!aBrowsingContext) { + return nullptr; + } + + RefPtr listener = + new SessionStoreChangeListener(aBrowsingContext); + listener->Init(); + + return listener.forget(); +} + +void SessionStoreChangeListener::Stop() { + RemoveEventListeners(); + Preferences::RemoveObservers(this, kObservedPrefs); +} + +void SessionStoreChangeListener::UpdateEventTargets() { + RemoveEventListeners(); + AddEventListeners(); +} + +static void CollectFormData(Document* aDocument, + Maybe& aFormData) { + aFormData.emplace(); + auto& formData = aFormData.ref(); + uint32_t size = SessionStoreUtils::CollectFormData(aDocument, formData); + + Element* body = aDocument->GetBody(); + if (aDocument->HasFlag(NODE_IS_EDITABLE) && body) { + IgnoredErrorResult result; + body->GetInnerHTML(formData.innerHTML(), result); + size += formData.innerHTML().Length(); + if (!result.Failed()) { + formData.hasData() = true; + } + } + + if (!formData.hasData()) { + return; + } + + nsIURI* documentURI = aDocument->GetDocumentURI(); + if (!documentURI) { + return; + } + + documentURI->GetSpecIgnoringRef(formData.uri()); + + if (size > StaticPrefs::browser_sessionstore_dom_form_max_limit()) { + aFormData = Nothing(); + } +} + +static void GetZoom(BrowsingContext* aBrowsingContext, + Maybe& aZoom) { + nsIDocShell* docShell = aBrowsingContext->GetDocShell(); + if (!docShell) { + return; + } + + PresShell* presShell = docShell->GetPresShell(); + if (!presShell) { + return; + } + + LayoutDeviceIntSize displaySize; + + if (!nsLayoutUtils::GetContentViewerSize(presShell->GetPresContext(), + displaySize)) { + return; + } + + aZoom.emplace(presShell->GetResolution(), displaySize.width, + displaySize.height); +} + +void SessionStoreChangeListener::FlushSessionStore() { + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + bool collectSessionHistory = false; + bool collectWireFrame = false; + bool didResize = false; + + for (auto& iter : mSessionStoreChanges) { + WindowContext* windowContext = iter.GetKey(); + if (!windowContext) { + continue; + } + + BrowsingContext* browsingContext = windowContext->GetBrowsingContext(); + + // This is a bit unfortunate, but is needed when a window context with a + // recorded change has become non-current before its data has been + // collected. This can happen either due to navigation or destruction, and + // in the previous case we don't want to collect, but in the latter we do. + // This could be cleaned up if we change. See bug 1770773. + if (!windowContext->IsCurrent() && !browsingContext->IsDiscarded()) { + continue; + } + + RefPtr document = windowContext->GetDocument(); + if (!document) { + continue; + } + + EnumSet changes = iter.GetData(); + Maybe maybeFormData; + if (changes.contains(Change::Input)) { + CollectFormData(document, maybeFormData); + } + + Maybe maybeScroll; + PresShell* presShell = document->GetPresShell(); + + if (presShell && changes.contains(Change::Scroll)) { + maybeScroll = Some(presShell->GetVisualViewportOffset()); + } + + collectWireFrame = collectWireFrame || changes.contains(Change::WireFrame); + + collectSessionHistory = + collectSessionHistory || changes.contains(Change::SessionHistory); + + if (presShell && changes.contains(Change::Resize)) { + didResize = true; + } + + mSessionStoreChild->IncrementalSessionStoreUpdate( + browsingContext, maybeFormData, maybeScroll, mEpoch); + } + + if (collectWireFrame) { + collectSessionHistory = CollectWireframe() || collectSessionHistory; + } + + mSessionStoreChanges.Clear(); + + Maybe zoom; + if (didResize) { + GetZoom(mBrowsingContext->Top(), zoom); + } + + mSessionStoreChild->UpdateSessionStore(collectSessionHistory, zoom); +} + +/* static */ +SessionStoreChangeListener* SessionStoreChangeListener::CollectSessionStoreData( + WindowContext* aWindowContext, const EnumSet& aChanges) { + SessionStoreChild* sessionStoreChild = + SessionStoreChild::From(aWindowContext->GetWindowGlobalChild()); + if (!sessionStoreChild) { + return nullptr; + } + + SessionStoreChangeListener* sessionStoreChangeListener = + sessionStoreChild->GetSessionStoreChangeListener(); + + if (!sessionStoreChangeListener) { + return nullptr; + } + + sessionStoreChangeListener->RecordChange(aWindowContext, aChanges); + + return sessionStoreChangeListener; +} + +void SessionStoreChangeListener::SetActor( + SessionStoreChild* aSessionStoreChild) { + mSessionStoreChild = aSessionStoreChild; +} + +bool SessionStoreChangeListener::CollectWireframe() { + if (auto* docShell = nsDocShell::Cast(mBrowsingContext->GetDocShell())) { + return docShell->CollectWireframe(); + } + + return false; +} + +void SessionStoreChangeListener::RecordChange(WindowContext* aWindowContext, + EnumSet aChange) { + EnsureTimer(); + + Unused << mSessionStoreChanges.WithEntryHandle( + aWindowContext, [&](auto entryHandle) -> EnumSet& { + if (entryHandle) { + *entryHandle += aChange; + return *entryHandle; + } + + return entryHandle.Insert(aChange); + }); +} + +SessionStoreChangeListener::SessionStoreChangeListener( + BrowsingContext* aBrowsingContext) + : mBrowsingContext(aBrowsingContext), + mEpoch(aBrowsingContext->GetSessionStoreEpoch()) {} + +void SessionStoreChangeListener::Init() { + AddEventListeners(); + + Preferences::AddStrongObservers(this, kObservedPrefs); +} + +EventTarget* SessionStoreChangeListener::GetEventTarget() { + if (mBrowsingContext->GetDOMWindow()) { + return mBrowsingContext->GetDOMWindow()->GetChromeEventHandler(); + } + + return nullptr; +} + +void SessionStoreChangeListener::AddEventListeners() { + if (EventTarget* target = GetEventTarget()) { + target->AddSystemEventListener(kInput, this, false); + target->AddSystemEventListener(kScroll, this, false); + if (StaticPrefs::browser_sessionstore_collect_zoom_AtStartup()) { + target->AddSystemEventListener(kResize, this, false); + } + mCurrentEventTarget = target; + } +} + +void SessionStoreChangeListener::RemoveEventListeners() { + if (mCurrentEventTarget) { + mCurrentEventTarget->RemoveSystemEventListener(kInput, this, false); + mCurrentEventTarget->RemoveSystemEventListener(kScroll, this, false); + if (StaticPrefs::browser_sessionstore_collect_zoom_AtStartup()) { + mCurrentEventTarget->RemoveSystemEventListener(kResize, this, false); + } + } + + mCurrentEventTarget = nullptr; +} + +void SessionStoreChangeListener::EnsureTimer() { + if (mTimer) { + return; + } + + if (!StaticPrefs::browser_sessionstore_debug_no_auto_updates()) { + auto result = NS_NewTimerWithCallback( + this, StaticPrefs::browser_sessionstore_interval(), + nsITimer::TYPE_ONE_SHOT); + if (result.isErr()) { + return; + } + + mTimer = result.unwrap(); + } +} diff --git a/toolkit/components/sessionstore/SessionStoreChangeListener.h b/toolkit/components/sessionstore/SessionStoreChangeListener.h new file mode 100644 index 0000000000..803a2137bd --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreChangeListener.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStoreChangeListener_h +#define mozilla_dom_SessionStoreChangeListener_h + +#include "ErrorList.h" + +#include "PLDHashTable.h" +#include "mozilla/EnumSet.h" +#include "nsCycleCollectionParticipant.h" +#include "nsHashKeys.h" +#include "nsIDOMEventListener.h" +#include "nsIObserver.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsINamed.h" +#include "nsHashtablesFwd.h" + +#include "mozilla/EnumSet.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Result.h" + +#include "mozilla/dom/WindowGlobalChild.h" + +namespace mozilla::dom { + +class BrowsingContext; +class Element; +class EventTarget; +class SessionStoreChild; +class WindowContext; + +class SessionStoreChangeListener final : public nsINamed, + public nsIObserver, + public nsITimerCallback, + public nsIDOMEventListener { + public: + NS_DECL_NSINAMED + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SessionStoreChangeListener, + nsIObserver) + + static already_AddRefed Create( + BrowsingContext* aBrowsingContext); + + void Stop(); + + void UpdateEventTargets(); + + void FlushSessionStore(); + + enum class Change { Input, Scroll, SessionHistory, WireFrame, Resize }; + + static SessionStoreChangeListener* CollectSessionStoreData( + WindowContext* aWindowContext, const EnumSet& aChanges); + + void SetActor(SessionStoreChild* aSessionStoreChild); + + void SetEpoch(uint32_t aEpoch) { mEpoch = aEpoch; } + + uint32_t GetEpoch() const { return mEpoch; } + + bool CollectWireframe(); + + private: + void RecordChange(WindowContext* aWindowContext, EnumSet aChanges); + + public: + using SessionStoreChangeTable = + nsTHashMap, EnumSet>; + + private: + explicit SessionStoreChangeListener(BrowsingContext* aBrowsingContext); + ~SessionStoreChangeListener() = default; + + void Init(); + + EventTarget* GetEventTarget(); + + void AddEventListeners(); + void RemoveEventListeners(); + + void EnsureTimer(); + + RefPtr mBrowsingContext; + RefPtr mCurrentEventTarget; + + uint32_t mEpoch; + nsCOMPtr mTimer; + RefPtr mSessionStoreChild; + SessionStoreChangeTable mSessionStoreChanges; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_SessionStoreChangeListener_h diff --git a/toolkit/components/sessionstore/SessionStoreChild.cpp b/toolkit/components/sessionstore/SessionStoreChild.cpp new file mode 100644 index 0000000000..0b6d0dd17e --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreChild.cpp @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SessionStoreChild.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/InProcessChild.h" +#include "mozilla/dom/InProcessParent.h" +#include "mozilla/dom/BrowserSessionStore.h" +#include "mozilla/dom/SessionStoreChangeListener.h" +#include "mozilla/dom/SessionStoreParent.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "nsCOMPtr.h" +#include "nsFrameLoader.h" + +using namespace mozilla; +using namespace mozilla::dom; + +class nsIDocShell; + +static already_AddRefed CreateTabListener(nsIDocShell* aDocShell) { + RefPtr tabListener = + mozilla::MakeRefPtr(aDocShell, nullptr); + nsresult rv = tabListener->Init(); + if (NS_FAILED(rv)) { + return nullptr; + } + + return tabListener.forget(); +} + +already_AddRefed SessionStoreChild::GetOrCreate( + BrowsingContext* aBrowsingContext, Element* aOwnerElement) { + RefPtr tabListener = + CreateTabListener(aBrowsingContext->GetDocShell()); + if (!tabListener) { + return nullptr; + } + + RefPtr sessionStoreChangeListener = + SessionStoreChangeListener::Create(aBrowsingContext); + if (!sessionStoreChangeListener) { + return nullptr; + } + + RefPtr sessionStoreChild = + new SessionStoreChild(tabListener, sessionStoreChangeListener); + + sessionStoreChangeListener->SetActor(sessionStoreChild); + + if (XRE_IsParentProcess()) { + MOZ_DIAGNOSTIC_ASSERT(aOwnerElement); + InProcessChild* inProcessChild = InProcessChild::Singleton(); + InProcessParent* inProcessParent = InProcessParent::Singleton(); + if (!inProcessChild || !inProcessParent) { + return nullptr; + } + + RefPtr sessionStore = + BrowserSessionStore::GetOrCreate(aBrowsingContext->Canonical()->Top()); + if (!sessionStore) { + return nullptr; + } + + CanonicalBrowsingContext* browsingContext = aBrowsingContext->Canonical(); + RefPtr sessionStoreParent = + new SessionStoreParent(browsingContext, sessionStore); + ManagedEndpoint endpoint = + inProcessChild->OpenPSessionStoreEndpoint(sessionStoreChild); + inProcessParent->BindPSessionStoreEndpoint(std::move(endpoint), + sessionStoreParent); + } else { + MOZ_DIAGNOSTIC_ASSERT(!aOwnerElement); + RefPtr browserChild = + BrowserChild::GetFrom(aBrowsingContext->GetDOMWindow()); + + MOZ_DIAGNOSTIC_ASSERT(browserChild); + MOZ_DIAGNOSTIC_ASSERT(aBrowsingContext->IsInProcess()); + sessionStoreChild = static_cast( + browserChild->SendPSessionStoreConstructor(sessionStoreChild)); + } + + return sessionStoreChild.forget(); +} + +/* static */ +SessionStoreChild* SessionStoreChild::From(WindowGlobalChild* aWindowChild) { + if (!aWindowChild) { + return nullptr; + } + + // If `aWindowChild` is inprocess + if (RefPtr browserChild = aWindowChild->GetBrowserChild()) { + return browserChild->GetSessionStoreChild(); + } + + if (XRE_IsContentProcess()) { + return nullptr; + } + + WindowGlobalParent* windowParent = aWindowChild->WindowContext()->Canonical(); + if (!windowParent) { + return nullptr; + } + + RefPtr frameLoader = windowParent->GetRootFrameLoader(); + if (!frameLoader) { + return nullptr; + } + + return frameLoader->GetSessionStoreChild(); +} + +SessionStoreChild::SessionStoreChild( + TabListener* aSessionStoreListener, + SessionStoreChangeListener* aSessionStoreChangeListener) + : mSessionStoreListener(aSessionStoreListener), + mSessionStoreChangeListener(aSessionStoreChangeListener) {} + +void SessionStoreChild::SetEpoch(uint32_t aEpoch) { + if (mSessionStoreListener) { + mSessionStoreListener->SetEpoch(aEpoch); + } + + if (mSessionStoreChangeListener) { + mSessionStoreChangeListener->SetEpoch(aEpoch); + } +} + +void SessionStoreChild::SetOwnerContent(Element* aElement) { + if (mSessionStoreChangeListener) { + mSessionStoreChangeListener->FlushSessionStore(); + } + + if (!aElement) { + return; + } + + if (mSessionStoreListener) { + mSessionStoreListener->SetOwnerContent(aElement); + } +} + +void SessionStoreChild::Stop() { + if (mSessionStoreListener) { + mSessionStoreListener->RemoveListeners(); + mSessionStoreListener = nullptr; + } + + if (mSessionStoreChangeListener) { + mSessionStoreChangeListener->Stop(); + } +} + +void SessionStoreChild::UpdateEventTargets() { + if (mSessionStoreChangeListener) { + mSessionStoreChangeListener->UpdateEventTargets(); + } +} + +void SessionStoreChild::UpdateSessionStore(bool aSessionHistoryUpdate, + const MaybeSessionStoreZoom& aZoom) { + if (!mSessionStoreListener) { + // This is the case when we're shutting down, and expect a final update. + SessionStoreUpdate(Nothing(), Nothing(), Nothing(), aSessionHistoryUpdate, + 0); + return; + } + + RefPtr store = mSessionStoreListener->GetSessionStore(); + + Maybe docShellCaps; + if (store->IsDocCapChanged()) { + docShellCaps.emplace(store->GetDocShellCaps()); + } + + Maybe privatedMode; + if (store->IsPrivateChanged()) { + privatedMode.emplace(store->GetPrivateModeEnabled()); + } + + SessionStoreUpdate( + docShellCaps, privatedMode, aZoom, + store->GetAndClearSHistoryChanged() || aSessionHistoryUpdate, + mSessionStoreListener->GetEpoch()); +} + +void SessionStoreChild::FlushSessionStore() { + if (mSessionStoreChangeListener) { + mSessionStoreChangeListener->FlushSessionStore(); + } +} + +void SessionStoreChild::UpdateSHistoryChanges() { + if (mSessionStoreListener) { + mSessionStoreListener->UpdateSHistoryChanges(); + } +} + +mozilla::ipc::IPCResult SessionStoreChild::RecvFlushTabState( + FlushTabStateResolver&& aResolver) { + if (mSessionStoreChangeListener) { + mSessionStoreChangeListener->FlushSessionStore(); + } + aResolver(true); + + return IPC_OK(); +} + +void SessionStoreChild::SessionStoreUpdate( + const Maybe& aDocShellCaps, const Maybe& aPrivatedMode, + const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory, + const uint32_t& aEpoch) { + if (XRE_IsContentProcess()) { + Unused << SendSessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom, + aNeedCollectSHistory, aEpoch); + } else if (SessionStoreParent* sessionStoreParent = + static_cast( + InProcessChild::ParentActorFor(this))) { + sessionStoreParent->SessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom, + aNeedCollectSHistory, aEpoch); + } +} + +void SessionStoreChild::IncrementalSessionStoreUpdate( + const MaybeDiscarded& aBrowsingContext, + const Maybe& aFormData, const Maybe& aScrollPosition, + uint32_t aEpoch) { + if (XRE_IsContentProcess()) { + Unused << SendIncrementalSessionStoreUpdate(aBrowsingContext, aFormData, + aScrollPosition, aEpoch); + } else if (SessionStoreParent* sessionStoreParent = + static_cast( + InProcessChild::ParentActorFor(this))) { + sessionStoreParent->IncrementalSessionStoreUpdate( + aBrowsingContext, aFormData, aScrollPosition, aEpoch); + } +} + +void SessionStoreChild::ResetSessionStore( + const MaybeDiscarded& aBrowsingContext, uint32_t aEpoch) { + if (XRE_IsContentProcess()) { + Unused << SendResetSessionStore(aBrowsingContext, aEpoch); + } else if (SessionStoreParent* sessionStoreParent = + static_cast( + InProcessChild::ParentActorFor(this))) { + sessionStoreParent->ResetSessionStore(aBrowsingContext, aEpoch); + } +} + +NS_IMPL_CYCLE_COLLECTION(SessionStoreChild, mSessionStoreListener, + mSessionStoreChangeListener) diff --git a/toolkit/components/sessionstore/SessionStoreChild.h b/toolkit/components/sessionstore/SessionStoreChild.h new file mode 100644 index 0000000000..606aa0f441 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreChild.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStoreChild_h +#define mozilla_dom_SessionStoreChild_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/PSessionStoreChild.h" +#include "mozilla/dom/SessionStoreScrollData.h" +#include "mozilla/dom/SessionStoreChangeListener.h" +#include "mozilla/dom/SessionStoreListener.h" +#include "mozilla/RefPtr.h" + +#include "nsCycleCollectionParticipant.h" + +namespace mozilla::dom { +class BrowsingContext; +class SessionStoreChangeListener; +class TabListener; + +class SessionStoreChild final : public PSessionStoreChild { + public: + static already_AddRefed GetOrCreate( + BrowsingContext* aBrowsingContext, Element* aOwnerElement = nullptr); + + static SessionStoreChild* From(WindowGlobalChild* aWindowChild); + + void SetEpoch(uint32_t aEpoch); + void SetOwnerContent(Element* aElement); + void Stop(); + void UpdateEventTargets(); + void UpdateSessionStore(bool aSessionHistoryUpdate = false, + const MaybeSessionStoreZoom& aZoom = Nothing()); + void FlushSessionStore(); + void UpdateSHistoryChanges(); + + void SessionStoreUpdate(const Maybe& aDocShellCaps, + const Maybe& aPrivatedMode, + const MaybeSessionStoreZoom& aZoom, + const bool aNeedCollectSHistory, + const uint32_t& aEpoch); + + void IncrementalSessionStoreUpdate( + const MaybeDiscarded& aBrowsingContext, + const Maybe& aFormData, const Maybe& aScrollPosition, + uint32_t aEpoch); + + void ResetSessionStore( + const MaybeDiscarded& aBrowsingContext, uint32_t aEpoch); + + SessionStoreChangeListener* GetSessionStoreChangeListener() const { + return mSessionStoreChangeListener; + } + + mozilla::ipc::IPCResult RecvFlushTabState(FlushTabStateResolver&& aResolver); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SessionStoreChild) + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(SessionStoreChild) + + private: + SessionStoreChild(TabListener* aSessionStoreListener, + SessionStoreChangeListener* aSessionStoreChangeListener); + ~SessionStoreChild() = default; + + RefPtr mSessionStoreListener; + RefPtr mSessionStoreChangeListener; +}; +} // namespace mozilla::dom + +#endif // mozilla_dom_SessionStoreChild_h diff --git a/toolkit/components/sessionstore/SessionStoreData.h b/toolkit/components/sessionstore/SessionStoreData.h new file mode 100644 index 0000000000..0df1ea5bf5 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreData.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStoreData_h +#define mozilla_dom_SessionStoreData_h + +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/dom/SessionStoreUtilsBinding.h" +#include "mozilla/Variant.h" + +typedef mozilla::Variant> + InputDataValue; + +/* + * Need two arrays based on this struct. + * One is for elements with id one is for XPath. + * + * id: id or XPath + * type: type of this input element + * bool: value is boolean + * string: value is nsString + * file: value is "arrayVal" + * singleSelect: value is "singleSelect" + * multipleSelect: value is "arrayVal" + * + * There are four value types: + * strVal: nsString + * boolVal: boolean + * singleSelect: single select value + * arrayVal: nsString array + */ +struct CollectedInputDataValue { + nsString id; + nsString type; + InputDataValue value{false}; + + CollectedInputDataValue() = default; +}; + +/* + * Each index of the following array is corresponging to each frame. + * descendants: number of child frames of this frame + * innerHTML: innerHTML of this frame + * url: url of this frame + * numId: number of containing elements with id for this frame + * numXPath: number of containing elements with XPath for this frame + */ +struct InputFormData { + int32_t descendants; + nsString innerHTML; + nsCString url; + int32_t numId; + int32_t numXPath; +}; + +#endif /* mozilla_dom_SessionStoreData_h */ diff --git a/toolkit/components/sessionstore/SessionStoreFormData.cpp b/toolkit/components/sessionstore/SessionStoreFormData.cpp new file mode 100644 index 0000000000..c792b3f2f0 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreFormData.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SessionStoreFormData.h" + +#include "mozilla/Assertions.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/BrowserSessionStoreBinding.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/sessionstore/SessionStoreTypes.h" + +#include "nsContentUtils.h" +#include "js/JSON.h" + +using namespace mozilla::dom; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStoreFormData) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStoreFormData) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(SessionStoreFormData, mChildren) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStoreFormData) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +nsISupports* SessionStoreFormData::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +JSObject* SessionStoreFormData::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return SessionStoreFormData_Binding::Wrap(aCx, this, aGivenProto); +} + +void SessionStoreFormData::GetUrl(nsACString& aUrl) const { + if (mUrl.IsEmpty()) { + aUrl.SetIsVoid(true); + } else { + aUrl = mUrl; + } +} + +void SessionStoreFormData::GetId( + JSContext* aCx, + Nullable>& aId) { + if (mId.IsEmpty() || + NS_FAILED(SessionStoreUtils::ConstructFormDataValues( + aCx, mId, aId.SetValue().Entries(), mParseSessionData))) { + aId.SetNull(); + } + + // SessionStoreFormData.id is now stored in a slot, so we can free our + // internal state. + mId.Clear(); +} + +void SessionStoreFormData::GetXpath( + JSContext* aCx, + Nullable>& aXpath) { + if (mXpath.IsEmpty() || + NS_FAILED(SessionStoreUtils::ConstructFormDataValues( + aCx, mXpath, aXpath.SetValue().Entries(), mParseSessionData))) { + aXpath.SetNull(); + } + + // SessionStoreFormData.xpath is now stored in a slot, so we can free our + // internal state. + mXpath.Clear(); +} + +void SessionStoreFormData::GetInnerHTML(nsAString& aInnerHTML) { + if (mInnerHTML.IsEmpty()) { + SetDOMStringToNull(aInnerHTML); + } else { + aInnerHTML = mInnerHTML; + } + + // SessionStoreFormData.innerHTML is now stored in a slot, so we can free our + // internal state. + mInnerHTML.SetIsVoid(true); +} + +SessionStoreFormData::ChildrenArray& SessionStoreFormData::Children() { + return mChildren; +} + +void SessionStoreFormData::GetChildren( + Nullable& aChildren) const { + if (!mChildren.IsEmpty()) { + aChildren.SetValue() = mChildren.Clone(); + } else { + aChildren.SetNull(); + } +} + +void SessionStoreFormData::ToJSON(JSContext* aCx, + JS::MutableHandle aRetval) { + JS::Rooted self(aCx); + { + JS::Rooted value(aCx); + if (!GetOrCreateDOMReflector(aCx, this, &value)) { + return; + } + + self.set(value.toObjectOrNull()); + } + + JS::Rooted result(aCx, JS_NewPlainObject(aCx)); + + if (!IsEmpty()) { + for (const auto& name : + {u"url"_ns, u"id"_ns, u"xpath"_ns, u"innerHTML"_ns}) { + if (!SessionStoreUtils::CopyProperty(aCx, result, self, name)) { + return; + } + } + + SessionStoreUtils::CopyChildren(aCx, result, mChildren); + } + + aRetval.set(result); +} + +void SessionStoreFormData::Update(const CollectedType& aFormData) { + SessionStoreFormData_Binding::ClearCachedUrlValue(this); + SessionStoreFormData_Binding::ClearCachedIdValue(this); + SessionStoreFormData_Binding::ClearCachedXpathValue(this); + SessionStoreFormData_Binding::ClearCachedInnerHTMLValue(this); + + if (!aFormData.hasData()) { + mParseSessionData = false; + mHasData = false; + mUrl = ""_ns; + mId.Clear(); + mXpath.Clear(); + mInnerHTML = u""_ns; + + return; + } + + mHasData = true; + + mUrl = aFormData.uri(); + // We want to avoid saving data for about:sessionrestore as a string. + // Since it's stored in the form as stringified JSON, stringifying + // further causes an explosion of escape characters. cf. bug 467409 + mParseSessionData = + mUrl == "about:sessionrestore"_ns || mUrl == "about:welcomeback"_ns; + + mInnerHTML = aFormData.innerHTML(); + + mId.Assign(aFormData.id()); + mXpath.Assign(aFormData.xpath()); +} + +void SessionStoreFormData::ClearCachedChildren() { + SessionStoreFormData_Binding::ClearCachedChildrenValue(this); +} + +/* static */ bool SessionStoreFormData::HasData( + const CollectedType& aFormData) { + return aFormData.hasData(); +} + +bool SessionStoreFormData::IsEmpty() const { + return !mHasData && mChildren.IsEmpty(); +} diff --git a/toolkit/components/sessionstore/SessionStoreFormData.h b/toolkit/components/sessionstore/SessionStoreFormData.h new file mode 100644 index 0000000000..0466d98441 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreFormData.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStoreFormData_h +#define mozilla_dom_SessionStoreFormData_h + +#include "mozilla/WeakPtr.h" +#include "mozilla/dom/UnionTypes.h" +#include "mozilla/dom/WindowGlobalParent.h" + +#include "nsTArrayForwardDeclare.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +namespace mozilla::dom { + +namespace sessionstore { +class FormData; +} + +class BrowsingContext; +class OwningByteStringOrObjectOrNull; +class OwningStringOrObjectOrNull; +class WindowGlobalParent; + +class SessionStoreFormData final : public nsISupports, + public nsWrapperCache, + public SupportsWeakPtr { + public: + using CollectedType = sessionstore::FormData; + using LocationType = WeakPtr; + using ChildrenArray = nsTArray>; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SessionStoreFormData) + nsISupports* GetParentObject() const; + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + void GetUrl(nsACString& aUrl) const; + + void GetId(JSContext* aCx, + Nullable>& aId); + + void GetXpath( + JSContext* aCx, + Nullable>& aXpath); + + void GetInnerHTML(nsAString& aInnerHTML); + + ChildrenArray& Children(); + + void GetChildren(Nullable& aChildren) const; + + void ToJSON(JSContext* aCx, JS::MutableHandle aRetval); + + void Update(const CollectedType& aFormData); + + void ClearCachedChildren(); + + static bool HasData(const CollectedType& aFormData); + + bool IsEmpty() const; + + private: + ~SessionStoreFormData() = default; + + bool mParseSessionData = false; + bool mHasData = false; + nsCString mUrl; + nsTArray mId; + nsTArray mXpath; + nsString mInnerHTML; + ChildrenArray mChildren; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_SessionStoreFormData_h diff --git a/toolkit/components/sessionstore/SessionStoreFunctions.idl b/toolkit/components/sessionstore/SessionStoreFunctions.idl new file mode 100644 index 0000000000..3c17015437 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreFunctions.idl @@ -0,0 +1,23 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +webidl BrowsingContext; +webidl Element; + +[scriptable, uuid(1A060FBA-A19D-11E9-B7EB-580D0EDD8E6F)] +interface nsISessionStoreFunctions : nsISupports { + // update sessionStore from the tabListener implemented by C++ + // aData is a UpdateSessionStoreData dictionary (From SessionStoreUtils.webidl) + void UpdateSessionStore( + in Element aBrowser, in BrowsingContext aBrowsingContext, + in jsval aPermanentKey, in uint32_t aEpoch, in boolean aCollectSHistory, + in jsval aData); + + void UpdateSessionStoreForStorage( + in Element aBrowser, in BrowsingContext aBrowsingContext, + in jsval aPermanentKey, in uint32_t aEpoch, in jsval aData); +}; diff --git a/toolkit/components/sessionstore/SessionStoreFunctions.sys.mjs b/toolkit/components/sessionstore/SessionStoreFunctions.sys.mjs new file mode 100644 index 0000000000..f42c371264 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreFunctions.sys.mjs @@ -0,0 +1,87 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { SessionStore } from "resource:///modules/sessionstore/SessionStore.sys.mjs"; + +export function UpdateSessionStore( + aBrowser, + aBrowsingContext, + aPermanentKey, + aEpoch, + aCollectSHistory, + aData +) { + return SessionStoreFuncInternal.updateSessionStore( + aBrowser, + aBrowsingContext, + aPermanentKey, + aEpoch, + aCollectSHistory, + aData + ); +} + +export function UpdateSessionStoreForStorage( + aBrowser, + aBrowsingContext, + aPermanentKey, + aEpoch, + aData +) { + return SessionStoreFuncInternal.updateSessionStoreForStorage( + aBrowser, + aBrowsingContext, + aPermanentKey, + aEpoch, + aData + ); +} + +var SessionStoreFuncInternal = { + updateSessionStore: function SSF_updateSessionStore( + aBrowser, + aBrowsingContext, + aPermanentKey, + aEpoch, + aCollectSHistory, + aData + ) { + let { formdata, scroll } = aData; + + if (formdata) { + aData.formdata = formdata.toJSON(); + } + + if (scroll) { + aData.scroll = scroll.toJSON(); + } + + SessionStore.updateSessionStoreFromTablistener( + aBrowser, + aBrowsingContext, + aPermanentKey, + { + data: aData, + epoch: aEpoch, + sHistoryNeeded: aCollectSHistory, + } + ); + }, + + updateSessionStoreForStorage: function SSF_updateSessionStoreForStorage( + aBrowser, + aBrowsingContext, + aPermanentKey, + aEpoch, + aData + ) { + SessionStore.updateSessionStoreFromTablistener( + aBrowser, + aBrowsingContext, + aPermanentKey, + { data: { storage: aData }, epoch: aEpoch }, + true + ); + }, +}; diff --git a/toolkit/components/sessionstore/SessionStoreListener.cpp b/toolkit/components/sessionstore/SessionStoreListener.cpp new file mode 100644 index 0000000000..19612af786 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreListener.cpp @@ -0,0 +1,494 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/PresShell.h" +#include "mozilla/dom/BrowserSessionStoreBinding.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SessionStoreListener.h" +#include "mozilla/dom/SessionStoreUtils.h" +#include "mozilla/dom/SessionStoreUtilsBinding.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/StaticPrefs_browser.h" +#include "nsGenericHTMLElement.h" +#include "nsDocShell.h" +#include "nsIAppWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsImportModule.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsITimer.h" +#include "nsIWebProgress.h" +#include "nsIXPConnect.h" +#include "nsIXULRuntime.h" +#include "nsPresContext.h" +#include "nsPrintfCString.h" +#include "SessionStoreFunctions.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// This pref controls whether or not we send updates to the parent on a timeout +// or not, and should only be used for tests or debugging. +static const char kTimeOutDisable[] = + "browser.sessionstore.debug.no_auto_updates"; +// Timeout for waiting an idle period to send data. +static const char kPrefInterval[] = "browser.sessionstore.interval"; + +NS_IMPL_CYCLE_COLLECTION(ContentSessionStore, mDocShell) + +ContentSessionStore::ContentSessionStore(nsIDocShell* aDocShell) + : mDocShell(aDocShell), + mPrivateChanged(false), + mIsPrivate(false), + mDocCapChanged(false), + mSHistoryChanged(false) { + MOZ_ASSERT(mDocShell); + // Check that value at startup as it might have + // been set before the frame script was loaded. + if (NS_SUCCEEDED(nsDocShell::Cast(mDocShell)->GetUsePrivateBrowsing( + &mPrivateChanged)) && + mPrivateChanged) { + mIsPrivate = true; + } +} + +nsCString ContentSessionStore::CollectDocShellCapabilities() { + bool allow; + nsCString aRetVal; + +#define TRY_ALLOWPROP(y) \ + PR_BEGIN_MACRO \ + nsresult rv = mDocShell->GetAllow##y(&allow); \ + if (NS_SUCCEEDED(rv) && !allow) { \ + if (!aRetVal.IsEmpty()) { \ + aRetVal.Append(','); \ + } \ + aRetVal.Append(#y); \ + } \ + PR_END_MACRO + + TRY_ALLOWPROP(Plugins); + // Bug 1328013 : Don't collect "AllowJavascript" property + // TRY_ALLOWPROP(Javascript); + TRY_ALLOWPROP(MetaRedirects); + TRY_ALLOWPROP(Subframes); + TRY_ALLOWPROP(Images); + TRY_ALLOWPROP(Media); + TRY_ALLOWPROP(DNSPrefetch); + TRY_ALLOWPROP(WindowControl); + TRY_ALLOWPROP(Auth); + TRY_ALLOWPROP(ContentRetargeting); + TRY_ALLOWPROP(ContentRetargetingOnChildren); +#undef TRY_ALLOWPROP + return aRetVal; +} + +void ContentSessionStore::OnPrivateModeChanged(bool aEnabled) { + mPrivateChanged = true; + mIsPrivate = aEnabled; +} + +nsCString ContentSessionStore::GetDocShellCaps() { + mDocCapChanged = false; + return mDocCaps; +} + +bool ContentSessionStore::GetPrivateModeEnabled() { + mPrivateChanged = false; + return mIsPrivate; +} + +void ContentSessionStore::SetSHistoryChanged() { + mSHistoryChanged = mozilla::SessionHistoryInParent(); +} + +void ContentSessionStore::OnDocumentStart() { + nsCString caps = CollectDocShellCapabilities(); + if (!mDocCaps.Equals(caps)) { + mDocCaps = caps; + mDocCapChanged = true; + } + + if (mozilla::SessionHistoryInParent()) { + mSHistoryChanged = true; + } +} + +void ContentSessionStore::OnDocumentEnd() { + if (mozilla::SessionHistoryInParent()) { + mSHistoryChanged = true; + } +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TabListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIPrivacyTransitionObserver) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_WEAK(TabListener, mDocShell, mSessionStore, + mOwnerContent) +NS_IMPL_CYCLE_COLLECTING_ADDREF(TabListener) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TabListener) + +TabListener::TabListener(nsIDocShell* aDocShell, Element* aElement) + : mDocShell(aDocShell), + mSessionStore(new ContentSessionStore(aDocShell)), + mOwnerContent(aElement), + mProgressListenerRegistered(false), + mEventListenerRegistered(false), + mPrefObserverRegistered(false), + mUpdatedTimer(nullptr), + mTimeoutDisabled(false), + mUpdateInterval(15000), + mEpoch(0) { + MOZ_ASSERT(mDocShell); +} + +EventTarget* TabListener::GetEventTarget() { + if (mOwnerContent) { + return mOwnerContent; + } + + nsCOMPtr window = do_GetInterface(mDocShell); + if (window) { + return window->GetChromeEventHandler(); + } + + return nullptr; +} + +nsresult TabListener::Init() { + TabListener::UpdateSessionStore(); + nsresult rv = mDocShell->AddWeakPrivacyTransitionObserver(this); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr webProgress = do_QueryInterface(mDocShell); + rv = webProgress->AddProgressListener(this, + nsIWebProgress::NOTIFY_STATE_DOCUMENT); + NS_ENSURE_SUCCESS(rv, rv); + mProgressListenerRegistered = true; + + nsCOMPtr prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + NS_WARNING_ASSERTION(prefBranch, "no prefservice"); + if (prefBranch) { + prefBranch->AddObserver(kTimeOutDisable, this, true); + prefBranch->AddObserver(kPrefInterval, this, true); + mPrefObserverRegistered = true; + } + + AddEventListeners(); + return NS_OK; +} + +void TabListener::AddEventListeners() { + if (nsCOMPtr eventTarget = GetEventTarget()) { + if (mozilla::SessionHistoryInParent()) { + eventTarget->AddSystemEventListener(u"DOMTitleChanged"_ns, this, false); + } + mEventListenerRegistered = true; + } +} + +void TabListener::RemoveEventListeners() { + if (nsCOMPtr eventTarget = GetEventTarget()) { + if (mEventListenerRegistered) { + if (mozilla::SessionHistoryInParent()) { + eventTarget->RemoveSystemEventListener(u"DOMTitleChanged"_ns, this, + false); + } + mEventListenerRegistered = false; + } + } +} + +void TabListener::SetOwnerContent(Element* aElement) { + MOZ_DIAGNOSTIC_ASSERT(aElement); + RemoveEventListeners(); + mOwnerContent = aElement; + AddEventListeners(); +} + +/* static */ +void TabListener::TimerCallback(nsITimer* aTimer, void* aClosure) { + auto listener = static_cast(aClosure); + listener->UpdateSessionStore(); + listener->StopTimerForUpdate(); +} + +void TabListener::StopTimerForUpdate() { + if (mUpdatedTimer) { + mUpdatedTimer->Cancel(); + mUpdatedTimer = nullptr; + } +} + +void TabListener::AddTimerForUpdate() { + if (mUpdatedTimer) { + return; + } + + if (mTimeoutDisabled) { + UpdateSessionStore(); + return; + } + + NS_NewTimerWithFuncCallback(getter_AddRefs(mUpdatedTimer), TimerCallback, + this, mUpdateInterval, nsITimer::TYPE_ONE_SHOT, + "TabListener::TimerCallback"); +} + +NS_IMETHODIMP TabListener::PrivateModeChanged(bool aEnabled) { + mSessionStore->OnPrivateModeChanged(aEnabled); + AddTimerForUpdate(); + return NS_OK; +} + +NS_IMETHODIMP TabListener::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) { + if (!mSessionStore) { + return NS_OK; + } + + // Ignore state changes for subframes because we're only interested in the + // top-document starting or stopping its load. + bool isTopLevel = false; + nsresult rv = aWebProgress->GetIsTopLevel(&isTopLevel); + NS_ENSURE_SUCCESS(rv, rv); + if (!isTopLevel) { + return NS_OK; + } + + nsCOMPtr webProgress = do_QueryInterface(mDocShell); + if (webProgress != aWebProgress) { + return NS_OK; + } + + // onStateChange will be fired when loading the initial about:blank URI for + // a browser, which we don't actually care about. This is particularly for + // the case of unrestored background tabs, where the content has not yet + // been restored: we don't want to accidentally send any updates to the + // parent when the about:blank placeholder page has loaded. + if (!mDocShell->GetHasLoadedNonBlankURI()) { + return NS_OK; + } + + if (aStateFlags & (nsIWebProgressListener::STATE_START)) { + mSessionStore->OnDocumentStart(); + } else if (aStateFlags & (nsIWebProgressListener::STATE_STOP)) { + mSessionStore->OnDocumentEnd(); + } + + return NS_OK; +} + +NS_IMETHODIMP +TabListener::HandleEvent(Event* aEvent) { + EventTarget* target = aEvent->GetTarget(); + if (!target) { + return NS_OK; + } + + nsPIDOMWindowOuter* outer = target->GetOwnerGlobalForBindingsInternal(); + if (!outer || !outer->GetDocShell()) { + return NS_OK; + } + + RefPtr context = outer->GetBrowsingContext(); + if (!context || context->CreatedDynamically()) { + return NS_OK; + } + + nsAutoString eventType; + aEvent->GetType(eventType); + if (eventType.EqualsLiteral("DOMTitleChanged")) { + mSessionStore->SetSHistoryChanged(); + AddTimerForUpdate(); + } + + return NS_OK; +} + +NS_IMETHODIMP TabListener::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP TabListener::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP TabListener::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP TabListener::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP TabListener::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult TabListener::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsCOMPtr prefBranch = do_QueryInterface(aSubject); + + bool timeoutDisabled; + if (NS_SUCCEEDED( + prefBranch->GetBoolPref(kTimeOutDisable, &timeoutDisabled))) { + if (mTimeoutDisabled != timeoutDisabled) { + mTimeoutDisabled = timeoutDisabled; + if (mUpdatedTimer) { + StopTimerForUpdate(); + AddTimerForUpdate(); + } + } + } + + int32_t interval = 0; + if (NS_SUCCEEDED(prefBranch->GetIntPref(kPrefInterval, &interval))) { + if (mUpdateInterval != interval) { + mUpdateInterval = interval; + if (mUpdatedTimer) { + StopTimerForUpdate(); + AddTimerForUpdate(); + } + } + } + return NS_OK; + } + + NS_ERROR("Unexpected topic"); + return NS_ERROR_UNEXPECTED; +} + +void TabListener::ForceFlushFromParent() { + if (!XRE_IsParentProcess()) { + return; + } + if (!mSessionStore) { + return; + } + + UpdateSessionStore(true); +} + +void TabListener::UpdateSessionStore(bool aIsFlush) { + if (!aIsFlush) { + if (!mSessionStore || !mSessionStore->UpdateNeeded()) { + return; + } + } + + if (!XRE_IsParentProcess()) { + BrowserChild* browserChild = BrowserChild::GetFrom(mDocShell); + if (browserChild) { + StopTimerForUpdate(); + browserChild->UpdateSessionStore(); + } + return; + } + + BrowsingContext* context = mDocShell->GetBrowsingContext(); + if (!context) { + return; + } + + uint32_t chromeFlags = 0; + nsCOMPtr treeOwner; + mDocShell->GetTreeOwner(getter_AddRefs(treeOwner)); + if (!treeOwner) { + return; + } + nsCOMPtr window(do_GetInterface(treeOwner)); + if (!window) { + return; + } + if (window && NS_FAILED(window->GetChromeFlags(&chromeFlags))) { + return; + } + + UpdateSessionStoreData data; + if (mSessionStore->IsDocCapChanged()) { + data.mDisallow.Construct() = mSessionStore->GetDocShellCaps(); + } + if (mSessionStore->IsPrivateChanged()) { + data.mIsPrivate.Construct() = mSessionStore->GetPrivateModeEnabled(); + } + + nsCOMPtr funcs = do_ImportESModule( + "resource://gre/modules/SessionStoreFunctions.sys.mjs", fallible); + nsCOMPtr wrapped = do_QueryInterface(funcs); + if (!wrapped) { + return; + } + + AutoJSAPI jsapi; + if (!jsapi.Init(wrapped->GetJSObjectGlobal())) { + return; + } + + JS::Rooted update(jsapi.cx()); + if (!ToJSValue(jsapi.cx(), data, &update)) { + return; + } + + JS::Rooted key(jsapi.cx(), + context->Canonical()->Top()->PermanentKey()); + + nsresult rv = funcs->UpdateSessionStore( + mOwnerContent, context, key, mEpoch, + mSessionStore->GetAndClearSHistoryChanged(), update); + if (NS_FAILED(rv)) { + return; + } + + StopTimerForUpdate(); +} + +void TabListener::RemoveListeners() { + if (mProgressListenerRegistered) { + nsCOMPtr webProgress = do_QueryInterface(mDocShell); + if (webProgress) { + webProgress->RemoveProgressListener(this); + mProgressListenerRegistered = false; + } + } + + RemoveEventListeners(); + + if (mPrefObserverRegistered) { + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (!obs) { + return; + } + if (mPrefObserverRegistered) { + obs->RemoveObserver(this, kTimeOutDisable); + obs->RemoveObserver(this, kPrefInterval); + mPrefObserverRegistered = false; + } + } +} + +TabListener::~TabListener() { RemoveListeners(); } diff --git a/toolkit/components/sessionstore/SessionStoreListener.h b/toolkit/components/sessionstore/SessionStoreListener.h new file mode 100644 index 0000000000..2cd35a00f5 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreListener.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStoreListener_h +#define mozilla_dom_SessionStoreListener_h + +#include "SessionStoreData.h" +#include "nsIDOMEventListener.h" +#include "nsIObserver.h" +#include "nsIPrivacyTransitionObserver.h" +#include "nsIWebProgressListener.h" +#include "nsWeakReference.h" + +class nsITimer; + +namespace mozilla::dom { + +class ContentSessionStore { + public: + explicit ContentSessionStore(nsIDocShell* aDocShell); + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ContentSessionStore) + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ContentSessionStore) + + void OnPrivateModeChanged(bool aEnabled); + bool IsDocCapChanged() { return mDocCapChanged; } + nsCString GetDocShellCaps(); + bool IsPrivateChanged() { return mPrivateChanged; } + bool GetPrivateModeEnabled(); + + void SetSHistoryChanged(); + // request "collect sessionHistory" which is happened in the parent process + bool GetAndClearSHistoryChanged() { + bool ret = mSHistoryChanged; + mSHistoryChanged = false; + return ret; + } + + void OnDocumentStart(); + void OnDocumentEnd(); + bool UpdateNeeded() { + return mPrivateChanged || mDocCapChanged || mSHistoryChanged; + } + + private: + virtual ~ContentSessionStore() = default; + nsCString CollectDocShellCapabilities(); + + nsCOMPtr mDocShell; + bool mPrivateChanged; + bool mIsPrivate; + bool mDocCapChanged; + nsCString mDocCaps; + // mSHistoryChanged means there are history changes which are found + // in the child process. The flag is set when + // 1. webProgress changes to STATE_START + // 2. webProgress changes to STATE_STOP + // 3. receiving "DOMTitleChanged" event + bool mSHistoryChanged; +}; + +class TabListener : public nsIDOMEventListener, + public nsIObserver, + public nsIPrivacyTransitionObserver, + public nsIWebProgressListener, + public nsSupportsWeakReference { + public: + explicit TabListener(nsIDocShell* aDocShell, Element* aElement); + EventTarget* GetEventTarget(); + nsresult Init(); + ContentSessionStore* GetSessionStore() { return mSessionStore; } + // the function is called only when TabListener is in parent process + void ForceFlushFromParent(); + void RemoveListeners(); + void SetEpoch(uint32_t aEpoch) { mEpoch = aEpoch; } + uint32_t GetEpoch() { return mEpoch; } + void UpdateSHistoryChanges() { AddTimerForUpdate(); } + void SetOwnerContent(Element* aElement); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TabListener, nsIDOMEventListener) + + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_NSIOBSERVER + NS_DECL_NSIPRIVACYTRANSITIONOBSERVER + NS_DECL_NSIWEBPROGRESSLISTENER + + private: + static void TimerCallback(nsITimer* aTimer, void* aClosure); + void AddTimerForUpdate(); + void StopTimerForUpdate(); + void AddEventListeners(); + void RemoveEventListeners(); + void UpdateSessionStore(bool aIsFlush = false); + virtual ~TabListener(); + + nsCOMPtr mDocShell; + RefPtr mSessionStore; + RefPtr mOwnerContent; + bool mProgressListenerRegistered; + bool mEventListenerRegistered; + bool mPrefObserverRegistered; + // Timer used to update data + nsCOMPtr mUpdatedTimer; + bool mTimeoutDisabled; + int32_t mUpdateInterval; + uint32_t mEpoch; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_SessionStoreListener_h diff --git a/toolkit/components/sessionstore/SessionStoreMessageUtils.h b/toolkit/components/sessionstore/SessionStoreMessageUtils.h new file mode 100644 index 0000000000..8475955f87 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreMessageUtils.h @@ -0,0 +1,134 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStoreMessageUtils_h +#define mozilla_dom_SessionStoreMessageUtils_h + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "mozilla/ipc/URIUtils.h" +#include "SessionStoreData.h" +#include "SessionStoreUtils.h" +#include "SessionStoreRestoreData.h" + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::dom::CollectedNonMultipleSelectValue paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mSelectedIndex); + WriteParam(aWriter, aParam.mValue); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mSelectedIndex) && + ReadParam(aReader, &aResult->mValue); + } +}; + +template <> +struct ParamTraits { + typedef CollectedInputDataValue paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.id); + WriteParam(aWriter, aParam.type); + WriteParam(aWriter, aParam.value); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->id) && + ReadParam(aReader, &aResult->type) && + ReadParam(aReader, &aResult->value); + } +}; + +template <> +struct ParamTraits { + typedef InputFormData paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.descendants); + WriteParam(aWriter, aParam.innerHTML); + WriteParam(aWriter, aParam.url); + WriteParam(aWriter, aParam.numId); + WriteParam(aWriter, aParam.numXPath); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->descendants) && + ReadParam(aReader, &aResult->innerHTML) && + ReadParam(aReader, &aResult->url) && + ReadParam(aReader, &aResult->numId) && + ReadParam(aReader, &aResult->numXPath); + } +}; + +} // namespace IPC + +namespace mozilla { +namespace ipc { + +template <> +struct IPDLParamTraits { + // Note that we intentionally don't de/serialize mChildren here. The receiver + // won't be doing anything with the children lists, and it avoids sending form + // data for subframes to the content processes of their embedders. + + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + mozilla::dom::SessionStoreRestoreData* aParam) { + bool isNull = !aParam; + WriteIPDLParam(aWriter, aActor, isNull); + if (isNull) { + return; + } + WriteIPDLParam(aWriter, aActor, aParam->mURI); + WriteIPDLParam(aWriter, aActor, aParam->mInnerHTML); + WriteIPDLParam(aWriter, aActor, aParam->mScroll); + WriteIPDLParam(aWriter, aActor, aParam->mEntries); + } + + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + RefPtr* aResult) { + *aResult = nullptr; + bool isNull; + if (!ReadIPDLParam(aReader, aActor, &isNull)) { + return false; + } + if (isNull) { + return true; + } + auto data = MakeRefPtr(); + if (!ReadIPDLParam(aReader, aActor, &data->mURI) || + !ReadIPDLParam(aReader, aActor, &data->mInnerHTML) || + !ReadIPDLParam(aReader, aActor, &data->mScroll) || + !ReadIPDLParam(aReader, aActor, &data->mEntries)) { + return false; + } + *aResult = std::move(data); + return true; + } +}; + +template <> +struct IPDLParamTraits { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + mozilla::dom::SessionStoreRestoreData::Entry aParam) { + WriteIPDLParam(aWriter, aActor, aParam.mData); + WriteIPDLParam(aWriter, aActor, aParam.mIsXPath); + } + + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + mozilla::dom::SessionStoreRestoreData::Entry* aResult) { + return ReadIPDLParam(aReader, aActor, &aResult->mData) && + ReadIPDLParam(aReader, aActor, &aResult->mIsXPath); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_dom_SessionStoreMessageUtils_h diff --git a/toolkit/components/sessionstore/SessionStoreParent.cpp b/toolkit/components/sessionstore/SessionStoreParent.cpp new file mode 100644 index 0000000000..58be84d4d9 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreParent.cpp @@ -0,0 +1,318 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SessionStoreParent.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowserSessionStore.h" +#include "mozilla/dom/BrowserSessionStoreBinding.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/InProcessChild.h" +#include "mozilla/dom/InProcessParent.h" +#include "mozilla/dom/SessionStoreChild.h" +#include "mozilla/dom/SessionStoreUtilsBinding.h" +#include "SessionStoreFunctions.h" +#include "nsISupports.h" +#include "nsIXULRuntime.h" +#include "nsImportModule.h" +#include "nsIXPConnect.h" + +#ifdef ANDROID +# include "mozilla/widget/nsWindow.h" +# include "mozilla/jni/GeckoBundleUtils.h" +# include "JavaBuiltins.h" +#endif /* ANDROID */ + +using namespace mozilla; +using namespace mozilla::dom; + +SessionStoreParent::SessionStoreParent( + CanonicalBrowsingContext* aBrowsingContext, + BrowserSessionStore* aSessionStore) + : mBrowsingContext(aBrowsingContext), mSessionStore(aSessionStore) {} + +#ifdef ANDROID +static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext, + const Maybe& aDocShellCaps, + const Maybe& aPrivatedMode, + SessionStoreFormData* aFormData, + SessionStoreScrollData* aScroll, + const MaybeSessionStoreZoom& aZoom, + bool aNeedCollectSHistory, uint32_t aEpoch) { + RefPtr sessionStore = + BrowserSessionStore::GetOrCreate(aBrowsingContext->Top()); + + nsCOMPtr widget = + aBrowsingContext->GetParentProcessWidgetContaining(); + if (RefPtr window = nsWindow::From(widget)) { + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::PrivilegedJunkScope())) { + return; + } + jni::Object::LocalRef formDataBundle(jni::GetGeckoThreadEnv()); + jni::Object::LocalRef scrollBundle(jni::GetGeckoThreadEnv()); + + if (aFormData) { + JS::Rooted object(jsapi.cx()); + ErrorResult rv; + aFormData->ToJSON(jsapi.cx(), &object); + + JS::Rooted value(jsapi.cx(), JS::ObjectValue(*object)); + + if (NS_FAILED(jni::BoxData(jsapi.cx(), value, formDataBundle, true))) { + JS_ClearPendingException(jsapi.cx()); + return; + } + } + + if (aScroll) { + JS::Rooted object(jsapi.cx()); + ErrorResult rv; + aScroll->ToJSON(jsapi.cx(), &object); + JS::Rooted value(jsapi.cx(), JS::ObjectValue(*object)); + + if (NS_FAILED(jni::BoxData(jsapi.cx(), value, scrollBundle, true))) { + JS_ClearPendingException(jsapi.cx()); + return; + } + } + + GECKOBUNDLE_START(update); + GECKOBUNDLE_PUT(update, "formdata", formDataBundle); + GECKOBUNDLE_PUT(update, "scroll", scrollBundle); + if (aZoom) { + GECKOBUNDLE_START(zoomBundle); + GECKOBUNDLE_PUT(zoomBundle, "resolution", + java::sdk::Double::New(std::get<0>(*aZoom))); + GECKOBUNDLE_START(displaySizeBundle); + GECKOBUNDLE_PUT(displaySizeBundle, "width", + java::sdk::Integer::ValueOf(std::get<1>(*aZoom))); + GECKOBUNDLE_PUT(displaySizeBundle, "height", + java::sdk::Integer::ValueOf(std::get<2>(*aZoom))); + GECKOBUNDLE_FINISH(displaySizeBundle); + GECKOBUNDLE_PUT(zoomBundle, "displaySize", displaySizeBundle); + GECKOBUNDLE_FINISH(zoomBundle); + GECKOBUNDLE_PUT(update, "zoom", zoomBundle); + } + GECKOBUNDLE_FINISH(update); + + window->OnUpdateSessionStore(update); + } +} +#else +static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext, + const Maybe& aDocShellCaps, + const Maybe& aPrivatedMode, + SessionStoreFormData* aFormData, + SessionStoreScrollData* aScroll, + const MaybeSessionStoreZoom& aZoom, + bool aNeedCollectSHistory, uint32_t aEpoch) { + UpdateSessionStoreData data; + if (aDocShellCaps.isSome()) { + auto& disallow = data.mDisallow.Construct(); + if (!aDocShellCaps->IsEmpty()) { + disallow = aDocShellCaps.value(); + } else { + disallow.SetIsVoid(true); + } + } + + if (aPrivatedMode.isSome()) { + data.mIsPrivate.Construct() = aPrivatedMode.value(); + } + + RefPtr sessionStore = + BrowserSessionStore::GetOrCreate(aBrowsingContext->Top()); + + if (!aFormData) { + SessionStoreFormData* formData = sessionStore->GetFormdata(); + data.mFormdata.Construct(formData); + } else { + data.mFormdata.Construct(aFormData); + } + + if (!aScroll) { + SessionStoreScrollData* scroll = sessionStore->GetScroll(); + data.mScroll.Construct(scroll); + } else { + data.mScroll.Construct(aScroll); + } + + nsCOMPtr funcs = do_ImportESModule( + "resource://gre/modules/SessionStoreFunctions.sys.mjs", fallible); + nsCOMPtr wrapped = do_QueryInterface(funcs); + if (!wrapped) { + return; + } + + AutoJSAPI jsapi; + if (!jsapi.Init(wrapped->GetJSObjectGlobal())) { + return; + } + + JS::Rooted update(jsapi.cx()); + if (!ToJSValue(jsapi.cx(), data, &update)) { + return; + } + + JS::Rooted key(jsapi.cx(), + aBrowsingContext->Top()->PermanentKey()); + + Unused << funcs->UpdateSessionStore(nullptr, aBrowsingContext, key, aEpoch, + aNeedCollectSHistory, update); +} +#endif + +void SessionStoreParent::FlushAllSessionStoreChildren( + const std::function& aDone) { + if (!mBrowsingContext) { + aDone(); + return; + } + + nsTArray> flushPromises; + + // We're special casing this for when the SessionStore{Child, Parent} have + // been created in the same process. This is only ever true for the parent + // process session store actor, and is needed because + // nsFrameLoader::RequestTabStateFlush expect flushes to happen faster than we + // can manage by using the common path of sending a message the + // SessionStoreChild. Ideally we should be able to do just that, but not + // without more work. + if (InProcessParent::ChildActorFor(this)) { + // Here we assume that the session store data collection only collect for in + // (parent-)process content type browsing contexts, which we only flush one + // session store actor. + flushPromises.AppendElement(FlushSessionStore()); + } else { + // While here we flush all participating actors. + BrowserParent* browserParent = static_cast(Manager()); + browserParent->VisitAll([&flushPromises](BrowserParent* aBrowser) { + if (PSessionStoreParent* sessionStoreParent = + SingleManagedOrNull(aBrowser->ManagedPSessionStoreParent())) { + flushPromises.AppendElement( + static_cast(sessionStoreParent) + ->FlushSessionStore()); + } + }); + } + + RefPtr + flushPromise = SessionStoreParent::FlushTabStatePromise::All( + GetMainThreadSerialEventTarget(), flushPromises); + + mBrowsingContext->UpdateSessionStoreSessionStorage([aDone, flushPromise]() { + flushPromise->Then(GetCurrentSerialEventTarget(), __func__, + [aDone]() { aDone(); }); + }); +} + +already_AddRefed +SessionStoreParent::FlushSessionStore() { + if (!mBrowsingContext) { + return nullptr; + } + + RefPtr promise = + SendFlushTabState(); + return promise.forget(); +} + +void SessionStoreParent::FinalFlushAllSessionStoreChildren( + const std::function& aDone) { + if (!mBrowsingContext) { + aDone(); + return; + } + + SessionStoreChild* sessionStoreChild = + static_cast(InProcessParent::ChildActorFor(this)); + if (!sessionStoreChild || mozilla::SessionHistoryInParent()) { + return FlushAllSessionStoreChildren(aDone); + } + + sessionStoreChild->FlushSessionStore(); + mBrowsingContext->UpdateSessionStoreSessionStorage(aDone); +} + +mozilla::ipc::IPCResult SessionStoreParent::RecvSessionStoreUpdate( + const Maybe& aDocShellCaps, const Maybe& aPrivatedMode, + const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory, + const uint32_t& aEpoch) { + if (!mBrowsingContext) { + return IPC_OK(); + } + + RefPtr formData = + mHasNewFormData ? mSessionStore->GetFormdata() : nullptr; + RefPtr scroll = + mHasNewScrollPosition ? mSessionStore->GetScroll() : nullptr; + + DoSessionStoreUpdate(mBrowsingContext, aDocShellCaps, aPrivatedMode, formData, + scroll, aZoom, aNeedCollectSHistory, aEpoch); + + mHasNewFormData = false; + mHasNewScrollPosition = false; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult SessionStoreParent::RecvIncrementalSessionStoreUpdate( + const MaybeDiscarded& aBrowsingContext, + const Maybe& aFormData, const Maybe& aScrollPosition, + uint32_t aEpoch) { + if (!aBrowsingContext.IsNull()) { + if (aFormData.isSome()) { + mHasNewFormData = true; + } + if (aScrollPosition.isSome()) { + mHasNewScrollPosition = true; + } + + mSessionStore->UpdateSessionStore( + aBrowsingContext.GetMaybeDiscarded()->Canonical(), aFormData, + aScrollPosition, aEpoch); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult SessionStoreParent::RecvResetSessionStore( + const MaybeDiscarded& aBrowsingContext, uint32_t aEpoch) { + if (!aBrowsingContext.IsNull()) { + mSessionStore->RemoveSessionStore( + aBrowsingContext.GetMaybeDiscarded()->Canonical()); + } + return IPC_OK(); +} + +void SessionStoreParent::SessionStoreUpdate( + const Maybe& aDocShellCaps, const Maybe& aPrivatedMode, + const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory, + const uint32_t& aEpoch) { + Unused << RecvSessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom, + aNeedCollectSHistory, aEpoch); +} + +void SessionStoreParent::IncrementalSessionStoreUpdate( + const MaybeDiscarded& aBrowsingContext, + const Maybe& aFormData, const Maybe& aScrollPosition, + uint32_t aEpoch) { + Unused << RecvIncrementalSessionStoreUpdate(aBrowsingContext, aFormData, + aScrollPosition, aEpoch); +} + +void SessionStoreParent::ResetSessionStore( + const MaybeDiscarded& aBrowsingContext, uint32_t aEpoch) { + Unused << RecvResetSessionStore(aBrowsingContext, aEpoch); +} + +NS_IMPL_CYCLE_COLLECTION(SessionStoreParent, mBrowsingContext, mSessionStore) diff --git a/toolkit/components/sessionstore/SessionStoreParent.h b/toolkit/components/sessionstore/SessionStoreParent.h new file mode 100644 index 0000000000..0cf723985e --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreParent.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStoreParent_h +#define mozilla_dom_SessionStoreParent_h + +#include "mozilla/dom/BrowserSessionStore.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/PSessionStoreParent.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/SessionStoreScrollData.h" + +namespace mozilla::dom { +class BrowserParent; + +class SessionStoreParent final : public PSessionStoreParent { + public: + SessionStoreParent(CanonicalBrowsingContext* aBrowsingContext, + BrowserSessionStore* aSessionStore); + + void FlushAllSessionStoreChildren(const std::function& aDone); + + void FinalFlushAllSessionStoreChildren(const std::function& aDone); + + /** + * Sends data to be stored and instructions to the session store to + * potentially collect data in the parent. + */ + mozilla::ipc::IPCResult RecvSessionStoreUpdate( + const Maybe& aDocShellCaps, const Maybe& aPrivatedMode, + const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory, + const uint32_t& aEpoch); + + mozilla::ipc::IPCResult RecvIncrementalSessionStoreUpdate( + const MaybeDiscarded& aBrowsingContext, + const Maybe& aFormData, const Maybe& aScrollPosition, + uint32_t aEpoch); + + mozilla::ipc::IPCResult RecvResetSessionStore( + const MaybeDiscarded& aBrowsingContext, uint32_t aEpoch); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SessionStoreParent) + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(SessionStoreParent) + + protected: + friend class SessionStoreChild; + void SessionStoreUpdate(const Maybe& aDocShellCaps, + const Maybe& aPrivatedMode, + const MaybeSessionStoreZoom& aZoom, + const bool aNeedCollectSHistory, + const uint32_t& aEpoch); + + void IncrementalSessionStoreUpdate( + const MaybeDiscarded& aBrowsingContext, + const Maybe& aFormData, const Maybe& aScrollPosition, + uint32_t aEpoch); + + void ResetSessionStore( + const MaybeDiscarded& aBrowsingContext, uint32_t aEpoch); + + private: + ~SessionStoreParent() = default; + + already_AddRefed + FlushSessionStore(); + + bool mHasNewFormData = false; + bool mHasNewScrollPosition = false; + + RefPtr mBrowsingContext; + RefPtr mSessionStore; +}; +} // namespace mozilla::dom + +#endif // mozilla_dom_SessionStoreParent_h diff --git a/toolkit/components/sessionstore/SessionStoreRestoreData.cpp b/toolkit/components/sessionstore/SessionStoreRestoreData.cpp new file mode 100644 index 0000000000..c7a92cf8ad --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreRestoreData.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SessionStoreUtils.h" +#include "mozilla/dom/sessionstore/SessionStoreTypes.h" +#include "mozilla/dom/WindowContext.h" +#include "nsISessionStoreRestoreData.h" +#include "nsNetUtil.h" + +namespace mozilla { +namespace dom { + +bool SessionStoreRestoreData::IsEmpty() { + return (!mURI && mScroll.IsEmpty() && mInnerHTML.IsEmpty() && + mEntries.IsEmpty() && mChildren.IsEmpty()); +} + +SessionStoreRestoreData* SessionStoreRestoreData::FindDataForChild( + BrowsingContext* aContext) { + nsTArray path; + if (!aContext->GetOffsetPath(path)) { + return nullptr; + } + + SessionStoreRestoreData* data = this; + + for (uint32_t offset : Reversed(path)) { + if (!data || data->mChildren.Length() <= offset || + !data->mChildren[offset] || data->mChildren[offset]->IsEmpty()) { + return nullptr; + } + data = data->mChildren[offset]; + } + + return data; +} + +bool SessionStoreRestoreData::CanRestoreInto(nsIURI* aDocumentURI) { + if (!mURI) { + // This should mean that we don't have form data. It's fine to restore this + // data into any document — the worst that will happen is that we restore an + // incorrect scroll position. + MOZ_ASSERT(mEntries.IsEmpty()); + MOZ_ASSERT(mInnerHTML.IsEmpty()); + return true; + } + bool equalsExceptRef = false; + return (aDocumentURI && + NS_SUCCEEDED(mURI->EqualsExceptRef(aDocumentURI, &equalsExceptRef)) && + equalsExceptRef); +} + +bool SessionStoreRestoreData::RestoreInto(RefPtr aContext) { + if (!aContext->IsInProcess()) { + return false; + } + + if (WindowContext* window = aContext->GetCurrentWindowContext()) { + if (!mScroll.IsEmpty()) { + if (nsGlobalWindowInner* inner = window->GetInnerWindow()) { + SessionStoreUtils::RestoreScrollPosition(*inner, mScroll); + } + } + if (mURI) { + if (nsCOMPtr doc = window->GetExtantDoc()) { + if (!CanRestoreInto(doc->GetDocumentURI())) { + return false; + } + SessionStoreUtils::RestoreFormData(*doc, mInnerHTML, mEntries); + } + } + } + + return true; +} + +void SessionStoreRestoreData::AddFormEntry( + bool aIsXPath, const nsAString& aIdOrXPath, + sessionstore::FormEntryValue aValue) { + mEntries.AppendElement( + Entry{sessionstore::FormEntry{nsString(aIdOrXPath), aValue}, aIsXPath}); +} + +NS_IMPL_ISUPPORTS(SessionStoreRestoreData, nsISessionStoreRestoreData, + SessionStoreRestoreData) + +NS_IMETHODIMP +SessionStoreRestoreData::GetUrl(nsACString& aURL) { + if (mURI) { + nsresult rv = mURI->GetSpec(aURL); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::SetUrl(const nsACString& aURL) { + nsresult rv = NS_NewURI(getter_AddRefs(mURI), aURL); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::GetInnerHTML(nsAString& aInnerHTML) { + aInnerHTML = mInnerHTML; + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::SetInnerHTML(const nsAString& aInnerHTML) { + mInnerHTML = aInnerHTML; + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::GetScroll(nsACString& aScroll) { + aScroll = mScroll; + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::SetScroll(const nsACString& aScroll) { + mScroll = aScroll; + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::AddTextField(bool aIsXPath, + const nsAString& aIdOrXPath, + const nsAString& aValue) { + AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::TextField{nsString(aValue)}); + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::AddCheckbox(bool aIsXPath, const nsAString& aIdOrXPath, + const bool aValue) { + AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::Checkbox{aValue}); + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::AddFileList(bool aIsXPath, const nsAString& aIdOrXPath, + const nsAString& aType, + const nsTArray& aFileList) { + AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::FileList{aFileList}); + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::AddSingleSelect(bool aIsXPath, + const nsAString& aIdOrXPath, + uint32_t aSelectedIndex, + const nsAString& aValue) { + AddFormEntry(aIsXPath, aIdOrXPath, + sessionstore::SingleSelect{aSelectedIndex, nsString(aValue)}); + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::AddMultipleSelect(bool aIsXPath, + const nsAString& aIdOrXPath, + const nsTArray& aValues) { + AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::MultipleSelect{aValues}); + return NS_OK; +} + +NS_IMETHODIMP +SessionStoreRestoreData::AddChild(nsISessionStoreRestoreData* aChild, + uint32_t aIndex) { + if (nsCOMPtr child = do_QueryInterface(aChild)) { + if (aIndex > mChildren.Length()) { + mChildren.SetLength(aIndex); + } + mChildren.InsertElementAt(aIndex, child); + } + return NS_OK; +} + +} // namespace dom +} // namespace mozilla diff --git a/toolkit/components/sessionstore/SessionStoreRestoreData.h b/toolkit/components/sessionstore/SessionStoreRestoreData.h new file mode 100644 index 0000000000..4952045e9d --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreRestoreData.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStoreRestoreData_h +#define mozilla_dom_SessionStoreRestoreData_h + +#include "mozilla/dom/sessionstore/SessionStoreTypes.h" +#include "mozilla/dom/SessionStoreData.h" +#include "nsISessionStoreRestoreData.h" + +namespace mozilla::dom { + +#define NS_SESSIONSTORERESTOREDATA_IID \ + { \ + 0x88800c5b, 0xe142, 0x4ac6, { \ + 0x91, 0x52, 0xca, 0xbd, 0x3b, 0x74, 0xb9, 0xf8 \ + } \ + } + +class SessionStoreRestoreData final : public nsISessionStoreRestoreData { + public: + SessionStoreRestoreData() = default; + bool IsEmpty(); + SessionStoreRestoreData* FindDataForChild(BrowsingContext* aContext); + bool CanRestoreInto(nsIURI* aDocumentURI); + MOZ_CAN_RUN_SCRIPT bool RestoreInto(RefPtr aContext); + + NS_DECL_ISUPPORTS + NS_DECL_NSISESSIONSTORERESTOREDATA + NS_DECLARE_STATIC_IID_ACCESSOR(NS_SESSIONSTORERESTOREDATA_IID) + + struct Entry { + sessionstore::FormEntry mData; + bool mIsXPath; + }; + + private: + virtual ~SessionStoreRestoreData() = default; + void AddFormEntry(bool aIsXPath, const nsAString& aIdOrXPath, + sessionstore::FormEntryValue aValue); + + nsCString mScroll; + nsCOMPtr mURI; + nsString mInnerHTML; + nsTArray mEntries; + nsTArray> mChildren; + + friend struct mozilla::ipc::IPDLParamTraits; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SessionStoreRestoreData, + NS_SESSIONSTORERESTOREDATA_IID) + +} // namespace mozilla::dom + +#endif diff --git a/toolkit/components/sessionstore/SessionStoreScrollData.cpp b/toolkit/components/sessionstore/SessionStoreScrollData.cpp new file mode 100644 index 0000000000..29d58befaa --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreScrollData.cpp @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SessionStoreScrollData.h" +#include + +#include "js/PropertyAndElement.h" +#include "js/TypeDecls.h" +#include "mozilla/Assertions.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/BrowserSessionStoreBinding.h" +#include "mozilla/dom/BrowsingContext.h" + +#include "nsPresContext.h" + +#include "mozilla/dom/WebIDLGlobalNameHash.h" +#include "js/PropertyDescriptor.h" + +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/GeneratedAtomList.h" +#include "js/StructuredClone.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "nsContentUtils.h" +#include "js/Array.h" +#include "js/JSON.h" + +using namespace mozilla::dom; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStoreScrollData) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStoreScrollData) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(SessionStoreScrollData, + mChildren) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStoreScrollData) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +nsISupports* SessionStoreScrollData::GetParentObject() const { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); + ; +} + +JSObject* SessionStoreScrollData::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return SessionStoreScrollData_Binding::Wrap(aCx, this, aGivenProto); +} + +void SessionStoreScrollData::GetScroll(nsACString& aScroll) const { + int scrollX = nsPresContext::AppUnitsToIntCSSPixels(mScroll.x); + int scrollY = nsPresContext::AppUnitsToIntCSSPixels(mScroll.y); + + if ((scrollX != 0) || (scrollY != 0)) { + aScroll = nsPrintfCString("%d,%d", scrollX, scrollY); + } else { + aScroll.SetIsVoid(true); + } +} + +SessionStoreScrollData::ChildrenArray& SessionStoreScrollData::Children() { + return mChildren; +} + +void SessionStoreScrollData::GetChildren( + Nullable& aChildren) const { + if (!mChildren.IsEmpty()) { + aChildren.SetValue() = mChildren.Clone(); + } else { + aChildren.SetNull(); + } +} + +void SessionStoreScrollData::ToJSON(JSContext* aCx, + JS::MutableHandle aRetval) { + JS::Rooted self(aCx); + { + JS::Rooted value(aCx); + if (!GetOrCreateDOMReflector(aCx, this, &value)) { + return; + } + + self.set(value.toObjectOrNull()); + } + + JS::Rooted result(aCx, JS_NewPlainObject(aCx)); + + if (!IsEmpty()) { + if (HasData(mScroll)) { + if (!SessionStoreUtils::CopyProperty(aCx, result, self, u"scroll"_ns)) { + return; + } + } + + SessionStoreUtils::CopyChildren(aCx, result, mChildren); + } + + aRetval.set(result); +} + +void SessionStoreScrollData::Update(const CollectedType& aUpdate) { + SessionStoreScrollData_Binding::ClearCachedScrollValue(this); + mScroll = aUpdate; +} + +void SessionStoreScrollData::ClearCachedChildren() { + SessionStoreScrollData_Binding::ClearCachedChildrenValue(this); +} + +/* static */ +bool SessionStoreScrollData::HasData(const CollectedType& aPoint) { + int scrollX = nsPresContext::AppUnitsToIntCSSPixels(aPoint.x); + int scrollY = nsPresContext::AppUnitsToIntCSSPixels(aPoint.y); + return scrollX != 0 || scrollY != 0; +} + +bool SessionStoreScrollData::IsEmpty() const { + return !HasData(mScroll) && mChildren.IsEmpty(); +} diff --git a/toolkit/components/sessionstore/SessionStoreScrollData.h b/toolkit/components/sessionstore/SessionStoreScrollData.h new file mode 100644 index 0000000000..b2f5d4bd32 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreScrollData.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_SessionStoreScrollData_h +#define mozilla_dom_SessionStoreScrollData_h + +#include "js/TypeDecls.h" +#include "mozilla/WeakPtr.h" +#include "nsPoint.h" +#include "nsTArray.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/SessionStoreChangeListener.h" + +namespace mozilla::dom { + +class BrowsingContext; +class WindowGlobalParent; +class OwningByteStringOrObjectOrNull; +struct SessionStoreZoomData; + +using SessionStoreZoom = std::tuple; +using MaybeSessionStoreZoom = + mozilla::Maybe>; + +class SessionStoreScrollData final : public nsISupports, + public nsWrapperCache, + public SupportsWeakPtr { + public: + using CollectedType = nsPoint; + using LocationType = WeakPtr; + using ChildrenArray = nsTArray>; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SessionStoreScrollData) + nsISupports* GetParentObject() const; + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + void GetScroll(nsACString& aScroll) const; + + ChildrenArray& Children(); + + void GetChildren(Nullable& aChildren) const; + + void ToJSON(JSContext* aCx, JS::MutableHandle aRetval); + + void Update(const CollectedType& aUpdate); + + void ClearCachedChildren(); + + static bool HasData(const CollectedType& aPoint); + + bool IsEmpty() const; + + private: + ~SessionStoreScrollData() = default; + + nsPoint mScroll; + MaybeSessionStoreZoom mZoom; + nsTArray> mChildren; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_SessionStoreScrollData_h diff --git a/toolkit/components/sessionstore/SessionStoreTypes.ipdlh b/toolkit/components/sessionstore/SessionStoreTypes.ipdlh new file mode 100644 index 0000000000..c375fa0b36 --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreTypes.ipdlh @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +using struct CollectedInputDataValue from "mozilla/dom/SessionStoreMessageUtils.h"; +using struct nsPoint from "nsPoint.h"; + +include DOMTypes; + +namespace mozilla { +namespace dom { + +namespace sessionstore { +struct Checkbox { + bool value; +}; + +struct TextField { + nsString value; +}; + +struct FileList { + nsString[] valueList; +}; + +struct SingleSelect { + uint32_t index; + nsString value; +}; + +struct MultipleSelect { + nsString[] valueList; +}; + +union FormEntryValue { + Checkbox; + TextField; + FileList; + SingleSelect; + MultipleSelect; +}; + +struct FormEntry { + nsString id; + FormEntryValue value; +}; + +struct FormData { + bool hasData; + FormEntry[] id; + FormEntry[] xpath; + nsString innerHTML; + nsCString uri; +}; + +struct DocShellRestoreState { + nullable nsIURI URI; + nsCString docShellCaps; +}; + +} // sessionstore + +} // dom +} // mozilla diff --git a/toolkit/components/sessionstore/SessionStoreUtils.cpp b/toolkit/components/sessionstore/SessionStoreUtils.cpp new file mode 100644 index 0000000000..a1e78e19aa --- /dev/null +++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp @@ -0,0 +1,1738 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject +#include "js/JSON.h" +#include "js/PropertyAndElement.h" // JS_GetElement +#include "js/TypeDecls.h" +#include "jsapi.h" +#include "mozilla/BasePrincipal.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" +#include "mozilla/dom/HTMLSelectElement.h" +#include "mozilla/dom/HTMLTextAreaElement.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/PBackgroundSessionStorageCache.h" +#include "mozilla/dom/SessionStoreChangeListener.h" +#include "mozilla/dom/SessionStoreChild.h" +#include "mozilla/dom/SessionStoreUtils.h" +#include "mozilla/dom/txIXPathContext.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/WindowProxyHolder.h" +#include "mozilla/dom/XPathResult.h" +#include "mozilla/dom/XPathEvaluator.h" +#include "mozilla/dom/XPathExpression.h" +#include "mozilla/dom/PBackgroundSessionStorageCache.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ReverseIterator.h" +#include "mozilla/UniquePtr.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentList.h" +#include "nsContentUtils.h" +#include "nsFocusManager.h" +#include "nsGlobalWindowOuter.h" +#include "nsIContentInlines.h" +#include "nsIDocShell.h" +#include "nsIFormControl.h" +#include "nsIScrollableFrame.h" +#include "nsISHistory.h" +#include "nsIXULRuntime.h" +#include "nsPresContext.h" +#include "nsPrintfCString.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::sessionstore; + +namespace { + +class DynamicFrameEventFilter final : public nsIDOMEventListener { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(DynamicFrameEventFilter) + + explicit DynamicFrameEventFilter(EventListener* aListener) + : mListener(aListener) {} + + NS_IMETHODIMP HandleEvent(Event* aEvent) override { + if (mListener && TargetInNonDynamicDocShell(aEvent)) { + mListener->HandleEvent(*aEvent); + } + + return NS_OK; + } + + private: + ~DynamicFrameEventFilter() = default; + + bool TargetInNonDynamicDocShell(Event* aEvent) { + EventTarget* target = aEvent->GetTarget(); + if (!target) { + return false; + } + + nsPIDOMWindowOuter* outer = target->GetOwnerGlobalForBindingsInternal(); + if (!outer || !outer->GetDocShell()) { + return false; + } + + RefPtr context = outer->GetBrowsingContext(); + return context && !context->CreatedDynamically(); + } + + RefPtr mListener; +}; + +NS_IMPL_CYCLE_COLLECTION(DynamicFrameEventFilter, mListener) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DynamicFrameEventFilter) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DynamicFrameEventFilter) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DynamicFrameEventFilter) + +} // anonymous namespace + +/* static */ +void SessionStoreUtils::ForEachNonDynamicChildFrame( + const GlobalObject& aGlobal, WindowProxyHolder& aWindow, + SessionStoreUtilsFrameCallback& aCallback, ErrorResult& aRv) { + if (!aWindow.get()) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + + nsCOMPtr docShell = aWindow.get()->GetDocShell(); + if (!docShell) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + int32_t length; + aRv = docShell->GetInProcessChildCount(&length); + if (aRv.Failed()) { + return; + } + + for (int32_t i = 0; i < length; ++i) { + nsCOMPtr item; + docShell->GetInProcessChildAt(i, getter_AddRefs(item)); + if (!item) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + RefPtr context = item->GetBrowsingContext(); + if (!context) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (!context->CreatedDynamically()) { + int32_t childOffset = context->ChildOffset(); + aCallback.Call(WindowProxyHolder(context.forget()), childOffset); + } + } +} + +/* static */ +already_AddRefed +SessionStoreUtils::AddDynamicFrameFilteredListener( + const GlobalObject& aGlobal, EventTarget& aTarget, const nsAString& aType, + JS::Handle aListener, bool aUseCapture, bool aMozSystemGroup, + ErrorResult& aRv) { + if (NS_WARN_IF(!aListener.isObject())) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return nullptr; + } + + JSContext* cx = aGlobal.Context(); + JS::Rooted obj(cx, &aListener.toObject()); + JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); + RefPtr listener = + new EventListener(cx, obj, global, GetIncumbentGlobal()); + + nsCOMPtr filter(new DynamicFrameEventFilter(listener)); + if (aMozSystemGroup) { + aRv = aTarget.AddSystemEventListener(aType, filter, aUseCapture); + } else { + aRv = aTarget.AddEventListener(aType, filter, aUseCapture); + } + if (aRv.Failed()) { + return nullptr; + } + + return filter.forget(); +} + +/* static */ +void SessionStoreUtils::RemoveDynamicFrameFilteredListener( + const GlobalObject& global, EventTarget& aTarget, const nsAString& aType, + nsISupports* aListener, bool aUseCapture, bool aMozSystemGroup, + ErrorResult& aRv) { + nsCOMPtr listener = do_QueryInterface(aListener); + if (!listener) { + aRv.Throw(NS_ERROR_NO_INTERFACE); + return; + } + + if (aMozSystemGroup) { + aTarget.RemoveSystemEventListener(aType, listener, aUseCapture); + } else { + aTarget.RemoveEventListener(aType, listener, aUseCapture); + } +} + +/* static */ +void SessionStoreUtils::CollectDocShellCapabilities(const GlobalObject& aGlobal, + nsIDocShell* aDocShell, + nsCString& aRetVal) { + bool allow; + +#define TRY_ALLOWPROP(y) \ + PR_BEGIN_MACRO \ + aDocShell->GetAllow##y(&allow); \ + if (!allow) { \ + if (!aRetVal.IsEmpty()) { \ + aRetVal.Append(','); \ + } \ + aRetVal.Append(#y); \ + } \ + PR_END_MACRO + + TRY_ALLOWPROP(Plugins); + // Bug 1328013 : Don't collect "AllowJavascript" property + // TRY_ALLOWPROP(Javascript); + TRY_ALLOWPROP(MetaRedirects); + TRY_ALLOWPROP(Subframes); + TRY_ALLOWPROP(Images); + TRY_ALLOWPROP(Media); + TRY_ALLOWPROP(DNSPrefetch); + TRY_ALLOWPROP(WindowControl); + TRY_ALLOWPROP(Auth); + TRY_ALLOWPROP(ContentRetargeting); + TRY_ALLOWPROP(ContentRetargetingOnChildren); +#undef TRY_ALLOWPROP +} + +/* static */ +void SessionStoreUtils::RestoreDocShellCapabilities( + nsIDocShell* aDocShell, const nsCString& aDisallowCapabilities) { + aDocShell->SetAllowPlugins(true); + aDocShell->SetAllowMetaRedirects(true); + aDocShell->SetAllowSubframes(true); + aDocShell->SetAllowImages(true); + aDocShell->SetAllowMedia(true); + aDocShell->SetAllowDNSPrefetch(true); + aDocShell->SetAllowWindowControl(true); + aDocShell->SetAllowContentRetargeting(true); + aDocShell->SetAllowContentRetargetingOnChildren(true); + + bool allowJavascript = true; + for (const nsACString& token : + nsCCharSeparatedTokenizer(aDisallowCapabilities, ',').ToRange()) { + if (token.EqualsLiteral("Plugins")) { + aDocShell->SetAllowPlugins(false); + } else if (token.EqualsLiteral("Javascript")) { + allowJavascript = false; + } else if (token.EqualsLiteral("MetaRedirects")) { + aDocShell->SetAllowMetaRedirects(false); + } else if (token.EqualsLiteral("Subframes")) { + aDocShell->SetAllowSubframes(false); + } else if (token.EqualsLiteral("Images")) { + aDocShell->SetAllowImages(false); + } else if (token.EqualsLiteral("Media")) { + aDocShell->SetAllowMedia(false); + } else if (token.EqualsLiteral("DNSPrefetch")) { + aDocShell->SetAllowDNSPrefetch(false); + } else if (token.EqualsLiteral("WindowControl")) { + aDocShell->SetAllowWindowControl(false); + } else if (token.EqualsLiteral("ContentRetargeting")) { + bool allow; + aDocShell->GetAllowContentRetargetingOnChildren(&allow); + aDocShell->SetAllowContentRetargeting( + false); // will also set AllowContentRetargetingOnChildren + aDocShell->SetAllowContentRetargetingOnChildren( + allow); // restore the allowProp to original + } else if (token.EqualsLiteral("ContentRetargetingOnChildren")) { + aDocShell->SetAllowContentRetargetingOnChildren(false); + } + } + + if (!mozilla::SessionHistoryInParent()) { + // With SessionHistoryInParent, this is set from the parent process. + BrowsingContext* bc = aDocShell->GetBrowsingContext(); + Unused << bc->SetAllowJavascript(allowJavascript); + } +} + +static void CollectCurrentScrollPosition(JSContext* aCx, Document& aDocument, + Nullable& aRetVal) { + PresShell* presShell = aDocument.GetPresShell(); + if (!presShell) { + return; + } + nsPoint scrollPos = presShell->GetVisualViewportOffset(); + int scrollX = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.x); + int scrollY = nsPresContext::AppUnitsToIntCSSPixels(scrollPos.y); + + if ((scrollX != 0) || (scrollY != 0)) { + aRetVal.SetValue().mScroll.Construct() = + nsPrintfCString("%d,%d", scrollX, scrollY); + } +} + +/* static */ +void SessionStoreUtils::RestoreScrollPosition(const GlobalObject& aGlobal, + nsGlobalWindowInner& aWindow, + const CollectedData& aData) { + if (aData.mScroll.WasPassed()) { + RestoreScrollPosition(aWindow, aData.mScroll.Value()); + } +} + +/* static */ +void SessionStoreUtils::RestoreScrollPosition( + nsGlobalWindowInner& aWindow, const nsCString& aScrollPosition) { + using Change = mozilla::dom::SessionStoreChangeListener::Change; + SessionStoreChangeListener::CollectSessionStoreData( + aWindow.GetWindowContext(), EnumSet(Change::Scroll)); + + nsCCharSeparatedTokenizer tokenizer(aScrollPosition, ','); + nsAutoCString token(tokenizer.nextToken()); + int pos_X = atoi(token.get()); + token = tokenizer.nextToken(); + int pos_Y = atoi(token.get()); + + aWindow.ScrollTo(pos_X, pos_Y); + + if (nsCOMPtr doc = aWindow.GetExtantDoc()) { + if (nsPresContext* presContext = doc->GetPresContext()) { + if (presContext->IsRootContentDocumentCrossProcess()) { + // Use eMainThread so this takes precedence over session history + // (ScrollFrameHelper::ScrollToRestoredPosition()). + presContext->PresShell()->ScrollToVisual( + CSSPoint::ToAppUnits(CSSPoint(pos_X, pos_Y)), + layers::FrameMetrics::eMainThread, ScrollMode::Instant); + } + } + } +} + +// Implements the Luhn checksum algorithm as described at +// http://wikipedia.org/wiki/Luhn_algorithm +// Number digit lengths vary with network, but should fall within 12-19 range. +// [2] More details at https://en.wikipedia.org/wiki/Payment_card_number +static bool IsValidCCNumber(nsAString& aValue) { + uint32_t total = 0; + uint32_t numLength = 0; + uint32_t strLen = aValue.Length(); + for (uint32_t i = 0; i < strLen; ++i) { + uint32_t idx = strLen - i - 1; + // ignore whitespace and dashes) + char16_t chr = aValue[idx]; + if (IsSpaceCharacter(chr) || chr == '-') { + continue; + } + // If our number is too long, note that fact + ++numLength; + if (numLength > 19) { + return false; + } + // Try to parse the character as a base-10 integer. + nsresult rv = NS_OK; + uint32_t val = Substring(aValue, idx, 1).ToInteger(&rv, 10); + if (NS_FAILED(rv)) { + return false; + } + if (i % 2 == 1) { + val *= 2; + if (val > 9) { + val -= 9; + } + } + total += val; + } + + return numLength >= 12 && total % 10 == 0; +} + +// Limit the number of XPath expressions for performance reasons. See bug +// 477564. +static const uint16_t kMaxTraversedXPaths = 100; + +// A helper function to append a element into mId or mXpath of CollectedData +static Record::EntryType* +AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId, + uint16_t& aGeneratedCount, + Nullable& aRetVal) { + Record::EntryType* entry; + if (!aId.IsEmpty()) { + if (!aRetVal.SetValue().mId.WasPassed()) { + aRetVal.SetValue().mId.Construct(); + } + auto& recordEntries = aRetVal.SetValue().mId.Value().Entries(); + entry = recordEntries.AppendElement(); + entry->mKey = aId; + } else { + if (!aRetVal.SetValue().mXpath.WasPassed()) { + aRetVal.SetValue().mXpath.Construct(); + } + auto& recordEntries = aRetVal.SetValue().mXpath.Value().Entries(); + entry = recordEntries.AppendElement(); + nsAutoString xpath; + aNode->GenerateXPath(xpath); + aGeneratedCount++; + entry->mKey = xpath; + } + return entry; +} + +/* for bool value */ +static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId, + const bool& aValue, + uint16_t& aGeneratedCount, + JSContext* aCx, + Nullable& aRetVal) { + Record::EntryType* entry = + AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); + entry->mValue.SetAsBoolean() = aValue; +} + +/* for nsString value */ +static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId, + const nsString& aValue, + uint16_t& aGeneratedCount, + Nullable& aRetVal) { + Record::EntryType* entry = + AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); + entry->mValue.SetAsString() = aValue; +} + +/* for single select value */ +static void AppendValueToCollectedData( + nsINode* aNode, const nsAString& aId, + const CollectedNonMultipleSelectValue& aValue, uint16_t& aGeneratedCount, + JSContext* aCx, Nullable& aRetVal) { + JS::Rooted jsval(aCx); + if (!ToJSValue(aCx, aValue, &jsval)) { + JS_ClearPendingException(aCx); + return; + } + Record::EntryType* entry = + AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); + entry->mValue.SetAsObject() = &jsval.toObject(); +} + +/* special handing for input element with string type */ +static void AppendValueToCollectedData(Document& aDocument, nsINode* aNode, + const nsAString& aId, + const nsString& aValue, + uint16_t& aGeneratedCount, + JSContext* aCx, + Nullable& aRetVal) { + if (!aId.IsEmpty()) { + // We want to avoid saving data for about:sessionrestore as a string. + // Since it's stored in the form as stringified JSON, stringifying + // further causes an explosion of escape characters. cf. bug 467409 + if (aId.EqualsLiteral("sessionData")) { + nsAutoCString url; + Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url); + if (url.EqualsLiteral("about:sessionrestore") || + url.EqualsLiteral("about:welcomeback")) { + JS::Rooted jsval(aCx); + if (JS_ParseJSON(aCx, aValue.get(), aValue.Length(), &jsval) && + jsval.isObject()) { + Record::EntryType* entry = + AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); + entry->mValue.SetAsObject() = &jsval.toObject(); + } else { + JS_ClearPendingException(aCx); + } + return; + } + } + } + AppendValueToCollectedData(aNode, aId, aValue, aGeneratedCount, aRetVal); +} + +/* for nsTArray: file and multipleSelect */ +static void AppendValueToCollectedData(nsINode* aNode, const nsAString& aId, + const nsAString& aValueType, + nsTArray& aValue, + uint16_t& aGeneratedCount, + JSContext* aCx, + Nullable& aRetVal) { + JS::Rooted jsval(aCx); + if (aValueType.EqualsLiteral("file")) { + CollectedFileListValue val; + val.mType = aValueType; + val.mFileList = std::move(aValue); + if (!ToJSValue(aCx, val, &jsval)) { + JS_ClearPendingException(aCx); + return; + } + } else { + if (!ToJSValue(aCx, aValue, &jsval)) { + JS_ClearPendingException(aCx); + return; + } + } + Record::EntryType* entry = + AppendEntryToCollectedData(aNode, aId, aGeneratedCount, aRetVal); + entry->mValue.SetAsObject() = &jsval.toObject(); +} + +// This isn't size as in binary size, just a heuristic to not store too large +// fields in session store. See StaticPrefs::browser_sessionstore_dom_form_limit +static uint32_t SizeOfFormEntry(const FormEntryValue& aValue) { + uint32_t size = 0; + switch (aValue.type()) { + case FormEntryValue::TCheckbox: + size = aValue.get_Checkbox().value() ? 4 : 5; + break; + case FormEntryValue::TTextField: + size = aValue.get_TextField().value().Length(); + break; + case FormEntryValue::TFileList: { + for (const auto& value : aValue.get_FileList().valueList()) { + size += value.Length(); + } + break; + } + case FormEntryValue::TSingleSelect: + size = aValue.get_SingleSelect().value().Length(); + break; + case FormEntryValue::TMultipleSelect: { + for (const auto& value : aValue.get_MultipleSelect().valueList()) { + size += value.Length(); + } + break; + } + default: + break; + } + return size; +} + +static uint32_t AppendEntry(nsINode* aNode, const nsString& aId, + const FormEntryValue& aValue, + sessionstore::FormData& aFormData) { + uint32_t size = SizeOfFormEntry(aValue); + if (size > StaticPrefs::browser_sessionstore_dom_form_limit()) { + return 0; + } + + if (aId.IsEmpty()) { + FormEntry* entry = aFormData.xpath().AppendElement(); + entry->value() = aValue; + aNode->GenerateXPath(entry->id()); + size += entry->id().Length(); + } else { + aFormData.id().AppendElement(FormEntry{aId, aValue}); + size += aId.Length(); + } + + return size; +} + +static uint32_t CollectTextAreaElement(Document* aDocument, + sessionstore::FormData& aFormData) { + uint32_t size = 0; + RefPtr 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; + } + + size += AppendEntry(textArea, id, TextField{value}, aFormData); + } + + return size; +} + +static uint32_t CollectInputElement(Document* aDocument, + sessionstore::FormData& aFormData) { + uint32_t size = 0; + RefPtr 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 formControl = + do_QueryInterface(inputlist->Item(i)); + if (formControl) { + auto controlType = formControl->ControlType(); + if (controlType == FormControlType::InputPassword || + controlType == FormControlType::InputHidden || + controlType == FormControlType::InputButton || + controlType == FormControlType::InputImage || + controlType == FormControlType::InputSubmit || + controlType == FormControlType::InputReset) { + continue; + } + } + RefPtr 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 aInfo; + input->GetAutocompleteInfo(aInfo); + if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) { + continue; + } + + FormEntryValue value; + if (input->ControlType() == FormControlType::InputCheckbox || + input->ControlType() == FormControlType::InputRadio) { + bool checked = input->Checked(); + if (checked == input->DefaultChecked()) { + continue; + } + size += AppendEntry(input, id, Checkbox{checked}, aFormData); + } else if (input->ControlType() == FormControlType::InputFile) { + IgnoredErrorResult rv; + sessionstore::FileList file; + input->MozGetFileNameArray(file.valueList(), rv); + if (rv.Failed() || file.valueList().IsEmpty()) { + continue; + } + size += AppendEntry(input, id, file, aFormData); + } else { + TextField field; + input->GetValue(field.value(), CallerType::System); + auto& value = field.value(); + // In order to reduce XPath generation (which is slow), we only save data + // for form fields that have been changed. (cf. bug 537289) + // Also, don't want to collect credit card number. + if (value.IsEmpty() || IsValidCCNumber(value) || + input->HasBeenTypePassword() || + input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value, + eCaseMatters)) { + continue; + } + size += AppendEntry(input, id, field, aFormData); + } + } + + return size; +} + +static uint32_t CollectSelectElement(Document* aDocument, + sessionstore::FormData& aFormData) { + uint32_t size = 0; + RefPtr 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 select = + HTMLSelectElement::FromNodeOrNull(selectlist->Item(i)); + if (!select) { + continue; + } + nsAutoString id; + select->GetId(id); + if (id.IsEmpty() && (aFormData.xpath().Length() > kMaxTraversedXPaths)) { + continue; + } + AutocompleteInfo aInfo; + select->GetAutocompleteInfo(aInfo); + if (!aInfo.mCanAutomaticallyPersist) { + continue; + } + + if (!select->Multiple()) { + HTMLOptionsCollection* options = select->GetOptions(); + if (!options) { + continue; + } + + uint32_t numOptions = options->Length(); + int32_t defaultIndex = 0; + for (uint32_t idx = 0; idx < numOptions; idx++) { + HTMLOptionElement* option = options->ItemAsOption(idx); + if (option->DefaultSelected()) { + defaultIndex = option->Index(); + } + } + + int32_t selectedIndex = select->SelectedIndex(); + if (selectedIndex == defaultIndex || selectedIndex < 0) { + continue; + } + + DOMString selectVal; + select->GetValue(selectVal); + size += AppendEntry(select, id, + SingleSelect{static_cast(selectedIndex), + selectVal.AsAString()}, + aFormData); + } else { + HTMLOptionsCollection* options = select->GetOptions(); + if (!options) { + continue; + } + bool hasDefaultValue = true; + nsTArray selectslist; + uint32_t numOptions = options->Length(); + for (uint32_t idx = 0; idx < numOptions; idx++) { + HTMLOptionElement* option = options->ItemAsOption(idx); + bool selected = option->Selected(); + + hasDefaultValue = + hasDefaultValue && (selected == option->DefaultSelected()); + + if (!selected) { + continue; + } + option->GetValue(*selectslist.AppendElement()); + } + // In order to reduce XPath generation (which is slow), we only save data + // for form fields that have been changed. (cf. bug 537289) + if (hasDefaultValue) { + continue; + } + + size += AppendEntry(select, id, MultipleSelect{selectslist}, aFormData); + } + } + + return size; +} + +/* static */ +uint32_t SessionStoreUtils::CollectFormData(Document* aDocument, + sessionstore::FormData& aFormData) { + MOZ_DIAGNOSTIC_ASSERT(aDocument); + uint32_t size = 0; + size += CollectTextAreaElement(aDocument, aFormData); + size += CollectInputElement(aDocument, aFormData); + size += CollectSelectElement(aDocument, aFormData); + + aFormData.hasData() = + !aFormData.id().IsEmpty() || !aFormData.xpath().IsEmpty(); + + return size; +} + +/* static */ +template +void SessionStoreUtils::CollectFromTextAreaElement(Document& aDocument, + uint16_t& aGeneratedCount, + ArgsT&&... args) { + RefPtr textlist = + NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"textarea"_ns); + uint32_t length = textlist->Length(true); + for (uint32_t i = 0; i < length; ++i) { + MOZ_ASSERT(textlist->Item(i), "null item in node list!"); + + HTMLTextAreaElement* textArea = + HTMLTextAreaElement::FromNodeOrNull(textlist->Item(i)); + if (!textArea) { + continue; + } + DOMString autocomplete; + textArea->GetAutocomplete(autocomplete); + if (autocomplete.AsAString().EqualsLiteral("off")) { + continue; + } + nsAutoString id; + textArea->GetId(id); + if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) { + continue; + } + nsString value; + textArea->GetValue(value); + // In order to reduce XPath generation (which is slow), we only save data + // for form fields that have been changed. (cf. bug 537289) + if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value, + eCaseMatters)) { + continue; + } + AppendValueToCollectedData(textArea, id, value, aGeneratedCount, + std::forward(args)...); + } +} + +/* static */ +template +void SessionStoreUtils::CollectFromInputElement(Document& aDocument, + uint16_t& aGeneratedCount, + ArgsT&&... args) { + RefPtr inputlist = + NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"input"_ns); + uint32_t length = inputlist->Length(true); + for (uint32_t i = 0; i < length; ++i) { + MOZ_ASSERT(inputlist->Item(i), "null item in node list!"); + nsCOMPtr formControl = + do_QueryInterface(inputlist->Item(i)); + if (formControl) { + auto controlType = formControl->ControlType(); + if (controlType == FormControlType::InputPassword || + controlType == FormControlType::InputHidden || + controlType == FormControlType::InputButton || + controlType == FormControlType::InputImage || + controlType == FormControlType::InputSubmit || + controlType == FormControlType::InputReset) { + continue; + } + } + RefPtr input = + HTMLInputElement::FromNodeOrNull(inputlist->Item(i)); + if (!input || !nsContentUtils::IsAutocompleteEnabled(input)) { + continue; + } + nsAutoString id; + input->GetId(id); + if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) { + continue; + } + Nullable aInfo; + input->GetAutocompleteInfo(aInfo); + if (!aInfo.IsNull() && !aInfo.Value().mCanAutomaticallyPersist) { + continue; + } + + if (input->ControlType() == FormControlType::InputCheckbox || + input->ControlType() == FormControlType::InputRadio) { + bool checked = input->Checked(); + if (checked == input->DefaultChecked()) { + continue; + } + AppendValueToCollectedData(input, id, checked, aGeneratedCount, + std::forward(args)...); + } else if (input->ControlType() == FormControlType::InputFile) { + IgnoredErrorResult rv; + nsTArray result; + input->MozGetFileNameArray(result, rv); + if (rv.Failed() || result.Length() == 0) { + continue; + } + AppendValueToCollectedData(input, id, u"file"_ns, result, aGeneratedCount, + std::forward(args)...); + } else { + nsString value; + input->GetValue(value, CallerType::System); + // In order to reduce XPath generation (which is slow), we only save data + // for form fields that have been changed. (cf. bug 537289) + // Also, don't want to collect credit card number. + if (value.IsEmpty() || IsValidCCNumber(value) || + input->HasBeenTypePassword() || + input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value, + eCaseMatters)) { + continue; + } + AppendValueToCollectedData(aDocument, input, id, value, aGeneratedCount, + std::forward(args)...); + } + } +} + +/* static */ +template +void SessionStoreUtils::CollectFromSelectElement(Document& aDocument, + uint16_t& aGeneratedCount, + ArgsT&&... args) { + RefPtr selectlist = + NS_GetContentList(&aDocument, kNameSpaceID_XHTML, u"select"_ns); + uint32_t length = selectlist->Length(true); + for (uint32_t i = 0; i < length; ++i) { + MOZ_ASSERT(selectlist->Item(i), "null item in node list!"); + RefPtr select = + HTMLSelectElement::FromNodeOrNull(selectlist->Item(i)); + if (!select) { + continue; + } + nsAutoString id; + select->GetId(id); + if (id.IsEmpty() && (aGeneratedCount > kMaxTraversedXPaths)) { + continue; + } + AutocompleteInfo aInfo; + select->GetAutocompleteInfo(aInfo); + if (!aInfo.mCanAutomaticallyPersist) { + continue; + } + nsAutoCString value; + if (!select->Multiple()) { + // s with the multiple attribute are easier to determine the + // default value since each