summaryrefslogtreecommitdiffstats
path: root/toolkit/components/sessionstore/BrowserSessionStore.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/sessionstore/BrowserSessionStore.cpp')
-rw-r--r--toolkit/components/sessionstore/BrowserSessionStore.cpp292
1 files changed, 292 insertions, 0 deletions
diff --git a/toolkit/components/sessionstore/BrowserSessionStore.cpp b/toolkit/components/sessionstore/BrowserSessionStore.cpp
new file mode 100644
index 0000000000..f4da9646b2
--- /dev/null
+++ b/toolkit/components/sessionstore/BrowserSessionStore.cpp
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BrowserSessionStore.h"
+#include <algorithm>
+#include <cstdint>
+#include <functional>
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPtr.h"
+
+#include "mozilla/dom/BrowserSessionStoreBinding.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/SessionStoreFormData.h"
+#include "mozilla/dom/SessionStoreScrollData.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+
+#include "nsTHashMap.h"
+#include "nsHashtablesFwd.h"
+
+#include "js/RootingAPI.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static StaticAutoPtr<nsTHashMap<nsUint64HashKey, BrowserSessionStore*>>
+ sSessionStore;
+
+NS_IMPL_CYCLE_COLLECTION(BrowserSessionStore, mBrowsingContext, mFormData,
+ mScrollData)
+
+/* static */
+already_AddRefed<BrowserSessionStore> BrowserSessionStore::GetOrCreate(
+ CanonicalBrowsingContext* aBrowsingContext) {
+ if (!aBrowsingContext->IsTop()) {
+ return nullptr;
+ }
+
+ if (!sSessionStore) {
+ sSessionStore = new nsTHashMap<nsUint64HashKey, BrowserSessionStore*>();
+ ClearOnShutdown(&sSessionStore);
+ }
+
+ return do_AddRef(sSessionStore->LookupOrInsertWith(
+ aBrowsingContext->Id(),
+ [&] { return new BrowserSessionStore(aBrowsingContext); }));
+}
+
+BrowserSessionStore::BrowserSessionStore(
+ CanonicalBrowsingContext* aBrowsingContext)
+ : mBrowsingContext(aBrowsingContext) {}
+
+SessionStoreFormData* BrowserSessionStore::GetFormdata() { return mFormData; }
+
+SessionStoreScrollData* BrowserSessionStore::GetScroll() { return mScrollData; }
+
+static bool ShouldUpdateSessionStore(CanonicalBrowsingContext* aBrowsingContext,
+ uint32_t aEpoch) {
+ if (!aBrowsingContext) {
+ return false;
+ }
+
+ if (aBrowsingContext->Top()->GetSessionStoreEpoch() != aEpoch) {
+ return false;
+ }
+
+ if (aBrowsingContext->IsReplaced()) {
+ return false;
+ }
+
+ if (aBrowsingContext->IsDynamic()) {
+ return false;
+ }
+
+ return true;
+}
+
+// With GetOrCreate we can create either of the weak fields:
+// WeakPtr<SessionStoreFormData> mFormdata;
+// WeakPtr<SessionStoreScrollData> mScroll;
+// in CanonicalBrowsingContext. If one already exists, then we return that.
+template <typename T, WeakPtr<T>& (CanonicalBrowsingContext::*GetWeakRef)()>
+static already_AddRefed<T> GetOrCreateEntry(
+ CanonicalBrowsingContext* aBrowsingContext) {
+ typename T::LocationType& location = (aBrowsingContext->*GetWeakRef)();
+ RefPtr<T> entry = location.get();
+ if (!entry) {
+ entry = MakeRefPtr<T>();
+ location = entry;
+ }
+
+ return entry.forget();
+}
+
+// With InsertEntry we can insert an entry in the session store data tree in
+// either of the weak fields:
+// WeakPtr<SessionStoreFormData> mFormdata;
+// WeakPtr<SessionStoreScrollData> mScroll;
+// in CanonicalBrowsingContext. If an entry is inserted where there is no parent
+// entry, a spine of entries will be created until one is found, or we reach the
+// top browsing context.
+template <typename T>
+void InsertEntry(BrowsingContext* aBrowsingContext, T* aParent, T* aUpdate) {
+ int32_t offset = aBrowsingContext->ChildOffset();
+ if (offset < 0) {
+ return;
+ }
+
+ aParent->ClearCachedChildren();
+
+ auto& children = aParent->Children();
+
+ children.EnsureLengthAtLeast(offset + 1);
+ if (children[offset] && !aBrowsingContext->Children().IsEmpty()) {
+ children[offset]->ClearCachedChildren();
+ aUpdate->ClearCachedChildren();
+ }
+
+ children[offset] = aUpdate;
+}
+
+// With RemoveEntry we can remove an entry in the session store data tree in
+// either of the weak fields:
+// WeakPtr<SessionStoreFormData> mFormdata;
+// WeakPtr<SessionStoreScrollData> mScroll;
+// in CanonicalBrowsingContext. If an entry is removed, where its parent doesn't
+// contain data, we'll remove the parent and repeat until we either find an
+// entry with data or reach the top browsing context.
+template <typename T>
+void RemoveEntry(BrowsingContext* aBrowsingContext, T* aParent) {
+ int32_t offset = aBrowsingContext->ChildOffset();
+ if (offset < 0) {
+ return;
+ }
+
+ if (!aParent) {
+ return;
+ }
+
+ aParent->ClearCachedChildren();
+
+ auto& children = aParent->Children();
+ size_t length = children.Length();
+ if (children.Length() <= static_cast<size_t>(offset)) {
+ // The children array doesn't extend to offset.
+ return;
+ }
+
+ if (static_cast<size_t>(offset) < length - 1) {
+ // offset is before the last item in the children array.
+ children[offset] = nullptr;
+ return;
+ }
+
+ // offset is the last item, find the first non-null item before it
+ // and remove anything after that item.
+ while (offset > 0 && !children[offset - 1]) {
+ --offset;
+ }
+
+ children.TruncateLength(offset);
+}
+
+// With UpdateSessionStoreField we can update an entry in the session store
+// data tree in either of the weak fields:
+// WeakPtr<SessionStoreFormData> mFormdata;
+// WeakPtr<SessionStoreScrollData> mScroll;
+// in CanonicalBrowsingContext. UpdateSessionStoreField uses the above
+// functions, `GetOrCreateEntry`, `InsertEntry` and `RemoveEntry` to operate on
+// the weak fields. We return the top-level entry attached to the top browsing
+// context through `aEntry`. If the entire browsing context tree contains no
+// session store data this will be set to nullptr.
+template <typename T, WeakPtr<T>& (CanonicalBrowsingContext::*GetWeakRef)()>
+void UpdateSessionStoreField(CanonicalBrowsingContext* aBrowsingContext,
+ const typename T::CollectedType& aUpdate,
+ T** aEntry) {
+ RefPtr<T> currentEntry;
+
+ if (T::HasData(aUpdate)) {
+ currentEntry = GetOrCreateEntry<T, GetWeakRef>(aBrowsingContext);
+ currentEntry->Update(aUpdate);
+
+ CanonicalBrowsingContext* currentBrowsingContext = aBrowsingContext;
+ while (CanonicalBrowsingContext* parent =
+ currentBrowsingContext->GetParent()) {
+ WeakPtr<T>& parentEntry = (parent->*GetWeakRef)();
+ if (parentEntry) {
+ InsertEntry(aBrowsingContext, parentEntry.get(), currentEntry.get());
+ break;
+ }
+
+ RefPtr<T> entry = GetOrCreateEntry<T, GetWeakRef>(parent);
+ InsertEntry(currentBrowsingContext, entry.get(), currentEntry.get());
+
+ currentEntry = entry;
+ currentBrowsingContext = parent;
+ }
+
+ currentEntry = (aBrowsingContext->Top()->*GetWeakRef)().get();
+ } else if ((currentEntry = (aBrowsingContext->*GetWeakRef)())) {
+ currentEntry->Update(aUpdate);
+
+ CanonicalBrowsingContext* currentBrowsingContext = aBrowsingContext;
+ while (CanonicalBrowsingContext* parent =
+ currentBrowsingContext->GetParent()) {
+ if (!currentEntry || !currentEntry->IsEmpty()) {
+ break;
+ }
+
+ T* parentEntry = (parent->*GetWeakRef)().get();
+ RemoveEntry(currentBrowsingContext, parentEntry);
+
+ currentEntry = parentEntry;
+ currentBrowsingContext = parent;
+ }
+
+ if (currentEntry && currentEntry->IsEmpty()) {
+ currentEntry = nullptr;
+ } else {
+ currentEntry = (aBrowsingContext->Top()->*GetWeakRef)().get();
+ }
+ } else {
+ currentEntry = (aBrowsingContext->Top()->*GetWeakRef)().get();
+ }
+
+ *aEntry = currentEntry.forget().take();
+}
+
+void BrowserSessionStore::UpdateSessionStore(
+ CanonicalBrowsingContext* aBrowsingContext,
+ const Maybe<sessionstore::FormData>& aFormData,
+ const Maybe<nsPoint>& aScrollPosition, uint32_t aEpoch) {
+ if (!aFormData && !aScrollPosition) {
+ return;
+ }
+
+ if (!ShouldUpdateSessionStore(aBrowsingContext, aEpoch)) {
+ return;
+ }
+
+ if (aFormData) {
+ UpdateSessionStoreField<
+ SessionStoreFormData,
+ &CanonicalBrowsingContext::GetSessionStoreFormDataRef>(
+ aBrowsingContext, *aFormData, getter_AddRefs(mFormData));
+ }
+
+ if (aScrollPosition) {
+ UpdateSessionStoreField<
+ SessionStoreScrollData,
+ &CanonicalBrowsingContext::GetSessionStoreScrollDataRef>(
+ aBrowsingContext, *aScrollPosition, getter_AddRefs(mScrollData));
+ }
+}
+
+void BrowserSessionStore::RemoveSessionStore(
+ CanonicalBrowsingContext* aBrowsingContext) {
+ if (!aBrowsingContext) {
+ return;
+ }
+
+ CanonicalBrowsingContext* parentContext = aBrowsingContext->GetParent();
+
+ if (parentContext) {
+ RemoveEntry(aBrowsingContext,
+ parentContext->GetSessionStoreFormDataRef().get());
+
+ RemoveEntry(aBrowsingContext,
+ parentContext->GetSessionStoreScrollDataRef().get());
+
+ return;
+ }
+
+ if (aBrowsingContext->IsTop()) {
+ mFormData = nullptr;
+ mScrollData = nullptr;
+ }
+}
+
+BrowserSessionStore::~BrowserSessionStore() {
+ if (sSessionStore) {
+ sSessionStore->Remove(mBrowsingContext->Id());
+ }
+}