Backed out 7 changesets (bug 1599580) for causing linting opt failure CLOSED TREE

Backed out changeset 64fd40663930 (bug 1599580)
Backed out changeset 7dc53077d58a (bug 1599580)
Backed out changeset 51a8fbed80c3 (bug 1599580)
Backed out changeset e7fbe7147d19 (bug 1599580)
Backed out changeset 176337e5ba59 (bug 1599580)
Backed out changeset 24d4083da050 (bug 1599580)
Backed out changeset 312f626fb657 (bug 1599580)
This commit is contained in:
shindli 2019-12-06 01:10:47 +02:00
Родитель 1bf300b45c
Коммит d0e6e72266
18 изменённых файлов: 245 добавлений и 1124 удалений

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

@ -169,6 +169,7 @@ pref("layout.spellcheckDefault", 0);
pref("dom.forms.datetime.others", true);
/* extension manager and xpinstall */
pref("xpinstall.whitelist.directRequest", false);
pref("xpinstall.whitelist.fileRequest", false);
pref("xpinstall.whitelist.add", "https://addons.mozilla.org");

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

@ -92,13 +92,8 @@ add_task(async function testTabEvents() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
applications: {
gecko: {
id: "test_tabs_events@tests.mozilla.org",
},
},
},
useAddonManager: "permanent",
background,
});
@ -145,13 +140,8 @@ add_task(async function testTabRemovalEvent() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
applications: {
gecko: {
id: "test_tabs_removal@tests.mozilla.org",
},
},
},
useAddonManager: "permanent",
background,
});

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

@ -220,15 +220,9 @@ add_task(async function testExecuteScript() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["http://mochi.test/", "http://example.com/", "webNavigation"],
applications: {
gecko: {
id: "test_execute_script@tests.mozilla.org",
},
},
},
background,
useAddonManager: "permanent",
files: {
"script.js": function() {

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

@ -16,13 +16,7 @@ add_task(async function() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
applications: {
gecko: {
id: "test_ext_tabs_get@tests.mozilla.org",
},
}
},
useAddonManager: "permanent",
async background() {
const tab1 = await browser.tabs.create({});
const tab2 = await browser.tabs.create({});

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

@ -15,11 +15,6 @@
add_task(async function test_onUpdated() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"applications": {
"gecko": {
"id": "test_on_updated@tests.mozilla.org",
},
},
"permissions": ["tabs"],
"content_scripts": [{
"matches": ["http://mochi.test/*/context_tabs_onUpdated_page.html"],
@ -28,7 +23,6 @@ add_task(async function test_onUpdated() {
}],
},
useAddonManager: "permanent",
background: function() {
let pageURL = "http://mochi.test:8888/tests/mobile/android/components/extensions/test/mochitest/context_tabs_onUpdated_page.html";
@ -100,22 +94,12 @@ add_task(async function test_onUpdated() {
await extension.unload();
});
function* do_test_update(name, background, withPermissions = true) {
let manifest = {
applications: {
gecko: {
id: "test_update_" + name + "@tests.mozilla.org",
},
},
};
function* do_test_update(background, withPermissions = true) {
let manifest = {};
if (withPermissions) {
manifest.permissions = ["tabs", "http://mochi.test/"];
}
let extension = ExtensionTestUtils.loadExtension({
manifest,
background,
useAddonManager: "permanent",
});
let extension = ExtensionTestUtils.loadExtension({manifest, background});
yield Promise.all([
yield extension.startup(),
@ -126,7 +110,7 @@ function* do_test_update(name, background, withPermissions = true) {
}
add_task(async function test_url() {
await do_test_update("url", function background() {
await do_test_update(function background() {
// Create a new tab for testing update.
browser.tabs.create({}, function(tab) {
browser.tabs.onUpdated.addListener(async function onUpdated(tabId, changeInfo) {
@ -148,7 +132,7 @@ add_task(async function test_url() {
});
add_task(async function test_title() {
await do_test_update("title", async function background() {
await do_test_update(async function background() {
const url = "http://mochi.test:8888/tests/mobile/android/components/extensions/test/mochitest/context_tabs_onUpdated_page.html";
const tab = await browser.tabs.create({url});
@ -168,7 +152,7 @@ add_task(async function test_title() {
});
add_task(async function test_without_tabs_permission() {
await do_test_update("tabs_permission", async function background() {
await do_test_update(async function background() {
const url = "http://mochi.test:8888/tests/mobile/android/components/extensions/test/mochitest/context_tabs_onUpdated_page.html";
const tab = await browser.tabs.create({url});
let count = 0;

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

@ -16,11 +16,7 @@ add_task(async function tabsSendMessageReply() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
"applications": {
"gecko": {
"id": "test_send_message_reply@tests.mozilla.org",
},
},
"content_scripts": [{
"matches": ["http://example.com/"],
"js": ["content-script.js"],
@ -28,8 +24,6 @@ add_task(async function tabsSendMessageReply() {
}],
},
useAddonManager: "permanent",
background: async function() {
let firstTab;
let promiseResponse = new Promise(resolve => {
@ -132,11 +126,7 @@ add_task(async function tabsSendHidden() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
"applications": {
"gecko": {
"id": "test_send_hidden@tests.mozilla.org",
},
},
"content_scripts": [{
"matches": ["http://example.com/content*"],
"js": ["content-script.js"],
@ -144,8 +134,6 @@ add_task(async function tabsSendHidden() {
}],
},
useAddonManager: "permanent",
background: async function() {
let resolveContent;
browser.runtime.onMessage.addListener((msg, sender) => {
@ -221,15 +209,8 @@ add_task(async function tabsSendMessageNoExceptionOnNonExistentTab() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
"applications": {
"gecko": {
"id": "test_send_hidden@tests.mozilla.org",
},
},
},
useAddonManager: "permanent",
async background() {
let url = "http://example.com/mochitest/tests/mobile/android/components/extensions/test/mochitest/file_dummy.html";
let tab = await browser.tabs.create({url});

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

@ -16,13 +16,7 @@ add_task(async function() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["webNavigation", "tabs"],
applications: {
gecko: {
id: "test_ext_webNavigation_onCommitted@tests.mozilla.org",
},
},
},
useAddonManager: "permanent",
async background() {
const url = "http://mochi.test:8888/";
const [tab, tabDetails] = await Promise.all([

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

@ -74,7 +74,6 @@ GeckoViewStartup.prototype = {
"GeckoView:PageAction:Click",
"GeckoView:RegisterWebExtension",
"GeckoView:UnregisterWebExtension",
"GeckoView:WebExtension:Get",
"GeckoView:WebExtension:Disable",
"GeckoView:WebExtension:Enable",
"GeckoView:WebExtension:Install",

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

@ -1395,7 +1395,6 @@ package org.mozilla.geckoview {
field public final long flags;
field @NonNull public final String id;
field @NonNull public final String location;
field @Nullable public final WebExtension.MetaData metaData;
}
@AnyThread public static class WebExtension.Action {
@ -1417,16 +1416,6 @@ package org.mozilla.geckoview {
method @UiThread @Nullable default public GeckoResult<GeckoSession> onTogglePopup(@NonNull WebExtension, @NonNull WebExtension.Action);
}
public static class WebExtension.BlocklistStateFlags {
ctor public BlocklistStateFlags();
field public static final int BLOCKED = 2;
field public static final int NOT_BLOCKED = 0;
field public static final int OUTDATED = 3;
field public static final int SOFTBLOCKED = 1;
field public static final int VULNERABLE_NO_UPDATE = 5;
field public static final int VULNERABLE_UPDATE_AVAILABLE = 4;
}
public static class WebExtension.Flags {
ctor protected Flags();
field public static final long ALLOW_CONTENT_MESSAGING = 1L;
@ -1438,22 +1427,6 @@ package org.mozilla.geckoview {
method @AnyThread @NonNull public GeckoResult<Bitmap> get(int);
}
public static class WebExtension.InstallException extends Exception {
ctor protected InstallException();
field public final int code;
}
public static class WebExtension.InstallException.ErrorCodes {
ctor protected ErrorCodes();
field public static final int ERROR_CORRUPT_FILE = -3;
field public static final int ERROR_FILE_ACCESS = -4;
field public static final int ERROR_INCORRECT_HASH = -2;
field public static final int ERROR_INCORRECT_ID = -7;
field public static final int ERROR_NETWORK_FAILURE = -1;
field public static final int ERROR_SIGNEDSTATE_REQUIRED = -5;
field public static final int ERROR_UNEXPECTED_ADDON_TYPE = -6;
}
@UiThread public static interface WebExtension.MessageDelegate {
method @Nullable default public void onConnect(@NonNull WebExtension.Port);
method @Nullable default public GeckoResult<Object> onMessage(@NonNull String, @NonNull Object, @NonNull WebExtension.MessageSender);
@ -1470,24 +1443,6 @@ package org.mozilla.geckoview {
field @NonNull public final WebExtension webExtension;
}
public class WebExtension.MetaData {
ctor protected MetaData();
field public final int blocklistState;
field @Nullable public final String creatorName;
field @Nullable public final String creatorUrl;
field @Nullable public final String description;
field @Nullable public final String homepageUrl;
field @NonNull public final WebExtension.Icon icon;
field public final boolean isRecommended;
field @Nullable public final String name;
field public final boolean openOptionsPageInTab;
field @Nullable public final String optionsPageUrl;
field @NonNull public final String[] origins;
field @NonNull public final String[] permissions;
field public final int signedState;
field @NonNull public final String version;
}
@UiThread public static class WebExtension.Port {
ctor protected Port();
method public void disconnect();
@ -1502,27 +1457,9 @@ package org.mozilla.geckoview {
method default public void onPortMessage(@NonNull Object, @NonNull WebExtension.Port);
}
public static class WebExtension.SignedStateFlags {
ctor public SignedStateFlags();
field public static final int MISSING = 0;
field public static final int PRELIMINARY = 1;
field public static final int PRIVILEGED = 4;
field public static final int SIGNED = 2;
field public static final int SYSTEM = 3;
field public static final int UNKNOWN = -1;
}
public class WebExtensionController {
method @UiThread @Nullable public WebExtensionController.PromptDelegate getPromptDelegate();
method @UiThread @Nullable public WebExtensionController.TabDelegate getTabDelegate();
method @NonNull @AnyThread public GeckoResult<WebExtension> install(@NonNull String);
method @UiThread public void setPromptDelegate(@Nullable WebExtensionController.PromptDelegate);
method @UiThread public void setTabDelegate(@Nullable WebExtensionController.TabDelegate);
method @NonNull @AnyThread public GeckoResult<Void> uninstall(@NonNull WebExtension);
}
@UiThread public static interface WebExtensionController.PromptDelegate {
method @Nullable default public GeckoResult<AllowOrDeny> onInstallPrompt(@NonNull WebExtension);
}
public static interface WebExtensionController.TabDelegate {

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

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

@ -12,11 +12,8 @@ import org.hamcrest.core.StringEndsWith.endsWith
import org.hamcrest.core.IsEqual.equalTo
import org.json.JSONObject
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.*
@ -40,16 +37,6 @@ class WebExtensionTest : BaseSessionTest() {
val MESSAGING_CONTENT: String = "resource://android/assets/web_extensions/messaging-content/"
}
@Before
fun setup() {
sessionRule.addExternalDelegateUntilTestEnd(
WebExtensionController.PromptDelegate::class,
sessionRule.runtime.webExtensionController::setPromptDelegate,
{ sessionRule.runtime.webExtensionController.promptDelegate = null },
object : WebExtensionController.PromptDelegate {}
)
}
@Test
fun registerWebExtension() {
mainSession.loadUri("example.com")
@ -86,130 +73,6 @@ class WebExtensionTest : BaseSessionTest() {
colorAfter as String, equalTo(""))
}
@Test
fun installWebExtension() {
mainSession.loadUri("example.com")
sessionRule.waitForPageStop()
// First let's check that the color of the border is empty before loading
// the WebExtension
val colorBefore = mainSession.evaluateJS("document.body.style.borderColor")
assertThat("The border color should be empty when loading without extensions.",
colorBefore as String, equalTo(""))
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
@AssertCalled
override fun onInstallPrompt(extension: WebExtension): GeckoResult<AllowOrDeny> {
assertEquals(extension.metaData!!.description,
"Adds a red border to all webpages matching example.com.")
assertEquals(extension.metaData!!.name, "Borderify")
assertEquals(extension.metaData!!.version, "1.0")
// TODO: Bug 1601067
// assertEquals(extension.isBuiltIn, false)
// TODO: Bug 1599585
// assertEquals(extension.isEnabled, false)
assertEquals(extension.metaData!!.signedState,
WebExtension.SignedStateFlags.SIGNED)
assertEquals(extension.metaData!!.blocklistState,
WebExtension.BlocklistStateFlags.NOT_BLOCKED)
return GeckoResult.fromValue(AllowOrDeny.ALLOW)
}
})
val borderify = sessionRule.waitForResult(
sessionRule.runtime.webExtensionController.install(
"resource://android/assets/web_extensions/borderify.xpi"))
mainSession.reload()
sessionRule.waitForPageStop()
// Check that the WebExtension was applied by checking the border color
val color = mainSession.evaluateJS("document.body.style.borderColor")
assertThat("Content script should have been applied",
color as String, equalTo("red"))
// Unregister WebExtension and check again
sessionRule.waitForResult(sessionRule.runtime.webExtensionController.uninstall(borderify))
mainSession.reload()
sessionRule.waitForPageStop()
// Check that the WebExtension was not applied after being unregistered
val colorAfter = mainSession.evaluateJS("document.body.style.borderColor")
assertThat("Content script should have been applied",
colorAfter as String, equalTo(""))
}
private fun testInstallError(name: String, expectedError: Int) {
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
@AssertCalled(count = 0)
override fun onInstallPrompt(extension: WebExtension): GeckoResult<AllowOrDeny> {
return GeckoResult.fromValue(AllowOrDeny.ALLOW)
}
})
sessionRule.waitForResult(
sessionRule.runtime.webExtensionController.install(
"resource://android/assets/web_extensions/$name")
.accept({
// We should not be able to install unsigned extensions
assertTrue(false)
}, { exception ->
val installException = exception as WebExtension.InstallException
assertEquals(installException.code, expectedError)
}))
}
@Test
fun installUnsignedExtensionSignatureNotRequired() {
sessionRule.setPrefsUntilTestEnd(mapOf(
"xpinstall.signatures.required" to false
))
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
override fun onInstallPrompt(extension: WebExtension): GeckoResult<AllowOrDeny> {
return GeckoResult.fromValue(AllowOrDeny.ALLOW)
}
})
val borderify = sessionRule.waitForResult(
sessionRule.runtime.webExtensionController.install(
"resource://android/assets/web_extensions/borderify-unsigned.xpi")
.then { extension ->
assertEquals(extension!!.metaData!!.signedState,
WebExtension.SignedStateFlags.MISSING)
assertEquals(extension.metaData!!.blocklistState,
WebExtension.BlocklistStateFlags.NOT_BLOCKED)
assertEquals(extension.metaData!!.name, "Borderify")
GeckoResult.fromValue(extension)
})
sessionRule.waitForResult(
sessionRule.runtime.webExtensionController.uninstall(borderify))
}
@Test
fun installUnsignedExtensionSignatureRequired() {
sessionRule.setPrefsUntilTestEnd(mapOf(
"xpinstall.signatures.required" to true
))
testInstallError("borderify-unsigned.xpi",
WebExtension.InstallException.ErrorCodes.ERROR_SIGNEDSTATE_REQUIRED)
}
@Test
fun installExtensionFileNotFound() {
testInstallError("file-not-found.xpi",
WebExtension.InstallException.ErrorCodes.ERROR_NETWORK_FAILURE)
}
@Test
fun installExtensionMissingId() {
testInstallError("borderify-missing-id.xpi",
WebExtension.InstallException.ErrorCodes.ERROR_CORRUPT_FILE)
}
// This test
// - Registers a web extension
// - Listens for messages and waits for a message

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

@ -54,9 +54,8 @@ public class WebExtension {
*/
public final @WebExtensionFlags long flags;
/** Provides information about this {@link WebExtension}. */
// TODO: move to @NonNull when we remove registerWebExtension
public final @Nullable MetaData metaData;
// TODO: make public
final MetaData metaData;
// TODO: make public
final boolean isBuiltIn;
@ -64,26 +63,6 @@ public class WebExtension {
// TODO: make public
final boolean isEnabled;
/** Called whenever a delegate is set or unset on this {@link WebExtension} instance.
/* package */ interface DelegateObserver {
void onMessageDelegate(final String nativeApp, final MessageDelegate delegate);
void onActionDelegate(final ActionDelegate delegate);
}
private WeakReference<DelegateObserver> mDelegateObserver = new WeakReference<>(null);
/* package */ void setDelegateObserver(final DelegateObserver observer) {
mDelegateObserver = new WeakReference<>(observer);
if (observer != null) {
// Notify observers of already attached delegates
for (final Map.Entry<String, MessageDelegate> entry : messageDelegates.entrySet()) {
observer.onMessageDelegate(entry.getKey(), entry.getValue());
}
observer.onActionDelegate(actionDelegate);
}
}
/**
* Delegates that handle messaging between this WebExtension and the app.
*/
@ -240,10 +219,6 @@ public class WebExtension {
@UiThread
public void setMessageDelegate(final @Nullable MessageDelegate messageDelegate,
final @NonNull String nativeApp) {
final DelegateObserver observer = mDelegateObserver.get();
if (observer != null) {
observer.onMessageDelegate(nativeApp, messageDelegate);
}
if (messageDelegate == null) {
messageDelegates.remove(nativeApp);
return;
@ -1047,58 +1022,6 @@ public class WebExtension {
}
}
/** Extension thrown when an error occurs during extension installation. */
public static class InstallException extends Exception {
public static class ErrorCodes {
/** The download failed due to network problems. */
public static final int ERROR_NETWORK_FAILURE = -1;
/** The downloaded file did not match the provided hash. */
public static final int ERROR_INCORRECT_HASH = -2;
/** The downloaded file seems to be corrupted in some way. */
public static final int ERROR_CORRUPT_FILE = -3;
/** An error occurred trying to write to the filesystem. */
public static final int ERROR_FILE_ACCESS = -4;
/** The extension must be signed and isn't. */
public static final int ERROR_SIGNEDSTATE_REQUIRED = -5;
/** The downloaded extension had a different type than expected. */
public static final int ERROR_UNEXPECTED_ADDON_TYPE = -6;
/** The extension did not have the expected ID. */
public static final int ERROR_INCORRECT_ID = -7;
/** For testing. */
protected ErrorCodes() {}
}
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
ErrorCodes.ERROR_NETWORK_FAILURE,
ErrorCodes.ERROR_INCORRECT_HASH,
ErrorCodes.ERROR_CORRUPT_FILE,
ErrorCodes.ERROR_FILE_ACCESS,
ErrorCodes.ERROR_SIGNEDSTATE_REQUIRED,
ErrorCodes.ERROR_UNEXPECTED_ADDON_TYPE,
ErrorCodes.ERROR_INCORRECT_ID
})
/* package */ @interface Codes {}
/** One of {@link ErrorCodes} that provides more information about this exception. */
public final @Codes int code;
/** For testing */
protected InstallException() {
this.code = ErrorCodes.ERROR_NETWORK_FAILURE;
}
@Override
public String toString() {
return "InstallException: " + code;
}
/* package */ InstallException(final @Codes int code) {
this.code = code;
}
}
/**
* Set the Action delegate for this WebExtension.
*
@ -1112,11 +1035,6 @@ public class WebExtension {
*/
@AnyThread
public void setActionDelegate(final @Nullable ActionDelegate delegate) {
final DelegateObserver observer = mDelegateObserver.get();
if (observer != null) {
observer.onActionDelegate(delegate);
}
actionDelegate = delegate;
final GeckoBundle bundle = new GeckoBundle(1);
@ -1126,27 +1044,15 @@ public class WebExtension {
"GeckoView:ActionDelegate:Attached", bundle);
}
/** Describes the signed status for a {@link WebExtension}.
*
* See <a href="https://support.mozilla.org/en-US/kb/add-on-signing-in-firefox">
* Add-on signing in Firefox.
* </a>
*/
public static class SignedStateFlags {
// Keep in sync with AddonManager.jsm
/** This extension may be signed but by a certificate that doesn't
* chain to our our trusted certificate. */
public final static int UNKNOWN = -1;
/** This extension is unsigned. */
public final static int MISSING = 0;
/** This extension has been preliminarily reviewed. */
public final static int PRELIMINARY = 1;
/** This extension has been fully reviewed. */
public final static int SIGNED = 2;
/** This extension is a system add-on. */
public final static int SYSTEM = 3;
/** This extension is signed with a "Mozilla Extensions" certificate. */
public final static int PRIVILEGED = 4;
// TODO: make public
// Keep in sync with AddonManager.jsm
static class SignedStateFlags {
final static int UNKNOWN = -1;
final static int MISSING = 0;
final static int PRELIMINARY = 1;
final static int SIGNED = 2;
final static int SYSTEM = 3;
final static int PRIVILEGED = 4;
/* package */ final static int LAST = PRIVILEGED;
}
@ -1156,136 +1062,40 @@ public class WebExtension {
SignedStateFlags.SIGNED, SignedStateFlags.SYSTEM, SignedStateFlags.PRIVILEGED})
@interface SignedState {}
/** Describes the blocklist state for a {@link WebExtension}.
* See <a href="https://support.mozilla.org/en-US/kb/add-ons-cause-issues-are-on-blocklist">
* Add-ons that cause stability or security issues are put on a blocklist
* </a>.
*/
public static class BlocklistStateFlags {
// Keep in sync with nsIBlocklistService.idl
/** This extension does not appear in the blocklist. */
public final static int NOT_BLOCKED = 0;
/** This extension is in the blocklist but the problem is not severe
* enough to warant forcibly blocking. */
public final static int SOFTBLOCKED = 1;
/** This extension should be blocked and never used. */
public final static int BLOCKED = 2;
/** This extension is considered outdated, and there is a known update
* available. */
public final static int OUTDATED = 3;
/** This extension is vulnerable and there is an update. */
public final static int VULNERABLE_UPDATE_AVAILABLE = 4;
/** This extension is vulnerable and there is no update. */
public final static int VULNERABLE_NO_UPDATE = 5;
// TODO: make public
// Keep in sync with nsIBlocklistService.idl
static class BlockedReasonFlags {
final static int NOT_BLOCKED = 0;
final static int SOFTBLOCKED = 1;
final static int BLOCKED = 2;
final static int OUTDATED = 3;
final static int VULNERABLE_UPDATE_AVAILABLE = 4;
final static int VULNERABLE_NO_UPDATE = 5;
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({ BlocklistStateFlags.NOT_BLOCKED, BlocklistStateFlags.SOFTBLOCKED,
BlocklistStateFlags.BLOCKED, BlocklistStateFlags.OUTDATED,
BlocklistStateFlags.VULNERABLE_UPDATE_AVAILABLE,
BlocklistStateFlags.VULNERABLE_NO_UPDATE})
@interface BlocklistState {}
@IntDef({ BlockedReasonFlags.NOT_BLOCKED, BlockedReasonFlags.SOFTBLOCKED,
BlockedReasonFlags.BLOCKED, BlockedReasonFlags.OUTDATED,
BlockedReasonFlags.VULNERABLE_UPDATE_AVAILABLE,
BlockedReasonFlags.VULNERABLE_NO_UPDATE})
@interface BlockedReason {}
/** Provides information about a {@link WebExtension}. */
public class MetaData {
/** Main {@link Icon} branding for this {@link WebExtension}.
* Can be used when displaying prompts. */
public final @NonNull Icon icon;
/** API permissions requested or granted to this extension.
*
* Permission identifiers match entries in the manifest, see
* <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#API_permissions">
* API permissions
* </a>.
*/
public final @NonNull String[] permissions;
/** Host permissions requested or granted to this extension.
*
* See <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#Host_permissions">
* Host permissions
* </a>.
*/
public final @NonNull String[] origins;
/** Branding name for this extension.
*
* See <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/name">
* manifest.json/name
* </a>
*/
public final @Nullable String name;
/** Branding description for this extension. This string will be
* localized using the current GeckoView language setting.
*
* See <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/description">
* manifest.json/description
* </a>
*/
public final @Nullable String description;
/** Version string for this extension.
*
* See <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version">
* manifest.json/version
* </a>
*/
public final @NonNull String version;
/** Creator name as provided in the manifest.
*
* See <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/developer">
* manifest.json/developer
* </a>
*/
public final @Nullable String creatorName;
/** Creator url as provided in the manifest.
*
* See <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/developer">
* manifest.json/developer
* </a>
*/
public final @Nullable String creatorUrl;
/** Homepage url as provided in the manifest.
*
* See <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/homepage_url">
* manifest.json/homepage_url
* </a>
*/
public final @Nullable String homepageUrl;
/** Options page as provided in the manifest.
*
* See <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/options_ui">
* manifest.json/options_ui
* </a>
*/
// TODO: Bug 1598792
final @Nullable String optionsPageUrl;
/** Whether the options page should be open in a Tab or not.
*
* See <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/options_ui#Syntax">
* manifest.json/options_ui#Syntax
* </a>
*/
// TODO: Bug 1598792
// TODO: make public
class MetaData {
final Icon icon;
final String[] permissions;
final String[] origins;
final String name;
final String description;
final String version;
final String creatorName;
final String creatorUrl;
final String homepageUrl;
final String optionsPageUrl;
final boolean openOptionsPageInTab;
/** Whether or not this is a recommended extension.
*
* See <a href="https://blog.mozilla.org/firefox/firefox-recommended-extensions/">
* Recommended Extensions program
* </a>
*/
public final boolean isRecommended;
/** Blocklist status for this extension.
*
* See <a href="https://support.mozilla.org/en-US/kb/add-ons-cause-issues-are-on-blocklist">
* Add-ons that cause stability or security issues are put on a blocklist
* </a>.
*/
public final @BlocklistState int blocklistState;
/** Signed status for this extension.
*
* See <a href="https://support.mozilla.org/en-US/kb/add-on-signing-in-firefox">
* Add-on signing in Firefox.
* </a>.
*/
public final @SignedState int signedState;
final boolean isRecommended;
final @BlockedReason int blockedReason;
final @SignedState int signedState;
/** Override for testing. */
protected MetaData() {
@ -1301,7 +1111,7 @@ public class WebExtension {
optionsPageUrl = null;
openOptionsPageInTab = false;
isRecommended = false;
blocklistState = BlocklistStateFlags.NOT_BLOCKED;
blockedReason = BlockedReasonFlags.NOT_BLOCKED;
signedState = SignedStateFlags.UNKNOWN;
}
@ -1317,7 +1127,7 @@ public class WebExtension {
optionsPageUrl = bundle.getString("optionsPageUrl");
openOptionsPageInTab = bundle.getBoolean("openOptionsPageInTab");
isRecommended = bundle.getBoolean("isRecommended");
blocklistState = bundle.getInt("blocklistState", BlocklistStateFlags.NOT_BLOCKED);
blockedReason = bundle.getInt("blockedReason", BlockedReasonFlags.NOT_BLOCKED);
int signedState = bundle.getInt("signedState", SignedStateFlags.UNKNOWN);
if (signedState <= SignedStateFlags.LAST) {

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

@ -1,6 +1,5 @@
package org.mozilla.geckoview;
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
@ -23,97 +22,31 @@ public class WebExtensionController {
private GeckoRuntime mRuntime;
private boolean mHandlerRegistered = false;
private TabDelegate mTabDelegate;
private PromptDelegate mPromptDelegate;
private static class ExtensionStore {
final private Map<String, WebExtension> mData = new HashMap<>();
public GeckoResult<WebExtension> get(final String id) {
final WebExtension extension = mData.get(id);
if (extension == null) {
final WebExtensionResult result = new WebExtensionResult("extension");
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putString("extensionId", id);
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:Get",
bundle, result);
return result.then(ext -> {
mData.put(ext.id, ext);
return GeckoResult.fromValue(ext);
});
}
return GeckoResult.fromValue(extension);
}
public void remove(final String id) {
mData.remove(id);
}
public void put(final String id, final WebExtension extension) {
mData.put(id, extension);
}
}
private ExtensionStore mExtensions = new ExtensionStore();
private Map<String, WebExtension> mExtensions = new HashMap<>();
private Map<Long, WebExtension.Port> mPorts = new HashMap<>();
private Internals mInternals = new Internals();
// Avoids exposing listeners to the API
private class Internals implements BundleEventListener,
WebExtension.Port.DisconnectDelegate,
WebExtension.DelegateObserver {
private boolean mMessageListenersAttached = false;
private boolean mActionListenersAttached = false;
WebExtension.Port.DisconnectDelegate {
@Override
// BundleEventListener
public void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
WebExtensionController.this.handleMessage(event, message, callback, null);
}
@Override
// WebExtension.Port.DisconnectDelegate
public void onDisconnectFromApp(final WebExtension.Port port) {
// If the port has been disconnected from the app side, we don't need to notify anyone and
// we just need to remove it from our list of ports.
mPorts.remove(port.id);
}
@Override
// WebExtension.DelegateObserver
public void onMessageDelegate(final String nativeApp,
final WebExtension.MessageDelegate delegate) {
if (delegate != null && !mMessageListenersAttached) {
EventDispatcher.getInstance().registerUiThreadListener(
this,
"GeckoView:WebExtension:Message",
"GeckoView:WebExtension:PortMessage",
"GeckoView:WebExtension:Connect",
"GeckoView:WebExtension:Disconnect");
mMessageListenersAttached = true;
}
}
@Override
// WebExtension.DelegateObserver
public void onActionDelegate(final WebExtension.ActionDelegate delegate) {
if (delegate != null && !mActionListenersAttached) {
EventDispatcher.getInstance().registerUiThreadListener(
this,
"GeckoView:BrowserAction:Update",
"GeckoView:BrowserAction:OpenPopup",
"GeckoView:PageAction:Update",
"GeckoView:PageAction:OpenPopup");
mActionListenersAttached = true;
}
}
}
public interface TabDelegate {
@ -178,64 +111,31 @@ public class WebExtensionController {
mTabDelegate = delegate;
}
/**
* This delegate will be called whenever an extension is about to be installed or it needs
* new permissions, e.g during an update or because it called <code>permissions.request</code>
*/
@UiThread
public interface PromptDelegate {
/**
* Called whenever a new extension is being installed. This is intended as an
* opportunity for the app to prompt the user for the permissions required by
* this extension.
*
* @param extension The {@link WebExtension} that is about to be installed.
* You can use {@link WebExtension#metaData} to gather information
* about this extension when building the user prompt dialog.
* @return A {@link GeckoResult} that completes to either {@link AllowOrDeny#ALLOW ALLOW}
* if this extension should be installed or {@link AllowOrDeny#DENY DENY} if
* this extension should not be installed. A null value will be interpreted as
* {@link AllowOrDeny#DENY DENY}.
*/
@Nullable
default GeckoResult<AllowOrDeny> onInstallPrompt(final @NonNull WebExtension extension) {
// TODO: make public
interface PromptDelegate {
default GeckoResult<AllowOrDeny> onInstallPrompt(WebExtension extension) {
return null;
}
/*
TODO: Bug 1599581
default GeckoResult<AllowOrDeny> onUpdatePrompt(
WebExtension currentlyInstalled,
WebExtension updatedExtension,
String[] newPermissions) {
return null;
}
TODO: Bug 1601420
default GeckoResult<AllowOrDeny> onOptionalPrompt(
WebExtension extension,
String[] optionalPermissions) {
return null;
} */
}
}
/**
* @return the current {@link PromptDelegate} instance.
* @see PromptDelegate
*/
@UiThread
@Nullable
public PromptDelegate getPromptDelegate() {
// TODO: make public
PromptDelegate getPromptDelegate() {
return mPromptDelegate;
}
/** Set the {@link PromptDelegate} for this instance. This delegate will be used
* to be notified whenever an extension is being installed or needs new permissions.
*
* @param delegate the delegate instance.
* @see PromptDelegate
*/
@UiThread
public void setPromptDelegate(final @Nullable PromptDelegate delegate) {
// TODO: make public
void setPromptDelegate(final PromptDelegate delegate) {
if (delegate == null && mPromptDelegate != null) {
EventDispatcher.getInstance().unregisterUiThreadListener(
mInternals,
@ -244,7 +144,7 @@ public class WebExtensionController {
"GeckoView:WebExtension:OptionalPrompt"
);
} else if (delegate != null && mPromptDelegate == null) {
EventDispatcher.getInstance().registerUiThreadListener(
EventDispatcher.getInstance().unregisterUiThreadListener(
mInternals,
"GeckoView:WebExtension:InstallPrompt",
"GeckoView:WebExtension:UpdatePrompt",
@ -255,8 +155,7 @@ public class WebExtensionController {
mPromptDelegate = delegate;
}
private static class WebExtensionResult extends GeckoResult<WebExtension>
implements EventCallback {
private static class WebExtensionResult extends CallbackResult<WebExtension> {
private final String mFieldName;
public WebExtensionResult(final String fieldName) {
@ -268,59 +167,11 @@ public class WebExtensionController {
final GeckoBundle bundle = (GeckoBundle) response;
complete(new WebExtension(bundle.getBundle(mFieldName)));
}
@Override
public void sendError(final Object response) {
if (response instanceof GeckoBundle
&& ((GeckoBundle) response).containsKey("installError")) {
final GeckoBundle bundle = (GeckoBundle) response;
final int errorCode = bundle.getInt("installError");
completeExceptionally(new WebExtension.InstallException(errorCode));
} else {
completeExceptionally(new Exception(response.toString()));
}
}
}
/**
* Install an extension.
*
* An installed extension will persist and will be available even when restarting the
* {@link GeckoRuntime}.
*
* Installed extensions through this method need to be signed by Mozilla, see
* <a href="https://extensionworkshop.com/documentation/publish/signing-and-distribution-overview/#distributing-your-addon">
* Distributing your add-on
* </a>.
*
* When calling this method, the GeckoView library will download the extension, validate
* its manifest and signature, and give you an opportunity to verify its permissions through
* {@link PromptDelegate#installPrompt}, you can use this method to prompt the user if
* appropriate.
*
* @param uri URI to the extension's <code>.xpi</code> package. This can be a remote
* <code>https:</code> URI or a local <code>file:</code> or <code>resource:</code>
* URI. Note: the app needs the appropriate permissions for local URIs.
*
* @return A {@link GeckoResult} that will complete when the installation process finishes.
* For successful installations, the GeckoResult will return the {@link WebExtension}
* object that you can use to set delegates and retrieve information about the
* WebExtension using {@link WebExtension#metaData}.
*
* If an error occurs during the installation process, the GeckoResult will complete
* exceptionally with a
* {@link WebExtension.InstallException InstallException} that will contain
* the relevant error code in
* {@link WebExtension.InstallException#code InstallException#code}.
*
* @see PromptDelegate#installPrompt
* @see WebExtension.InstallException.ErrorCodes
* @see WebExtension#metaData
*/
@NonNull
@AnyThread
public GeckoResult<WebExtension> install(final @NonNull String uri) {
final WebExtensionResult result = new WebExtensionResult("extension");
// TODO: make public
GeckoResult<WebExtension> install(final String uri) {
final CallbackResult<WebExtension> result = new WebExtensionResult("extension");
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putString("locationUri", uri);
@ -328,15 +179,12 @@ public class WebExtensionController {
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:Install",
bundle, result);
return result.then(extension -> {
registerWebExtension(extension);
return GeckoResult.fromValue(extension);
});
return result;
}
// TODO: Bug 1601067 make public
// TODO: make public
GeckoResult<WebExtension> installBuiltIn(final String uri) {
final WebExtensionResult result = new WebExtensionResult("extension");
final CallbackResult<WebExtension> result = new WebExtensionResult("extension");
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putString("locationUri", uri);
@ -344,25 +192,11 @@ public class WebExtensionController {
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:InstallBuiltIn",
bundle, result);
return result.then(extension -> {
registerWebExtension(extension);
return GeckoResult.fromValue(extension);
});
return result;
}
/**
* Uninstall an extension.
*
* Uninstalling an extension will remove it from the current {@link GeckoRuntime} instance,
* delete all its data and trigger a request to close all extension pages currently open.
*
* @param extension The {@link WebExtension} to be uninstalled.
*
* @return A {@link GeckoResult} that will complete when the uninstall process is completed.
*/
@NonNull
@AnyThread
public GeckoResult<Void> uninstall(final @NonNull WebExtension extension) {
// TODO: make public
GeckoResult<Void> uninstall(final WebExtension extension) {
final CallbackResult<Void> result = new CallbackResult<Void>() {
@Override
public void sendSuccess(final Object response) {
@ -370,8 +204,6 @@ public class WebExtensionController {
}
};
unregisterWebExtension(extension);
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putString("webExtensionId", extension.id);
@ -381,9 +213,9 @@ public class WebExtensionController {
return result;
}
// TODO: Bug 1599585 make public
// TODO: make public
GeckoResult<WebExtension> enable(final WebExtension extension) {
final WebExtensionResult result = new WebExtensionResult("extension");
final CallbackResult<WebExtension> result = new WebExtensionResult("extension");
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putString("webExtensionId", extension.id);
@ -391,15 +223,12 @@ public class WebExtensionController {
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:Enable",
bundle, result);
return result.then(newExtension -> {
registerWebExtension(newExtension);
return GeckoResult.fromValue(newExtension);
});
return result;
}
// TODO: Bug 1599585 make public
// TODO: make public
GeckoResult<WebExtension> disable(final WebExtension extension) {
final WebExtensionResult result = new WebExtensionResult("extension");
final CallbackResult<WebExtension> result = new WebExtensionResult("extension");
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putString("webExtensionId", extension.id);
@ -407,13 +236,10 @@ public class WebExtensionController {
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:Disable",
bundle, result);
return result.then(newExtension -> {
registerWebExtension(newExtension);
return GeckoResult.fromValue(newExtension);
});
return result;
}
// TODO: Bug 1600742 make public
// TODO: make public
GeckoResult<List<WebExtension>> listInstalled() {
final CallbackResult<List<WebExtension>> result = new CallbackResult<List<WebExtension>>() {
@Override
@ -422,9 +248,7 @@ public class WebExtensionController {
.getBundleArray("extensions");
final List<WebExtension> list = new ArrayList<>(bundles.length);
for (GeckoBundle bundle : bundles) {
final WebExtension extension = new WebExtension(bundle);
registerWebExtension(extension);
list.add(extension);
list.add(new WebExtension(bundle));
}
complete(list);
@ -437,9 +261,9 @@ public class WebExtensionController {
return result;
}
// TODO: Bug 1599581 make public
// TODO: make public
GeckoResult<WebExtension> update(final WebExtension extension) {
final WebExtensionResult result = new WebExtensionResult("extension");
final CallbackResult<WebExtension> result = new WebExtensionResult("extension");
final GeckoBundle bundle = new GeckoBundle(1);
bundle.putString("webExtensionId", extension.id);
@ -447,10 +271,7 @@ public class WebExtensionController {
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:Update",
bundle, result);
return result.then(newExtension -> {
registerWebExtension(newExtension);
return GeckoResult.fromValue(newExtension);
});
return result;
}
/* package */ WebExtensionController(final GeckoRuntime runtime) {
@ -458,7 +279,23 @@ public class WebExtensionController {
}
/* package */ void registerWebExtension(final WebExtension webExtension) {
webExtension.setDelegateObserver(mInternals);
if (!mHandlerRegistered) {
EventDispatcher.getInstance().registerUiThreadListener(
mInternals,
"GeckoView:WebExtension:Message",
"GeckoView:WebExtension:PortMessage",
"GeckoView:WebExtension:Connect",
"GeckoView:WebExtension:Disconnect",
// {Browser,Page}Actions
"GeckoView:BrowserAction:Update",
"GeckoView:BrowserAction:OpenPopup",
"GeckoView:PageAction:Update",
"GeckoView:PageAction:OpenPopup"
);
mHandlerRegistered = true;
}
mExtensions.put(webExtension.id, webExtension);
}
@ -485,9 +322,6 @@ public class WebExtensionController {
} else if ("GeckoView:PageAction:OpenPopup".equals(event)) {
openPopup(message, session, WebExtension.Action.TYPE_PAGE_ACTION);
return;
} else if ("GeckoView:WebExtension:InstallPrompt".equals(event)) {
installPrompt(message, callback);
return;
}
final String nativeApp = message.getString("nativeApp");
@ -499,60 +333,19 @@ public class WebExtensionController {
return;
}
final GeckoBundle senderBundle = message.getBundle("sender");
final String extensionId = senderBundle.getString("extensionId");
mExtensions.get(extensionId).accept(extension -> {
final WebExtension.MessageSender sender = fromBundle(extension, senderBundle, session);
if (sender == null) {
if (callback != null) {
callback.sendError("Could not find recipient for " + message.getBundle("sender"));
}
return;
final WebExtension.MessageSender sender = fromBundle(message.getBundle("sender"), session);
if (sender == null) {
if (callback != null) {
callback.sendError("Could not find recipient for " + message.getBundle("sender"));
}
if ("GeckoView:WebExtension:Connect".equals(event)) {
connect(nativeApp, message.getLong("portId", -1), callback, sender);
} else if ("GeckoView:WebExtension:Message".equals(event)) {
message(nativeApp, message, callback, sender);
}
});
}
private void installPrompt(final GeckoBundle message, final EventCallback callback) {
final GeckoBundle extensionBundle = message.getBundle("extension");
if (extensionBundle == null || !extensionBundle.containsKey("webExtensionId")
|| !extensionBundle.containsKey("locationURI")) {
if (BuildConfig.DEBUG) {
throw new RuntimeException("Missing webExtensionId or locationURI");
}
Log.e(LOGTAG, "Missing webExtensionId or locationURI");
return;
}
final WebExtension extension = new WebExtension(extensionBundle);
if (mPromptDelegate == null) {
Log.e(LOGTAG, "Tried to install extension " + extension.id +
" but no delegate is registered");
return;
if ("GeckoView:WebExtension:Connect".equals(event)) {
connect(nativeApp, message.getLong("portId", -1), callback, sender);
} else if ("GeckoView:WebExtension:Message".equals(event)) {
message(nativeApp, message, callback, sender);
}
final GeckoResult<AllowOrDeny> promptResponse = mPromptDelegate.onInstallPrompt(extension);
if (promptResponse == null) {
return;
}
promptResponse.accept(allowOrDeny -> {
GeckoBundle response = new GeckoBundle(1);
if (AllowOrDeny.ALLOW.equals(allowOrDeny)) {
response.putBoolean("allow", true);
} else {
response.putBoolean("allow", false);
}
callback.sendSuccess(response);
});
}
private void newTab(final GeckoBundle message, final EventCallback callback) {
@ -561,9 +354,16 @@ public class WebExtensionController {
return;
}
mExtensions.get(message.getString("extensionId")).then(extension ->
mTabDelegate.onNewTab(extension, message.getString("uri"))
).accept(session -> {
WebExtension extension = mExtensions.get(message.getString("extensionId"));
final GeckoResult<GeckoSession> result = mTabDelegate.onNewTab(extension, message.getString("uri"));
if (result == null) {
callback.sendSuccess(null);
return;
}
result.accept(session -> {
if (session == null) {
callback.sendSuccess(null);
return;
@ -585,13 +385,11 @@ public class WebExtensionController {
return;
}
mExtensions.get(message.getString("extensionId")).then(
extension -> mTabDelegate.onCloseTab(extension, session),
// On uninstall, we close all extension pages, in that case
// the extension object may be gone already so we can't
// send it to the delegate
exception -> mTabDelegate.onCloseTab(null, session)
).accept(value -> {
WebExtension extension = mExtensions.get(message.getString("extensionId"));
GeckoResult<AllowOrDeny> result = mTabDelegate.onCloseTab(extension, session);
result.accept(value -> {
if (value == AllowOrDeny.ALLOW) {
callback.sendSuccess(null);
} else {
@ -603,7 +401,6 @@ public class WebExtensionController {
/* package */ void unregisterWebExtension(final WebExtension webExtension) {
mExtensions.remove(webExtension.id);
webExtension.setDelegateObserver(null);
// Some ports may still be open so we need to go through the list and close all of the
// ports tied to this web extension
@ -617,9 +414,15 @@ public class WebExtensionController {
}
}
private WebExtension.MessageSender fromBundle(final WebExtension extension,
final GeckoBundle sender,
/* package */ @Nullable WebExtension getWebExtension(final String id) {
return mExtensions.get(id);
}
private WebExtension.MessageSender fromBundle(final GeckoBundle sender,
final GeckoSession session) {
final String extensionId = sender.getString("extensionId");
WebExtension extension = mExtensions.get(extensionId);
if (extension == null) {
// All senders should have an extension
return null;
@ -777,29 +580,39 @@ public class WebExtensionController {
exception -> callback.sendError(exception));
}
private GeckoResult<WebExtension> extensionFromBundle(final GeckoBundle message) {
private WebExtension extensionFromBundle(final GeckoBundle message) {
final String extensionId = message.getString("extensionId");
return mExtensions.get(extensionId);
final WebExtension extension = mExtensions.get(extensionId);
if (extension == null) {
if (BuildConfig.DEBUG) {
// TODO: Bug 1582185 Some gecko tests install WebExtensions that we
// don't know about and cause this to trigger.
// throw new RuntimeException("Could not find extension: " + extensionId);
}
Log.e(LOGTAG, "Could not find extension: " + extensionId);
}
return extension;
}
private void openPopup(final GeckoBundle message, final GeckoSession session,
final @WebExtension.Action.ActionType int actionType) {
extensionFromBundle(message).accept(extension -> {
if (extension == null) {
return;
}
final WebExtension extension = extensionFromBundle(message);
if (extension == null) {
return;
}
final WebExtension.Action action = new WebExtension.Action(
actionType, message.getBundle("action"), extension);
final WebExtension.Action action = new WebExtension.Action(
actionType, message.getBundle("action"), extension);
final WebExtension.ActionDelegate delegate = actionDelegateFor(extension, session);
if (delegate == null) {
return;
}
final WebExtension.ActionDelegate delegate = actionDelegateFor(extension, session);
if (delegate == null) {
return;
}
final GeckoResult<GeckoSession> popup = delegate.onOpenPopup(extension, action);
action.openPopup(popup);
});
final GeckoResult<GeckoSession> popup = delegate.onOpenPopup(extension, action);
action.openPopup(popup);
}
private WebExtension.ActionDelegate actionDelegateFor(final WebExtension extension,
@ -813,23 +626,22 @@ public class WebExtensionController {
private void actionUpdate(final GeckoBundle message, final GeckoSession session,
final @WebExtension.Action.ActionType int actionType) {
extensionFromBundle(message).accept(extension -> {
if (extension == null) {
return;
}
final WebExtension extension = extensionFromBundle(message);
if (extension == null) {
return;
}
final WebExtension.ActionDelegate delegate = actionDelegateFor(extension, session);
if (delegate == null) {
return;
}
final WebExtension.ActionDelegate delegate = actionDelegateFor(extension, session);
if (delegate == null) {
return;
}
final WebExtension.Action action = new WebExtension.Action(
actionType, message.getBundle("action"), extension);
if (actionType == WebExtension.Action.TYPE_BROWSER_ACTION) {
delegate.onBrowserAction(extension, session, action);
} else if (actionType == WebExtension.Action.TYPE_PAGE_ACTION) {
delegate.onPageAction(extension, session, action);
}
});
final WebExtension.Action action = new WebExtension.Action(
actionType, message.getBundle("action"), extension);
if (actionType == WebExtension.Action.TYPE_BROWSER_ACTION) {
delegate.onBrowserAction(extension, session, action);
} else if (actionType == WebExtension.Action.TYPE_PAGE_ACTION) {
delegate.onPageAction(extension, session, action);
}
}
}

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

@ -13,13 +13,6 @@ exclude: true
⚠️ breaking change
## v73
- Added [`WebExtensionController.install`][73.1] and [`uninstall`][73.2] to
manage installed extensions
[73.1]: {{javadoc_uri}}/WebExtensionController.html#install-java.lang.String-
[73.2]: {{javadoc_uri}}/WebExtensionController.html#uninstall-org.mozilla.geckoview.WebExtension-
## v72
- Added [`GeckoSession.NavigationDelegate.LoadRequest#hasUserGesture`][72.1]. This indicates
if a load was requested while a user gesture was active (e.g., a tap).
@ -483,4 +476,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]: b216d2a3b1bfee233cbae01acad8130b369baaa4
[api-version]: 4c9f04038d8478206efac05b518920819faeacea

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

@ -19,20 +19,12 @@ const { GeckoViewUtils } = ChromeUtils.import(
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
AddonManager: "resource://gre/modules/AddonManager.jsm",
EventDispatcher: "resource://gre/modules/Messaging.jsm",
Extension: "resource://gre/modules/Extension.jsm",
ExtensionChild: "resource://gre/modules/ExtensionChild.jsm",
GeckoViewTabBridge: "resource://gre/modules/GeckoViewTab.jsm",
});
XPCOMUtils.defineLazyServiceGetter(
this,
"mimeService",
"@mozilla.org/mime;1",
"nsIMIMEService"
);
const { debug, warn } = GeckoViewUtils.initLogging("Console"); // eslint-disable-line no-unused-vars
/** Provides common logic between page and browser actions */
@ -239,133 +231,6 @@ class GeckoViewConnection {
}
}
function exportExtension(aAddon, aPermissions, aSourceURI) {
const { origins, permissions } = aPermissions;
const {
creator,
description,
homepageURL,
signedState,
name,
icons,
version,
optionsURL,
optionsBrowserStyle,
isRecommended,
blocklistState,
isActive,
isBuiltin,
id,
} = aAddon;
let creatorName = null;
let creatorURL = null;
if (creator) {
const { name, url } = creator;
creatorName = name;
creatorURL = url;
}
const openOptionsPageInTab =
optionsBrowserStyle === AddonManager.OPTIONS_TYPE_TAB;
return {
webExtensionId: id,
locationURI: aSourceURI != null ? aSourceURI.spec : "",
isEnabled: isActive,
isBuiltIn: isBuiltin,
metaData: {
permissions,
origins,
description,
version,
creatorName,
creatorURL,
homepageURL,
name,
optionsPageURL: optionsURL,
openOptionsPageInTab,
isRecommended,
blocklistState,
signedState,
icons,
},
};
}
class ExtensionInstallListener {
constructor(aResolve) {
this.resolve = aResolve;
}
onDownloadCancelled(aInstall) {
const { error: installError } = aInstall;
this.resolve({ installError });
}
onDownloadFailed(aInstall) {
const { error: installError } = aInstall;
this.resolve({ installError });
}
onDownloadEnded() {
// Nothing to do
}
onInstallCancelled(aInstall) {
const { error: installError } = aInstall;
this.resolve({ installError });
}
onInstallFailed(aInstall) {
const { error: installError } = aInstall;
this.resolve({ installError });
}
onInstallEnded(aInstall, aAddon) {
const extension = exportExtension(
aAddon,
aAddon.userPermissions,
aInstall.sourceURI
);
this.resolve({ extension });
}
}
class ExtensionPromptObserver {
constructor() {
Services.obs.addObserver(this, "webextension-permission-prompt");
}
async permissionPrompt(aInstall, aAddon, aInfo) {
const { sourceURI } = aInstall;
const { permissions } = aInfo;
const extension = exportExtension(aAddon, permissions, sourceURI);
const response = await EventDispatcher.instance.sendRequestForResult({
type: "GeckoView:WebExtension:InstallPrompt",
extension,
});
if (response.allow) {
aInfo.resolve();
} else {
aInfo.reject();
}
}
observe(aSubject, aTopic, aData) {
debug`observe ${aTopic}`;
switch (aTopic) {
case "webextension-permission-prompt": {
const { info } = aSubject.wrappedJSObject;
const { addon, install } = info;
this.permissionPrompt(install, addon, info);
break;
}
}
}
}
new ExtensionPromptObserver();
var GeckoViewWebExtension = {
async registerWebExtension(aId, aUri, allowContentMessaging, aCallback) {
const params = {
@ -402,107 +267,45 @@ var GeckoViewWebExtension = {
}
},
async extensionById(aId) {
let scope = this.extensionScopes.get(aId);
extensionById(aId) {
const scope = this.extensionScopes.get(aId);
if (!scope) {
// Check if this is an installed extension we haven't seen yet
const addon = await AddonManager.getAddonByID(aId);
if (!addon) {
debug`Could not find extension with id=${aId}`;
return null;
}
scope = {
allowContentMessaging: false,
extension: addon,
};
return null;
}
return scope.extension;
},
async installWebExtension(aUri) {
const install = await AddonManager.getInstallForURL(aUri.spec);
const promise = new Promise(resolve => {
install.addListener(new ExtensionInstallListener(resolve));
});
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
const mimeType = mimeService.getTypeFromURI(aUri);
AddonManager.installAddonFromWebpage(
mimeType,
null,
systemPrincipal,
install
);
return promise;
},
async uninstallWebExtension(aId) {
const extension = await this.extensionById(aId);
if (!extension) {
throw new Error(`Could not find an extension with id='${aId}'.`);
}
return extension.uninstall();
},
async browserActionClick(aId) {
const extension = await this.extensionById(aId);
if (!extension) {
return;
}
const browserAction = this.browserActions.get(extension);
if (!browserAction) {
return;
}
browserAction.click();
},
async pageActionClick(aId) {
const extension = await this.extensionById(aId);
if (!extension) {
return;
}
const pageAction = this.pageActions.get(extension);
if (!pageAction) {
return;
}
pageAction.click();
},
async actionDelegateAttached(aId) {
const extension = await this.extensionById(aId);
if (!extension) {
return;
}
const browserAction = this.browserActions.get(extension);
if (browserAction) {
// Send information about this action to the delegate
browserAction.updateOnChange(null);
}
const pageAction = this.pageActions.get(extension);
if (pageAction) {
pageAction.updateOnChange(null);
}
},
async onEvent(aEvent, aData, aCallback) {
onEvent(aEvent, aData, aCallback) {
debug`onEvent ${aEvent} ${aData}`;
switch (aEvent) {
case "GeckoView:BrowserAction:Click": {
this.browserActionClick(aData.extensionId);
const extension = this.extensionById(aData.extensionId);
if (!extension) {
return;
}
const browserAction = this.browserActions.get(extension);
if (!browserAction) {
return;
}
browserAction.click();
break;
}
case "GeckoView:PageAction:Click": {
this.pageActionClick(aData.extensionId);
const extension = this.extensionById(aData.extensionId);
if (!extension) {
return;
}
const pageAction = this.pageActions.get(extension);
if (!pageAction) {
return;
}
pageAction.click();
break;
}
case "GeckoView:RegisterWebExtension": {
@ -550,7 +353,21 @@ var GeckoViewWebExtension = {
}
case "GeckoView:ActionDelegate:Attached": {
this.actionDelegateAttached(aData.extensionId);
const extension = this.extensionById(aData.extensionId);
if (!extension) {
return;
}
const browserAction = this.browserActions.get(extension);
if (browserAction) {
// Send information about this action to the delegate
browserAction.updateOnChange(null);
}
const pageAction = this.pageActions.get(extension);
if (pageAction) {
pageAction.updateOnChange(null);
}
break;
}
@ -566,44 +383,9 @@ var GeckoViewWebExtension = {
break;
}
case "GeckoView:WebExtension:Get": {
const extension = await this.extensionById(aData.extensionId);
if (!extension) {
aCallback.onError(
`Could not find extension with id: ${aData.extensionId}`
);
return;
}
aCallback.onSuccess({
extension: exportExtension(
extension,
extension.userPermissions,
/* aSourceURI */ null
),
});
break;
}
case "GeckoView:WebExtension:Install": {
const uri = Services.io.newURI(aData.locationUri);
if (uri == null) {
aCallback.onError(`Could not parse uri: ${uri}`);
return;
}
try {
const result = await this.installWebExtension(uri);
if (result.extension) {
aCallback.onSuccess(result);
} else {
aCallback.onError(result);
}
} catch (ex) {
debug`Install exception error ${ex}`;
aCallback.onError(`Unexpected error: ${ex}`);
}
// TODO
aCallback.onError(`Not implemented`);
break;
}
@ -614,15 +396,8 @@ var GeckoViewWebExtension = {
}
case "GeckoView:WebExtension:Uninstall": {
try {
await this.uninstallWebExtension(aData.webExtensionId);
aCallback.onSuccess();
} catch (ex) {
debug`Failed uninstall ${ex}`;
aCallback.onError(
`This extension cannot be uninstalled. Error: ${ex}.`
);
}
// TODO
aCallback.onError(`Not implemented`);
break;
}

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

@ -2182,12 +2182,12 @@ var AddonManagerInternal = {
/**
* Starts installation of an AddonInstall notifying the registered
* web install listener of a blocked or started install.
* web install listener of blocked or started installs.
*
* @param aMimetype
* The mimetype of the add-on being installed
* The mimetype of add-ons being installed
* @param aBrowser
* The optional browser element that started the install
* The optional browser element that started the installs
* @param aInstallingPrincipal
* The nsIPrincipal that initiated the install
* @param aInstall
@ -2231,18 +2231,15 @@ var AddonManagerInternal = {
// main tab's browser). Check this by seeing if the browser we've been
// passed is in a content type docshell and if so get the outer-browser.
let topBrowser = aBrowser;
// GeckoView does not pass a browser.
if (aBrowser) {
let docShell = aBrowser.ownerGlobal.docShell;
if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent) {
topBrowser = docShell.chromeEventHandler;
}
let docShell = aBrowser.ownerGlobal.docShell;
if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent) {
topBrowser = docShell.chromeEventHandler;
}
try {
// Use fullscreenElement to check for DOM fullscreen, while still allowing
// macOS fullscreen, which still has a browser chrome.
if (topBrowser && topBrowser.ownerDocument.fullscreenElement) {
if (topBrowser.ownerDocument.fullscreenElement) {
// Addon installation and the resulting notifications should be
// blocked in DOM fullscreen for security and usability reasons.
// Installation prompts in fullscreen can trick the user into
@ -2270,20 +2267,19 @@ var AddonManagerInternal = {
return;
} else if (
aInstallingPrincipal.isNullPrincipal ||
(aBrowser &&
(!aBrowser.contentPrincipal ||
// When we attempt to handle an XPI load immediately after a
// process switch, the DocShell it's being loaded into will have
// a null principal, since it won't have been initialized yet.
// Allowing installs in this case is relatively safe, since
// there isn't much to gain by spoofing an install request from
// a null principal in any case. This exception can be removed
// once content handlers are triggered by DocumentChannel in the
// parent process.
!(
aBrowser.contentPrincipal.isNullPrincipal ||
aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)
))) ||
!aBrowser.contentPrincipal ||
// When we attempt to handle an XPI load immediately after a
// process switch, the DocShell it's being loaded into will have
// a null principal, since it won't have been initialized yet.
// Allowing installs in this case is relatively safe, since
// there isn't much to gain by spoofing an install request from
// a null principal in any case. This exception can be removed
// once content handlers are triggered by DocumentChannel in the
// parent process.
!(
aBrowser.contentPrincipal.isNullPrincipal ||
aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)
) ||
!this.isInstallAllowedByPolicy(
aInstallingPrincipal,
aInstall,
@ -2301,12 +2297,10 @@ var AddonManagerInternal = {
return;
}
if (aBrowser) {
// The install may start now depending on the web install listener,
// listen for the browser navigating to a new origin and cancel the
// install in that case.
new BrowserListener(aBrowser, aInstallingPrincipal, aInstall);
}
// The install may start now depending on the web install listener,
// listen for the browser navigating to a new origin and cancel the
// install in that case.
new BrowserListener(aBrowser, aInstallingPrincipal, aInstall);
let startInstall = source => {
AddonManagerInternal.setupPromptHandler(