From a652dc85e4a6b9b9519d9b7b82470bd6a8af6db2 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Fri, 5 May 2023 15:18:01 +0200 Subject: [PATCH] [GeckoView] Add a button to download and open the file in an external app (bug 1829367) --- l10n/en-US/viewer.properties | 2 ++ web/app.js | 27 +++++++++++++++-------- web/download_manager.js | 4 ++-- web/firefoxcom.js | 6 +++-- web/images/gv-toolbarButton-openinapp.svg | 11 +++++++++ web/interfaces.js | 6 +++-- web/toolbar-geckoview.js | 5 ++++- web/viewer-geckoview.css | 5 +++++ web/viewer-geckoview.html | 3 +++ web/viewer-geckoview.js | 1 + 10 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 web/images/gv-toolbarButton-openinapp.svg diff --git a/l10n/en-US/viewer.properties b/l10n/en-US/viewer.properties index b3241bd5b93c6..e66fa1046e0db 100644 --- a/l10n/en-US/viewer.properties +++ b/l10n/en-US/viewer.properties @@ -43,6 +43,8 @@ save.title=Save save_label=Save bookmark1.title=Current Page (View URL from Current Page) bookmark1_label=Current Page +open_in_app.title=Open in app +open_in_app_label=Open in app # Secondary toolbar and context menu tools.title=Tools diff --git a/web/app.js b/web/app.js index 75a10cd618c60..3a01bfa257e92 100644 --- a/web/app.js +++ b/web/app.js @@ -1012,7 +1012,7 @@ const PDFViewerApplication = { throw new Error("PDF document not downloaded."); }, - async download() { + async download(options = {}) { const url = this._downloadUrl, filename = this._docFilename; try { @@ -1021,15 +1021,15 @@ const PDFViewerApplication = { const data = await this.pdfDocument.getData(); const blob = new Blob([data], { type: "application/pdf" }); - await this.downloadManager.download(blob, url, filename); + await this.downloadManager.download(blob, url, filename, options); } catch (reason) { // When the PDF document isn't ready, or the PDF file is still // downloading, simply download using the URL. - await this.downloadManager.downloadUrl(url, filename); + await this.downloadManager.downloadUrl(url, filename, options); } }, - async save() { + async save(options = {}) { if (this._saveInProgress) { return; } @@ -1044,12 +1044,12 @@ const PDFViewerApplication = { const data = await this.pdfDocument.saveDocument(); const blob = new Blob([data], { type: "application/pdf" }); - await this.downloadManager.download(blob, url, filename); + await this.downloadManager.download(blob, url, filename, options); } catch (reason) { // When the PDF document isn't ready, or the PDF file is still // downloading, simply fallback to a "regular" download. console.error(`Error when saving the document: ${reason.message}`); - await this.download(); + await this.download(options); } finally { await this.pdfScriptingManager.dispatchDidSave(); this._saveInProgress = false; @@ -1063,14 +1063,18 @@ const PDFViewerApplication = { } }, - downloadOrSave() { + downloadOrSave(options = {}) { if (this.pdfDocument?.annotationStorage.size > 0) { - this.save(); + this.save(options); } else { - this.download(); + this.download(options); } }, + openInExternalApp() { + this.downloadOrSave({ openInExternalApp: true }); + }, + /** * Report the error; used for errors affecting loading and/or parsing of * the entire PDF document. @@ -1852,6 +1856,7 @@ const PDFViewerApplication = { ); eventBus._on("print", webViewerPrint); eventBus._on("download", webViewerDownload); + eventBus._on("openinexternalapp", webViewerOpenInExternalApp); eventBus._on("firstpage", webViewerFirstPage); eventBus._on("lastpage", webViewerLastPage); eventBus._on("nextpage", webViewerNextPage); @@ -1984,6 +1989,7 @@ const PDFViewerApplication = { eventBus._off("presentationmode", webViewerPresentationMode); eventBus._off("print", webViewerPrint); eventBus._off("download", webViewerDownload); + eventBus._off("openinexternalapp", webViewerOpenInExternalApp); eventBus._off("firstpage", webViewerFirstPage); eventBus._off("lastpage", webViewerLastPage); eventBus._off("nextpage", webViewerNextPage); @@ -2500,6 +2506,9 @@ function webViewerPrint() { function webViewerDownload() { PDFViewerApplication.downloadOrSave(); } +function webViewerOpenInExternalApp() { + PDFViewerApplication.openInExternalApp(); +} function webViewerFirstPage() { PDFViewerApplication.page = 1; } diff --git a/web/download_manager.js b/web/download_manager.js index cb1cc6d4c6d78..6117297a8605a 100644 --- a/web/download_manager.js +++ b/web/download_manager.js @@ -49,7 +49,7 @@ function download(blobUrl, filename) { class DownloadManager { #openBlobUrls = new WeakMap(); - downloadUrl(url, filename) { + downloadUrl(url, filename, _options) { if (!createValidAbsoluteUrl(url, "http://example.com")) { console.error(`downloadUrl - not a valid URL: ${url}`); return; // restricted/invalid URL @@ -110,7 +110,7 @@ class DownloadManager { return false; } - download(blob, url, filename) { + download(blob, url, filename, _options) { const blobUrl = URL.createObjectURL(blob); download(blobUrl, filename); } diff --git a/web/firefoxcom.js b/web/firefoxcom.js index 137af0c3fe6bc..b8cb4a417034f 100644 --- a/web/firefoxcom.js +++ b/web/firefoxcom.js @@ -108,10 +108,11 @@ class FirefoxCom { class DownloadManager { #openBlobUrls = new WeakMap(); - downloadUrl(url, filename) { + downloadUrl(url, filename, options = {}) { FirefoxCom.request("download", { originalUrl: url, filename, + options, }); } @@ -160,13 +161,14 @@ class DownloadManager { return false; } - download(blob, url, filename) { + download(blob, url, filename, options = {}) { const blobUrl = URL.createObjectURL(blob); FirefoxCom.request("download", { blobUrl, originalUrl: url, filename, + options, }); } } diff --git a/web/images/gv-toolbarButton-openinapp.svg b/web/images/gv-toolbarButton-openinapp.svg new file mode 100644 index 0000000000000..80ec891aad643 --- /dev/null +++ b/web/images/gv-toolbarButton-openinapp.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/web/interfaces.js b/web/interfaces.js index 579a1f55e3349..396ea465ea7f4 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -156,8 +156,9 @@ class IDownloadManager { /** * @param {string} url * @param {string} filename + * @param {Object} [options] */ - downloadUrl(url, filename) {} + downloadUrl(url, filename, options) {} /** * @param {Uint8Array} data @@ -178,8 +179,9 @@ class IDownloadManager { * @param {Blob} blob * @param {string} url * @param {string} filename + * @param {Object} [options] */ - download(blob, url, filename) {} + download(blob, url, filename, options) {} } /** diff --git a/web/toolbar-geckoview.js b/web/toolbar-geckoview.js index fbde707fefd02..37888e0195ab3 100644 --- a/web/toolbar-geckoview.js +++ b/web/toolbar-geckoview.js @@ -32,7 +32,10 @@ class Toolbar { */ constructor(options, eventBus, _l10n) { this.#eventBus = eventBus; - this.#buttons = [{ element: options.download, eventName: "download" }]; + this.#buttons = [ + { element: options.download, eventName: "download" }, + { element: options.openInApp, eventName: "openinexternalapp" }, + ]; // Bind the event listeners for click and various other actions. this.#bindListeners(options); diff --git a/web/viewer-geckoview.css b/web/viewer-geckoview.css index 6f9c1d1ccbc9b..45f228d0c483d 100644 --- a/web/viewer-geckoview.css +++ b/web/viewer-geckoview.css @@ -35,6 +35,7 @@ --toolbar-fg-color: #15141a; --toolbarButton-download-icon: url(images/gv-toolbarButton-download.svg); + --toolbarButton-openinapp-icon: url(images/gv-toolbarButton-openinapp.svg); } :root:dir(rtl) { @@ -217,6 +218,10 @@ body { mask-image: var(--toolbarButton-download-icon); } +#openInApp::before { + mask-image: var(--toolbarButton-openinapp-icon); +} + .dialogButton { width: auto; margin: 3px 4px 2px !important; diff --git a/web/viewer-geckoview.html b/web/viewer-geckoview.html index 2e2653ce73fd5..4c4997f674fe6 100644 --- a/web/viewer-geckoview.html +++ b/web/viewer-geckoview.html @@ -85,6 +85,9 @@ +
diff --git a/web/viewer-geckoview.js b/web/viewer-geckoview.js index ace58e946ea74..43f23135fe0a9 100644 --- a/web/viewer-geckoview.js +++ b/web/viewer-geckoview.js @@ -45,6 +45,7 @@ function getViewerConfiguration() { mainContainer, container: document.getElementById("floatingToolbar"), download: document.getElementById("download"), + openInApp: document.getElementById("openInApp"), }, passwordOverlay: {