diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/sessionstore/SessionStoreChangeListener.cpp | 393 |
1 files changed, 393 insertions, 0 deletions
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> +SessionStoreChangeListener::Create(BrowsingContext* aBrowsingContext) { + MOZ_RELEASE_ASSERT( + StaticPrefs::browser_sessionstore_platform_collection_AtStartup()); + if (!aBrowsingContext) { + return nullptr; + } + + RefPtr<SessionStoreChangeListener> listener = + new SessionStoreChangeListener(aBrowsingContext); + listener->Init(); + + return listener.forget(); +} + +void SessionStoreChangeListener::Stop() { + RemoveEventListeners(); + Preferences::RemoveObservers(this, kObservedPrefs); +} + +void SessionStoreChangeListener::UpdateEventTargets() { + RemoveEventListeners(); + AddEventListeners(); +} + +static void CollectFormData(Document* aDocument, + Maybe<sessionstore::FormData>& 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<SessionStoreZoom>& 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> document = windowContext->GetDocument(); + if (!document) { + continue; + } + + EnumSet<Change> changes = iter.GetData(); + Maybe<sessionstore::FormData> maybeFormData; + if (changes.contains(Change::Input)) { + CollectFormData(document, maybeFormData); + } + + Maybe<nsPoint> 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<SessionStoreZoom> zoom; + if (didResize) { + GetZoom(mBrowsingContext->Top(), zoom); + } + + mSessionStoreChild->UpdateSessionStore(collectSessionHistory, zoom); +} + +/* static */ +SessionStoreChangeListener* SessionStoreChangeListener::CollectSessionStoreData( + WindowContext* aWindowContext, const EnumSet<Change>& 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<Change> aChange) { + EnsureTimer(); + + Unused << mSessionStoreChanges.WithEntryHandle( + aWindowContext, [&](auto entryHandle) -> EnumSet<Change>& { + 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(); + } +} |