From df9bafbf73edb85fd2aadf500be0e8f93cd01e90 Mon Sep 17 00:00:00 2001 From: Cathy Lu Date: Fri, 15 Jul 2022 20:44:55 +0000 Subject: [PATCH] Bug 1734394 - Make Geckoview use the session store collector r=geckoview-reviewers,agi,farre,peterv When the session storage prefs are enabled, GeckoSession updateSessionState will provide the bundle of information, including zoom, scroll, and form data, to the delegate. Currently works for Fission and on Fenix. Differential Revision: https://phabricator.services.mozilla.com/D148215 --- browser/app/profile/firefox.js | 4 + docshell/base/CanonicalBrowsingContext.cpp | 4 +- docshell/base/nsDocShell.cpp | 4 +- dom/base/nsFrameLoader.cpp | 2 +- dom/chrome-webidl/BrowserSessionStore.webidl | 12 + dom/ipc/BrowserChild.cpp | 2 +- dom/storage/SessionStorageManager.cpp | 2 +- .../geckoview/SessionStateAggregator.js | 10 +- .../org/mozilla/geckoview/GeckoSession.java | 30 ++ modules/libpref/init/StaticPrefList.yaml | 18 ++ .../sessionstore/PSessionStore.ipdl | 5 +- .../SessionStoreChangeListener.cpp | 50 ++- .../sessionstore/SessionStoreChangeListener.h | 2 +- .../sessionstore/SessionStoreChild.cpp | 15 +- .../sessionstore/SessionStoreChild.h | 5 +- .../sessionstore/SessionStoreParent.cpp | 123 +++++++- .../sessionstore/SessionStoreParent.h | 8 +- .../sessionstore/SessionStoreScrollData.h | 8 + .../sessionstore/SessionStoreUtils.h | 11 +- widget/android/EventDispatcher.cpp | 260 +--------------- widget/android/GeckoViewSupport.h | 2 + widget/android/jni/GeckoBundleUtils.cpp | 289 ++++++++++++++++++ widget/android/jni/GeckoBundleUtils.h | 5 + widget/android/jni/moz.build | 1 + widget/android/nsWindow.cpp | 19 ++ widget/android/nsWindow.h | 2 + 26 files changed, 594 insertions(+), 299 deletions(-) create mode 100644 widget/android/jni/GeckoBundleUtils.cpp diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index b1dbe4b6c767..22bb2b5673ed 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1109,6 +1109,10 @@ pref("browser.sessionstore.upgradeBackup.maxUpgradeBackups", 3); pref("browser.sessionstore.debug", false); // Forget closed windows/tabs after two weeks pref("browser.sessionstore.cleanup.forget_closed_after", 1209600000); +// Platform collects data for session store +pref("browser.sessionstore.platform_collection", true); +// Platform collects session storage data for session store +pref("browser.sessionstore.collect_session_storage", true); // Don't quit the browser when Ctrl + Q is pressed. pref("browser.quitShortcut.disabled", false); diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp index 98567277234f..f9baff051dfc 100644 --- a/docshell/base/CanonicalBrowsingContext.cpp +++ b/docshell/base/CanonicalBrowsingContext.cpp @@ -2445,7 +2445,7 @@ nsresult CanonicalBrowsingContext::WriteSessionStorageToSessionStore( void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage( const std::function& aDone) { - if constexpr (!SessionStoreUtils::NATIVE_LISTENER) { + if (!StaticPrefs::browser_sessionstore_collect_session_storage_AtStartup()) { aDone(); return; } @@ -2478,7 +2478,7 @@ void CanonicalBrowsingContext::UpdateSessionStoreForStorage( } void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() { - if constexpr (!SessionStoreUtils::NATIVE_LISTENER) { + if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { return; } diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 20edf5184ce8..e7f3305fae1c 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5781,7 +5781,7 @@ nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest, } } - if constexpr (SessionStoreUtils::NATIVE_LISTENER) { + if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { if (IsForceReloadType(mLoadType)) { if (WindowContext* windowContext = mBrowsingContext->GetCurrentWindowContext()) { @@ -6525,7 +6525,7 @@ nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress, // incorrectly overrides session store data from the following load. return NS_OK; } - if constexpr (SessionStoreUtils::NATIVE_LISTENER) { + if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { if (WindowContext* windowContext = mBrowsingContext->GetCurrentWindowContext()) { using Change = SessionStoreChangeListener::Change; diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp index 06b2c63e31ff..b796b3a8c937 100644 --- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -3089,7 +3089,7 @@ nsresult nsFrameLoader::EnsureMessageManager() { NS_ENSURE_TRUE(mChildMessageManager, NS_ERROR_UNEXPECTED); // Set up session store - if constexpr (SessionStoreUtils::NATIVE_LISTENER) { + if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { if (XRE_IsParentProcess() && mIsTopLevelContent) { mSessionStoreChild = SessionStoreChild::GetOrCreate( GetExtantBrowsingContext(), mOwnerContent); diff --git a/dom/chrome-webidl/BrowserSessionStore.webidl b/dom/chrome-webidl/BrowserSessionStore.webidl index 2b6362eed635..23d3df945766 100644 --- a/dom/chrome-webidl/BrowserSessionStore.webidl +++ b/dom/chrome-webidl/BrowserSessionStore.webidl @@ -26,6 +26,18 @@ interface SessionStoreFormData { object toJSON(); }; +[GenerateConversionToJS] +dictionary SessionStoreDisplaySize { + unsigned long width; + unsigned long height; +}; + +[GenerateConversionToJS] +dictionary SessionStoreZoomData { + double resolution; + SessionStoreDisplaySize displaySize; +}; + [ChromeOnly, Exposed=Window] interface SessionStoreScrollData { [Cached, Pure] diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp index 554824afd6c0..92d1c09e5994 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -519,7 +519,7 @@ nsresult BrowserChild::Init(mozIDOMWindowProxy* aParent, mIPCOpen = true; - if constexpr (SessionStoreUtils::NATIVE_LISTENER) { + if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { mSessionStoreChild = SessionStoreChild::GetOrCreate(mBrowsingContext); } diff --git a/dom/storage/SessionStorageManager.cpp b/dom/storage/SessionStorageManager.cpp index 30745423eba2..353056a3de4e 100644 --- a/dom/storage/SessionStorageManager.cpp +++ b/dom/storage/SessionStorageManager.cpp @@ -918,7 +918,7 @@ void BackgroundSessionStorageManager::SetCurrentBrowsingContextId( } void BackgroundSessionStorageManager::MaybeScheduleSessionStoreUpdate() { - if constexpr (!SessionStoreUtils::NATIVE_LISTENER) { + if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { return; } diff --git a/mobile/android/chrome/geckoview/SessionStateAggregator.js b/mobile/android/chrome/geckoview/SessionStateAggregator.js index 4d8f16b9db00..12b600b9a390 100644 --- a/mobile/android/chrome/geckoview/SessionStateAggregator.js +++ b/mobile/android/chrome/geckoview/SessionStateAggregator.js @@ -27,6 +27,7 @@ const DEFAULT_INTERVAL_MS = 1500; const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates"; const PREF_INTERVAL = "browser.sessionstore.interval"; +const PREF_SESSION_COLLECTION = "browser.sessionstore.platform_collection"; class Handler { constructor(store) { @@ -598,13 +599,18 @@ class SessionStateAggregator extends GeckoViewChildModule { this.stateChangeNotifier = new StateChangeNotifier(this); this.handlers = [ - new FormDataListener(this), new SessionHistoryListener(this), - new ScrollPositionListener(this), this.stateChangeNotifier, this.messageQueue, ]; + if (!Services.prefs.getBoolPref(PREF_SESSION_COLLECTION, false)) { + this.handlers.push( + new FormDataListener(this), + new ScrollPositionListener(this) + ); + } + this.messageManager.addMessageListener("GeckoView:FlushSessionState", this); } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index 0fe6410b9401..f87a5a114a4c 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -1311,6 +1311,36 @@ public class GeckoSession { } }); } + + @WrapForJNI(calledFrom = "gecko") + private void onUpdateSessionStore(final GeckoBundle aBundle) { + ThreadUtils.runOnUiThread( + () -> { + final GeckoSession session = mOwner.get(); + if (session == null) { + return; + } + GeckoBundle scroll = aBundle.getBundle("scroll"); + if (scroll == null) { + scroll = new GeckoBundle(); + aBundle.putBundle("scroll", scroll); + } + + // Here we unfortunately need to do some re-mapping since `zoom` is passed in a separate + // bunds and we wish to keep the bundle format. + scroll.putBundle("zoom", aBundle.getBundle("zoom")); + final SessionState stateCache = session.mStateCache; + stateCache.updateSessionState(aBundle); + final SessionState state = new SessionState(stateCache); + if (!state.isEmpty()) { + final ProgressDelegate progressDelegate = session.getProgressDelegate(); + if (progressDelegate != null) { + progressDelegate.onSessionStateChange(session, state); + } else { + } + } + }); + } } private class Listener implements BundleEventListener { diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index be7c990d7ac3..325576f304c0 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -1455,6 +1455,24 @@ value: 15000 mirror: always +# Platform collection of data for session store +- name: browser.sessionstore.platform_collection + type: bool + value: @IS_NOT_ANDROID@ + mirror: once + +# Platform collection of session storage data for session store +- name: browser.sessionstore.collect_session_storage + type: bool + value: @IS_NOT_ANDROID@ + mirror: once + +# Platform collection of zoom data for session store +- name: browser.sessionstore.collect_zoom + type: bool + value: @IS_NOT_ANDROID@ + mirror: once + # Causes SessionStore to ignore non-final update messages from # browser tabs that were not caused by a flush from the parent. # This is a testing flag and should not be used by end-users. diff --git a/toolkit/components/sessionstore/PSessionStore.ipdl b/toolkit/components/sessionstore/PSessionStore.ipdl index 2f1e436de673..de0e9d75891a 100644 --- a/toolkit/components/sessionstore/PSessionStore.ipdl +++ b/toolkit/components/sessionstore/PSessionStore.ipdl @@ -9,6 +9,7 @@ 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 { @@ -31,8 +32,8 @@ parent: * collected incrementally. */ async SessionStoreUpdate( - nsCString? aDocShellCaps, bool? aPrivateMode, bool aNeedCollectSHistory, - uint32_t aEpoch); + nsCString? aDocShellCaps, bool? aPrivateMode, MaybeSessionStoreZoom aZoom, + bool aNeedCollectSHistory, uint32_t aEpoch); /** * Sends data to be stored to the session store. The collected data diff --git a/toolkit/components/sessionstore/SessionStoreChangeListener.cpp b/toolkit/components/sessionstore/SessionStoreChangeListener.cpp index 41124bb22f23..95a324bcfe9c 100644 --- a/toolkit/components/sessionstore/SessionStoreChangeListener.cpp +++ b/toolkit/components/sessionstore/SessionStoreChangeListener.cpp @@ -24,6 +24,7 @@ #include "nsPIDOMWindow.h" #include "nsTHashMap.h" #include "nsTHashtable.h" +#include "nsLayoutUtils.h" using namespace mozilla; using namespace mozilla::dom; @@ -31,6 +32,7 @@ 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"; @@ -120,14 +122,16 @@ SessionStoreChangeListener::HandleEvent(dom::Event* aEvent) { 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(SessionStoreUtils::NATIVE_LISTENER); + MOZ_RELEASE_ASSERT( + StaticPrefs::browser_sessionstore_platform_collection_AtStartup()); if (!aBrowsingContext) { return nullptr; } @@ -181,6 +185,29 @@ static void CollectFormData(Document* aDocument, } } +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(); @@ -189,6 +216,7 @@ void SessionStoreChangeListener::FlushSessionStore() { bool collectSessionHistory = false; bool collectWireFrame = false; + bool didResize = false; for (auto& iter : mSessionStoreChanges) { WindowContext* windowContext = iter.GetKey(); @@ -230,6 +258,10 @@ void SessionStoreChangeListener::FlushSessionStore() { collectSessionHistory = collectSessionHistory || changes.contains(Change::SessionHistory); + if (presShell && changes.contains(Change::Resize)) { + didResize = true; + } + mSessionStoreChild->IncrementalSessionStoreUpdate( browsingContext, maybeFormData, maybeScroll, mEpoch); } @@ -239,7 +271,13 @@ void SessionStoreChangeListener::FlushSessionStore() { } mSessionStoreChanges.Clear(); - mSessionStoreChild->UpdateSessionStore(collectSessionHistory); + + Maybe zoom; + if (didResize) { + GetZoom(mBrowsingContext->Top(), zoom); + } + + mSessionStoreChild->UpdateSessionStore(collectSessionHistory, zoom); } /* static */ @@ -314,6 +352,9 @@ 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; } } @@ -322,6 +363,9 @@ 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; diff --git a/toolkit/components/sessionstore/SessionStoreChangeListener.h b/toolkit/components/sessionstore/SessionStoreChangeListener.h index 3eba6f4c42cb..803a2137bd5a 100644 --- a/toolkit/components/sessionstore/SessionStoreChangeListener.h +++ b/toolkit/components/sessionstore/SessionStoreChangeListener.h @@ -56,7 +56,7 @@ class SessionStoreChangeListener final : public nsINamed, void FlushSessionStore(); - enum class Change { Input, Scroll, SessionHistory, WireFrame }; + enum class Change { Input, Scroll, SessionHistory, WireFrame, Resize }; static SessionStoreChangeListener* CollectSessionStoreData( WindowContext* aWindowContext, const EnumSet& aChanges); diff --git a/toolkit/components/sessionstore/SessionStoreChild.cpp b/toolkit/components/sessionstore/SessionStoreChild.cpp index 5574a9aca41a..f89dccd289e9 100644 --- a/toolkit/components/sessionstore/SessionStoreChild.cpp +++ b/toolkit/components/sessionstore/SessionStoreChild.cpp @@ -167,10 +167,12 @@ void SessionStoreChild::UpdateEventTargets() { } } -void SessionStoreChild::UpdateSessionStore(bool aSessionHistoryUpdate) { +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(), aSessionHistoryUpdate, 0); + SessionStoreUpdate(Nothing(), Nothing(), Nothing(), aSessionHistoryUpdate, + 0); return; } @@ -187,7 +189,7 @@ void SessionStoreChild::UpdateSessionStore(bool aSessionHistoryUpdate) { } SessionStoreUpdate( - docShellCaps, privatedMode, + docShellCaps, privatedMode, aZoom, store->GetAndClearSHistoryChanged() || aSessionHistoryUpdate, mSessionStoreListener->GetEpoch()); } @@ -216,14 +218,15 @@ mozilla::ipc::IPCResult SessionStoreChild::RecvFlushTabState( void SessionStoreChild::SessionStoreUpdate( const Maybe& aDocShellCaps, const Maybe& aPrivatedMode, - const bool aNeedCollectSHistory, const uint32_t& aEpoch) { + const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory, + const uint32_t& aEpoch) { if (XRE_IsContentProcess()) { - Unused << SendSessionStoreUpdate(aDocShellCaps, aPrivatedMode, + Unused << SendSessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom, aNeedCollectSHistory, aEpoch); } else if (SessionStoreParent* sessionStoreParent = static_cast( InProcessChild::ParentActorFor(this))) { - sessionStoreParent->SessionStoreUpdate(aDocShellCaps, aPrivatedMode, + sessionStoreParent->SessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom, aNeedCollectSHistory, aEpoch); } } diff --git a/toolkit/components/sessionstore/SessionStoreChild.h b/toolkit/components/sessionstore/SessionStoreChild.h index 60b5122efccc..606aa0f441c2 100644 --- a/toolkit/components/sessionstore/SessionStoreChild.h +++ b/toolkit/components/sessionstore/SessionStoreChild.h @@ -9,6 +9,7 @@ #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" @@ -31,12 +32,14 @@ class SessionStoreChild final : public PSessionStoreChild { void SetOwnerContent(Element* aElement); void Stop(); void UpdateEventTargets(); - void UpdateSessionStore(bool aSessionHistoryUpdate = false); + 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); diff --git a/toolkit/components/sessionstore/SessionStoreParent.cpp b/toolkit/components/sessionstore/SessionStoreParent.cpp index acb37b84ff40..372bf47ce6ce 100644 --- a/toolkit/components/sessionstore/SessionStoreParent.cpp +++ b/toolkit/components/sessionstore/SessionStoreParent.cpp @@ -25,6 +25,12 @@ #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; @@ -33,9 +39,81 @@ SessionStoreParent::SessionStoreParent( 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::RootedObject object(jsapi.cx()); + ErrorResult rv; + aFormData->ToJSON(jsapi.cx(), &object); + + JS::RootedValue value(jsapi.cx(), JS::ObjectValue(*object)); + + if (NS_FAILED(jni::BoxData(jsapi.cx(), value, formDataBundle, true))) { + JS_ClearPendingException(jsapi.cx()); + return; + } + } + + if (aScroll) { + JS::RootedObject object(jsapi.cx()); + ErrorResult rv; + aScroll->ToJSON(jsapi.cx(), &object); + JS::RootedValue 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(Get<0>(*aZoom))); + GECKOBUNDLE_START(displaySizeBundle); + GECKOBUNDLE_PUT(displaySizeBundle, "width", + java::sdk::Integer::ValueOf(Get<1>(*aZoom))); + GECKOBUNDLE_PUT(displaySizeBundle, "height", + java::sdk::Integer::ValueOf(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()) { @@ -54,11 +132,19 @@ static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext, RefPtr sessionStore = BrowserSessionStore::GetOrCreate(aBrowsingContext->Top()); - SessionStoreFormData* formData = sessionStore->GetFormdata(); - data.mFormdata.Construct(formData); + if (!aFormData) { + SessionStoreFormData* formData = sessionStore->GetFormdata(); + data.mFormdata.Construct(formData); + } else { + data.mFormdata.Construct(aFormData); + } - SessionStoreScrollData* scroll = sessionStore->GetScroll(); - data.mScroll.Construct(scroll); + if (!aScroll) { + SessionStoreScrollData* scroll = sessionStore->GetScroll(); + data.mScroll.Construct(scroll); + } else { + data.mScroll.Construct(aScroll); + } nsCOMPtr funcs = do_ImportModule( "resource://gre/modules/SessionStoreFunctions.jsm", fallible); @@ -83,6 +169,7 @@ static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext, Unused << funcs->UpdateSessionStore(nullptr, aBrowsingContext, key, aEpoch, aNeedCollectSHistory, update); } +#endif void SessionStoreParent::FlushAllSessionStoreChildren( const std::function& aDone) { @@ -158,13 +245,23 @@ void SessionStoreParent::FinalFlushAllSessionStoreChildren( mozilla::ipc::IPCResult SessionStoreParent::RecvSessionStoreUpdate( const Maybe& aDocShellCaps, const Maybe& aPrivatedMode, - const bool aNeedCollectSHistory, const uint32_t& aEpoch) { + const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory, + const uint32_t& aEpoch) { if (!mBrowsingContext) { return IPC_OK(); } - DoSessionStoreUpdate(mBrowsingContext, aDocShellCaps, aPrivatedMode, - aNeedCollectSHistory, aEpoch); + 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(); } @@ -173,6 +270,13 @@ mozilla::ipc::IPCResult SessionStoreParent::RecvIncrementalSessionStoreUpdate( 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); @@ -192,8 +296,9 @@ mozilla::ipc::IPCResult SessionStoreParent::RecvResetSessionStore( void SessionStoreParent::SessionStoreUpdate( const Maybe& aDocShellCaps, const Maybe& aPrivatedMode, - const bool aNeedCollectSHistory, const uint32_t& aEpoch) { - Unused << RecvSessionStoreUpdate(aDocShellCaps, aPrivatedMode, + const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory, + const uint32_t& aEpoch) { + Unused << RecvSessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom, aNeedCollectSHistory, aEpoch); } diff --git a/toolkit/components/sessionstore/SessionStoreParent.h b/toolkit/components/sessionstore/SessionStoreParent.h index 43d57616b417..0cf723985e03 100644 --- a/toolkit/components/sessionstore/SessionStoreParent.h +++ b/toolkit/components/sessionstore/SessionStoreParent.h @@ -12,6 +12,7 @@ #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; @@ -31,7 +32,8 @@ class SessionStoreParent final : public PSessionStoreParent { */ mozilla::ipc::IPCResult RecvSessionStoreUpdate( const Maybe& aDocShellCaps, const Maybe& aPrivatedMode, - const bool aNeedCollectSHistory, const uint32_t& aEpoch); + const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory, + const uint32_t& aEpoch); mozilla::ipc::IPCResult RecvIncrementalSessionStoreUpdate( const MaybeDiscarded& aBrowsingContext, @@ -48,6 +50,7 @@ class SessionStoreParent final : public PSessionStoreParent { friend class SessionStoreChild; void SessionStoreUpdate(const Maybe& aDocShellCaps, const Maybe& aPrivatedMode, + const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory, const uint32_t& aEpoch); @@ -65,6 +68,9 @@ class SessionStoreParent final : public PSessionStoreParent { already_AddRefed FlushSessionStore(); + bool mHasNewFormData = false; + bool mHasNewScrollPosition = false; + RefPtr mBrowsingContext; RefPtr mSessionStore; }; diff --git a/toolkit/components/sessionstore/SessionStoreScrollData.h b/toolkit/components/sessionstore/SessionStoreScrollData.h index 95eee80e4383..cf9c75803a8b 100644 --- a/toolkit/components/sessionstore/SessionStoreScrollData.h +++ b/toolkit/components/sessionstore/SessionStoreScrollData.h @@ -7,17 +7,24 @@ #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 = mozilla::Tuple; +using MaybeSessionStoreZoom = + mozilla::Maybe>; class SessionStoreScrollData final : public nsISupports, public nsWrapperCache, @@ -53,6 +60,7 @@ class SessionStoreScrollData final : public nsISupports, ~SessionStoreScrollData() = default; nsPoint mScroll; + MaybeSessionStoreZoom mZoom; nsTArray> mChildren; }; diff --git a/toolkit/components/sessionstore/SessionStoreUtils.h b/toolkit/components/sessionstore/SessionStoreUtils.h index 9db8a2cb5e28..e43155d53b0d 100644 --- a/toolkit/components/sessionstore/SessionStoreUtils.h +++ b/toolkit/components/sessionstore/SessionStoreUtils.h @@ -139,15 +139,8 @@ class SessionStoreUtils { const nsTArray& aValues, Record>& aStorage); -#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_THUNDERBIRD) || \ - defined(MOZ_SUITE) - static constexpr bool NATIVE_LISTENER = false; -#else - static constexpr bool NATIVE_LISTENER = true; -#endif - - static bool CopyProperty(JSContext* aCx, JS::Handle aDst, - JS::Handle aSrc, const nsAString& aName); + static bool CopyProperty(JSContext* aCx, JS::HandleObject aDst, + JS::HandleObject aSrc, const nsAString& aName); template static bool CopyChildren(JSContext* aCx, JS::Handle aDst, diff --git a/widget/android/EventDispatcher.cpp b/widget/android/EventDispatcher.cpp index 902b1362c5e3..bbd1956469d6 100644 --- a/widget/android/EventDispatcher.cpp +++ b/widget/android/EventDispatcher.cpp @@ -19,6 +19,7 @@ #include "mozilla/ScopeExit.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/java/EventCallbackWrappers.h" +#include "mozilla/jni/GeckoBundleUtils.h" // Disable the C++ 2a warning. See bug #1509926 #if defined(__clang__) @@ -38,266 +39,9 @@ bool CheckJS(JSContext* aCx, bool aResult) { return aResult; } -nsresult BoxString(JSContext* aCx, JS::HandleValue aData, - jni::Object::LocalRef& aOut) { - if (aData.isNullOrUndefined()) { - aOut = nullptr; - return NS_OK; - } - - MOZ_ASSERT(aData.isString()); - - JS::RootedString str(aCx, aData.toString()); - - if (JS::StringHasLatin1Chars(str)) { - nsAutoJSString autoStr; - NS_ENSURE_TRUE(CheckJS(aCx, autoStr.init(aCx, str)), NS_ERROR_FAILURE); - - // StringParam can automatically convert a nsString to jstring. - aOut = jni::StringParam(autoStr, aOut.Env(), fallible); - if (!aOut) { - return NS_ERROR_FAILURE; - } - return NS_OK; - } - - // Two-byte string - JNIEnv* const env = aOut.Env(); - const char16_t* chars; - { - JS::AutoCheckCannotGC nogc; - size_t len = 0; - chars = JS_GetTwoByteStringCharsAndLength(aCx, nogc, str, &len); - if (chars) { - aOut = jni::String::LocalRef::Adopt( - env, env->NewString(reinterpret_cast(chars), len)); - } - } - if (NS_WARN_IF(!CheckJS(aCx, !!chars) || !aOut)) { - env->ExceptionClear(); - return NS_ERROR_FAILURE; - } - return NS_OK; -} - -nsresult BoxObject(JSContext* aCx, JS::HandleValue aData, - jni::Object::LocalRef& aOut); - -template -nsresult BoxArrayPrimitive(JSContext* aCx, JS::HandleObject aData, - jni::Object::LocalRef& aOut, size_t aLength, - JS::HandleValue aElement) { - JS::RootedValue element(aCx); - auto data = MakeUnique(aLength); - data[0] = (aElement.get().*ToType)(); - - for (size_t i = 1; i < aLength; i++) { - NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)), - NS_ERROR_FAILURE); - NS_ENSURE_TRUE((element.get().*IsType)(), NS_ERROR_INVALID_ARG); - - data[i] = (element.get().*ToType)(); - } - aOut = (*NewArray)(data.get(), aLength); - return NS_OK; -} - -template -nsresult BoxArrayObject(JSContext* aCx, JS::HandleObject aData, - jni::Object::LocalRef& aOut, size_t aLength, - JS::HandleValue aElement, IsType&& aIsType) { - auto out = jni::ObjectArray::New(aLength); - JS::RootedValue element(aCx); - jni::Object::LocalRef jniElement(aOut.Env()); - - nsresult rv = (*Box)(aCx, aElement, jniElement); - NS_ENSURE_SUCCESS(rv, rv); - out->SetElement(0, jniElement); - - for (size_t i = 1; i < aLength; i++) { - NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)), - NS_ERROR_FAILURE); - NS_ENSURE_TRUE(element.isNullOrUndefined() || aIsType(element), - NS_ERROR_INVALID_ARG); - - rv = (*Box)(aCx, element, jniElement); - NS_ENSURE_SUCCESS(rv, rv); - out->SetElement(i, jniElement); - } - aOut = out; - return NS_OK; -} - -nsresult BoxArray(JSContext* aCx, JS::HandleObject aData, - jni::Object::LocalRef& aOut) { - uint32_t length = 0; - NS_ENSURE_TRUE(CheckJS(aCx, JS::GetArrayLength(aCx, aData, &length)), - NS_ERROR_FAILURE); - - if (!length) { - // Always represent empty arrays as an empty boolean array. - aOut = java::GeckoBundle::EMPTY_BOOLEAN_ARRAY(); - return NS_OK; - } - - // We only check the first element's type. If the array has mixed types, - // we'll throw an error during actual conversion. - JS::RootedValue element(aCx); - NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, 0, &element)), - NS_ERROR_FAILURE); - - if (element.isBoolean()) { - return BoxArrayPrimitive( - aCx, aData, aOut, length, element); - } - - if (element.isInt32()) { - nsresult rv = - BoxArrayPrimitive(aCx, aData, aOut, - length, element); - if (rv != NS_ERROR_INVALID_ARG) { - return rv; - } - // Not int32, but we can still try a double array. - } - - if (element.isNumber()) { - return BoxArrayPrimitive( - aCx, aData, aOut, length, element); - } - - if (element.isNullOrUndefined() || element.isString()) { - const auto isString = [](JS::HandleValue val) -> bool { - return val.isString(); - }; - nsresult rv = BoxArrayObject( - aCx, aData, aOut, length, element, isString); - if (element.isString() || rv != NS_ERROR_INVALID_ARG) { - return rv; - } - // First element was null/undefined, so it may still be an object array. - } - - const auto isObject = [aCx](JS::HandleValue val) -> bool { - if (!val.isObject()) { - return false; - } - bool array = false; - JS::RootedObject obj(aCx, &val.toObject()); - // We don't support array of arrays. - return CheckJS(aCx, JS::IsArrayObject(aCx, obj, &array)) && !array; - }; - - if (element.isNullOrUndefined() || isObject(element)) { - return BoxArrayObject( - aCx, aData, aOut, length, element, isObject); - } - - NS_WARNING("Unknown type"); - return NS_ERROR_INVALID_ARG; -} - -nsresult BoxValue(JSContext* aCx, JS::HandleValue aData, - jni::Object::LocalRef& aOut); - -nsresult BoxObject(JSContext* aCx, JS::HandleValue aData, - jni::Object::LocalRef& aOut) { - if (aData.isNullOrUndefined()) { - aOut = nullptr; - return NS_OK; - } - - MOZ_ASSERT(aData.isObject()); - - JS::Rooted ids(aCx, JS::IdVector(aCx)); - JS::RootedObject obj(aCx, &aData.toObject()); - - bool isArray = false; - if (CheckJS(aCx, JS::IsArrayObject(aCx, obj, &isArray)) && isArray) { - return BoxArray(aCx, obj, aOut); - } - - NS_ENSURE_TRUE(CheckJS(aCx, JS_Enumerate(aCx, obj, &ids)), NS_ERROR_FAILURE); - - const size_t length = ids.length(); - auto keys = jni::ObjectArray::New(length); - auto values = jni::ObjectArray::New(length); - - // Iterate through each property of the JS object. - for (size_t i = 0; i < ids.length(); i++) { - const JS::RootedId id(aCx, ids[i]); - JS::RootedValue idVal(aCx); - JS::RootedValue val(aCx); - jni::Object::LocalRef key(aOut.Env()); - jni::Object::LocalRef value(aOut.Env()); - - NS_ENSURE_TRUE(CheckJS(aCx, JS_IdToValue(aCx, id, &idVal)), - NS_ERROR_FAILURE); - - JS::RootedString idStr(aCx, JS::ToString(aCx, idVal)); - NS_ENSURE_TRUE(CheckJS(aCx, !!idStr), NS_ERROR_FAILURE); - - idVal.setString(idStr); - NS_ENSURE_SUCCESS(BoxString(aCx, idVal, key), NS_ERROR_FAILURE); - NS_ENSURE_TRUE(CheckJS(aCx, JS_GetPropertyById(aCx, obj, id, &val)), - NS_ERROR_FAILURE); - - nsresult rv = BoxValue(aCx, val, value); - if (rv == NS_ERROR_INVALID_ARG && !JS_IsExceptionPending(aCx)) { - nsAutoJSString autoStr; - if (CheckJS(aCx, autoStr.init(aCx, idVal.toString()))) { - JS_ReportErrorUTF8(aCx, u8"Invalid event data property %s", - NS_ConvertUTF16toUTF8(autoStr).get()); - } - } - NS_ENSURE_SUCCESS(rv, rv); - - keys->SetElement(i, key); - values->SetElement(i, value); - } - - aOut = java::GeckoBundle::New(keys, values); - return NS_OK; -} - -nsresult BoxValue(JSContext* aCx, JS::HandleValue aData, - jni::Object::LocalRef& aOut) { - if (aData.isNullOrUndefined()) { - aOut = nullptr; - } else if (aData.isBoolean()) { - aOut = aData.toBoolean() ? java::sdk::Boolean::TRUE() - : java::sdk::Boolean::FALSE(); - } else if (aData.isInt32()) { - aOut = java::sdk::Integer::ValueOf(aData.toInt32()); - } else if (aData.isNumber()) { - aOut = java::sdk::Double::New(aData.toNumber()); - } else if (aData.isString()) { - return BoxString(aCx, aData, aOut); - } else if (aData.isObject()) { - return BoxObject(aCx, aData, aOut); - } else { - NS_WARNING("Unknown type"); - return NS_ERROR_INVALID_ARG; - } - return NS_OK; -} - nsresult BoxData(const nsAString& aEvent, JSContext* aCx, JS::HandleValue aData, jni::Object::LocalRef& aOut, bool aObjectOnly) { - nsresult rv = NS_ERROR_INVALID_ARG; - - if (!aObjectOnly) { - rv = BoxValue(aCx, aData, aOut); - } else if (aData.isObject() || aData.isNullOrUndefined()) { - rv = BoxObject(aCx, aData, aOut); - } + nsresult rv = jni::BoxData(aCx, aData, aOut, aObjectOnly); if (rv != NS_ERROR_INVALID_ARG) { return rv; } diff --git a/widget/android/GeckoViewSupport.h b/widget/android/GeckoViewSupport.h index db62d933a568..569e87a204f1 100644 --- a/widget/android/GeckoViewSupport.h +++ b/widget/android/GeckoViewSupport.h @@ -91,6 +91,8 @@ class GeckoViewSupport final void OnShowDynamicToolbar() const; + void OnUpdateSessionStore(mozilla::jni::Object::Param aBundle); + void PassExternalResponse(java::WebResponse::Param aResponse); void AttachMediaSessionController( diff --git a/widget/android/jni/GeckoBundleUtils.cpp b/widget/android/jni/GeckoBundleUtils.cpp new file mode 100644 index 000000000000..55c4e47857c0 --- /dev/null +++ b/widget/android/jni/GeckoBundleUtils.cpp @@ -0,0 +1,289 @@ +/* -*- 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/jni/GeckoBundleUtils.h" + +#include "JavaBuiltins.h" +#include "js/Warnings.h" +#include "nsJSUtils.h" + +#include "js/Array.h" + +namespace mozilla::jni { +namespace detail { +bool CheckJS(JSContext* aCx, bool aResult) { + if (!aResult) { + JS_ClearPendingException(aCx); + } + return aResult; +} + +nsresult BoxString(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut) { + if (aData.isNullOrUndefined()) { + aOut = nullptr; + return NS_OK; + } + + MOZ_ASSERT(aData.isString()); + + JS::RootedString str(aCx, aData.toString()); + + if (JS::StringHasLatin1Chars(str)) { + nsAutoJSString autoStr; + NS_ENSURE_TRUE(CheckJS(aCx, autoStr.init(aCx, str)), NS_ERROR_FAILURE); + + // StringParam can automatically convert a nsString to jstring. + aOut = jni::StringParam(autoStr, aOut.Env(), fallible); + if (!aOut) { + return NS_ERROR_FAILURE; + } + return NS_OK; + } + + // Two-byte string + JNIEnv* const env = aOut.Env(); + const char16_t* chars; + { + JS::AutoCheckCannotGC nogc; + size_t len = 0; + chars = JS_GetTwoByteStringCharsAndLength(aCx, nogc, str, &len); + if (chars) { + aOut = jni::String::LocalRef::Adopt( + env, env->NewString(reinterpret_cast(chars), len)); + } + } + if (NS_WARN_IF(!CheckJS(aCx, !!chars) || !aOut)) { + env->ExceptionClear(); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult BoxObject(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut); + +template +nsresult BoxArrayPrimitive(JSContext* aCx, JS::HandleObject aData, + jni::Object::LocalRef& aOut, size_t aLength, + JS::HandleValue aElement) { + JS::RootedValue element(aCx); + auto data = MakeUnique(aLength); + data[0] = (aElement.get().*ToType)(); + + for (size_t i = 1; i < aLength; i++) { + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)), + NS_ERROR_FAILURE); + NS_ENSURE_TRUE((element.get().*IsType)(), NS_ERROR_INVALID_ARG); + + data[i] = (element.get().*ToType)(); + } + aOut = (*NewArray)(data.get(), aLength); + return NS_OK; +} + +template +nsresult BoxArrayObject(JSContext* aCx, JS::HandleObject aData, + jni::Object::LocalRef& aOut, size_t aLength, + JS::HandleValue aElement, IsType&& aIsType) { + auto out = jni::ObjectArray::New(aLength); + JS::RootedValue element(aCx); + jni::Object::LocalRef jniElement(aOut.Env()); + + nsresult rv = (*Box)(aCx, aElement, jniElement); + NS_ENSURE_SUCCESS(rv, rv); + out->SetElement(0, jniElement); + + for (size_t i = 1; i < aLength; i++) { + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)), + NS_ERROR_FAILURE); + NS_ENSURE_TRUE(element.isNullOrUndefined() || aIsType(element), + NS_ERROR_INVALID_ARG); + + rv = (*Box)(aCx, element, jniElement); + NS_ENSURE_SUCCESS(rv, rv); + out->SetElement(i, jniElement); + } + aOut = out; + return NS_OK; +} + +nsresult BoxArray(JSContext* aCx, JS::HandleObject aData, + jni::Object::LocalRef& aOut) { + uint32_t length = 0; + NS_ENSURE_TRUE(CheckJS(aCx, JS::GetArrayLength(aCx, aData, &length)), + NS_ERROR_FAILURE); + + if (!length) { + // Always represent empty arrays as an empty boolean array. + aOut = java::GeckoBundle::EMPTY_BOOLEAN_ARRAY(); + return NS_OK; + } + + // We only check the first element's type. If the array has mixed types, + // we'll throw an error during actual conversion. + JS::RootedValue element(aCx); + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, 0, &element)), + NS_ERROR_FAILURE); + + if (element.isBoolean()) { + return BoxArrayPrimitive( + aCx, aData, aOut, length, element); + } + + if (element.isInt32()) { + nsresult rv = + BoxArrayPrimitive(aCx, aData, aOut, + length, element); + if (rv != NS_ERROR_INVALID_ARG) { + return rv; + } + // Not int32, but we can still try a double array. + } + + if (element.isNumber()) { + return BoxArrayPrimitive( + aCx, aData, aOut, length, element); + } + + if (element.isNullOrUndefined() || element.isString()) { + const auto isString = [](JS::HandleValue val) -> bool { + return val.isString(); + }; + nsresult rv = BoxArrayObject( + aCx, aData, aOut, length, element, isString); + if (element.isString() || rv != NS_ERROR_INVALID_ARG) { + return rv; + } + // First element was null/undefined, so it may still be an object array. + } + + const auto isObject = [aCx](JS::HandleValue val) -> bool { + if (!val.isObject()) { + return false; + } + bool array = false; + JS::RootedObject obj(aCx, &val.toObject()); + // We don't support array of arrays. + return CheckJS(aCx, JS::IsArrayObject(aCx, obj, &array)) && !array; + }; + + if (element.isNullOrUndefined() || isObject(element)) { + return BoxArrayObject( + aCx, aData, aOut, length, element, isObject); + } + + NS_WARNING("Unknown type"); + return NS_ERROR_INVALID_ARG; +} + +nsresult BoxValue(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut); + +nsresult BoxObject(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut) { + if (aData.isNullOrUndefined()) { + aOut = nullptr; + return NS_OK; + } + + MOZ_ASSERT(aData.isObject()); + + JS::Rooted ids(aCx, JS::IdVector(aCx)); + JS::RootedObject obj(aCx, &aData.toObject()); + + bool isArray = false; + if (CheckJS(aCx, JS::IsArrayObject(aCx, obj, &isArray)) && isArray) { + return BoxArray(aCx, obj, aOut); + } + + NS_ENSURE_TRUE(CheckJS(aCx, JS_Enumerate(aCx, obj, &ids)), NS_ERROR_FAILURE); + + const size_t length = ids.length(); + auto keys = jni::ObjectArray::New(length); + auto values = jni::ObjectArray::New(length); + + // Iterate through each property of the JS object. + for (size_t i = 0; i < ids.length(); i++) { + const JS::RootedId id(aCx, ids[i]); + JS::RootedValue idVal(aCx); + JS::RootedValue val(aCx); + jni::Object::LocalRef key(aOut.Env()); + jni::Object::LocalRef value(aOut.Env()); + + NS_ENSURE_TRUE(CheckJS(aCx, JS_IdToValue(aCx, id, &idVal)), + NS_ERROR_FAILURE); + + JS::RootedString idStr(aCx, JS::ToString(aCx, idVal)); + NS_ENSURE_TRUE(CheckJS(aCx, !!idStr), NS_ERROR_FAILURE); + + idVal.setString(idStr); + NS_ENSURE_SUCCESS(BoxString(aCx, idVal, key), NS_ERROR_FAILURE); + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetPropertyById(aCx, obj, id, &val)), + NS_ERROR_FAILURE); + + nsresult rv = BoxValue(aCx, val, value); + if (rv == NS_ERROR_INVALID_ARG && !JS_IsExceptionPending(aCx)) { + nsAutoJSString autoStr; + if (CheckJS(aCx, autoStr.init(aCx, idVal.toString()))) { + JS_ReportErrorUTF8(aCx, "Invalid event data property %s", + NS_ConvertUTF16toUTF8(autoStr).get()); + } + } + NS_ENSURE_SUCCESS(rv, rv); + + keys->SetElement(i, key); + values->SetElement(i, value); + } + + aOut = java::GeckoBundle::New(keys, values); + return NS_OK; +} + +nsresult BoxValue(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut) { + if (aData.isNullOrUndefined()) { + aOut = nullptr; + } else if (aData.isBoolean()) { + aOut = aData.toBoolean() ? java::sdk::Boolean::TRUE() + : java::sdk::Boolean::FALSE(); + } else if (aData.isInt32()) { + aOut = java::sdk::Integer::ValueOf(aData.toInt32()); + } else if (aData.isNumber()) { + aOut = java::sdk::Double::New(aData.toNumber()); + } else if (aData.isString()) { + return BoxString(aCx, aData, aOut); + } else if (aData.isObject()) { + return BoxObject(aCx, aData, aOut); + } else { + NS_WARNING("Unknown type"); + return NS_ERROR_INVALID_ARG; + } + return NS_OK; +} + +} // namespace detail + +nsresult BoxData(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut, bool aObjectOnly) { + nsresult rv = NS_ERROR_INVALID_ARG; + + if (!aObjectOnly) { + rv = detail::BoxValue(aCx, aData, aOut); + } else if (aData.isObject() || aData.isNullOrUndefined()) { + rv = detail::BoxObject(aCx, aData, aOut); + } + + return rv; +} +} // namespace mozilla::jni diff --git a/widget/android/jni/GeckoBundleUtils.h b/widget/android/jni/GeckoBundleUtils.h index df62b0c7601e..edc570b4bb3e 100644 --- a/widget/android/jni/GeckoBundleUtils.h +++ b/widget/android/jni/GeckoBundleUtils.h @@ -9,6 +9,8 @@ #include "mozilla/java/GeckoBundleWrappers.h" +#include "jsapi.h" + namespace mozilla { namespace jni { @@ -35,6 +37,9 @@ namespace jni { auto name = \ mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues); +nsresult BoxData(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut, bool aObjectOnly); + } // namespace jni } // namespace mozilla diff --git a/widget/android/jni/moz.build b/widget/android/jni/moz.build index 2651f0540fda..b3db8f730a19 100644 --- a/widget/android/jni/moz.build +++ b/widget/android/jni/moz.build @@ -21,6 +21,7 @@ EXPORTS.mozilla.jni += [ UNIFIED_SOURCES += [ "Conversions.cpp", + "GeckoBundleUtils.cpp", "Utils.cpp", ] diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index ccc40eaa51b1..0aa3dd0fcf47 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -2093,6 +2093,15 @@ RefPtr> nsWindow::OnLoadRequest( : nullptr; } +void nsWindow::OnUpdateSessionStore(mozilla::jni::Object::Param aBundle) { + auto geckoViewSupport(mGeckoViewSupport.Access()); + if (!geckoViewSupport) { + return; + } + + geckoViewSupport->OnUpdateSessionStore(aBundle); +} + float nsWindow::GetDPI() { float dpi = 160.0f; @@ -2430,6 +2439,16 @@ void nsWindow::ShowDynamicToolbar() { acc->OnShowDynamicToolbar(); } +void GeckoViewSupport::OnUpdateSessionStore( + mozilla::jni::Object::Param aBundle) { + GeckoSession::Window::LocalRef window(mGeckoViewWindow); + if (!window) { + return; + } + + window->OnUpdateSessionStore(aBundle); +} + void nsWindow::OnSizeChanged(const gfx::IntSize& aSize) { ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width, aSize.height); diff --git a/widget/android/nsWindow.h b/widget/android/nsWindow.h index bce46a681662..9e8c358c69c0 100644 --- a/widget/android/nsWindow.h +++ b/widget/android/nsWindow.h @@ -69,6 +69,8 @@ class nsWindow final : public nsBaseWidget { nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture, bool aIsTopLevel); + void OnUpdateSessionStore(mozilla::jni::Object::Param aBundle); + private: // Unique ID given to each widget, used to map Surfaces to widgets // in the CompositorSurfaceManager.