/* -*- 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()); } }