Bug 1734394 - Make Geckoview use the session store collector r=geckoview-reviewers,agi,farre

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
This commit is contained in:
Cathy Lu
2022-06-23 16:44:32 +00:00
parent 763f918deb
commit eb6e2ad0ed
26 changed files with 589 additions and 298 deletions

View File

@@ -1098,6 +1098,10 @@ pref("browser.sessionstore.upgradeBackup.maxUpgradeBackups", 3);
pref("browser.sessionstore.debug", false); pref("browser.sessionstore.debug", false);
// Forget closed windows/tabs after two weeks // Forget closed windows/tabs after two weeks
pref("browser.sessionstore.cleanup.forget_closed_after", 1209600000); 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. // Don't quit the browser when Ctrl + Q is pressed.
pref("browser.quitShortcut.disabled", false); pref("browser.quitShortcut.disabled", false);

View File

@@ -2441,7 +2441,7 @@ nsresult CanonicalBrowsingContext::WriteSessionStorageToSessionStore(
void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage( void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage(
const std::function<void()>& aDone) { const std::function<void()>& aDone) {
if constexpr (!SessionStoreUtils::NATIVE_LISTENER) { if (!StaticPrefs::browser_sessionstore_collect_session_storage_AtStartup()) {
aDone(); aDone();
return; return;
} }
@@ -2474,7 +2474,7 @@ void CanonicalBrowsingContext::UpdateSessionStoreForStorage(
} }
void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() { void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() {
if constexpr (!SessionStoreUtils::NATIVE_LISTENER) { if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
return; return;
} }

View File

@@ -5791,7 +5791,7 @@ nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest,
} }
} }
if constexpr (SessionStoreUtils::NATIVE_LISTENER) { if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
if (IsForceReloadType(mLoadType)) { if (IsForceReloadType(mLoadType)) {
if (WindowContext* windowContext = if (WindowContext* windowContext =
mBrowsingContext->GetCurrentWindowContext()) { mBrowsingContext->GetCurrentWindowContext()) {
@@ -6535,7 +6535,7 @@ nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
// incorrectly overrides session store data from the following load. // incorrectly overrides session store data from the following load.
return NS_OK; return NS_OK;
} }
if constexpr (SessionStoreUtils::NATIVE_LISTENER) { if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
if (WindowContext* windowContext = if (WindowContext* windowContext =
mBrowsingContext->GetCurrentWindowContext()) { mBrowsingContext->GetCurrentWindowContext()) {
using Change = SessionStoreChangeListener::Change; using Change = SessionStoreChangeListener::Change;

View File

@@ -3089,7 +3089,7 @@ nsresult nsFrameLoader::EnsureMessageManager() {
NS_ENSURE_TRUE(mChildMessageManager, NS_ERROR_UNEXPECTED); NS_ENSURE_TRUE(mChildMessageManager, NS_ERROR_UNEXPECTED);
// Set up session store // Set up session store
if constexpr (SessionStoreUtils::NATIVE_LISTENER) { if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
if (XRE_IsParentProcess() && mIsTopLevelContent) { if (XRE_IsParentProcess() && mIsTopLevelContent) {
mSessionStoreChild = SessionStoreChild::GetOrCreate( mSessionStoreChild = SessionStoreChild::GetOrCreate(
GetExtantBrowsingContext(), mOwnerContent); GetExtantBrowsingContext(), mOwnerContent);

View File

@@ -26,6 +26,18 @@ interface SessionStoreFormData {
object toJSON(); object toJSON();
}; };
[GenerateConversionToJS]
dictionary SessionStoreDisplaySize {
unsigned long width;
unsigned long height;
};
[GenerateConversionToJS]
dictionary SessionStoreZoomData {
double resolution;
SessionStoreDisplaySize displaySize;
};
[ChromeOnly, Exposed=Window] [ChromeOnly, Exposed=Window]
interface SessionStoreScrollData { interface SessionStoreScrollData {
[Cached, Pure] [Cached, Pure]

View File

@@ -519,7 +519,7 @@ nsresult BrowserChild::Init(mozIDOMWindowProxy* aParent,
mIPCOpen = true; mIPCOpen = true;
if constexpr (SessionStoreUtils::NATIVE_LISTENER) { if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
mSessionStoreChild = SessionStoreChild::GetOrCreate(mBrowsingContext); mSessionStoreChild = SessionStoreChild::GetOrCreate(mBrowsingContext);
} }

View File

@@ -918,7 +918,7 @@ void BackgroundSessionStorageManager::SetCurrentBrowsingContextId(
} }
void BackgroundSessionStorageManager::MaybeScheduleSessionStoreUpdate() { void BackgroundSessionStorageManager::MaybeScheduleSessionStoreUpdate() {
if constexpr (!SessionStoreUtils::NATIVE_LISTENER) { if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
return; return;
} }

View File

@@ -28,6 +28,7 @@ const DEFAULT_INTERVAL_MS = 1500;
const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates"; const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
const PREF_INTERVAL = "browser.sessionstore.interval"; const PREF_INTERVAL = "browser.sessionstore.interval";
const PREF_SESSION_COLLECTION = "browser.sessionstore.platform_collection";
class Handler { class Handler {
constructor(store) { constructor(store) {
@@ -424,6 +425,10 @@ class MessageQueue extends Handler {
TIMEOUT_DISABLED_PREF, TIMEOUT_DISABLED_PREF,
false false
); );
this.sessionCollection = Services.prefs.getBoolPref(
PREF_SESSION_COLLECTION,
false
);
this._timeoutWaitIdlePeriodMs = Services.prefs.getIntPref( this._timeoutWaitIdlePeriodMs = Services.prefs.getIntPref(
PREF_INTERVAL, PREF_INTERVAL,
DEFAULT_INTERVAL_MS DEFAULT_INTERVAL_MS
@@ -599,12 +604,16 @@ class SessionStateAggregator extends GeckoViewChildModule {
this.stateChangeNotifier = new StateChangeNotifier(this); this.stateChangeNotifier = new StateChangeNotifier(this);
this.handlers = [ this.handlers = [
new FormDataListener(this),
new SessionHistoryListener(this), new SessionHistoryListener(this),
new ScrollPositionListener(this),
this.stateChangeNotifier, this.stateChangeNotifier,
this.messageQueue, this.messageQueue,
]; ];
if (!this.sessionCollection) {
this.handlers.push(
new FormDataListener(this),
new ScrollPositionListener(this)
);
}
this.messageManager.addMessageListener("GeckoView:FlushSessionState", this); this.messageManager.addMessageListener("GeckoView:FlushSessionState", this);
} }

View File

@@ -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 { private class Listener implements BundleEventListener {

View File

@@ -1454,6 +1454,24 @@
value: 15000 value: 15000
mirror: always mirror: always
# Platform collection of data for session store
- name: browser.sessionstore.platform_collection
type: bool
value: false
mirror: once
# Platform collection of session storage data for session store
- name: browser.sessionstore.collect_session_storage
type: bool
value: false
mirror: once
# Platform collection of zoom data for session store
- name: browser.sessionstore.collect_zoom
type: bool
value: false
mirror: once
# Causes SessionStore to ignore non-final update messages from # Causes SessionStore to ignore non-final update messages from
# browser tabs that were not caused by a flush from the parent. # 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. # This is a testing flag and should not be used by end-users.

View File

@@ -9,6 +9,7 @@ include protocol PInProcess;
include SessionStoreTypes; include SessionStoreTypes;
using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h"; using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h";
using mozilla::dom::MaybeSessionStoreZoom from "mozilla/dom/SessionStoreScrollData.h";
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@@ -31,8 +32,8 @@ parent:
* collected incrementally. * collected incrementally.
*/ */
async SessionStoreUpdate( async SessionStoreUpdate(
nsCString? aDocShellCaps, bool? aPrivateMode, bool aNeedCollectSHistory, nsCString? aDocShellCaps, bool? aPrivateMode, MaybeSessionStoreZoom aZoom,
uint32_t aEpoch); bool aNeedCollectSHistory, uint32_t aEpoch);
/** /**
* Sends data to be stored to the session store. The collected data * Sends data to be stored to the session store. The collected data

View File

@@ -24,6 +24,7 @@
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "nsTHashMap.h" #include "nsTHashMap.h"
#include "nsTHashtable.h" #include "nsTHashtable.h"
#include "nsLayoutUtils.h"
using namespace mozilla; using namespace mozilla;
using namespace mozilla::dom; using namespace mozilla::dom;
@@ -31,6 +32,7 @@ using namespace mozilla::dom;
namespace { namespace {
constexpr auto kInput = u"input"_ns; constexpr auto kInput = u"input"_ns;
constexpr auto kScroll = u"mozvisualscroll"_ns; constexpr auto kScroll = u"mozvisualscroll"_ns;
constexpr auto kResize = u"mozvisualresize"_ns;
static constexpr char kNoAutoUpdates[] = static constexpr char kNoAutoUpdates[] =
"browser.sessionstore.debug.no_auto_updates"; "browser.sessionstore.debug.no_auto_updates";
@@ -120,14 +122,16 @@ SessionStoreChangeListener::HandleEvent(dom::Event* aEvent) {
RecordChange(windowContext, Change::Input); RecordChange(windowContext, Change::Input);
} else if (eventType == kScroll) { } else if (eventType == kScroll) {
RecordChange(windowContext, Change::Scroll); RecordChange(windowContext, Change::Scroll);
} else if (eventType == kResize && browsingContext->IsTop()) {
RecordChange(windowContext, Change::Resize);
} }
return NS_OK; return NS_OK;
} }
/* static */ already_AddRefed<SessionStoreChangeListener> /* static */ already_AddRefed<SessionStoreChangeListener>
SessionStoreChangeListener::Create(BrowsingContext* aBrowsingContext) { SessionStoreChangeListener::Create(BrowsingContext* aBrowsingContext) {
MOZ_RELEASE_ASSERT(SessionStoreUtils::NATIVE_LISTENER); MOZ_RELEASE_ASSERT(
StaticPrefs::browser_sessionstore_platform_collection_AtStartup());
if (!aBrowsingContext) { if (!aBrowsingContext) {
return nullptr; return nullptr;
} }
@@ -181,6 +185,29 @@ static void CollectFormData(Document* aDocument,
} }
} }
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() { void SessionStoreChangeListener::FlushSessionStore() {
if (mTimer) { if (mTimer) {
mTimer->Cancel(); mTimer->Cancel();
@@ -189,6 +216,7 @@ void SessionStoreChangeListener::FlushSessionStore() {
bool collectSessionHistory = false; bool collectSessionHistory = false;
bool collectWireFrame = false; bool collectWireFrame = false;
bool didResize = false;
for (auto& iter : mSessionStoreChanges) { for (auto& iter : mSessionStoreChanges) {
WindowContext* windowContext = iter.GetKey(); WindowContext* windowContext = iter.GetKey();
@@ -230,6 +258,10 @@ void SessionStoreChangeListener::FlushSessionStore() {
collectSessionHistory = collectSessionHistory =
collectSessionHistory || changes.contains(Change::SessionHistory); collectSessionHistory || changes.contains(Change::SessionHistory);
if (presShell && changes.contains(Change::Resize)) {
didResize = true;
}
mSessionStoreChild->IncrementalSessionStoreUpdate( mSessionStoreChild->IncrementalSessionStoreUpdate(
browsingContext, maybeFormData, maybeScroll, mEpoch); browsingContext, maybeFormData, maybeScroll, mEpoch);
} }
@@ -239,7 +271,13 @@ void SessionStoreChangeListener::FlushSessionStore() {
} }
mSessionStoreChanges.Clear(); mSessionStoreChanges.Clear();
mSessionStoreChild->UpdateSessionStore(collectSessionHistory);
Maybe<SessionStoreZoom> zoom;
if (didResize) {
GetZoom(mBrowsingContext->Top(), zoom);
}
mSessionStoreChild->UpdateSessionStore(collectSessionHistory, zoom);
} }
/* static */ /* static */
@@ -314,6 +352,9 @@ void SessionStoreChangeListener::AddEventListeners() {
if (EventTarget* target = GetEventTarget()) { if (EventTarget* target = GetEventTarget()) {
target->AddSystemEventListener(kInput, this, false); target->AddSystemEventListener(kInput, this, false);
target->AddSystemEventListener(kScroll, this, false); target->AddSystemEventListener(kScroll, this, false);
if (StaticPrefs::browser_sessionstore_collect_zoom_AtStartup()) {
target->AddSystemEventListener(kResize, this, false);
}
mCurrentEventTarget = target; mCurrentEventTarget = target;
} }
} }
@@ -322,6 +363,9 @@ void SessionStoreChangeListener::RemoveEventListeners() {
if (mCurrentEventTarget) { if (mCurrentEventTarget) {
mCurrentEventTarget->RemoveSystemEventListener(kInput, this, false); mCurrentEventTarget->RemoveSystemEventListener(kInput, this, false);
mCurrentEventTarget->RemoveSystemEventListener(kScroll, this, false); mCurrentEventTarget->RemoveSystemEventListener(kScroll, this, false);
if (StaticPrefs::browser_sessionstore_collect_zoom_AtStartup()) {
mCurrentEventTarget->RemoveSystemEventListener(kResize, this, false);
}
} }
mCurrentEventTarget = nullptr; mCurrentEventTarget = nullptr;

View File

@@ -56,7 +56,7 @@ class SessionStoreChangeListener final : public nsINamed,
void FlushSessionStore(); void FlushSessionStore();
enum class Change { Input, Scroll, SessionHistory, WireFrame }; enum class Change { Input, Scroll, SessionHistory, WireFrame, Resize };
static SessionStoreChangeListener* CollectSessionStoreData( static SessionStoreChangeListener* CollectSessionStoreData(
WindowContext* aWindowContext, const EnumSet<Change>& aChanges); WindowContext* aWindowContext, const EnumSet<Change>& aChanges);

View File

@@ -167,10 +167,12 @@ void SessionStoreChild::UpdateEventTargets() {
} }
} }
void SessionStoreChild::UpdateSessionStore(bool aSessionHistoryUpdate) { void SessionStoreChild::UpdateSessionStore(bool aSessionHistoryUpdate,
const MaybeSessionStoreZoom& aZoom) {
if (!mSessionStoreListener) { if (!mSessionStoreListener) {
// This is the case when we're shutting down, and expect a final update. // 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; return;
} }
@@ -187,7 +189,7 @@ void SessionStoreChild::UpdateSessionStore(bool aSessionHistoryUpdate) {
} }
SessionStoreUpdate( SessionStoreUpdate(
docShellCaps, privatedMode, docShellCaps, privatedMode, aZoom,
store->GetAndClearSHistoryChanged() || aSessionHistoryUpdate, store->GetAndClearSHistoryChanged() || aSessionHistoryUpdate,
mSessionStoreListener->GetEpoch()); mSessionStoreListener->GetEpoch());
} }
@@ -216,14 +218,15 @@ mozilla::ipc::IPCResult SessionStoreChild::RecvFlushTabState(
void SessionStoreChild::SessionStoreUpdate( void SessionStoreChild::SessionStoreUpdate(
const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode, const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
const bool aNeedCollectSHistory, const uint32_t& aEpoch) { const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory,
const uint32_t& aEpoch) {
if (XRE_IsContentProcess()) { if (XRE_IsContentProcess()) {
Unused << SendSessionStoreUpdate(aDocShellCaps, aPrivatedMode, Unused << SendSessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom,
aNeedCollectSHistory, aEpoch); aNeedCollectSHistory, aEpoch);
} else if (SessionStoreParent* sessionStoreParent = } else if (SessionStoreParent* sessionStoreParent =
static_cast<SessionStoreParent*>( static_cast<SessionStoreParent*>(
InProcessChild::ParentActorFor(this))) { InProcessChild::ParentActorFor(this))) {
sessionStoreParent->SessionStoreUpdate(aDocShellCaps, aPrivatedMode, sessionStoreParent->SessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom,
aNeedCollectSHistory, aEpoch); aNeedCollectSHistory, aEpoch);
} }
} }

View File

@@ -9,6 +9,7 @@
#include "mozilla/AlreadyAddRefed.h" #include "mozilla/AlreadyAddRefed.h"
#include "mozilla/dom/PSessionStoreChild.h" #include "mozilla/dom/PSessionStoreChild.h"
#include "mozilla/dom/SessionStoreScrollData.h"
#include "mozilla/dom/SessionStoreChangeListener.h" #include "mozilla/dom/SessionStoreChangeListener.h"
#include "mozilla/dom/SessionStoreListener.h" #include "mozilla/dom/SessionStoreListener.h"
#include "mozilla/RefPtr.h" #include "mozilla/RefPtr.h"
@@ -31,12 +32,14 @@ class SessionStoreChild final : public PSessionStoreChild {
void SetOwnerContent(Element* aElement); void SetOwnerContent(Element* aElement);
void Stop(); void Stop();
void UpdateEventTargets(); void UpdateEventTargets();
void UpdateSessionStore(bool aSessionHistoryUpdate = false); void UpdateSessionStore(bool aSessionHistoryUpdate = false,
const MaybeSessionStoreZoom& aZoom = Nothing());
void FlushSessionStore(); void FlushSessionStore();
void UpdateSHistoryChanges(); void UpdateSHistoryChanges();
void SessionStoreUpdate(const Maybe<nsCString>& aDocShellCaps, void SessionStoreUpdate(const Maybe<nsCString>& aDocShellCaps,
const Maybe<bool>& aPrivatedMode, const Maybe<bool>& aPrivatedMode,
const MaybeSessionStoreZoom& aZoom,
const bool aNeedCollectSHistory, const bool aNeedCollectSHistory,
const uint32_t& aEpoch); const uint32_t& aEpoch);

View File

@@ -25,6 +25,12 @@
#include "nsImportModule.h" #include "nsImportModule.h"
#include "nsIXPConnect.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;
using namespace mozilla::dom; using namespace mozilla::dom;
@@ -33,9 +39,81 @@ SessionStoreParent::SessionStoreParent(
BrowserSessionStore* aSessionStore) BrowserSessionStore* aSessionStore)
: mBrowsingContext(aBrowsingContext), mSessionStore(aSessionStore) {} : mBrowsingContext(aBrowsingContext), mSessionStore(aSessionStore) {}
#ifdef ANDROID
static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext, static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext,
const Maybe<nsCString>& aDocShellCaps, const Maybe<nsCString>& aDocShellCaps,
const Maybe<bool>& aPrivatedMode, const Maybe<bool>& aPrivatedMode,
SessionStoreFormData* aFormData,
SessionStoreScrollData* aScroll,
const MaybeSessionStoreZoom& aZoom,
bool aNeedCollectSHistory, uint32_t aEpoch) {
RefPtr<BrowserSessionStore> sessionStore =
BrowserSessionStore::GetOrCreate(aBrowsingContext->Top());
nsCOMPtr<nsIWidget> widget =
aBrowsingContext->GetParentProcessWidgetContaining();
if (RefPtr<nsWindow> 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<nsCString>& aDocShellCaps,
const Maybe<bool>& aPrivatedMode,
SessionStoreFormData* aFormData,
SessionStoreScrollData* aScroll,
const MaybeSessionStoreZoom& aZoom,
bool aNeedCollectSHistory, uint32_t aEpoch) { bool aNeedCollectSHistory, uint32_t aEpoch) {
UpdateSessionStoreData data; UpdateSessionStoreData data;
if (aDocShellCaps.isSome()) { if (aDocShellCaps.isSome()) {
@@ -54,11 +132,12 @@ static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext,
RefPtr<BrowserSessionStore> sessionStore = RefPtr<BrowserSessionStore> sessionStore =
BrowserSessionStore::GetOrCreate(aBrowsingContext->Top()); BrowserSessionStore::GetOrCreate(aBrowsingContext->Top());
SessionStoreFormData* formData = sessionStore->GetFormdata(); if (aFormData) {
data.mFormdata.Construct(formData); data.mFormdata.Construct(aFormData);
}
SessionStoreScrollData* scroll = sessionStore->GetScroll(); if (aScroll) {
data.mScroll.Construct(scroll); data.mScroll.Construct(aScroll);
}
nsCOMPtr<nsISessionStoreFunctions> funcs = do_ImportModule( nsCOMPtr<nsISessionStoreFunctions> funcs = do_ImportModule(
"resource://gre/modules/SessionStoreFunctions.jsm", fallible); "resource://gre/modules/SessionStoreFunctions.jsm", fallible);
@@ -82,6 +161,7 @@ static void DoSessionStoreUpdate(CanonicalBrowsingContext* aBrowsingContext,
Unused << funcs->UpdateSessionStore(nullptr, aBrowsingContext, key, aEpoch, Unused << funcs->UpdateSessionStore(nullptr, aBrowsingContext, key, aEpoch,
aNeedCollectSHistory, update); aNeedCollectSHistory, update);
} }
#endif
void SessionStoreParent::FlushAllSessionStoreChildren( void SessionStoreParent::FlushAllSessionStoreChildren(
const std::function<void()>& aDone) { const std::function<void()>& aDone) {
@@ -157,13 +237,23 @@ void SessionStoreParent::FinalFlushAllSessionStoreChildren(
mozilla::ipc::IPCResult SessionStoreParent::RecvSessionStoreUpdate( mozilla::ipc::IPCResult SessionStoreParent::RecvSessionStoreUpdate(
const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode, const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
const bool aNeedCollectSHistory, const uint32_t& aEpoch) { const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory,
const uint32_t& aEpoch) {
if (!mBrowsingContext) { if (!mBrowsingContext) {
return IPC_OK(); return IPC_OK();
} }
DoSessionStoreUpdate(mBrowsingContext, aDocShellCaps, aPrivatedMode, RefPtr<SessionStoreFormData> formData =
aNeedCollectSHistory, aEpoch); mHasNewFormData ? mSessionStore->GetFormdata() : nullptr;
RefPtr<SessionStoreScrollData> scroll =
mHasNewScrollPosition ? mSessionStore->GetScroll() : nullptr;
DoSessionStoreUpdate(mBrowsingContext, aDocShellCaps, aPrivatedMode, formData,
scroll, aZoom, aNeedCollectSHistory, aEpoch);
mHasNewFormData = false;
mHasNewScrollPosition = false;
return IPC_OK(); return IPC_OK();
} }
@@ -172,6 +262,13 @@ mozilla::ipc::IPCResult SessionStoreParent::RecvIncrementalSessionStoreUpdate(
const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition, const Maybe<FormData>& aFormData, const Maybe<nsPoint>& aScrollPosition,
uint32_t aEpoch) { uint32_t aEpoch) {
if (!aBrowsingContext.IsNull()) { if (!aBrowsingContext.IsNull()) {
if (aFormData.isSome()) {
mHasNewFormData = true;
}
if (aScrollPosition.isSome()) {
mHasNewScrollPosition = true;
}
mSessionStore->UpdateSessionStore( mSessionStore->UpdateSessionStore(
aBrowsingContext.GetMaybeDiscarded()->Canonical(), aFormData, aBrowsingContext.GetMaybeDiscarded()->Canonical(), aFormData,
aScrollPosition, aEpoch); aScrollPosition, aEpoch);
@@ -191,8 +288,9 @@ mozilla::ipc::IPCResult SessionStoreParent::RecvResetSessionStore(
void SessionStoreParent::SessionStoreUpdate( void SessionStoreParent::SessionStoreUpdate(
const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode, const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
const bool aNeedCollectSHistory, const uint32_t& aEpoch) { const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory,
Unused << RecvSessionStoreUpdate(aDocShellCaps, aPrivatedMode, const uint32_t& aEpoch) {
Unused << RecvSessionStoreUpdate(aDocShellCaps, aPrivatedMode, aZoom,
aNeedCollectSHistory, aEpoch); aNeedCollectSHistory, aEpoch);
} }

View File

@@ -12,6 +12,7 @@
#include "mozilla/dom/Element.h" #include "mozilla/dom/Element.h"
#include "mozilla/dom/PSessionStoreParent.h" #include "mozilla/dom/PSessionStoreParent.h"
#include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/SessionStoreScrollData.h"
namespace mozilla::dom { namespace mozilla::dom {
class BrowserParent; class BrowserParent;
@@ -31,7 +32,8 @@ class SessionStoreParent final : public PSessionStoreParent {
*/ */
mozilla::ipc::IPCResult RecvSessionStoreUpdate( mozilla::ipc::IPCResult RecvSessionStoreUpdate(
const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode, const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode,
const bool aNeedCollectSHistory, const uint32_t& aEpoch); const MaybeSessionStoreZoom& aZoom, const bool aNeedCollectSHistory,
const uint32_t& aEpoch);
mozilla::ipc::IPCResult RecvIncrementalSessionStoreUpdate( mozilla::ipc::IPCResult RecvIncrementalSessionStoreUpdate(
const MaybeDiscarded<BrowsingContext>& aBrowsingContext, const MaybeDiscarded<BrowsingContext>& aBrowsingContext,
@@ -48,6 +50,7 @@ class SessionStoreParent final : public PSessionStoreParent {
friend class SessionStoreChild; friend class SessionStoreChild;
void SessionStoreUpdate(const Maybe<nsCString>& aDocShellCaps, void SessionStoreUpdate(const Maybe<nsCString>& aDocShellCaps,
const Maybe<bool>& aPrivatedMode, const Maybe<bool>& aPrivatedMode,
const MaybeSessionStoreZoom& aZoom,
const bool aNeedCollectSHistory, const bool aNeedCollectSHistory,
const uint32_t& aEpoch); const uint32_t& aEpoch);
@@ -65,6 +68,9 @@ class SessionStoreParent final : public PSessionStoreParent {
already_AddRefed<SessionStoreParent::FlushTabStatePromise> already_AddRefed<SessionStoreParent::FlushTabStatePromise>
FlushSessionStore(); FlushSessionStore();
bool mHasNewFormData = false;
bool mHasNewScrollPosition = false;
RefPtr<CanonicalBrowsingContext> mBrowsingContext; RefPtr<CanonicalBrowsingContext> mBrowsingContext;
RefPtr<BrowserSessionStore> mSessionStore; RefPtr<BrowserSessionStore> mSessionStore;
}; };

View File

@@ -7,17 +7,24 @@
#ifndef mozilla_dom_SessionStoreScrollData_h #ifndef mozilla_dom_SessionStoreScrollData_h
#define mozilla_dom_SessionStoreScrollData_h #define mozilla_dom_SessionStoreScrollData_h
#include "js/TypeDecls.h"
#include "mozilla/WeakPtr.h" #include "mozilla/WeakPtr.h"
#include "nsPoint.h" #include "nsPoint.h"
#include "nsTArray.h" #include "nsTArray.h"
#include "nsWrapperCache.h" #include "nsWrapperCache.h"
#include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/SessionStoreChangeListener.h"
namespace mozilla::dom { namespace mozilla::dom {
class BrowsingContext; class BrowsingContext;
class WindowGlobalParent; class WindowGlobalParent;
class OwningByteStringOrObjectOrNull; class OwningByteStringOrObjectOrNull;
struct SessionStoreZoomData;
using SessionStoreZoom = mozilla::Tuple<float, uint32_t, uint32_t>;
using MaybeSessionStoreZoom =
mozilla::Maybe<mozilla::Tuple<float, uint32_t, uint32_t>>;
class SessionStoreScrollData final : public nsISupports, class SessionStoreScrollData final : public nsISupports,
public nsWrapperCache, public nsWrapperCache,
@@ -53,6 +60,7 @@ class SessionStoreScrollData final : public nsISupports,
~SessionStoreScrollData() = default; ~SessionStoreScrollData() = default;
nsPoint mScroll; nsPoint mScroll;
MaybeSessionStoreZoom mZoom;
nsTArray<RefPtr<SessionStoreScrollData>> mChildren; nsTArray<RefPtr<SessionStoreScrollData>> mChildren;
}; };

View File

@@ -139,13 +139,6 @@ class SessionStoreUtils {
const nsTArray<SSCacheCopy>& aValues, const nsTArray<SSCacheCopy>& aValues,
Record<nsCString, Record<nsString, nsString>>& aStorage); Record<nsCString, Record<nsString, nsString>>& 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::HandleObject aDst, static bool CopyProperty(JSContext* aCx, JS::HandleObject aDst,
JS::HandleObject aSrc, const nsAString& aName); JS::HandleObject aSrc, const nsAString& aName);

View File

@@ -19,6 +19,7 @@
#include "mozilla/ScopeExit.h" #include "mozilla/ScopeExit.h"
#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ScriptSettings.h"
#include "mozilla/java/EventCallbackWrappers.h" #include "mozilla/java/EventCallbackWrappers.h"
#include "mozilla/jni/GeckoBundleUtils.h"
// Disable the C++ 2a warning. See bug #1509926 // Disable the C++ 2a warning. See bug #1509926
#if defined(__clang__) #if defined(__clang__)
@@ -38,266 +39,9 @@ bool CheckJS(JSContext* aCx, bool aResult) {
return 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<const jchar*>(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 <typename Type, bool (JS::Value::*IsType)() const,
Type (JS::Value::*ToType)() const, class ArrayType,
typename ArrayType::LocalRef (*NewArray)(const Type*, size_t)>
nsresult BoxArrayPrimitive(JSContext* aCx, JS::HandleObject aData,
jni::Object::LocalRef& aOut, size_t aLength,
JS::HandleValue aElement) {
JS::RootedValue element(aCx);
auto data = MakeUnique<Type[]>(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 <class Type,
nsresult (*Box)(JSContext*, JS::HandleValue, jni::Object::LocalRef&),
typename IsType>
nsresult BoxArrayObject(JSContext* aCx, JS::HandleObject aData,
jni::Object::LocalRef& aOut, size_t aLength,
JS::HandleValue aElement, IsType&& aIsType) {
auto out = jni::ObjectArray::New<Type>(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<bool, &JS::Value::isBoolean, &JS::Value::toBoolean,
jni::BooleanArray, &jni::BooleanArray::New>(
aCx, aData, aOut, length, element);
}
if (element.isInt32()) {
nsresult rv =
BoxArrayPrimitive<int32_t, &JS::Value::isInt32, &JS::Value::toInt32,
jni::IntArray, &jni::IntArray::New>(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<double, &JS::Value::isNumber, &JS::Value::toNumber,
jni::DoubleArray, &jni::DoubleArray::New>(
aCx, aData, aOut, length, element);
}
if (element.isNullOrUndefined() || element.isString()) {
const auto isString = [](JS::HandleValue val) -> bool {
return val.isString();
};
nsresult rv = BoxArrayObject<jni::String, &BoxString>(
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<java::GeckoBundle, &BoxObject>(
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<JS::IdVector> 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<jni::String>(length);
auto values = jni::ObjectArray::New<jni::Object>(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, nsresult BoxData(const nsAString& aEvent, JSContext* aCx, JS::HandleValue aData,
jni::Object::LocalRef& aOut, bool aObjectOnly) { jni::Object::LocalRef& aOut, bool aObjectOnly) {
nsresult rv = NS_ERROR_INVALID_ARG; nsresult rv = jni::BoxData(aCx, aData, aOut, aObjectOnly);
if (!aObjectOnly) {
rv = BoxValue(aCx, aData, aOut);
} else if (aData.isObject() || aData.isNullOrUndefined()) {
rv = BoxObject(aCx, aData, aOut);
}
if (rv != NS_ERROR_INVALID_ARG) { if (rv != NS_ERROR_INVALID_ARG) {
return rv; return rv;
} }

View File

@@ -91,6 +91,8 @@ class GeckoViewSupport final
void OnShowDynamicToolbar() const; void OnShowDynamicToolbar() const;
void OnUpdateSessionStore(mozilla::jni::Object::Param aBundle);
void PassExternalResponse(java::WebResponse::Param aResponse); void PassExternalResponse(java::WebResponse::Param aResponse);
void AttachMediaSessionController( void AttachMediaSessionController(

View File

@@ -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<const jchar*>(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 <typename Type, bool (JS::Value::*IsType)() const,
Type (JS::Value::*ToType)() const, class ArrayType,
typename ArrayType::LocalRef (*NewArray)(const Type*, size_t)>
nsresult BoxArrayPrimitive(JSContext* aCx, JS::HandleObject aData,
jni::Object::LocalRef& aOut, size_t aLength,
JS::HandleValue aElement) {
JS::RootedValue element(aCx);
auto data = MakeUnique<Type[]>(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 <class Type,
nsresult (*Box)(JSContext*, JS::HandleValue, jni::Object::LocalRef&),
typename IsType>
nsresult BoxArrayObject(JSContext* aCx, JS::HandleObject aData,
jni::Object::LocalRef& aOut, size_t aLength,
JS::HandleValue aElement, IsType&& aIsType) {
auto out = jni::ObjectArray::New<Type>(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<bool, &JS::Value::isBoolean, &JS::Value::toBoolean,
jni::BooleanArray, &jni::BooleanArray::New>(
aCx, aData, aOut, length, element);
}
if (element.isInt32()) {
nsresult rv =
BoxArrayPrimitive<int32_t, &JS::Value::isInt32, &JS::Value::toInt32,
jni::IntArray, &jni::IntArray::New>(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<double, &JS::Value::isNumber, &JS::Value::toNumber,
jni::DoubleArray, &jni::DoubleArray::New>(
aCx, aData, aOut, length, element);
}
if (element.isNullOrUndefined() || element.isString()) {
const auto isString = [](JS::HandleValue val) -> bool {
return val.isString();
};
nsresult rv = BoxArrayObject<jni::String, &BoxString>(
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<java::GeckoBundle, &BoxObject>(
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<JS::IdVector> 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<jni::String>(length);
auto values = jni::ObjectArray::New<jni::Object>(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;
}
} // 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

View File

@@ -9,6 +9,8 @@
#include "mozilla/java/GeckoBundleWrappers.h" #include "mozilla/java/GeckoBundleWrappers.h"
#include "jsapi.h"
namespace mozilla { namespace mozilla {
namespace jni { namespace jni {
@@ -35,6 +37,9 @@ namespace jni {
auto name = \ auto name = \
mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues); mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues);
nsresult BoxData(JSContext* aCx, JS::HandleValue aData,
jni::Object::LocalRef& aOut, bool aObjectOnly);
} // namespace jni } // namespace jni
} // namespace mozilla } // namespace mozilla

View File

@@ -21,6 +21,7 @@ EXPORTS.mozilla.jni += [
UNIFIED_SOURCES += [ UNIFIED_SOURCES += [
"Conversions.cpp", "Conversions.cpp",
"GeckoBundleUtils.cpp",
"Utils.cpp", "Utils.cpp",
] ]

View File

@@ -2093,6 +2093,15 @@ RefPtr<MozPromise<bool, bool, false>> nsWindow::OnLoadRequest(
: nullptr; : nullptr;
} }
void nsWindow::OnUpdateSessionStore(mozilla::jni::Object::Param aBundle) {
auto geckoViewSupport(mGeckoViewSupport.Access());
if (!geckoViewSupport) {
return;
}
geckoViewSupport->OnUpdateSessionStore(aBundle);
}
float nsWindow::GetDPI() { float nsWindow::GetDPI() {
float dpi = 160.0f; float dpi = 160.0f;
@@ -2430,6 +2439,16 @@ void nsWindow::ShowDynamicToolbar() {
acc->OnShowDynamicToolbar(); 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) { void nsWindow::OnSizeChanged(const gfx::IntSize& aSize) {
ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width, ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width,
aSize.height); aSize.height);

View File

@@ -69,6 +69,8 @@ class nsWindow final : public nsBaseWidget {
nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture, nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture,
bool aIsTopLevel); bool aIsTopLevel);
void OnUpdateSessionStore(mozilla::jni::Object::Param aBundle);
private: private:
// Unique ID given to each widget, used to map Surfaces to widgets // Unique ID given to each widget, used to map Surfaces to widgets
// in the CompositorSurfaceManager. // in the CompositorSurfaceManager.