forked from mirrors/gecko-dev
Bug 1684923 - Implement Web Extension downloads.onChanged event in GeckoView r=agi,robwu,geckoview-reviewers,esawin
Differential Revision: https://phabricator.services.mozilla.com/D101377
This commit is contained in:
parent
c245e071f7
commit
0ff7359c48
10 changed files with 433 additions and 4 deletions
|
|
@ -10,6 +10,9 @@ ChromeUtils.defineModuleGetter(
|
||||||
"DownloadPaths",
|
"DownloadPaths",
|
||||||
"resource://gre/modules/DownloadPaths.jsm"
|
"resource://gre/modules/DownloadPaths.jsm"
|
||||||
);
|
);
|
||||||
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
|
DownloadTracker: "resource://gre/modules/GeckoViewWebExtension.jsm",
|
||||||
|
});
|
||||||
|
|
||||||
var { ignoreEvent } = ExtensionCommon;
|
var { ignoreEvent } = ExtensionCommon;
|
||||||
|
|
||||||
|
|
@ -51,8 +54,43 @@ const STATE_MAP = new Map([
|
||||||
[2, State.COMPLETE],
|
[2, State.COMPLETE],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const INTERRUPT_REASON_MAP = new Map([
|
||||||
|
[0, undefined],
|
||||||
|
[1, "FILE_FAILED"],
|
||||||
|
[2, "FILE_ACCESS_DENIED"],
|
||||||
|
[3, "FILE_NO_SPACE"],
|
||||||
|
[4, "FILE_NAME_TOO_LONG"],
|
||||||
|
[5, "FILE_TOO_LARGE"],
|
||||||
|
[6, "FILE_VIRUS_INFECTED"],
|
||||||
|
[7, "FILE_TRANSIENT_ERROR"],
|
||||||
|
[8, "FILE_BLOCKED"],
|
||||||
|
[9, "FILE_SECURITY_CHECK_FAILED"],
|
||||||
|
[10, "FILE_TOO_SHORT"],
|
||||||
|
[11, "NETWORK_FAILED"],
|
||||||
|
[12, "NETWORK_TIMEOUT"],
|
||||||
|
[13, "NETWORK_DISCONNECTED"],
|
||||||
|
[14, "NETWORK_SERVER_DOWN"],
|
||||||
|
[15, "NETWORK_INVALID_REQUEST"],
|
||||||
|
[16, "SERVER_FAILED"],
|
||||||
|
[17, "SERVER_NO_RANGE"],
|
||||||
|
[18, "SERVER_BAD_CONTENT"],
|
||||||
|
[19, "SERVER_UNAUTHORIZED"],
|
||||||
|
[20, "SERVER_CERT_PROBLEM"],
|
||||||
|
[21, "SERVER_FORBIDDEN"],
|
||||||
|
[22, "USER_CANCELED"],
|
||||||
|
[23, "USER_SHUTDOWN"],
|
||||||
|
[24, "CRASH"],
|
||||||
|
]);
|
||||||
|
|
||||||
// TODO Bug 1247794: make id and extension info persistent
|
// TODO Bug 1247794: make id and extension info persistent
|
||||||
class DownloadItem {
|
class DownloadItem {
|
||||||
|
/**
|
||||||
|
* Initializes an object that represents a download
|
||||||
|
*
|
||||||
|
* @param {Object} downloadInfo - an object from Java when creating a download
|
||||||
|
* @param {Object} options - an object passed in to download() function
|
||||||
|
* @param {Extension} extension - instance of an extension object
|
||||||
|
*/
|
||||||
constructor(downloadInfo, options, extension) {
|
constructor(downloadInfo, options, extension) {
|
||||||
this.id = downloadInfo.id;
|
this.id = downloadInfo.id;
|
||||||
this.url = options.url;
|
this.url = options.url;
|
||||||
|
|
@ -72,6 +110,41 @@ class DownloadItem {
|
||||||
this.byExtensionId = extension?.id;
|
this.byExtensionId = extension?.id;
|
||||||
this.byExtensionName = extension?.name;
|
this.byExtensionName = extension?.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function updates the download item it was called on.
|
||||||
|
*
|
||||||
|
* @param {Object} data that arrived from the app (Java)
|
||||||
|
* @returns {Object|null} an object of <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/downloads/onChanged#downloaddelta>downloadDelta type</a>
|
||||||
|
*/
|
||||||
|
update(data) {
|
||||||
|
const { downloadItemId } = data;
|
||||||
|
const delta = {};
|
||||||
|
|
||||||
|
data.state = STATE_MAP.get(data.state);
|
||||||
|
data.error = INTERRUPT_REASON_MAP.get(data.error);
|
||||||
|
delete data.downloadItemId;
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
for (const prop in data) {
|
||||||
|
const current = data[prop] ?? null;
|
||||||
|
const previous = this[prop] ?? null;
|
||||||
|
if (current !== previous) {
|
||||||
|
delta[prop] = { current, previous };
|
||||||
|
this[prop] = current;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't send empty onChange events
|
||||||
|
if (!changed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
delta.id = downloadItemId;
|
||||||
|
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.downloads = class extends ExtensionAPI {
|
this.downloads = class extends ExtensionAPI {
|
||||||
|
|
@ -141,6 +214,7 @@ this.downloads = class extends ExtensionAPI {
|
||||||
})
|
})
|
||||||
.then(value => {
|
.then(value => {
|
||||||
const downloadItem = new DownloadItem(value, options, extension);
|
const downloadItem = new DownloadItem(value, options, extension);
|
||||||
|
DownloadTracker.addDownloadItem(downloadItem);
|
||||||
return downloadItem.id;
|
return downloadItem.id;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -185,7 +259,23 @@ this.downloads = class extends ExtensionAPI {
|
||||||
throw new ExtensionError("Not implemented");
|
throw new ExtensionError("Not implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
onChanged: ignoreEvent(context, "downloads.onChanged"),
|
onChanged: new EventManager({
|
||||||
|
context,
|
||||||
|
name: "downloads.onChanged",
|
||||||
|
register: fire => {
|
||||||
|
const listener = (eventName, event) => {
|
||||||
|
const { delta, downloadItem } = event;
|
||||||
|
if (context.privateBrowsingAllowed || !downloadItem.incognito) {
|
||||||
|
fire.async(delta);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DownloadTracker.on("download-changed", listener);
|
||||||
|
return () => {
|
||||||
|
DownloadTracker.off("download-changed", listener);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}).api(),
|
||||||
|
|
||||||
onCreated: ignoreEvent(context, "downloads.onCreated"),
|
onCreated: ignoreEvent(context, "downloads.onCreated"),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,11 @@ class GeckoViewStartup {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
GeckoViewUtils.addLazyGetter(this, "DownloadTracker", {
|
||||||
|
module: "resource://gre/modules/GeckoViewWebExtension.jsm",
|
||||||
|
ged: ["GeckoView:WebExtension:DownloadChanged"],
|
||||||
|
});
|
||||||
|
|
||||||
ChromeUtils.import("resource://gre/modules/NotificationDB.jsm");
|
ChromeUtils.import("resource://gre/modules/NotificationDB.jsm");
|
||||||
|
|
||||||
// Initialize safe browsing module. This is required for content
|
// Initialize safe browsing module. This is required for content
|
||||||
|
|
|
||||||
|
|
@ -1728,6 +1728,7 @@ package org.mozilla.geckoview {
|
||||||
|
|
||||||
public static class WebExtension.Download {
|
public static class WebExtension.Download {
|
||||||
ctor protected Download(int);
|
ctor protected Download(int);
|
||||||
|
method @Nullable @UiThread public GeckoResult<Void> update(@NonNull WebExtension.Download.Info);
|
||||||
field public static final int INTERRUPT_REASON_CRASH = 24;
|
field public static final int INTERRUPT_REASON_CRASH = 24;
|
||||||
field public static final int INTERRUPT_REASON_FILE_ACCESS_DENIED = 2;
|
field public static final int INTERRUPT_REASON_FILE_ACCESS_DENIED = 2;
|
||||||
field public static final int INTERRUPT_REASON_FILE_BLOCKED = 8;
|
field public static final int INTERRUPT_REASON_FILE_BLOCKED = 8;
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ addons = {
|
||||||
"download.js",
|
"download.js",
|
||||||
"manifest.json",
|
"manifest.json",
|
||||||
],
|
],
|
||||||
|
"download-onChanged": [
|
||||||
|
"download.js",
|
||||||
|
"manifest.json",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
for addon, files in addons.items():
|
for addon, files in addons.items():
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
async function test() {
|
||||||
|
browser.downloads.onChanged.addListener(async delta => {
|
||||||
|
const changes = { current: {}, previous: {} };
|
||||||
|
changes.id = delta.id;
|
||||||
|
delete delta.id;
|
||||||
|
for (const prop in delta) {
|
||||||
|
changes.current[prop] = delta[prop].current;
|
||||||
|
changes.previous[prop] = delta[prop].previous;
|
||||||
|
}
|
||||||
|
await browser.runtime.sendNativeMessage("browser", changes);
|
||||||
|
});
|
||||||
|
|
||||||
|
await browser.downloads.download({
|
||||||
|
url: "http://localhost:4245/assets/www/images/test.gif",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test();
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Download",
|
||||||
|
"version": "1.0",
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "download-onChanged@tests.mozilla.org"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Downloads a file",
|
||||||
|
"background": {
|
||||||
|
"scripts": ["download.js"]
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"downloads",
|
||||||
|
"geckoViewAddons",
|
||||||
|
"nativeMessaging"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ import org.junit.Assume.assumeThat
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.gecko.EventDispatcher
|
||||||
import org.mozilla.geckoview.*
|
import org.mozilla.geckoview.*
|
||||||
import org.mozilla.geckoview.WebExtension.*
|
import org.mozilla.geckoview.WebExtension.*
|
||||||
import org.mozilla.geckoview.WebExtension.BrowsingDataDelegate.Type.*
|
import org.mozilla.geckoview.WebExtension.BrowsingDataDelegate.Type.*
|
||||||
|
|
@ -2299,4 +2300,202 @@ class WebExtensionTest : BaseSessionTest() {
|
||||||
assertNotNull(downloadCreated.id)
|
assertNotNull(downloadCreated.id)
|
||||||
sessionRule.waitForResult(controller.uninstall(webExtension))
|
sessionRule.waitForResult(controller.uninstall(webExtension))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnChanged() {
|
||||||
|
val uri = createTestUrl("/assets/www/images/test.gif")
|
||||||
|
val downloadId = 4
|
||||||
|
val unfinishedDownloadSize = 5L
|
||||||
|
val finishedDownloadSize = 25L
|
||||||
|
val expectedFilename = "test.gif"
|
||||||
|
val expectedMime = "image/gif"
|
||||||
|
val expectedEndTime = Date().time
|
||||||
|
val expectedFilesize = 48L
|
||||||
|
|
||||||
|
// first and second update
|
||||||
|
val downloadData = object : Download.Info {
|
||||||
|
var endTime : Long? = null
|
||||||
|
val startTime = Date().time - 50000
|
||||||
|
var fileExists = false
|
||||||
|
var totalBytes: Long = -1
|
||||||
|
var mime = ""
|
||||||
|
var fileSize: Long = -1
|
||||||
|
var filename = ""
|
||||||
|
var state = Download.STATE_IN_PROGRESS
|
||||||
|
|
||||||
|
override fun state(): Int {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun endTime(): Long? {
|
||||||
|
return endTime
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startTime(): Long {
|
||||||
|
return startTime
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fileExists(): Boolean {
|
||||||
|
return fileExists;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun totalBytes(): Long {
|
||||||
|
return totalBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mime(): String {
|
||||||
|
return mime
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fileSize(): Long {
|
||||||
|
return fileSize
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun filename(): String {
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val webExtension = sessionRule.waitForResult(
|
||||||
|
controller.installBuiltIn("resource://android/assets/web_extensions/download-onChanged/"))
|
||||||
|
|
||||||
|
val assertOnDownloadCalled = GeckoResult<Download>()
|
||||||
|
val downloadDelegate = object : DownloadDelegate {
|
||||||
|
override fun onDownload(source: WebExtension, request: DownloadRequest): GeckoResult<WebExtension.DownloadInitData>? {
|
||||||
|
assertEquals(webExtension!!.id, source.id)
|
||||||
|
assertEquals(uri, request.request.uri)
|
||||||
|
|
||||||
|
val download = controller.createDownload(downloadId)
|
||||||
|
assertOnDownloadCalled.complete(download)
|
||||||
|
return GeckoResult.fromValue(DownloadInitData(download, downloadData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val updates = mutableListOf<JSONObject>()
|
||||||
|
|
||||||
|
val thirdUpdateReceived = GeckoResult<JSONObject>()
|
||||||
|
val messageDelegate = object : MessageDelegate {
|
||||||
|
override fun onMessage(nativeApp: String, message: Any, sender: MessageSender): GeckoResult<Any>? {
|
||||||
|
val current = (message as JSONObject).getJSONObject("current")
|
||||||
|
|
||||||
|
updates.add(message)
|
||||||
|
|
||||||
|
// Once we get the size finished download, that means we got the last update
|
||||||
|
if (current.getLong("totalBytes") == finishedDownloadSize) {
|
||||||
|
thirdUpdateReceived.complete(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GeckoResult.fromValue(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webExtension.setDownloadDelegate(downloadDelegate)
|
||||||
|
webExtension.setMessageDelegate(messageDelegate, "browser")
|
||||||
|
|
||||||
|
mainSession.reload()
|
||||||
|
sessionRule.waitForPageStop()
|
||||||
|
|
||||||
|
val downloadCreated = sessionRule.waitForResult(assertOnDownloadCalled)
|
||||||
|
assertEquals(downloadId, downloadCreated.id)
|
||||||
|
|
||||||
|
// first and second update (they are identical)
|
||||||
|
downloadData.filename = expectedFilename
|
||||||
|
downloadData.mime = expectedMime
|
||||||
|
downloadData.totalBytes = unfinishedDownloadSize
|
||||||
|
|
||||||
|
downloadCreated.update(downloadData)
|
||||||
|
downloadCreated.update(downloadData)
|
||||||
|
|
||||||
|
downloadData.fileSize = expectedFilesize
|
||||||
|
downloadData.endTime = expectedEndTime
|
||||||
|
downloadData.totalBytes = finishedDownloadSize
|
||||||
|
downloadData.state = Download.STATE_COMPLETE
|
||||||
|
downloadCreated.update(downloadData)
|
||||||
|
|
||||||
|
sessionRule.waitForResult(thirdUpdateReceived)
|
||||||
|
|
||||||
|
// The second update should not be there because the data was identical
|
||||||
|
assertEquals(2, updates.size)
|
||||||
|
|
||||||
|
val firstUpdateCurrent = updates[0].getJSONObject("current")
|
||||||
|
val firstUpdatePrevious = updates[0].getJSONObject("previous")
|
||||||
|
assertEquals(3, firstUpdateCurrent.length())
|
||||||
|
assertEquals(3, firstUpdatePrevious.length())
|
||||||
|
assertEquals(expectedMime, firstUpdateCurrent.getString("mime"))
|
||||||
|
assertEquals("", firstUpdatePrevious.getString("mime"))
|
||||||
|
assertEquals(expectedFilename, firstUpdateCurrent.getString("filename"))
|
||||||
|
assertEquals("", firstUpdatePrevious.getString("filename"))
|
||||||
|
assertEquals(unfinishedDownloadSize, firstUpdateCurrent.getLong("totalBytes"))
|
||||||
|
assertEquals(-1, firstUpdatePrevious.getLong("totalBytes"))
|
||||||
|
|
||||||
|
val secondUpdateCurrent = updates[1].getJSONObject("current")
|
||||||
|
val secondUpdatePrevious = updates[1].getJSONObject("previous")
|
||||||
|
assertEquals(4, secondUpdateCurrent.length())
|
||||||
|
assertEquals(4, secondUpdatePrevious.length())
|
||||||
|
assertEquals(finishedDownloadSize, secondUpdateCurrent.getLong("totalBytes"))
|
||||||
|
assertEquals(firstUpdateCurrent.getLong("totalBytes"), secondUpdatePrevious.getLong("totalBytes"))
|
||||||
|
assertEquals("complete", secondUpdateCurrent.get("state").toString())
|
||||||
|
assertEquals("in_progress", secondUpdatePrevious.get("state").toString())
|
||||||
|
assertEquals(expectedEndTime.toString(), secondUpdateCurrent.getString("endTime"))
|
||||||
|
assertEquals("null", secondUpdatePrevious.getString("endTime"))
|
||||||
|
assertEquals(expectedFilesize, secondUpdateCurrent.getLong("fileSize"))
|
||||||
|
assertEquals(-1, secondUpdatePrevious.getLong("fileSize"))
|
||||||
|
|
||||||
|
sessionRule.waitForResult(controller.uninstall(webExtension))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnChangedWrongId() {
|
||||||
|
val uri = createTestUrl("/assets/www/images/test.gif")
|
||||||
|
val downloadId = 5
|
||||||
|
|
||||||
|
val webExtension = sessionRule.waitForResult(
|
||||||
|
controller.installBuiltIn("resource://android/assets/web_extensions/download-onChanged/"))
|
||||||
|
|
||||||
|
val assertOnDownloadCalled = GeckoResult<WebExtension.Download>()
|
||||||
|
val downloadDelegate = object : DownloadDelegate {
|
||||||
|
override fun onDownload(source: WebExtension, request: DownloadRequest): GeckoResult<WebExtension.DownloadInitData>? {
|
||||||
|
assertEquals(webExtension!!.id, source.id)
|
||||||
|
assertEquals(uri, request.request.uri)
|
||||||
|
|
||||||
|
val download = controller.createDownload(downloadId)
|
||||||
|
assertOnDownloadCalled.complete(download)
|
||||||
|
return GeckoResult.fromValue(DownloadInitData(download, object : Download.Info {}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val onMessageCalled = GeckoResult<String>()
|
||||||
|
val messageDelegate = object : MessageDelegate {
|
||||||
|
override fun onMessage(nativeApp: String, message: Any, sender: MessageSender): GeckoResult<Any>? {
|
||||||
|
onMessageCalled.complete(message as String)
|
||||||
|
return GeckoResult.fromValue(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webExtension.setDownloadDelegate(downloadDelegate)
|
||||||
|
webExtension.setMessageDelegate(messageDelegate, "browser")
|
||||||
|
|
||||||
|
mainSession.reload()
|
||||||
|
sessionRule.waitForPageStop()
|
||||||
|
|
||||||
|
val updateData = object : WebExtension.Download.Info {
|
||||||
|
override fun state(): Int {
|
||||||
|
return WebExtension.Download.STATE_COMPLETE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val randomDownload = controller.createDownload(25)
|
||||||
|
|
||||||
|
val r = randomDownload!!.update(updateData)
|
||||||
|
|
||||||
|
try {
|
||||||
|
sessionRule.waitForResult(r!!)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
val a = ex.message!!
|
||||||
|
assertEquals("Error: Trying to update unknown download", a)
|
||||||
|
sessionRule.waitForResult(controller.uninstall(webExtension))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2300,8 +2300,47 @@ public class WebExtension {
|
||||||
|
|
||||||
/* package */ void setDelegate(final Delegate delegate) { }
|
/* package */ void setDelegate(final Delegate delegate) { }
|
||||||
|
|
||||||
/* package */ GeckoResult<Void> update(final Info data) {
|
/**
|
||||||
return null;
|
* Updates the download state.
|
||||||
|
* This will trigger a call to <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/downloads/onChanged">downloads.onChanged</a> event
|
||||||
|
* to the corresponding `DownloadItem` on the extension side.
|
||||||
|
*
|
||||||
|
* @param data - current metadata associated with the download. {@link Download.Info} implementation instance
|
||||||
|
* @return GeckoResult with nothing or error inside
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@UiThread
|
||||||
|
public GeckoResult<Void> update(final @NonNull Download.Info data) {
|
||||||
|
final GeckoBundle bundle = new GeckoBundle(12);
|
||||||
|
|
||||||
|
bundle.putInt("downloadItemId", this.id);
|
||||||
|
|
||||||
|
bundle.putString("filename", data.filename());
|
||||||
|
bundle.putString("mime", data.mime());
|
||||||
|
bundle.putString("startTime", String.valueOf(data.startTime()));
|
||||||
|
bundle.putString("endTime", data.endTime() == null ? null : String.valueOf(data.endTime()));
|
||||||
|
bundle.putInt("state", data.state());
|
||||||
|
bundle.putBoolean("canResume", data.canResume());
|
||||||
|
bundle.putBoolean("paused", data.paused());
|
||||||
|
Integer error = data.error();
|
||||||
|
if (error != null) {
|
||||||
|
bundle.putInt("error", error);
|
||||||
|
}
|
||||||
|
bundle.putLong("totalBytes", data.totalBytes());
|
||||||
|
bundle.putLong("fileSize", data.fileSize());
|
||||||
|
bundle.putBoolean("exists", data.fileExists());
|
||||||
|
|
||||||
|
return EventDispatcher.getInstance().queryVoid(
|
||||||
|
"GeckoView:WebExtension:DownloadChanged", bundle
|
||||||
|
).map(null, e -> {
|
||||||
|
if (e instanceof EventDispatcher.QueryException) {
|
||||||
|
EventDispatcher.QueryException queryException = (EventDispatcher.QueryException) e;
|
||||||
|
if (queryException.data instanceof String) {
|
||||||
|
return new IllegalArgumentException((String) queryException.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ interface Delegate {
|
/* package */ interface Delegate {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,13 @@ exclude: true
|
||||||
|
|
||||||
⚠️ breaking change and deprecation notices
|
⚠️ breaking change and deprecation notices
|
||||||
|
|
||||||
|
## v88
|
||||||
|
- Added [`WebExtension.Download#update`][88.1] that can be used to
|
||||||
|
implement the WebExtension `downloads` API. This method is used to communicate
|
||||||
|
updates in the download status to the Web Extension
|
||||||
|
|
||||||
|
[88.1]: {{javadoc_uri}}/WebExtension.Download.html#update-org.mozilla.geckoview.WebExtension.Download.Info-
|
||||||
|
|
||||||
## v87
|
## v87
|
||||||
- ⚠ Added [`WebExtension.DownloadInitData`][87.1] class that can be used to
|
- ⚠ Added [`WebExtension.DownloadInitData`][87.1] class that can be used to
|
||||||
implement the WebExtension `downloads` API. This class represents initial state of a download.
|
implement the WebExtension `downloads` API. This class represents initial state of a download.
|
||||||
|
|
@ -896,4 +903,4 @@ to allow adding gecko profiler markers.
|
||||||
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
|
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
|
||||||
[65.25]: {{javadoc_uri}}/GeckoResult.html
|
[65.25]: {{javadoc_uri}}/GeckoResult.html
|
||||||
|
|
||||||
[api-version]: d9171ae05286c279c35515eb3ac3e42258cec583
|
[api-version]: 2ca865a6a509dfc7100d38f906877c5467c43cd0
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ var EXPORTED_SYMBOLS = [
|
||||||
"GeckoViewConnection",
|
"GeckoViewConnection",
|
||||||
"GeckoViewWebExtension",
|
"GeckoViewWebExtension",
|
||||||
"mobileWindowTracker",
|
"mobileWindowTracker",
|
||||||
|
"DownloadTracker",
|
||||||
];
|
];
|
||||||
|
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
|
@ -47,6 +48,52 @@ XPCOMUtils.defineLazyServiceGetter(
|
||||||
|
|
||||||
const { debug, warn } = GeckoViewUtils.initLogging("Console");
|
const { debug, warn } = GeckoViewUtils.initLogging("Console");
|
||||||
|
|
||||||
|
const DOWNLOAD_CHANGED_MESSAGE = "GeckoView:WebExtension:DownloadChanged";
|
||||||
|
|
||||||
|
var DownloadTracker = new (class extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// maps numeric IDs to DownloadItem objects
|
||||||
|
this._downloads = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
onEvent(event, data, callback) {
|
||||||
|
switch (event) {
|
||||||
|
case "GeckoView:WebExtension:DownloadChanged": {
|
||||||
|
const downloadItem = this.getDownloadItemById(data.downloadItemId);
|
||||||
|
|
||||||
|
if (!downloadItem) {
|
||||||
|
callback.onError("Error: Trying to update unknown download");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = downloadItem.update(data);
|
||||||
|
if (delta) {
|
||||||
|
this.emit("download-changed", {
|
||||||
|
delta,
|
||||||
|
downloadItem,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addDownloadItem(item) {
|
||||||
|
this._downloads.set(item.id, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and returns a DownloadItem with a certain numeric ID
|
||||||
|
*
|
||||||
|
* @param {number} id
|
||||||
|
* @returns {DownloadItem} download item
|
||||||
|
*/
|
||||||
|
getDownloadItemById(id) {
|
||||||
|
return this._downloads.get(id);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
/** Provides common logic between page and browser actions */
|
/** Provides common logic between page and browser actions */
|
||||||
class ExtensionActionHelper {
|
class ExtensionActionHelper {
|
||||||
constructor({
|
constructor({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue