зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
d0840b96b4
Коммит
45283ccbf3
|
@ -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 tab’s 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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче