mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 15:55:36 +00:00
Bug 1612097 - Add ability to cancel the GeckoResult returned by WebExtensionControll.install(BuiltIn); r=snorp,agi
Make the GeckoResult<WebExtension> returned by WebExtensionControll.install(BuiltIn) cancellable Differential Revision: https://phabricator.services.mozilla.com/D64953 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
25b913bc5c
commit
c5824ee72d
@ -88,6 +88,7 @@ GeckoViewStartup.prototype = {
|
|||||||
"GeckoView:WebExtension:Get",
|
"GeckoView:WebExtension:Get",
|
||||||
"GeckoView:WebExtension:Disable",
|
"GeckoView:WebExtension:Disable",
|
||||||
"GeckoView:WebExtension:Enable",
|
"GeckoView:WebExtension:Enable",
|
||||||
|
"GeckoView:WebExtension:CancelInstall",
|
||||||
"GeckoView:WebExtension:Install",
|
"GeckoView:WebExtension:Install",
|
||||||
"GeckoView:WebExtension:InstallBuiltIn",
|
"GeckoView:WebExtension:InstallBuiltIn",
|
||||||
"GeckoView:WebExtension:List",
|
"GeckoView:WebExtension:List",
|
||||||
|
@ -6,6 +6,7 @@ package org.mozilla.geckoview.test
|
|||||||
|
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
import org.hamcrest.core.StringEndsWith.endsWith
|
import org.hamcrest.core.StringEndsWith.endsWith
|
||||||
import org.hamcrest.core.IsEqual.equalTo
|
import org.hamcrest.core.IsEqual.equalTo
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@ -1338,6 +1339,33 @@ class WebExtensionTest : BaseSessionTest() {
|
|||||||
assertBodyBorderEqualTo("")
|
assertBodyBorderEqualTo("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = CancellationException::class)
|
||||||
|
fun cancelInstall() {
|
||||||
|
val install = controller.install("$TEST_ENDPOINT/stall/test.xpi")
|
||||||
|
val cancel = sessionRule.waitForResult(install.cancel())
|
||||||
|
assertTrue(cancel)
|
||||||
|
|
||||||
|
sessionRule.waitForResult(install)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cancelInstallFailsAfterInstalled() {
|
||||||
|
sessionRule.delegateDuringNextWait(object : WebExtensionController.PromptDelegate {
|
||||||
|
@AssertCalled
|
||||||
|
override fun onInstallPrompt(extension: WebExtension): GeckoResult<AllowOrDeny> {
|
||||||
|
return GeckoResult.fromValue(AllowOrDeny.ALLOW)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var install = controller.install("resource://android/assets/web_extensions/borderify.xpi");
|
||||||
|
val borderify = sessionRule.waitForResult(install)
|
||||||
|
|
||||||
|
val cancel = sessionRule.waitForResult(install.cancel())
|
||||||
|
assertFalse(cancel)
|
||||||
|
|
||||||
|
sessionRule.waitForResult(controller.uninstall(borderify))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun updatePostpone() {
|
fun updatePostpone() {
|
||||||
sessionRule.setPrefsUntilTestEnd(mapOf(
|
sessionRule.setPrefsUntilTestEnd(mapOf(
|
||||||
|
@ -18,6 +18,7 @@ import java.util.*
|
|||||||
class TestServer {
|
class TestServer {
|
||||||
private val server = AsyncHttpServer()
|
private val server = AsyncHttpServer()
|
||||||
private val assets: AssetManager
|
private val assets: AssetManager
|
||||||
|
private val stallingResponses = Vector<AsyncHttpServerResponse>()
|
||||||
|
|
||||||
constructor(context: Context) {
|
constructor(context: Context) {
|
||||||
assets = context.resources.assets
|
assets = context.resources.assets
|
||||||
@ -132,6 +133,25 @@ class TestServer {
|
|||||||
|
|
||||||
response.end()
|
response.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.get("/stall/.*") { _, response ->
|
||||||
|
// keep trickling data for a long time (until we are stopped)
|
||||||
|
stallingResponses.add(response)
|
||||||
|
|
||||||
|
val count = 100
|
||||||
|
response.setContentType("InstallException")
|
||||||
|
response.headers.set("Content-Length", "${count}")
|
||||||
|
response.writeHead()
|
||||||
|
|
||||||
|
val payload = byteArrayOf(1)
|
||||||
|
for (i in 1..count - 1) {
|
||||||
|
response.write(ByteBufferList(payload))
|
||||||
|
SystemClock.sleep(250)
|
||||||
|
}
|
||||||
|
|
||||||
|
stallingResponses.remove(response)
|
||||||
|
response.end()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(port: Int) {
|
fun start(port: Int) {
|
||||||
@ -139,6 +159,9 @@ class TestServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
|
for (response in stallingResponses) {
|
||||||
|
response.end()
|
||||||
|
}
|
||||||
server.stop()
|
server.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.mozilla.geckoview.WebExtension.InstallException.ErrorCodes.ERROR_POSTPONED;
|
import static org.mozilla.geckoview.WebExtension.InstallException.ErrorCodes.ERROR_POSTPONED;
|
||||||
import static org.mozilla.geckoview.WebExtension.InstallException.ErrorCodes.ERROR_USER_CANCELED;
|
import static org.mozilla.geckoview.WebExtension.InstallException.ErrorCodes.ERROR_USER_CANCELED;
|
||||||
@ -440,6 +441,67 @@ public class WebExtensionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class WebExtensionInstallResult extends WebExtensionResult {
|
||||||
|
private static class InstallCanceller implements GeckoResult.CancellationDelegate {
|
||||||
|
private static class CancelResult extends GeckoResult<Boolean>
|
||||||
|
implements EventCallback {
|
||||||
|
@Override
|
||||||
|
public void sendSuccess(final Object response) {
|
||||||
|
final boolean result = ((GeckoBundle) response).getBoolean("cancelled");
|
||||||
|
complete(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendError(final Object response) {
|
||||||
|
completeExceptionally(new Exception(response.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String mInstallId;
|
||||||
|
private boolean mCancelled;
|
||||||
|
|
||||||
|
public InstallCanceller(@NonNull final String aInstallId) {
|
||||||
|
mInstallId = aInstallId;
|
||||||
|
mCancelled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GeckoResult<Boolean> cancel() {
|
||||||
|
CancelResult result = new CancelResult();
|
||||||
|
|
||||||
|
final GeckoBundle bundle = new GeckoBundle(1);
|
||||||
|
bundle.putString("installId", mInstallId);
|
||||||
|
|
||||||
|
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:CancelInstall",
|
||||||
|
bundle, result);
|
||||||
|
|
||||||
|
return result.then(wasCancelled -> {
|
||||||
|
mCancelled = wasCancelled;
|
||||||
|
return GeckoResult.fromValue(wasCancelled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ final @NonNull String installId;
|
||||||
|
|
||||||
|
private final InstallCanceller mInstallCanceller;
|
||||||
|
|
||||||
|
public WebExtensionInstallResult() {
|
||||||
|
super("extension");
|
||||||
|
|
||||||
|
installId = UUID.randomUUID().toString();
|
||||||
|
mInstallCanceller = new InstallCanceller(installId);
|
||||||
|
setCancellationDelegate(mInstallCanceller);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendError(final Object response) {
|
||||||
|
if (!mInstallCanceller.mCancelled) {
|
||||||
|
super.sendError(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install an extension.
|
* Install an extension.
|
||||||
*
|
*
|
||||||
@ -478,14 +540,12 @@ public class WebExtensionController {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@AnyThread
|
@AnyThread
|
||||||
public GeckoResult<WebExtension> install(final @NonNull String uri) {
|
public GeckoResult<WebExtension> install(final @NonNull String uri) {
|
||||||
final WebExtensionResult result = new WebExtensionResult("extension");
|
WebExtensionInstallResult result = new WebExtensionInstallResult();
|
||||||
|
final GeckoBundle bundle = new GeckoBundle(2);
|
||||||
final GeckoBundle bundle = new GeckoBundle(1);
|
|
||||||
bundle.putString("locationUri", uri);
|
bundle.putString("locationUri", uri);
|
||||||
|
bundle.putString("installId", result.installId);
|
||||||
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:Install",
|
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:Install",
|
||||||
bundle, result);
|
bundle, result);
|
||||||
|
|
||||||
return result.then(extension -> {
|
return result.then(extension -> {
|
||||||
registerWebExtension(extension);
|
registerWebExtension(extension);
|
||||||
return GeckoResult.fromValue(extension);
|
return GeckoResult.fromValue(extension);
|
||||||
@ -523,14 +583,12 @@ public class WebExtensionController {
|
|||||||
|
|
||||||
// TODO: Bug 1601067 make public
|
// TODO: Bug 1601067 make public
|
||||||
GeckoResult<WebExtension> installBuiltIn(final String uri) {
|
GeckoResult<WebExtension> installBuiltIn(final String uri) {
|
||||||
final WebExtensionResult result = new WebExtensionResult("extension");
|
WebExtensionInstallResult result = new WebExtensionInstallResult();
|
||||||
|
final GeckoBundle bundle = new GeckoBundle(2);
|
||||||
final GeckoBundle bundle = new GeckoBundle(1);
|
|
||||||
bundle.putString("locationUri", uri);
|
bundle.putString("locationUri", uri);
|
||||||
|
bundle.putString("installId", result.installId);
|
||||||
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:InstallBuiltIn",
|
EventDispatcher.getInstance().dispatch("GeckoView:WebExtension:InstallBuiltIn",
|
||||||
bundle, result);
|
bundle, result);
|
||||||
|
|
||||||
return result.then(extension -> {
|
return result.then(extension -> {
|
||||||
registerWebExtension(extension);
|
registerWebExtension(extension);
|
||||||
return GeckoResult.fromValue(extension);
|
return GeckoResult.fromValue(extension);
|
||||||
|
@ -308,8 +308,40 @@ function exportExtension(aAddon, aPermissions, aSourceURI) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ExtensionInstallListener {
|
class ExtensionInstallListener {
|
||||||
constructor(aResolve) {
|
constructor(aResolve, aInstall, aInstallId) {
|
||||||
this.resolve = aResolve;
|
this.install = aInstall;
|
||||||
|
this.installId = aInstallId;
|
||||||
|
this.resolve = result => {
|
||||||
|
aResolve(result);
|
||||||
|
EventDispatcher.instance.unregisterListener(this, [
|
||||||
|
"GeckoView:WebExtension:CancelInstall",
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
EventDispatcher.instance.registerListener(this, [
|
||||||
|
"GeckoView:WebExtension:CancelInstall",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onEvent(aEvent, aData, aCallback) {
|
||||||
|
debug`onEvent ${aEvent} ${aData}`;
|
||||||
|
|
||||||
|
switch (aEvent) {
|
||||||
|
case "GeckoView:WebExtension:CancelInstall": {
|
||||||
|
const { installId } = aData;
|
||||||
|
if (this.installId !== installId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cancelled = false;
|
||||||
|
try {
|
||||||
|
this.install.cancel();
|
||||||
|
cancelled = true;
|
||||||
|
} catch (_) {
|
||||||
|
// install may have already failed or been cancelled
|
||||||
|
}
|
||||||
|
aCallback.onSuccess({ cancelled });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDownloadCancelled(aInstall) {
|
onDownloadCancelled(aInstall) {
|
||||||
@ -534,14 +566,16 @@ var GeckoViewWebExtension = {
|
|||||||
return scope.extension;
|
return scope.extension;
|
||||||
},
|
},
|
||||||
|
|
||||||
async installWebExtension(aUri) {
|
async installWebExtension(aInstallId, aUri) {
|
||||||
const install = await AddonManager.getInstallForURL(aUri.spec, {
|
const install = await AddonManager.getInstallForURL(aUri.spec, {
|
||||||
telemetryInfo: {
|
telemetryInfo: {
|
||||||
source: "geckoview-app",
|
source: "geckoview-app",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const promise = new Promise(resolve => {
|
const promise = new Promise(resolve => {
|
||||||
install.addListener(new ExtensionInstallListener(resolve));
|
install.addListener(
|
||||||
|
new ExtensionInstallListener(resolve, install, aInstallId)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||||
@ -794,14 +828,15 @@ var GeckoViewWebExtension = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "GeckoView:WebExtension:Install": {
|
case "GeckoView:WebExtension:Install": {
|
||||||
const uri = Services.io.newURI(aData.locationUri);
|
const { locationUri, installId } = aData;
|
||||||
|
const uri = Services.io.newURI(locationUri);
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
aCallback.onError(`Could not parse uri: ${uri}`);
|
aCallback.onError(`Could not parse uri: ${locationUri}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this.installWebExtension(uri);
|
const result = await this.installWebExtension(installId, uri);
|
||||||
if (result.extension) {
|
if (result.extension) {
|
||||||
aCallback.onSuccess(result);
|
aCallback.onSuccess(result);
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user