Bug 1616625 - Implement active parameter for tabs.create and update. r=mixedpuppy,snorp,esawin

Differential Revision: https://phabricator.services.mozilla.com/D64800

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Agi Sferro 2020-03-03 23:19:03 +00:00
Родитель d0840b96b4
Коммит 45283ccbf3
15 изменённых файлов: 476 добавлений и 135 удалений

Просмотреть файл

@ -309,58 +309,64 @@ this.tabs = class extends ExtensionAPI {
},
}).api(),
async create(createProperties) {
let principal = context.principal;
let window =
createProperties.windowId !== null
? windowTracker.getWindow(createProperties.windowId, context)
: windowTracker.topWindow;
async create({
active,
discarded,
index,
openInReaderMode,
pinned,
title,
url,
} = {}) {
if (active === null) {
active = true;
}
let { BrowserApp } = window;
let url;
tabListener.initTabReady();
if (createProperties.url !== null) {
url = context.uri.resolve(createProperties.url);
if (url !== null) {
url = context.uri.resolve(url);
if (!context.checkLoadURL(url, { dontReportErrors: true })) {
return Promise.reject({ message: `Illegal URL: ${url}` });
}
} else {
// Falling back to system here as about:newtab requires it, however is safe.
principal = Services.scriptSecurityManager.getSystemPrincipal();
}
let options = {};
const nativeTab = await GeckoViewTabBridge.createNewTab({
extensionId: context.extension.id,
createProperties: {
active,
discarded,
index,
openInReaderMode,
pinned,
url,
},
});
let active = true;
if (createProperties.active !== null) {
active = createProperties.active;
}
options.selected = active;
const { browser } = nativeTab;
if (createProperties.index !== null) {
options.tabIndex = createProperties.index;
}
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
// Make sure things like about:blank URIs never inherit,
// and instead always get a NullPrincipal.
if (url && url.startsWith("about:")) {
options.disallowInheritPrincipal = true;
if (url !== null) {
tabListener.initializingTabs.add(nativeTab);
} else {
options.triggeringPrincipal = context.principal;
url = "about:blank";
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
options.parentId = BrowserApp.selectedTab.id;
browser.loadURI(url, {
flags,
// GeckoView doesn't support about:newtab so we don't need to worry
// about using the system principal here.
triggeringPrincipal: context.principal,
});
tabListener.initTabReady();
options.triggeringPrincipal = principal;
options.extensionId = context.extension.id;
options.url = url;
let nativeTab = await GeckoViewTabBridge.createNewTab(options);
if (createProperties.url) {
tabListener.initializingTabs.add(nativeTab);
if (active) {
const newWindow = browser.ownerGlobal;
mobileWindowTracker.setTabActive(newWindow, true);
}
return tabManager.convert(nativeTab);
@ -386,28 +392,44 @@ this.tabs = class extends ExtensionAPI {
);
},
async update(tabId, updateProperties) {
let nativeTab = getTabOrActive(tabId);
async update(
tabId,
{ active, autoDiscardable, highlighted, muted, pinned, url } = {}
) {
const nativeTab = getTabOrActive(tabId);
const window = nativeTab.browser.ownerGlobal;
let { BrowserApp } = nativeTab.browser.ownerGlobal;
if (updateProperties.url !== null) {
let url = context.uri.resolve(updateProperties.url);
if (url !== null) {
url = context.uri.resolve(url);
if (!context.checkLoadURL(url, { dontReportErrors: true })) {
return Promise.reject({ message: `Illegal URL: ${url}` });
}
}
let options = {
await GeckoViewTabBridge.updateTab({
window,
extensionId: context.extension.id,
updateProperties: {
active,
autoDiscardable,
highlighted,
muted,
pinned,
url,
},
});
if (url !== null) {
nativeTab.browser.loadURI(url, {
triggeringPrincipal: context.principal,
};
nativeTab.browser.loadURI(url, options);
});
}
if (updateProperties.active) {
BrowserApp.selectTab(nativeTab);
// FIXME: openerTabId, successorTabId
if (active) {
mobileWindowTracker.setTabActive(window, true);
}
// FIXME: highlighted/selected, muted, pinned, openerTabId, successorTabId
return tabManager.convert(nativeTab);
},

Просмотреть файл

@ -20,7 +20,6 @@ skip-if = !is_fennec # times out
skip-if = !is_fennec # times out; bug 1534640 webextension url
[test_ext_tabs_captureVisibleTab.html]
[test_ext_tabs_create.html]
skip-if = !is_fennec # times out; bug 1507167; bug 1534640 webextension url
[test_ext_tabs_events.html]
[test_ext_tabs_executeScript.html]
[test_ext_tabs_executeScript_bad.html]
@ -30,7 +29,6 @@ skip-if = true # Currently fails in emulator runs
[test_ext_tabs_executeScript_runAt.html]
[test_ext_tabs_get.html]
[test_ext_tabs_getCurrent.html]
skip-if = !is_fennec # times out
[test_ext_tabs_insertCSS.html]
[test_ext_tabs_lastAccessed.html]
skip-if = !is_fennec # tab.lastAccessed not implemented
@ -42,5 +40,4 @@ skip-if = !is_fennec # times out
[test_ext_tabs_query.html]
[test_ext_tabs_sendMessage.html]
[test_ext_tabs_update_url.html]
skip-if = !is_fennec # bug 1534640 webextension url
[test_ext_webNavigation_onCommitted.html]

Просмотреть файл

@ -14,7 +14,9 @@
add_task(async function() {
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "permanent",
manifest: {
"applications": { "gecko": { "id": "tabs-create@tests.mozilla.org" } },
"permissions": ["tabs"],
"background": {"page": "bg/background.html"},
@ -30,11 +32,9 @@ add_task(async function() {
"bg/background.js": function() {
let activeTab;
let activeWindow;
function runTests() {
const DEFAULTS = {
windowId: activeWindow,
active: true,
url: "about:blank",
};
@ -74,7 +74,9 @@ add_task(async function() {
let updatedPromise = new Promise(resolve => {
let onUpdated = (changedTabId, changed) => {
if (changed.url) {
// Loading an extension page causes two `about:blank` messages
// because of the process switch
if (changed.url && (expected.url == "about:blank" || changed.url != "about:blank")) {
browser.tabs.onUpdated.removeListener(onUpdated);
resolve({tabId: changedTabId, url: changed.url});
}
@ -120,7 +122,6 @@ add_task(async function() {
browser.tabs.query({active: true, currentWindow: true}, tabs => {
activeTab = tabs[0].id;
activeWindow = tabs[0].windowId;
runTests();
});

Просмотреть файл

@ -107,10 +107,15 @@ add_task(async function testExecuteScript() {
}
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "permanent",
manifest: {
"applications": {
"gecko": {
"id": "execute-script-run-at@tests.mozilla.org",
}
},
"permissions": ["http://mochi.test/", "tabs"],
},
background: `(${background})(${AppConstants.DEBUG})`,
});

Просмотреть файл

@ -14,7 +14,9 @@
add_task(async function() {
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "permanent",
manifest: {
"applications": { "gecko": { "id": "get-current@tests.mozilla.org" } },
"permissions": ["tabs"],
},

Просмотреть файл

@ -14,7 +14,9 @@
async function testTabsUpdateURL(existentTabURL, tabsUpdateURL, isErrorExpected) {
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "permanent",
manifest: {
"applications": { "gecko": { "id": "tabs-update-url@tests.mozilla.org" } },
"permissions": ["tabs"],
},
@ -38,7 +40,7 @@ async function testTabsUpdateURL(existentTabURL, tabsUpdateURL, isErrorExpected)
let tabs = await browser.tabs.query({lastFocusedWindow: true});
try {
let tab = await browser.tabs.update(tabs[1].id, {url: tabsUpdateURL});
let tab = await browser.tabs.update(tabs[0].id, {url: tabsUpdateURL});
browser.test.assertFalse(isErrorExpected, `tabs.update with URL ${tabsUpdateURL} should be rejected`);
browser.test.assertTrue(tab, "on success the tab should be defined");

Просмотреть файл

@ -1491,6 +1491,16 @@ package org.mozilla.geckoview {
field public static final int VULNERABLE_UPDATE_AVAILABLE = 4;
}
public static class WebExtension.CreateTabDetails {
ctor protected CreateTabDetails();
field @Nullable public final Boolean active;
field @Nullable public final Boolean discarded;
field @Nullable public final Integer index;
field @Nullable public final Boolean openInReaderMode;
field @Nullable public final Boolean pinned;
field @Nullable public final String url;
}
public static class WebExtension.DisabledFlags {
ctor public DisabledFlags();
field public static final int APP = 8;
@ -1587,6 +1597,7 @@ package org.mozilla.geckoview {
public static interface WebExtension.SessionTabDelegate {
method @UiThread @NonNull default public GeckoResult<AllowOrDeny> onCloseTab(@Nullable WebExtension, @NonNull GeckoSession);
method @UiThread @NonNull default public GeckoResult<AllowOrDeny> onUpdateTab(@NonNull WebExtension, @NonNull GeckoSession, @NonNull WebExtension.UpdateTabDetails);
}
public static class WebExtension.SignedStateFlags {
@ -1600,7 +1611,17 @@ package org.mozilla.geckoview {
}
public static interface WebExtension.TabDelegate {
method @UiThread @Nullable default public GeckoResult<GeckoSession> onNewTab(@Nullable WebExtension, @Nullable String);
method @UiThread @Nullable default public GeckoResult<GeckoSession> onNewTab(@NonNull WebExtension, @NonNull WebExtension.CreateTabDetails);
}
public static class WebExtension.UpdateTabDetails {
ctor protected UpdateTabDetails();
field @Nullable public final Boolean active;
field @Nullable public final Boolean autoDiscardable;
field @Nullable public final Boolean highlighted;
field @Nullable public final Boolean muted;
field @Nullable public final Boolean pinned;
field @Nullable public final String url;
}
public class WebExtensionController {

Просмотреть файл

@ -190,6 +190,24 @@ public class TestRunnerActivity extends Activity {
return createSession(null);
}
private WebExtension.SessionTabDelegate mSessionTabDelegate = new WebExtension.SessionTabDelegate() {
@NonNull
@Override
public GeckoResult<AllowOrDeny> onCloseTab(@Nullable WebExtension source,
@NonNull GeckoSession session) {
closeSession(session);
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
@Override
public GeckoResult<AllowOrDeny> onUpdateTab(@NonNull WebExtension source,
@NonNull GeckoSession session,
@NonNull WebExtension.UpdateTabDetails updateDetails) {
webExtensionController().setTabActive(mActiveSession, false);
mActiveSession = session;
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
};
private GeckoSession createSession(GeckoSessionSettings settings) {
if (settings == null) {
settings = new GeckoSessionSettings();
@ -203,15 +221,7 @@ public class TestRunnerActivity extends Activity {
final WebExtension.SessionController sessionController =
session.getWebExtensionController();
for (final WebExtension extension : mExtensions) {
sessionController.setTabDelegate(extension, new WebExtension.SessionTabDelegate() {
@NonNull
@Override
public GeckoResult<AllowOrDeny> onCloseTab(@Nullable WebExtension source,
@NonNull GeckoSession session) {
closeSession(session);
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
});
sessionController.setTabDelegate(extension, mSessionTabDelegate);
}
mOwnedSessions.add(session);
@ -311,13 +321,21 @@ public class TestRunnerActivity extends Activity {
for (WebExtension extension : mExtensions) {
extension.setTabDelegate(new WebExtension.TabDelegate() {
@Override
public GeckoResult<GeckoSession> onNewTab(WebExtension source, String uri) {
webExtensionController().setTabActive(mActiveSession, false);
mActiveSession = createSession();
webExtensionController().setTabActive(mActiveSession, true);
return GeckoResult.fromValue(mActiveSession);
public GeckoResult<GeckoSession> onNewTab(WebExtension source,
WebExtension.CreateTabDetails details) {
GeckoSession newSession = createSession();
if (details.active == Boolean.TRUE) {
webExtensionController().setTabActive(mActiveSession, false);
mActiveSession = newSession;
}
return GeckoResult.fromValue(newSession);
}
});
for (final GeckoSession session : mOwnedSessions) {
session.getWebExtensionController()
.setTabDelegate(extension, mSessionTabDelegate);
}
}
});
}

Просмотреть файл

@ -447,11 +447,12 @@ class WebExtensionTest : BaseSessionTest() {
val tabsExtension = WebExtension(TABS_CREATE_BACKGROUND, controller)
val tabDelegate = object : WebExtension.TabDelegate {
override fun onNewTab(source: WebExtension?, uri: String?): GeckoResult<GeckoSession> {
assertEquals(uri, "https://www.mozilla.org/en-US/")
override fun onNewTab(source: WebExtension, details: WebExtension.CreateTabDetails): GeckoResult<GeckoSession> {
assertEquals(details.url, "https://www.mozilla.org/en-US/")
assertEquals(details.active, true)
assertEquals(tabsExtension, source)
tabsCreateResult.complete(null)
return GeckoResult.fromValue(GeckoSession(sessionRule.session.settings))
return GeckoResult.fromValue(null)
}
}
tabsExtension.setTabDelegate(tabDelegate)
@ -480,12 +481,13 @@ class WebExtensionTest : BaseSessionTest() {
tabsExtension::setTabDelegate,
{ tabsExtension.setTabDelegate(null) },
object : WebExtension.TabDelegate {
override fun onNewTab(source: WebExtension?, uri: String?): GeckoResult<GeckoSession> {
override fun onNewTab(source: WebExtension, details: WebExtension.CreateTabDetails): GeckoResult<GeckoSession> {
val extensionCreatedSession = GeckoSession(sessionRule.session.settings)
extensionCreatedSession.webExtensionController.setTabDelegate(tabsExtension, object : WebExtension.SessionTabDelegate {
override fun onCloseTab(source: WebExtension?, session: GeckoSession): GeckoResult<AllowOrDeny> {
assertEquals(tabsExtension, source)
assertEquals(details.active, true)
assertNotEquals(null, extensionCreatedSession)
assertEquals(extensionCreatedSession, session)
onCloseRequestResult.complete(null)
@ -938,8 +940,15 @@ class WebExtensionTest : BaseSessionTest() {
controller)
sessionRule.waitForResult(sessionRule.runtime.registerWebExtension(extension))
mainSession.webExtensionController
.setMessageDelegate(extension, messageDelegate, "browser")
val sessionController = mainSession.webExtensionController
sessionController.setMessageDelegate(extension, messageDelegate, "browser")
sessionController.setTabDelegate(extension, object: WebExtension.SessionTabDelegate {
override fun onUpdateTab(extension: WebExtension,
session: GeckoSession,
details: WebExtension.UpdateTabDetails): GeckoResult<AllowOrDeny> {
return GeckoResult.fromValue(AllowOrDeny.ALLOW)
}
})
mainSession.loadUri("http://example.com")

Просмотреть файл

@ -128,6 +128,30 @@ public final class GeckoBundle implements Parcelable {
return getBoolean(key, false);
}
/**
* Returns the value associated with a Boolean mapping, or defaultValue if the mapping
* does not exist.
*
* @param key Key to look for.
* @param defaultValue Value to return if mapping does not exist.
* @return Boolean value
*/
public Boolean getBooleanObject(final String key, final Boolean defaultValue) {
final Object value = mMap.get(key);
return value == null ? defaultValue : (Boolean) value;
}
/**
* Returns the value associated with a Boolean mapping, or null if the mapping does
* not exist.
*
* @param key Key to look for.
* @return Boolean value
*/
public Boolean getBooleanObject(final String key) {
return getBooleanObject(key, null);
}
/**
* Returns the value associated with a boolean array mapping, or null if the mapping
* does not exist.
@ -211,6 +235,30 @@ public final class GeckoBundle implements Parcelable {
return getInt(key, 0);
}
/**
* Returns the value associated with an Integer mapping, or defaultValue if the mapping
* does not exist.
*
* @param key Key to look for.
* @param defaultValue Value to return if mapping does not exist.
* @return Int value
*/
public Integer getInteger(final String key, final Integer defaultValue) {
final Object value = mMap.get(key);
return value == null ? defaultValue : ((Integer) value);
}
/**
* Returns the value associated with an Integer mapping, or null if the mapping does not
* exist.
*
* @param key Key to look for.
* @return Int value
*/
public Integer getInteger(final String key) {
return getInteger(key, null);
}
private static int[] getIntArray(final double[] array) {
final int len = array.length;
final int[] ret = new int[len];

Просмотреть файл

@ -409,6 +409,9 @@ public class WebExtension {
* tab. In case WebExtension can close the tab, it should close passed GeckoSession and
* return GeckoResult.ALLOW or GeckoResult.DENY in case tab cannot be closed.
*
* See also: <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/remove">
* WebExtensions/API/tabs/remove</a>
*
* @param source An instance of {@link WebExtension} or null if extension was not registered
* with GeckoRuntime.registerWebextension
* @param session An instance of {@link GeckoSession} to be closed.
@ -420,6 +423,167 @@ public class WebExtension {
@NonNull GeckoSession session) {
return GeckoResult.DENY;
}
/**
* Called when tabs.update is invoked. The uri is provided for informational purposes,
* there's no need to call <code>loadURI</code> on it. The page will be loaded if
* this method returns GeckoResult.ALLOW.
*
* See also: <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/update">
* WebExtensions/API/tabs/update</a>
*
* @param extension The extension that requested to update the tab.
* @param session The {@link GeckoSession} instance that needs to be updated.
* @param details {@link UpdateTabDetails} instance that describes what
* needs to be updated for this tab.
* @return <code>GeckoResult.ALLOW</code> if the tab will be updated,
* <code>GeckoResult.DENY</code> otherwise.
*/
@UiThread
@NonNull
default GeckoResult<AllowOrDeny> onUpdateTab(final @NonNull WebExtension extension,
final @NonNull GeckoSession session,
final @NonNull UpdateTabDetails details) {
return GeckoResult.DENY;
}
}
/**
* Provides details about upating a tab with <code>tabs.update</code>.
*
* Whenever a field is not passed in by the extension that value will be <code>null</code>.
*
* See also: <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/update">
* WebExtensions/API/tabs/update
* </a>.
*/
public static class UpdateTabDetails {
/**
* Whether the tab should become active. If <code>true</code>,
* non-active highlighted tabs should stop being highlighted. If
* <code>false</code>, does nothing.
*/
@Nullable
public final Boolean active;
/**
* Whether the tab should be discarded automatically by the app when
* resources are low.
*/
@Nullable
public final Boolean autoDiscardable;
/**
* If <code>true</code> and the tab is not highlighted, it should become
* active by default.
*/
@Nullable
public final Boolean highlighted;
/**
* Whether the tab should be muted.
*/
@Nullable
public final Boolean muted;
/**
* Whether the tab should be pinned.
*/
@Nullable
public final Boolean pinned;
/**
* The url that the tab will be navigated to. This url is provided just
* for informational purposes, there is no need to call
* <code>loadUri</code> on it. The corresponding {@link GeckoSession} will be
* navigated to the right URL after returning
* <code>GeckoResult.ALLOW</code> from {@link SessionTabDelegate#onUpdateTab}
*/
@Nullable
public final String url;
/** For testing. */
protected UpdateTabDetails() {
active = null;
autoDiscardable = null;
highlighted = null;
muted = null;
pinned = null;
url = null;
}
/* package */ UpdateTabDetails(final GeckoBundle bundle) {
active = bundle.getBooleanObject("active");
autoDiscardable = bundle.getBooleanObject("autoDiscardable");
highlighted = bundle.getBooleanObject("highlighted");
muted = bundle.getBooleanObject("muted");
pinned = bundle.getBooleanObject("pinned");
url = bundle.getString("url");
}
}
/**
* Provides details about creating a tab with <code>tabs.create</code>.
* See also: <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/create">
* WebExtensions/API/tabs/create
* </a>.
*
* Whenever a field is not passed in by the extension that value will be <code>null</code>.
*/
public static class CreateTabDetails {
/**
* Whether the tab should become active. If <code>true</code>,
* non-active highlighted tabs should stop being highlighted. If
* <code>false</code>, does nothing.
*/
@Nullable
public final Boolean active;
/**
* Whether the tab is created and made visible in the tab bar
* without any content loaded into memory, a state known as
* discarded. The tabs content should be loaded when the tab is
* activated.
*/
@Nullable
public final Boolean discarded;
/**
* The position the tab should take in the window.
*/
@Nullable
public final Integer index;
/**
* If true, open this tab in Reader Mode.
*/
@Nullable
public final Boolean openInReaderMode;
/**
* Whether the tab should be pinned.
*/
@Nullable
public final Boolean pinned;
/**
* The url that the tab will be navigated to. This url is provided just
* for informational purposes, there is no need to call
* <code>loadUri</code> on it. The corresponding {@link GeckoSession} will be
* navigated to the right URL after returning
* <code>GeckoResult.ALLOW</code> from {@link TabDelegate#onNewTab}
*/
@Nullable
public final String url;
/** For testing. */
protected CreateTabDetails() {
active = null;
discarded = null;
index = null;
openInReaderMode = null;
pinned = null;
url = null;
}
/* package */ CreateTabDetails(final GeckoBundle bundle) {
active = bundle.getBooleanObject("active");
discarded = bundle.getBooleanObject("discarded");
index = bundle.getInteger("index");
openInReaderMode = bundle.getBooleanObject("openInReaderMode");
pinned = bundle.getBooleanObject("pinned");
url = bundle.getString("url");
}
}
/**
@ -433,10 +597,8 @@ public class WebExtension {
* that GeckoView will use to load the requested page on. If the returned value
* is null the page will not be opened.
*
* @param source An instance of {@link WebExtension} or null if extension was not registered
* with GeckoRuntime.registerWebextension
* @param uri The URI to be loaded. This is provided for informational purposes only,
* do not call {@link GeckoSession#loadUri} on it.
* @param source An instance of {@link WebExtension}
* @param createDetails Information about this tab.
* @return A {@link GeckoResult} which holds the returned GeckoSession. May be null, in
* which case the request for a new tab by the extension will fail.
* The implementation of onNewTab is responsible for maintaining a reference
@ -444,7 +606,8 @@ public class WebExtension {
*/
@UiThread
@Nullable
default GeckoResult<GeckoSession> onNewTab(@Nullable WebExtension source, @Nullable String uri) {
default GeckoResult<GeckoSession> onNewTab(@NonNull WebExtension source,
@NonNull CreateTabDetails createDetails) {
return null;
}
}
@ -700,6 +863,7 @@ public class WebExtension {
mEventDispatcher.registerUiThreadListener(
this,
"GeckoView:WebExtension:NewTab",
"GeckoView:WebExtension:UpdateTab",
"GeckoView:WebExtension:CloseTab"
);
mTabDelegateRegistered = true;

Просмотреть файл

@ -717,6 +717,9 @@ public class WebExtensionController {
} else if ("GeckoView:WebExtension:NewTab".equals(event)) {
newTab(bundle, callback);
return;
} else if ("GeckoView:WebExtension:UpdateTab".equals(event)) {
updateTab(bundle, callback, session);
return;
} else if ("GeckoView:WebExtension:CloseTab".equals(event)) {
closeTab(bundle, callback, session);
return;
@ -860,10 +863,12 @@ public class WebExtensionController {
final TabDelegate legacyDelegate) {
extensionFromBundle(message).then(extension -> {
final WebExtension.TabDelegate delegate = mListener.getTabDelegate(extension);
final WebExtension.CreateTabDetails details =
new WebExtension.CreateTabDetails(message.getBundle("createProperties"));
if (delegate != null) {
return delegate.onNewTab(extension, message.getString("uri"));
return delegate.onNewTab(extension, details);
} else if (legacyDelegate != null) {
return legacyDelegate.onNewTab(extension, message.getString("uri"));
return legacyDelegate.onNewTab(extension, details.url);
}
return null;
}).accept(session -> {
@ -882,6 +887,26 @@ public class WebExtensionController {
});
}
/* package */ void updateTab(final GeckoBundle message,
final EventCallback callback,
final GeckoSession session) {
extensionFromBundle(message).then(extension -> {
final WebExtension.SessionTabDelegate delegate = session.getWebExtensionController()
.getTabDelegate(extension);
if (delegate == null) {
return GeckoResult.fromValue(AllowOrDeny.DENY);
}
return delegate.onUpdateTab(extension, session,
new WebExtension.UpdateTabDetails(message.getBundle("updateProperties")));
}).accept(value -> {
if (value == AllowOrDeny.ALLOW) {
callback.sendSuccess(null);
} else {
callback.sendError(null);
}
});
}
/* package */ void closeTab(final GeckoBundle message,
final EventCallback callback,
final GeckoSession session) {

Просмотреть файл

@ -38,6 +38,13 @@ exclude: true
per-`WebExtension` object instead of being global. The existing global
[`TabDelegate`][75.11] is now deprecated and will be removed in GeckoView 77.
([bug 1616625]({{bugzilla}}1616625))
- Added [`SessionTabDelegate#onUpdateTab`][75.12] which is called whenever an
extension calls `tabs.update` on the corresponding `GeckoSession`.
[`TabDelegate#onCreateTab`][75.13] now takes a [`CreateTabDetails`][75.14]
object which contains additional information about the newly created tab
(including the `url` which used to be passed in directly).
([bug 1616625]({{bugzilla}}1616625))
[75.1]: {{javadoc_uri}}/GeckoRuntimeSettings.Builder.html#useMultiprocess-boolean-
[75.2]: {{javadoc_uri}}/WebExtensionController.DebuggerDelegate.html#onExtensionListUpdated--
@ -50,6 +57,9 @@ exclude: true
[75.9]: {{javadoc_uri}}/WebExtension.SessionTabDelegate.html
[75.10]: {{javadoc_uri}}/WebExtension.TabDelegate.html
[75.11]: {{javadoc_uri}}/WebExtensionRuntime.TabDelegate.html
[75.12]: {{javadoc_uri}}/WebExtension.SessionTabDelegate.html#onUpdateTab-org.mozilla.geckoview.WebExtension-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.WebExtension.UpdateTabDetails-
[75.13]: {{javadoc_uri}}/WebExtension.TabDelegate.html#onNewTab-org.mozilla.geckoview.WebExtension-org.mozilla.geckoview.WebExtension.CreateTabDetails-
[75.14]: {{javadoc_uri}}/WebExtension.CreateTabDetails.html
## v74
- Added [`WebExtensionController.enable`][74.1] and [`disable`][74.2] to
@ -613,4 +623,4 @@ exclude: true
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: 5f39aa0f1916565230a07c0e7113fe202f6a94a0
[api-version]: 8b880533b33a0ad178f88ade00aaae5897625ef9

Просмотреть файл

@ -91,7 +91,8 @@ interface WebExtensionDelegate {
return null;
}
default void closeTab(TabSession session) {}
default TabSession openNewTab() { return null; }
default void updateTab(TabSession session, WebExtension.UpdateTabDetails details) {}
default TabSession openNewTab(WebExtension.CreateTabDetails details) { return null; }
}
class WebExtensionManager implements WebExtension.ActionDelegate,
@ -151,12 +152,13 @@ class WebExtensionManager implements WebExtension.ActionDelegate,
}
@Override
public GeckoResult<GeckoSession> onNewTab(WebExtension source, String uri) {
public GeckoResult<GeckoSession> onNewTab(WebExtension source,
WebExtension.CreateTabDetails details) {
WebExtensionDelegate delegate = mExtensionDelegate.get();
if (delegate == null) {
return GeckoResult.fromValue(null);
}
return GeckoResult.fromValue(delegate.openNewTab());
return GeckoResult.fromValue(delegate.openNewTab(details));
}
@Override
@ -174,6 +176,23 @@ class WebExtensionManager implements WebExtension.ActionDelegate,
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
@Override
public GeckoResult<AllowOrDeny> onUpdateTab(WebExtension extension,
GeckoSession session,
WebExtension.UpdateTabDetails updateDetails) {
final WebExtensionDelegate delegate = mExtensionDelegate.get();
if (delegate == null) {
return GeckoResult.fromValue(AllowOrDeny.DENY);
}
final TabSession tabSession = mTabManager.getSession(session);
if (tabSession != null) {
delegate.updateTab(tabSession, updateDetails);
}
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
@Override
public void onPageAction(final WebExtension extension,
final GeckoSession session,
@ -376,10 +395,12 @@ public class GeckoViewActivity
};
@Override
public TabSession openNewTab() {
public TabSession openNewTab(WebExtension.CreateTabDetails details) {
final TabSession newSession = createSession();
mToolbarView.updateTabCount();
setGeckoViewSession(newSession);
if (details.active == Boolean.TRUE) {
setGeckoViewSession(newSession, false);
}
return newSession;
}
@ -585,7 +606,9 @@ public class GeckoViewActivity
@Override
public void onCloseRequest(final GeckoSession session) {
setPopupVisibility(false);
mPopupSession.close();
if (mPopupSession != null) {
mPopupSession.close();
}
mPopupSession = null;
mPopupView = null;
}
@ -872,28 +895,45 @@ public class GeckoViewActivity
}
}
@Override
public void updateTab(TabSession session, WebExtension.UpdateTabDetails details) {
if (details.active == Boolean.TRUE) {
switchToSession(session, false);
}
}
public void onBrowserActionClick() {
sExtensionManager.onClicked(mTabSessionManager.getCurrentSession());
}
public void switchToTab(int index) {
TabSession nextSession = mTabSessionManager.getSession(index);
public void switchToSession(TabSession session, boolean activateTab) {
TabSession currentSession = mTabSessionManager.getCurrentSession();
if(nextSession != currentSession) {
setGeckoViewSession(nextSession);
mCurrentUri = nextSession.getUri();
if (session != currentSession) {
setGeckoViewSession(session, activateTab);
mCurrentUri = session.getUri();
mToolbarView.getLocationView().setText(mCurrentUri);
}
}
public void switchToTab(int index) {
TabSession nextSession = mTabSessionManager.getSession(index);
switchToSession(nextSession, true);
}
private void setGeckoViewSession(TabSession session) {
setGeckoViewSession(session, true);
}
private void setGeckoViewSession(TabSession session, boolean activateTab) {
final WebExtensionController controller = sGeckoRuntime.getWebExtensionController();
final GeckoSession previousSession = mGeckoView.releaseSession();
if (previousSession != null) {
controller.setTabActive(previousSession, false);
}
mGeckoView.setSession(session);
controller.setTabActive(session, true);
if (activateTab) {
controller.setTabActive(session, true);
}
mTabSessionManager.setCurrentSession(session);
}

Просмотреть файл

@ -122,29 +122,18 @@ const GeckoViewTabBridge = {
* @throws {Error}
* Throws an error if the GeckoView app doesn't support tabs.create or fails to handle the request.
*/
async createNewTab(options = {}) {
const url = options.url || "about:blank";
if (!options.extensionId) {
throw new Error("options.extensionId missing");
}
const message = {
async createNewTab({ extensionId, createProperties } = {}) {
const sessionId = await EventDispatcher.instance.sendRequestForResult({
type: "GeckoView:WebExtension:NewTab",
uri: url,
extensionId: options.extensionId,
};
const sessionId = await EventDispatcher.instance.sendRequestForResult(
message
);
extensionId,
createProperties,
});
if (!sessionId) {
throw new Error("Cannot create new tab");
}
let window;
const browser = await new Promise(resolve => {
const window = await new Promise(resolve => {
const handler = {
observe(aSubject, aTopic, aData) {
if (
@ -152,25 +141,13 @@ const GeckoViewTabBridge = {
aSubject.name === sessionId
) {
Services.obs.removeObserver(handler, "geckoview-window-created");
window = aSubject;
resolve(window.browser);
resolve(aSubject);
}
},
};
Services.obs.addObserver(handler, "geckoview-window-created");
});
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
if (options.disallowInheritPrincipal) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
browser.loadURI(url, {
flags,
triggeringPrincipal: options.triggeringPrincipal,
});
return BrowserAppShim.getBrowserApp(window).selectedTab;
},
@ -188,19 +165,19 @@ const GeckoViewTabBridge = {
* Throws an error if the GeckoView app doesn't allow extension to close tab.
*/
async closeTab({ window, extensionId } = {}) {
if (!extensionId) {
throw new Error("extensionId is required");
}
if (!window) {
throw new Error("window is required");
}
await window.WindowEventDispatcher.sendRequestForResult({
type: "GeckoView:WebExtension:CloseTab",
extensionId,
});
},
async updateTab({ window, extensionId, updateProperties } = {}) {
await window.WindowEventDispatcher.sendRequestForResult({
type: "GeckoView:WebExtension:UpdateTab",
extensionId,
updateProperties,
});
},
};
class GeckoViewTab extends GeckoViewModule {