summaryrefslogtreecommitdiffstats
path: root/mobile/android/modules
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/modules')
-rw-r--r--mobile/android/modules/geckoview/GeckoViewNavigation.sys.mjs107
-rw-r--r--mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs95
-rw-r--r--mobile/android/modules/geckoview/GeckoViewSettings.sys.mjs16
-rw-r--r--mobile/android/modules/geckoview/GeckoViewTestUtils.sys.mjs7
-rw-r--r--mobile/android/modules/geckoview/GeckoViewTranslations.sys.mjs13
-rw-r--r--mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs54
6 files changed, 227 insertions, 65 deletions
diff --git a/mobile/android/modules/geckoview/GeckoViewNavigation.sys.mjs b/mobile/android/modules/geckoview/GeckoViewNavigation.sys.mjs
index 483f0b01f2..911b08a70e 100644
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.sys.mjs
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.sys.mjs
@@ -291,10 +291,10 @@ export class GeckoViewNavigation extends GeckoViewModule {
waitAndSetupWindow(aSessionId, aOpenWindowInfo, aName) {
if (!aSessionId) {
- return Promise.resolve(null);
+ return Promise.reject();
}
- return new Promise(resolve => {
+ return new Promise((resolve, reject) => {
const handler = {
observe(aSubject, aTopic) {
if (
@@ -318,6 +318,10 @@ export class GeckoViewNavigation extends GeckoViewModule {
aSubject.browser.removeAttribute("remoteType");
}
Services.obs.removeObserver(handler, "geckoview-window-created");
+ if (!aSubject) {
+ reject();
+ return;
+ }
resolve(aSubject);
}
},
@@ -332,8 +336,45 @@ export class GeckoViewNavigation extends GeckoViewModule {
debug`handleNewSession: uri=${aUri && aUri.spec}
where=${aWhere} flags=${aFlags}`;
+ const setupPromise = this.#handleNewSessionAsync(
+ aUri,
+ aOpenWindowInfo,
+ aFlags,
+ aName
+ );
+
+ let browser = undefined;
+ setupPromise.then(
+ window => {
+ browser = window.browser;
+ },
+ () => {
+ browser = null;
+ }
+ );
+
+ // Wait indefinitely for app to respond with a browser or null
+ Services.tm.spinEventLoopUntil(
+ "GeckoViewNavigation.jsm:handleNewSession",
+ () => this.window.closed || browser !== undefined
+ );
+ return browser || null;
+ }
+
+ #isNewTab(aWhere) {
+ return [
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND,
+ ].includes(aWhere);
+ }
+
+ /**
+ * Similar to handleNewSession. But this returns a promise to wait for new
+ * browser.
+ */
+ #handleNewSessionAsync(aUri, aOpenWindowInfo, aFlags, aName) {
if (!this.enabled) {
- return null;
+ return Promise.reject();
}
const newSessionId = Services.uuid
@@ -356,30 +397,15 @@ export class GeckoViewNavigation extends GeckoViewModule {
aName
);
- let browser = undefined;
- this.eventDispatcher
+ return this.eventDispatcher
.sendRequestForResult(message)
.then(didOpenSession => {
if (!didOpenSession) {
+ // New session cannot be opened, so we should throw NS_ERROR_ABORT.
return Promise.reject();
}
return setupPromise;
- })
- .then(
- window => {
- browser = window.browser;
- },
- () => {
- browser = null;
- }
- );
-
- // Wait indefinitely for app to respond with a browser or null
- Services.tm.spinEventLoopUntil(
- "GeckoViewNavigation.sys.mjs:handleNewSession",
- () => this.window.closed || browser !== undefined
- );
- return browser || null;
+ });
}
// nsIBrowserDOMWindow.
@@ -393,6 +419,11 @@ export class GeckoViewNavigation extends GeckoViewModule {
debug`createContentWindow: uri=${aUri && aUri.spec}
where=${aWhere} flags=${aFlags}`;
+ if (!this.enabled) {
+ Components.returnCode = Cr.NS_ERROR_ABORT;
+ return null;
+ }
+
if (
lazy.LoadURIDelegate.load(
this.window,
@@ -408,13 +439,45 @@ export class GeckoViewNavigation extends GeckoViewModule {
return null;
}
- const browser = this.handleNewSession(
+ const newTab = this.#isNewTab(aWhere);
+ const promise = this.#handleNewSessionAsync(
aUri,
aOpenWindowInfo,
aWhere,
aFlags,
null
);
+
+ // Actually, GeckoView's createContentWindow always creates new window even
+ // if OPEN_NEWTAB. So the browsing context will be observed via
+ // nsFrameLoader.
+ if (aOpenWindowInfo && !newTab) {
+ promise.catch(() => {
+ aOpenWindowInfo.cancel();
+ });
+ // If nsIOpenWindowInfo isn't null, caller should use the callback.
+ // Also, nsIWindowProvider.provideWindow doesn't use callback, if new
+ // tab option, we have to return browsing context instead of async.
+ return null;
+ }
+
+ let browser = undefined;
+ promise.then(
+ window => {
+ browser = window.browser;
+ },
+ () => {
+ browser = null;
+ }
+ );
+
+ // Wait indefinitely for app to respond with a browser or null.
+ // if browser is null, return error.
+ Services.tm.spinEventLoopUntil(
+ "GeckoViewNavigation.sys.mjs:createContentWindow",
+ () => this.window.closed || browser !== undefined
+ );
+
if (!browser) {
Components.returnCode = Cr.NS_ERROR_ABORT;
return null;
diff --git a/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs b/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs
index faa5c5f280..5c176a17f1 100644
--- a/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs
+++ b/mobile/android/modules/geckoview/GeckoViewSessionStore.sys.mjs
@@ -117,19 +117,11 @@ export var GeckoViewSessionStore = {
switch (aTopic) {
case "browsing-context-did-set-embedder": {
- if (
- aSubject &&
- aSubject === aSubject.top &&
- aSubject.isContent &&
- aSubject.embedderElement &&
- aSubject.embedderElement.permanentKey
- ) {
- const permanentKey = aSubject.embedderElement.permanentKey;
- this._browserSHistoryListener
- .get(permanentKey)
- ?.unregister(permanentKey);
-
- this.getOrCreateSHistoryListener(permanentKey, aSubject, true);
+ if (aSubject === aSubject.top && aSubject.isContent) {
+ const permanentKey = aSubject.embedderElement?.permanentKey;
+ if (permanentKey) {
+ this.maybeRecreateSHistoryListener(permanentKey, aSubject);
+ }
}
break;
}
@@ -151,26 +143,34 @@ export var GeckoViewSessionStore = {
});
},
- getOrCreateSHistoryListener(
- permanentKey,
- browsingContext,
- collectImmediately = false
- ) {
+ getOrCreateSHistoryListener(permanentKey, browsingContext) {
if (!permanentKey || browsingContext !== browsingContext.top) {
return null;
}
+ const listener = this._browserSHistoryListener.get(permanentKey);
+ if (listener) {
+ return listener;
+ }
+
+ return this.createSHistoryListener(permanentKey, browsingContext, false);
+ },
+
+ maybeRecreateSHistoryListener(permanentKey, browsingContext) {
+ const listener = this._browserSHistoryListener.get(permanentKey);
+ if (!listener || listener._browserId != browsingContext.browserId) {
+ listener?.unregister(permanentKey);
+ this.createSHistoryListener(permanentKey, browsingContext, true);
+ }
+ },
+
+ createSHistoryListener(permanentKey, browsingContext, collectImmediately) {
const sessionHistory = browsingContext.sessionHistory;
if (!sessionHistory) {
return null;
}
- let listener = this._browserSHistoryListener.get(permanentKey);
- if (listener) {
- return listener;
- }
-
- listener = new SHistoryListener(browsingContext);
+ const listener = new SHistoryListener(browsingContext);
sessionHistory.addSHistoryListener(listener);
this._browserSHistoryListener.set(permanentKey, listener);
@@ -184,4 +184,51 @@ export var GeckoViewSessionStore = {
return listener;
},
+
+ updateSessionStoreFromTabListener(
+ browser,
+ browsingContext,
+ permanentKey,
+ update,
+ forStorage = false
+ ) {
+ permanentKey = browser?.permanentKey ?? permanentKey;
+ if (!permanentKey) {
+ return;
+ }
+
+ if (browsingContext.isReplaced) {
+ return;
+ }
+
+ const listener = this.getOrCreateSHistoryListener(
+ permanentKey,
+ browsingContext
+ );
+
+ if (listener) {
+ const historychange =
+ // If it is not the scheduled update (tab closed, window closed etc),
+ // try to store the loading non-web-controlled page opened in _blank
+ // first.
+ (forStorage &&
+ lazy.SessionHistory.collectNonWebControlledBlankLoadingSession(
+ browsingContext
+ )) ||
+ listener.collect(permanentKey, browsingContext, {
+ collectFull: !!update.sHistoryNeeded,
+ writeToCache: false,
+ });
+
+ if (historychange) {
+ update.data.historychange = historychange;
+ }
+ }
+
+ const win =
+ browsingContext.embedderElement?.ownerGlobal ||
+ browsingContext.currentWindowGlobal?.browsingContext?.window;
+
+ this.onTabStateUpdate(permanentKey, win, update);
+ },
};
diff --git a/mobile/android/modules/geckoview/GeckoViewSettings.sys.mjs b/mobile/android/modules/geckoview/GeckoViewSettings.sys.mjs
index ec927b0af6..f2dd7590ab 100644
--- a/mobile/android/modules/geckoview/GeckoViewSettings.sys.mjs
+++ b/mobile/android/modules/geckoview/GeckoViewSettings.sys.mjs
@@ -6,6 +6,10 @@ import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs"
const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
+});
+
ChromeUtils.defineLazyGetter(lazy, "MOBILE_USER_AGENT", function () {
return Cc["@mozilla.org/network/protocol;1?name=http"].getService(
Ci.nsIHttpProtocolHandler
@@ -74,6 +78,18 @@ export class GeckoViewSettings extends GeckoViewModule {
this.viewportMode = settings.viewportMode;
this.useTrackingProtection = !!settings.useTrackingProtection;
+ if (settings.isExtensionPopup) {
+ // NOTE: Only add the webextension-view-type and emit extension-browser-inserted
+ // once, an extension popup should never change webextension-view-type once set.
+ if (!this.browser.hasAttribute("webextension-view-type")) {
+ this.browser.setAttribute("webextension-view-type", "popup");
+ lazy.ExtensionParent.apiManager.emit(
+ "extension-browser-inserted",
+ this.browser
+ );
+ }
+ }
+
// When the page is loading from the main process (e.g. from an extension
// page) we won't be able to query the actor here.
this.getActor("GeckoViewSettings")?.sendAsyncMessage(
diff --git a/mobile/android/modules/geckoview/GeckoViewTestUtils.sys.mjs b/mobile/android/modules/geckoview/GeckoViewTestUtils.sys.mjs
index a2ca252cdd..f0fdee5d81 100644
--- a/mobile/android/modules/geckoview/GeckoViewTestUtils.sys.mjs
+++ b/mobile/android/modules/geckoview/GeckoViewTestUtils.sys.mjs
@@ -24,12 +24,15 @@ export const GeckoViewTabUtil = {
if (subject.name === sessionId) {
Services.obs.removeObserver(
openingObserver,
- "geckoview-window-created"
+ "browser-delayed-startup-finished"
);
resolve(subject);
}
};
- Services.obs.addObserver(openingObserver, "geckoview-window-created");
+ Services.obs.addObserver(
+ openingObserver,
+ "browser-delayed-startup-finished"
+ );
});
try {
diff --git a/mobile/android/modules/geckoview/GeckoViewTranslations.sys.mjs b/mobile/android/modules/geckoview/GeckoViewTranslations.sys.mjs
index 7e74e7bf30..45b43ac9f9 100644
--- a/mobile/android/modules/geckoview/GeckoViewTranslations.sys.mjs
+++ b/mobile/android/modules/geckoview/GeckoViewTranslations.sys.mjs
@@ -50,8 +50,11 @@ export class GeckoViewTranslations extends GeckoViewModule {
aData.toLanguage
);
try {
- this.getActor("Translations").translate(fromLanguage, toLanguage);
- aCallback.onSuccess();
+ this.getActor("Translations")
+ .translate(fromLanguage, toLanguage)
+ .then(() => {
+ aCallback.onSuccess();
+ });
} catch (error) {
aCallback.onError(`Could not translate: ${error}`);
}
@@ -101,10 +104,11 @@ export class GeckoViewTranslations extends GeckoViewModule {
type: "GeckoView:Translations:Offer",
});
break;
- case "TranslationsParent:LanguageState":
+ case "TranslationsParent:LanguageState": {
const {
detectedLanguages,
requestedTranslationPair,
+ hasVisibleChange,
error,
isEngineReady,
} = aEvent.detail.actor.languageState;
@@ -112,6 +116,7 @@ export class GeckoViewTranslations extends GeckoViewModule {
const data = {
detectedLanguages,
requestedTranslationPair,
+ hasVisibleChange,
error,
isEngineReady,
};
@@ -120,7 +125,9 @@ export class GeckoViewTranslations extends GeckoViewModule {
type: "GeckoView:Translations:StateChange",
data,
});
+
break;
+ }
}
}
}
diff --git a/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs b/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs
index 749f753626..421839264a 100644
--- a/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs
+++ b/mobile/android/modules/geckoview/GeckoViewWebExtension.sys.mjs
@@ -283,6 +283,16 @@ function exportFlags(aPolicy) {
return flags;
}
+function normalizePermissions(perms) {
+ if (perms?.permissions) {
+ perms = { ...perms };
+ perms.permissions = perms.permissions.filter(
+ perm => !perm.startsWith("internal:")
+ );
+ }
+ return perms;
+}
+
async function exportExtension(aAddon, aPermissions, aSourceURI) {
// First, let's make sure the policy is ready if present
let policy = WebExtensionPolicy.getByID(aAddon.id);
@@ -360,22 +370,12 @@ async function exportExtension(aAddon, aPermissions, aSourceURI) {
updateDate = null;
}
- const normalizePermissions = perms => {
- if (perms?.permissions) {
- perms = { ...perms };
- perms.permissions = perms.permissions.filter(
- perm => !perm.startsWith("internal:")
- );
- }
- return perms;
- };
-
const optionalPermissions = aAddon.optionalPermissions?.permissions ?? [];
- const optionalOrigins = aAddon.optionalPermissions?.origins ?? [];
+ const optionalOrigins = aAddon.optionalOriginsNormalized;
const grantedPermissions =
- normalizePermissions(await lazy.ExtensionPermissions.get(id)) ?? [];
- const grantedOptionalPermissions = grantedPermissions?.permissions ?? [];
- const grantedOptionalOrigins = grantedPermissions?.origins ?? [];
+ normalizePermissions(await lazy.ExtensionPermissions.get(id)) ?? {};
+ const grantedOptionalPermissions = grantedPermissions.permissions ?? [];
+ const grantedOptionalOrigins = grantedPermissions.origins ?? [];
return {
webExtensionId: id,
@@ -623,6 +623,32 @@ class AddonManagerListener {
// the GeckoView side when it is actually going to be available.
this.onExtensionReady = this.onExtensionReady.bind(this);
lazy.Management.on("ready", this.onExtensionReady);
+ lazy.Management.on("change-permissions", this.onOptionalPermissionsChanged);
+ }
+
+ async onOptionalPermissionsChanged(type, { extensionId }) {
+ // In xpcshell tests there wil be test extensions that trigger this event while the
+ // AddonManager has not been started at all, on the contrary on a regular browser
+ // instance the AddonManager is expected to be already fully started for an extension
+ // for the extension to be able to reach the "ready" state, and so we just silently
+ // early exit here if the AddonManager is not ready.
+ if (!lazy.AddonManager.isReady) {
+ return;
+ }
+
+ const addon = await lazy.AddonManager.getAddonByID(extensionId);
+ if (!addon) {
+ return;
+ }
+ const extension = await exportExtension(
+ addon,
+ addon.userPermissions,
+ /* aSourceURI */ null
+ );
+ lazy.EventDispatcher.instance.sendRequest({
+ type: "GeckoView:WebExtension:OnOptionalPermissionsChanged",
+ extension,
+ });
}
async onExtensionReady(name, extInstance) {