browser(firefox): support downloads (#1683)
This commit is contained in:
parent
889cf8f7b6
commit
7b2736b4c9
|
@ -1 +1 @@
|
||||||
1072
|
1073
|
||||||
|
|
|
@ -1148,10 +1148,10 @@ index 25c5b01fc54c8d45da8ceb7cf6ba163bee3c5361..490c5ce49cd9b5f804df59abbfb0450f
|
||||||
void internalResyncICUDefaultTimeZone();
|
void internalResyncICUDefaultTimeZone();
|
||||||
diff --git a/juggler/Helper.js b/juggler/Helper.js
|
diff --git a/juggler/Helper.js b/juggler/Helper.js
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000000000000000000000000000000000..862c680198bbb503a5f04c19bdb8fdf2cd8c9cef
|
index 0000000000000000000000000000000000000000..b8e6649fb91be6cd72b000426fb4d58216745c4f
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/juggler/Helper.js
|
+++ b/juggler/Helper.js
|
||||||
@@ -0,0 +1,102 @@
|
@@ -0,0 +1,115 @@
|
||||||
+const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
+const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||||
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
+
|
+
|
||||||
|
@ -1195,6 +1195,19 @@ index 0000000000000000000000000000000000000000..862c680198bbb503a5f04c19bdb8fdf2
|
||||||
+ return string.substring(1, string.length - 1);
|
+ return string.substring(1, string.length - 1);
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
|
+ getLoadContext(httpChannel) {
|
||||||
|
+ let loadContext = null;
|
||||||
|
+ try {
|
||||||
|
+ if (httpChannel.notificationCallbacks)
|
||||||
|
+ loadContext = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
||||||
|
+ } catch (e) {}
|
||||||
|
+ try {
|
||||||
|
+ if (!loadContext && httpChannel.loadGroup)
|
||||||
|
+ loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
||||||
|
+ } catch (e) { }
|
||||||
|
+ return loadContext;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
+ getNetworkErrorStatusText(status) {
|
+ getNetworkErrorStatusText(status) {
|
||||||
+ if (!status)
|
+ if (!status)
|
||||||
+ return null;
|
+ return null;
|
||||||
|
@ -1256,10 +1269,10 @@ index 0000000000000000000000000000000000000000..862c680198bbb503a5f04c19bdb8fdf2
|
||||||
+
|
+
|
||||||
diff --git a/juggler/NetworkObserver.js b/juggler/NetworkObserver.js
|
diff --git a/juggler/NetworkObserver.js b/juggler/NetworkObserver.js
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000000000000000000000000000000000..2e3a2c60b5c5052a85ad1a6712d46587fe00838b
|
index 0000000000000000000000000000000000000000..052f893eb0e984914ac59f8cb24580449dc12a66
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/juggler/NetworkObserver.js
|
+++ b/juggler/NetworkObserver.js
|
||||||
@@ -0,0 +1,768 @@
|
@@ -0,0 +1,760 @@
|
||||||
+"use strict";
|
+"use strict";
|
||||||
+
|
+
|
||||||
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
|
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
|
||||||
|
@ -1462,15 +1475,7 @@ index 0000000000000000000000000000000000000000..2e3a2c60b5c5052a85ad1a6712d46587
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ _getBrowserForChannel(httpChannel) {
|
+ _getBrowserForChannel(httpChannel) {
|
||||||
+ let loadContext = null;
|
+ let loadContext = helper.getLoadContext(httpChannel);
|
||||||
+ try {
|
|
||||||
+ if (httpChannel.notificationCallbacks)
|
|
||||||
+ loadContext = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
|
||||||
+ } catch (e) {}
|
|
||||||
+ try {
|
|
||||||
+ if (!loadContext && httpChannel.loadGroup)
|
|
||||||
+ loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
|
||||||
+ } catch (e) { }
|
|
||||||
+ if (!loadContext || !this._browserSessionCount.has(loadContext.topFrameElement))
|
+ if (!loadContext || !this._browserSessionCount.has(loadContext.topFrameElement))
|
||||||
+ return;
|
+ return;
|
||||||
+ return loadContext.topFrameElement;
|
+ return loadContext.topFrameElement;
|
||||||
|
@ -2166,10 +2171,10 @@ index 0000000000000000000000000000000000000000..ba34976ad05e7f5f1a99777f76ac08b1
|
||||||
+this.SimpleChannel = SimpleChannel;
|
+this.SimpleChannel = SimpleChannel;
|
||||||
diff --git a/juggler/TargetRegistry.js b/juggler/TargetRegistry.js
|
diff --git a/juggler/TargetRegistry.js b/juggler/TargetRegistry.js
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000000000000000000000000000000000..345aa8b0ebbd9d1e8c9c79913bd7ec3fbb1cc768
|
index 0000000000000000000000000000000000000000..27cfe133cab5dc4b1218c0e5624b7eb2f8bc08e3
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/juggler/TargetRegistry.js
|
+++ b/juggler/TargetRegistry.js
|
||||||
@@ -0,0 +1,561 @@
|
@@ -0,0 +1,661 @@
|
||||||
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
|
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
|
||||||
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||||
+const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
|
+const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
|
||||||
|
@ -2197,6 +2202,88 @@ index 0000000000000000000000000000000000000000..345aa8b0ebbd9d1e8c9c79913bd7ec3f
|
||||||
+ 'desktop-notification',
|
+ 'desktop-notification',
|
||||||
+];
|
+];
|
||||||
+
|
+
|
||||||
|
+class DownloadInterceptor {
|
||||||
|
+ constructor(registry) {
|
||||||
|
+ this._registry = registry
|
||||||
|
+ this._handlerToUuid = new Map();
|
||||||
|
+ helper.addObserver(this._onRequest.bind(this), 'http-on-modify-request');
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ _onRequest(httpChannel, topic) {
|
||||||
|
+ let loadContext = helper.getLoadContext(httpChannel);
|
||||||
|
+ if (!loadContext)
|
||||||
|
+ return;
|
||||||
|
+ if (!loadContext.topFrameElement)
|
||||||
|
+ return;
|
||||||
|
+ const target = this._registry.targetForBrowser(loadContext.topFrameElement);
|
||||||
|
+ if (!target)
|
||||||
|
+ return;
|
||||||
|
+ target._httpChannelIds.add(httpChannel.channelId);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ //
|
||||||
|
+ // nsIDownloadInterceptor implementation.
|
||||||
|
+ //
|
||||||
|
+ interceptDownloadRequest(externalAppHandler, request, outFile) {
|
||||||
|
+ const httpChannel = request.QueryInterface(Ci.nsIHttpChannel);
|
||||||
|
+ if (!httpChannel)
|
||||||
|
+ return false;
|
||||||
|
+ if (!httpChannel.loadInfo)
|
||||||
|
+ return false;
|
||||||
|
+ const userContextId = httpChannel.loadInfo.originAttributes.userContextId;
|
||||||
|
+ const browserContext = this._registry._userContextIdToBrowserContext.get(userContextId);
|
||||||
|
+ const options = browserContext.options.downloadOptions;
|
||||||
|
+ if (!options)
|
||||||
|
+ return false;
|
||||||
|
+
|
||||||
|
+ const pageTarget = this._registry._targetForChannel(httpChannel);
|
||||||
|
+ if (!pageTarget)
|
||||||
|
+ return false;
|
||||||
|
+
|
||||||
|
+ const uuid = helper.generateId();
|
||||||
|
+ let file = null;
|
||||||
|
+ if (options.behavior === 'saveToDisk') {
|
||||||
|
+ file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||||
|
+ file.initWithPath(options.downloadsDir);
|
||||||
|
+ file.append(uuid);
|
||||||
|
+
|
||||||
|
+ try {
|
||||||
|
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
|
||||||
|
+ } catch (e) {
|
||||||
|
+ dump(`interceptDownloadRequest failed to create file: ${e}\n`);
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ outFile.value = file;
|
||||||
|
+ this._handlerToUuid.set(externalAppHandler, uuid);
|
||||||
|
+ const downloadInfo = {
|
||||||
|
+ uuid,
|
||||||
|
+ browserContextId: browserContext.browserContextId,
|
||||||
|
+ pageTargetId: pageTarget.id(),
|
||||||
|
+ url: httpChannel.URI.spec,
|
||||||
|
+ suggestedFileName: externalAppHandler.suggestedFileName,
|
||||||
|
+ };
|
||||||
|
+ this._registry.emit(TargetRegistry.Events.DownloadCreated, downloadInfo);
|
||||||
|
+ return true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ onDownloadComplete(externalAppHandler, canceled, errorName) {
|
||||||
|
+ const uuid = this._handlerToUuid.get(externalAppHandler);
|
||||||
|
+ if (!uuid)
|
||||||
|
+ return;
|
||||||
|
+ this._handlerToUuid.delete(externalAppHandler);
|
||||||
|
+ const downloadInfo = {
|
||||||
|
+ uuid,
|
||||||
|
+ };
|
||||||
|
+ if (errorName === 'NS_BINDING_ABORTED') {
|
||||||
|
+ downloadInfo.canceled = true;
|
||||||
|
+ } else {
|
||||||
|
+ downloadInfo.error = errorName;
|
||||||
|
+ }
|
||||||
|
+ this._registry.emit(TargetRegistry.Events.DownloadFinished, downloadInfo);
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
+class TargetRegistry {
|
+class TargetRegistry {
|
||||||
+ constructor() {
|
+ constructor() {
|
||||||
+ EventEmitter.decorate(this);
|
+ EventEmitter.decorate(this);
|
||||||
|
@ -2322,6 +2409,9 @@ index 0000000000000000000000000000000000000000..345aa8b0ebbd9d1e8c9c79913bd7ec3f
|
||||||
+ onTabCloseListener({ target: tab });
|
+ onTabCloseListener({ target: tab });
|
||||||
+ },
|
+ },
|
||||||
+ });
|
+ });
|
||||||
|
+
|
||||||
|
+ const extHelperAppSvc = Cc["@mozilla.org/uriloader/external-helper-app-service;1"].getService(Ci.nsIExternalHelperAppService);
|
||||||
|
+ extHelperAppSvc.setDownloadInterceptor(new DownloadInterceptor(this));
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ defaultContext() {
|
+ defaultContext() {
|
||||||
|
@ -2395,6 +2485,18 @@ index 0000000000000000000000000000000000000000..345aa8b0ebbd9d1e8c9c79913bd7ec3f
|
||||||
+ targetForBrowser(browser) {
|
+ targetForBrowser(browser) {
|
||||||
+ return this._browserToTarget.get(browser);
|
+ return this._browserToTarget.get(browser);
|
||||||
+ }
|
+ }
|
||||||
|
+
|
||||||
|
+ _targetForChannel(httpChannel) {
|
||||||
|
+ let loadContext = helper.getLoadContext(httpChannel);
|
||||||
|
+ if (loadContext)
|
||||||
|
+ return this.targetForBrowser(loadContext.topFrameElement);
|
||||||
|
+ const channelId = httpChannel.channelId;
|
||||||
|
+ for (const target of this._browserToTarget.values()) {
|
||||||
|
+ if (target._httpChannelIds.has(channelId))
|
||||||
|
+ return target;
|
||||||
|
+ }
|
||||||
|
+ return null;
|
||||||
|
+ }
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
+class PageTarget {
|
+class PageTarget {
|
||||||
|
@ -2410,6 +2512,7 @@ index 0000000000000000000000000000000000000000..345aa8b0ebbd9d1e8c9c79913bd7ec3f
|
||||||
+ this._url = '';
|
+ this._url = '';
|
||||||
+ this._openerId = opener ? opener.id() : undefined;
|
+ this._openerId = opener ? opener.id() : undefined;
|
||||||
+ this._channel = SimpleChannel.createForMessageManager(`browser::page[${this._targetId}]`, this._linkedBrowser.messageManager);
|
+ this._channel = SimpleChannel.createForMessageManager(`browser::page[${this._targetId}]`, this._linkedBrowser.messageManager);
|
||||||
|
+ this._httpChannelIds = new Set();
|
||||||
+
|
+
|
||||||
+ const navigationListener = {
|
+ const navigationListener = {
|
||||||
+ QueryInterface: ChromeUtils.generateQI([ Ci.nsIWebProgressListener]),
|
+ QueryInterface: ChromeUtils.generateQI([ Ci.nsIWebProgressListener]),
|
||||||
|
@ -2727,6 +2830,8 @@ index 0000000000000000000000000000000000000000..345aa8b0ebbd9d1e8c9c79913bd7ec3f
|
||||||
+TargetRegistry.Events = {
|
+TargetRegistry.Events = {
|
||||||
+ TargetCreated: Symbol('TargetRegistry.Events.TargetCreated'),
|
+ TargetCreated: Symbol('TargetRegistry.Events.TargetCreated'),
|
||||||
+ TargetDestroyed: Symbol('TargetRegistry.Events.TargetDestroyed'),
|
+ TargetDestroyed: Symbol('TargetRegistry.Events.TargetDestroyed'),
|
||||||
|
+ DownloadCreated: Symbol('TargetRegistry.Events.DownloadCreated'),
|
||||||
|
+ DownloadFinished: Symbol('TargetRegistry.Events.DownloadFinished'),
|
||||||
+};
|
+};
|
||||||
+
|
+
|
||||||
+var EXPORTED_SYMBOLS = ['TargetRegistry'];
|
+var EXPORTED_SYMBOLS = ['TargetRegistry'];
|
||||||
|
@ -3316,10 +3421,10 @@ index 0000000000000000000000000000000000000000..5a1df2837d70531a670163b7c8601088
|
||||||
+
|
+
|
||||||
diff --git a/juggler/content/NetworkMonitor.js b/juggler/content/NetworkMonitor.js
|
diff --git a/juggler/content/NetworkMonitor.js b/juggler/content/NetworkMonitor.js
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000000000000000000000000000000000..be70ea364f9534bb3b344f64970366c32e8c11be
|
index 0000000000000000000000000000000000000000..155d0770ddf704728829272a41a31ce8c9509a25
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/juggler/content/NetworkMonitor.js
|
+++ b/juggler/content/NetworkMonitor.js
|
||||||
@@ -0,0 +1,62 @@
|
@@ -0,0 +1,48 @@
|
||||||
+"use strict";
|
+"use strict";
|
||||||
+const Ci = Components.interfaces;
|
+const Ci = Components.interfaces;
|
||||||
+const Cr = Components.results;
|
+const Cr = Components.results;
|
||||||
|
@ -3343,7 +3448,7 @@ index 0000000000000000000000000000000000000000..be70ea364f9534bb3b344f64970366c3
|
||||||
+ if (!(channel instanceof Ci.nsIHttpChannel))
|
+ if (!(channel instanceof Ci.nsIHttpChannel))
|
||||||
+ return;
|
+ return;
|
||||||
+ const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
|
+ const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
|
||||||
+ const loadContext = getLoadContext(httpChannel);
|
+ const loadContext = helper.getLoadContext(httpChannel);
|
||||||
+ if (!loadContext)
|
+ if (!loadContext)
|
||||||
+ return;
|
+ return;
|
||||||
+ const window = loadContext.associatedWindow;
|
+ const window = loadContext.associatedWindow;
|
||||||
|
@ -3365,20 +3470,6 @@ index 0000000000000000000000000000000000000000..be70ea364f9534bb3b344f64970366c3
|
||||||
+ }
|
+ }
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
+function getLoadContext(httpChannel) {
|
|
||||||
+ let loadContext = null;
|
|
||||||
+ try {
|
|
||||||
+ if (httpChannel.notificationCallbacks)
|
|
||||||
+ loadContext = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
|
||||||
+ } catch (e) {}
|
|
||||||
+ try {
|
|
||||||
+ if (!loadContext && httpChannel.loadGroup)
|
|
||||||
+ loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
|
|
||||||
+ } catch (e) { }
|
|
||||||
+ return loadContext;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+var EXPORTED_SYMBOLS = ['NetworkMonitor'];
|
+var EXPORTED_SYMBOLS = ['NetworkMonitor'];
|
||||||
+this.NetworkMonitor = NetworkMonitor;
|
+this.NetworkMonitor = NetworkMonitor;
|
||||||
+
|
+
|
||||||
|
@ -5362,10 +5453,10 @@ index 0000000000000000000000000000000000000000..bf37558bccc48f4d90eadc971c1eb3e4
|
||||||
+this.AccessibilityHandler = AccessibilityHandler;
|
+this.AccessibilityHandler = AccessibilityHandler;
|
||||||
diff --git a/juggler/protocol/BrowserHandler.js b/juggler/protocol/BrowserHandler.js
|
diff --git a/juggler/protocol/BrowserHandler.js b/juggler/protocol/BrowserHandler.js
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000000000000000000000000000000000..97e88dd582090971d122064b8a131096a317b6be
|
index 0000000000000000000000000000000000000000..21ea3ef1451d02b36f884cf3c6ef3df81bd1a9b9
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/juggler/protocol/BrowserHandler.js
|
+++ b/juggler/protocol/BrowserHandler.js
|
||||||
@@ -0,0 +1,189 @@
|
@@ -0,0 +1,199 @@
|
||||||
+"use strict";
|
+"use strict";
|
||||||
+
|
+
|
||||||
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
@ -5408,6 +5499,8 @@ index 0000000000000000000000000000000000000000..97e88dd582090971d122064b8a131096
|
||||||
+ this._eventListeners = [
|
+ this._eventListeners = [
|
||||||
+ helper.on(this._targetRegistry, TargetRegistry.Events.TargetCreated, this._onTargetCreated.bind(this)),
|
+ helper.on(this._targetRegistry, TargetRegistry.Events.TargetCreated, this._onTargetCreated.bind(this)),
|
||||||
+ helper.on(this._targetRegistry, TargetRegistry.Events.TargetDestroyed, this._onTargetDestroyed.bind(this)),
|
+ helper.on(this._targetRegistry, TargetRegistry.Events.TargetDestroyed, this._onTargetDestroyed.bind(this)),
|
||||||
|
+ helper.on(this._targetRegistry, TargetRegistry.Events.DownloadCreated, this._onDownloadCreated.bind(this)),
|
||||||
|
+ helper.on(this._targetRegistry, TargetRegistry.Events.DownloadFinished, this._onDownloadFinished.bind(this)),
|
||||||
+ ];
|
+ ];
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
|
@ -5473,6 +5566,14 @@ index 0000000000000000000000000000000000000000..97e88dd582090971d122064b8a131096
|
||||||
+ });
|
+ });
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
|
+ _onDownloadCreated(downloadInfo) {
|
||||||
|
+ this._session.emitEvent('Browser.downloadCreated', downloadInfo);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ _onDownloadFinished(downloadInfo) {
|
||||||
|
+ this._session.emitEvent('Browser.downloadFinished', downloadInfo);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
+ async newPage({browserContextId}) {
|
+ async newPage({browserContextId}) {
|
||||||
+ const targetId = await this._targetRegistry.newPage({browserContextId});
|
+ const targetId = await this._targetRegistry.newPage({browserContextId});
|
||||||
+ return {targetId};
|
+ return {targetId};
|
||||||
|
@ -6361,10 +6462,10 @@ index 0000000000000000000000000000000000000000..78b6601b91d0b7fcda61114e6846aa07
|
||||||
+this.EXPORTED_SYMBOLS = ['t', 'checkScheme'];
|
+this.EXPORTED_SYMBOLS = ['t', 'checkScheme'];
|
||||||
diff --git a/juggler/protocol/Protocol.js b/juggler/protocol/Protocol.js
|
diff --git a/juggler/protocol/Protocol.js b/juggler/protocol/Protocol.js
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 0000000000000000000000000000000000000000..6e187212618130bc716a0fd0121ed0dd23d35770
|
index 0000000000000000000000000000000000000000..ae13d7ad1ce2a9776121ffcfcf7e68c5118e6e5c
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/juggler/protocol/Protocol.js
|
+++ b/juggler/protocol/Protocol.js
|
||||||
@@ -0,0 +1,781 @@
|
@@ -0,0 +1,800 @@
|
||||||
+const {t, checkScheme} = ChromeUtils.import('chrome://juggler/content/protocol/PrimitiveTypes.js');
|
+const {t, checkScheme} = ChromeUtils.import('chrome://juggler/content/protocol/PrimitiveTypes.js');
|
||||||
+
|
+
|
||||||
+// Protocol-specific types.
|
+// Protocol-specific types.
|
||||||
|
@ -6409,6 +6510,11 @@ index 0000000000000000000000000000000000000000..6e187212618130bc716a0fd0121ed0dd
|
||||||
+ accuracy: t.Optional(t.Number),
|
+ accuracy: t.Optional(t.Number),
|
||||||
+};
|
+};
|
||||||
+
|
+
|
||||||
|
+browserTypes.DownloadOptions = {
|
||||||
|
+ behavior: t.Optional(t.Enum(['saveToDisk', 'cancel'])),
|
||||||
|
+ downloadsDir: t.Optional(t.String),
|
||||||
|
+};
|
||||||
|
+
|
||||||
+const pageTypes = {};
|
+const pageTypes = {};
|
||||||
+pageTypes.DOMPoint = {
|
+pageTypes.DOMPoint = {
|
||||||
+ x: t.Number,
|
+ x: t.Number,
|
||||||
|
@ -6550,6 +6656,7 @@ index 0000000000000000000000000000000000000000..6e187212618130bc716a0fd0121ed0dd
|
||||||
+ validTo: t.Number,
|
+ validTo: t.Number,
|
||||||
+};
|
+};
|
||||||
+
|
+
|
||||||
|
+
|
||||||
+const Browser = {
|
+const Browser = {
|
||||||
+ targets: ['browser'],
|
+ targets: ['browser'],
|
||||||
+
|
+
|
||||||
|
@ -6564,6 +6671,18 @@ index 0000000000000000000000000000000000000000..6e187212618130bc716a0fd0121ed0dd
|
||||||
+ sessionId: t.String,
|
+ sessionId: t.String,
|
||||||
+ targetId: t.String,
|
+ targetId: t.String,
|
||||||
+ },
|
+ },
|
||||||
|
+ 'downloadCreated': {
|
||||||
|
+ uuid: t.String,
|
||||||
|
+ browserContextId: t.String,
|
||||||
|
+ pageTargetId: t.String,
|
||||||
|
+ url: t.String,
|
||||||
|
+ suggestedFileName: t.String,
|
||||||
|
+ },
|
||||||
|
+ 'downloadFinished': {
|
||||||
|
+ uuid: t.String,
|
||||||
|
+ canceled: t.Optional(t.Boolean),
|
||||||
|
+ error: t.Optional(t.String),
|
||||||
|
+ },
|
||||||
+ },
|
+ },
|
||||||
+
|
+
|
||||||
+ methods: {
|
+ methods: {
|
||||||
|
@ -6582,6 +6701,7 @@ index 0000000000000000000000000000000000000000..6e187212618130bc716a0fd0121ed0dd
|
||||||
+ viewport: t.Optional(pageTypes.Viewport),
|
+ viewport: t.Optional(pageTypes.Viewport),
|
||||||
+ locale: t.Optional(t.String),
|
+ locale: t.Optional(t.String),
|
||||||
+ timezoneId: t.Optional(t.String),
|
+ timezoneId: t.Optional(t.String),
|
||||||
|
+ downloadOptions: t.Optional(browserTypes.DownloadOptions),
|
||||||
+ },
|
+ },
|
||||||
+ returns: {
|
+ returns: {
|
||||||
+ browserContextId: t.String,
|
+ browserContextId: t.String,
|
||||||
|
@ -7515,3 +7635,192 @@ index 87701f8d2cfee8bd84acd28c62b3be4989c9474c..ae1aa85c019cb21d4f7e79c35e8afe72
|
||||||
+ in nsIURI aLocation,
|
+ in nsIURI aLocation,
|
||||||
+ [optional] in unsigned long aFlags);
|
+ [optional] in unsigned long aFlags);
|
||||||
};
|
};
|
||||||
|
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp
|
||||||
|
index a9a0f395bd6afcb995b786f83f1ba56ff0d56b00..b134791c28101ed4fd34004e23ee674691333964 100644
|
||||||
|
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
|
||||||
|
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
|
||||||
|
@@ -100,6 +100,7 @@
|
||||||
|
|
||||||
|
#include "mozilla/Components.h"
|
||||||
|
#include "mozilla/ClearOnShutdown.h"
|
||||||
|
+#include "mozilla/ErrorNames.h"
|
||||||
|
#include "mozilla/Preferences.h"
|
||||||
|
#include "mozilla/ipc/URIUtils.h"
|
||||||
|
|
||||||
|
@@ -841,6 +842,12 @@ NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
+NS_IMETHODIMP nsExternalHelperAppService::SetDownloadInterceptor(
|
||||||
|
+ nsIDownloadInterceptor* interceptor) {
|
||||||
|
+ mInterceptor = interceptor;
|
||||||
|
+ return NS_OK;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
nsresult nsExternalHelperAppService::GetFileTokenForPath(
|
||||||
|
const char16_t* aPlatformAppPath, nsIFile** aFile) {
|
||||||
|
nsDependentString platformAppPath(aPlatformAppPath);
|
||||||
|
@@ -1407,7 +1414,12 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) {
|
||||||
|
// Strip off the ".part" from mTempLeafName
|
||||||
|
mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
|
||||||
|
|
||||||
|
+ return CreateSaverForTempFile();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+nsresult nsExternalAppHandler::CreateSaverForTempFile() {
|
||||||
|
MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
|
||||||
|
+ nsresult rv;
|
||||||
|
mSaver =
|
||||||
|
do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
@@ -1567,7 +1579,36 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
- rv = SetUpTempFile(aChannel);
|
||||||
|
+ bool isIntercepted = false;
|
||||||
|
+ nsCOMPtr<nsIDownloadInterceptor> interceptor = mExtProtSvc->mInterceptor;
|
||||||
|
+ if (interceptor) {
|
||||||
|
+ nsCOMPtr<nsIFile> fileToUse;
|
||||||
|
+ rv = interceptor->InterceptDownloadRequest(this, request, getter_AddRefs(fileToUse), &isIntercepted);
|
||||||
|
+ if (!NS_SUCCEEDED(rv)) {
|
||||||
|
+ LOG((" failed to call nsIDowloadInterceptor.interceptDownloadRequest"));
|
||||||
|
+ return rv;
|
||||||
|
+ }
|
||||||
|
+ if (isIntercepted) {
|
||||||
|
+ LOG((" request interceped by nsIDowloadInterceptor"));
|
||||||
|
+ if (fileToUse) {
|
||||||
|
+ mTempFile = fileToUse;
|
||||||
|
+ rv = mTempFile->GetLeafName(mTempLeafName);
|
||||||
|
+ NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
+ } else {
|
||||||
|
+ Cancel(NS_BINDING_ABORTED);
|
||||||
|
+ return NS_OK;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Temp file is the final destination when download is intercepted. In that
|
||||||
|
+ // case we only need to create saver (and not create transfer later). Not creating
|
||||||
|
+ // mTransfer also cuts off all downloads handling logic in the js compoenents and
|
||||||
|
+ // browser UI.
|
||||||
|
+ if (isIntercepted)
|
||||||
|
+ rv = CreateSaverForTempFile();
|
||||||
|
+ else
|
||||||
|
+ rv = SetUpTempFile(aChannel);
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
nsresult transferError = rv;
|
||||||
|
|
||||||
|
@@ -1615,6 +1656,11 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
|
||||||
|
mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
|
||||||
|
nsAutoCString MIMEType;
|
||||||
|
mMimeInfo->GetMIMEType(MIMEType);
|
||||||
|
+
|
||||||
|
+ if (isIntercepted) {
|
||||||
|
+ return NS_OK;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
if (alwaysAsk) {
|
||||||
|
// But we *don't* ask if this mimeInfo didn't come from
|
||||||
|
// our user configuration datastore and the user has said
|
||||||
|
@@ -2015,6 +2061,15 @@ nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver,
|
||||||
|
NotifyTransfer(aStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if (!mCanceled) {
|
||||||
|
+ nsCOMPtr<nsIDownloadInterceptor> interceptor = mExtProtSvc->mInterceptor;
|
||||||
|
+ if (interceptor) {
|
||||||
|
+ nsCString noError;
|
||||||
|
+ nsresult rv = interceptor->OnDownloadComplete(this, noError);
|
||||||
|
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to call nsIDowloadInterceptor.OnDownloadComplete");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -2385,6 +2440,14 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ nsCOMPtr<nsIDownloadInterceptor> interceptor = mExtProtSvc->mInterceptor;
|
||||||
|
+ if (interceptor) {
|
||||||
|
+ nsCString errorName;
|
||||||
|
+ GetErrorName(aReason, errorName);
|
||||||
|
+ nsresult rv = interceptor->OnDownloadComplete(this, errorName);
|
||||||
|
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed notify nsIDowloadInterceptor about cancel");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
// Break our reference cycle with the helper app dialog (set up in
|
||||||
|
// OnStartRequest)
|
||||||
|
mDialog = nullptr;
|
||||||
|
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h
|
||||||
|
index f3d18ce83858b9cb876091ea04fcf6f079f5471c..abb0be752aeceae3e396d65b92d0b3eac215a2e9 100644
|
||||||
|
--- a/uriloader/exthandler/nsExternalHelperAppService.h
|
||||||
|
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
|
||||||
|
@@ -189,6 +189,8 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService,
|
||||||
|
mozilla::dom::BrowsingContext* aContentContext, bool aForceSave,
|
||||||
|
nsIInterfaceRequestor* aWindowContext,
|
||||||
|
nsIStreamListener** aStreamListener);
|
||||||
|
+
|
||||||
|
+ nsCOMPtr<nsIDownloadInterceptor> mInterceptor;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
@@ -366,6 +368,9 @@ class nsExternalAppHandler final : public nsIStreamListener,
|
||||||
|
* Upon successful return, both mTempFile and mSaver will be valid.
|
||||||
|
*/
|
||||||
|
nsresult SetUpTempFile(nsIChannel* aChannel);
|
||||||
|
+
|
||||||
|
+ nsresult CreateSaverForTempFile();
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* When we download a helper app, we are going to retarget all load
|
||||||
|
* notifications into our own docloader and load group instead of
|
||||||
|
diff --git a/uriloader/exthandler/nsIExternalHelperAppService.idl b/uriloader/exthandler/nsIExternalHelperAppService.idl
|
||||||
|
index 8a55c1bd666c4f7a032863f1527a2315830643c5..c8bfff858079216798e0c71cc757e67466ad4ce1 100644
|
||||||
|
--- a/uriloader/exthandler/nsIExternalHelperAppService.idl
|
||||||
|
+++ b/uriloader/exthandler/nsIExternalHelperAppService.idl
|
||||||
|
@@ -6,6 +6,7 @@
|
||||||
|
|
||||||
|
#include "nsICancelable.idl"
|
||||||
|
|
||||||
|
+interface nsIHelperAppLauncher;
|
||||||
|
interface nsIURI;
|
||||||
|
interface nsIRequest;
|
||||||
|
interface nsIStreamListener;
|
||||||
|
@@ -20,6 +21,17 @@ webidl BrowsingContext;
|
||||||
|
class nsExternalAppHandler;
|
||||||
|
%}
|
||||||
|
|
||||||
|
+/**
|
||||||
|
+ * Interceptor interface used by Juggler.
|
||||||
|
+ */
|
||||||
|
+[scriptable, uuid(9a20e9b0-75d0-11ea-bc55-0242ac130003)]
|
||||||
|
+interface nsIDownloadInterceptor : nsISupports
|
||||||
|
+{
|
||||||
|
+ bool interceptDownloadRequest(in nsIHelperAppLauncher aHandler, in nsIRequest aRequest, out nsIFile file);
|
||||||
|
+
|
||||||
|
+ void onDownloadComplete(in nsIHelperAppLauncher aHandler, in ACString aErrorName);
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* The external helper app service is used for finding and launching
|
||||||
|
* platform specific external applications for a given mime content type.
|
||||||
|
@@ -49,7 +61,7 @@ interface nsIExternalHelperAppService : nsISupports
|
||||||
|
in nsIInterfaceRequestor aContentContext,
|
||||||
|
in boolean aForceSave,
|
||||||
|
[optional] in nsIInterfaceRequestor aWindowContext);
|
||||||
|
-
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Binds an external helper application to a stream listener. The caller
|
||||||
|
* should pump data into the returned stream listener. When the OnStopRequest
|
||||||
|
@@ -82,6 +94,7 @@ interface nsIExternalHelperAppService : nsISupports
|
||||||
|
boolean applyDecodingForExtension(in AUTF8String aExtension,
|
||||||
|
in ACString aEncodingType);
|
||||||
|
|
||||||
|
+ void setDownloadInterceptor(in nsIDownloadInterceptor interceptor);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
Loading…
Reference in New Issue