diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index adb3337de40a..9bd25e88c900 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -62,7 +62,7 @@ XPCOMUtils.defineLazyServiceGetter( "nsIPushService" ); -const PREF_PDFJS_ENABLED_CACHE_STATE = "pdfjs.enabledCache.state"; +const PREF_PDFJS_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state"; /** * Fission-compatible JSWindowActor implementations. @@ -1282,13 +1282,8 @@ BrowserGlue.prototype = { // handle any UI migration this._migrateUI(); - if (Services.prefs.prefHasUserValue(PREF_PDFJS_ENABLED_CACHE_STATE)) { - Services.ppmm.sharedData.set( - "pdfjs.enabled", - Services.prefs.getBoolPref(PREF_PDFJS_ENABLED_CACHE_STATE) - ); - } else { - PdfJs.earlyInit(this._isNewProfile); + if (!Services.prefs.prefHasUserValue(PREF_PDFJS_ISDEFAULT_CACHE_STATE)) { + PdfJs.checkIsDefault(this._isNewProfile); } listeners.init(); diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js index 2e513d2e8fe8..9f36871d6303 100644 --- a/browser/components/preferences/main.js +++ b/browser/components/preferences/main.js @@ -49,7 +49,6 @@ XPCOMUtils.defineLazyServiceGetters(this, { const TYPE_PDF = "application/pdf"; const PREF_PDFJS_DISABLED = "pdfjs.disabled"; -const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged"; const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types"; @@ -3648,7 +3647,6 @@ class InternalHandlerInfoWrapper extends HandlerInfoWrapper { // or unregistration of this handler. store() { super.store(); - Services.obs.notifyObservers(null, this._handlerChanged); } get enabled() { @@ -3665,10 +3663,6 @@ class PDFHandlerInfoWrapper extends InternalHandlerInfoWrapper { super(TYPE_PDF); } - get _handlerChanged() { - return TOPIC_PDFJS_HANDLER_CHANGED; - } - get _appPrefLabel() { return "applications-type-pdf"; } diff --git a/browser/extensions/pdfjs/content/PdfJs.jsm b/browser/extensions/pdfjs/content/PdfJs.jsm index 21b07abd8f45..cc87df1ffec3 100644 --- a/browser/extensions/pdfjs/content/PdfJs.jsm +++ b/browser/extensions/pdfjs/content/PdfJs.jsm @@ -23,7 +23,7 @@ const PREF_MIGRATION_VERSION = PREF_PREFIX + ".migrationVersion"; const PREF_PREVIOUS_ACTION = PREF_PREFIX + ".previousHandler.preferredAction"; const PREF_PREVIOUS_ASK = PREF_PREFIX + ".previousHandler.alwaysAskBeforeHandling"; -const PREF_ENABLED_CACHE_STATE = PREF_PREFIX + ".enabledCache.state"; +const PREF_ISDEFAULT_CACHE_STATE = PREF_PREFIX + ".enabledCache.state"; const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged"; const PDF_CONTENT_TYPE = "application/pdf"; @@ -98,6 +98,7 @@ const gPdfFakeHandlerInfo = { var PdfJs = { QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]), _initialized: false, + _cachedIsDefault: true, init: function init(isNewProfile) { if ( @@ -109,20 +110,7 @@ var PdfJs = { } PdfjsChromeUtils.init(); this.initPrefs(); - - Services.ppmm.sharedData.set( - "pdfjs.enabled", - this.checkEnabled(isNewProfile) - ); - }, - - earlyInit(isNewProfile) { - // Note: Please keep this in sync with the duplicated logic in - // BrowserGlue.jsm. - Services.ppmm.sharedData.set( - "pdfjs.enabled", - this.checkEnabled(isNewProfile) - ); + this.checkIsDefault(isNewProfile); }, initPrefs: function initPrefs() { @@ -131,15 +119,13 @@ var PdfJs = { } this._initialized = true; - if (Services.prefs.getBoolPref(PREF_DISABLED, false)) { + if (this._prefDisabled) { this._unbecomeHandler(); } else { this._migrate(); } - // Listen for when pdf.js is completely disabled or a different pdf handler - // is chosen. - Services.prefs.addObserver(PREF_DISABLED, this); + // Listen for when a different pdf handler is chosen. Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED); initializeDefaultPreferences(); @@ -147,7 +133,6 @@ var PdfJs = { uninit: function uninit() { if (this._initialized) { - Services.prefs.removeObserver(PREF_DISABLED, this); Services.obs.removeObserver(this, TOPIC_PDFJS_HANDLER_CHANGED); this._initialized = false; } @@ -232,15 +217,15 @@ var PdfJs = { * pdfs differently. If we're on a new profile, * there's no need to check. */ - _isEnabled(isNewProfile) { + _isDefault(isNewProfile) { let { processType, PROCESS_TYPE_DEFAULT } = Services.appinfo; if (processType !== PROCESS_TYPE_DEFAULT) { throw new Error( - "isEnabled should only get called in the parent process." + "isDefault should only get called in the parent process." ); } - if (Services.prefs.getBoolPref(PREF_DISABLED, true)) { + if (this._prefDisabled) { return false; } @@ -252,28 +237,32 @@ var PdfJs = { let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, "pdf"); return ( !handlerInfo.alwaysAskBeforeHandling && - handlerInfo.preferredAction === Ci.nsIHandlerInfo.handleInternally + handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally ); }, - checkEnabled(isNewProfile) { - let isEnabled = this._isEnabled(isNewProfile); - // This will be updated any time we observe a dependency changing, since - // updateRegistration internally calls enabled. - Services.prefs.setBoolPref(PREF_ENABLED_CACHE_STATE, isEnabled); - return isEnabled; + checkIsDefault(isNewProfile) { + this._cachedIsDefault = this._isDefault(isNewProfile); + Services.prefs.setBoolPref( + PREF_ISDEFAULT_CACHE_STATE, + this._cachedIsDefault + ); + }, + + cachedIsDefault() { + return this._cachedIsDefault; }, // nsIObserver - observe: function observe(aSubject, aTopic, aData) { - if ( - Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT - ) { - throw new Error( - "Only the parent process should be observing PDF handler changes." - ); - } - - Services.ppmm.sharedData.set("pdfjs.enabled", this.checkEnabled()); + observe(aSubject, aTopic, aData) { + this.checkIsDefault(); }, }; + +XPCOMUtils.defineLazyPreferenceGetter( + PdfJs, + "_prefDisabled", + PREF_DISABLED, + false, + () => PdfJs.checkIsDefault() +); diff --git a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm index 69a53f5b7afe..7e1b617a1210 100644 --- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm +++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm @@ -23,11 +23,15 @@ const PDF_VIEWER_ORIGIN = "resource://pdf.js"; const PDF_VIEWER_WEB_PAGE = "resource://pdf.js/web/viewer.html"; const MAX_NUMBER_OF_PREFS = 50; const MAX_STRING_PREF_LENGTH = 128; +const PDF_CONTENT_TYPE = "application/pdf"; const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); ChromeUtils.defineModuleGetter( this, @@ -58,6 +62,7 @@ ChromeUtils.defineModuleGetter( "PdfjsContentUtils", "resource://pdf.js/PdfjsContentUtils.jsm" ); +ChromeUtils.defineModuleGetter(this, "PdfJs", "resource://pdf.js/PdfJs.jsm"); XPCOMUtils.defineLazyGlobalGetters(this, ["XMLHttpRequest"]); @@ -68,6 +73,26 @@ XPCOMUtils.defineLazyServiceGetter( "@mozilla.org/mime;1", "nsIMIMEService" ); +XPCOMUtils.defineLazyServiceGetter( + Svc, + "handlers", + "@mozilla.org/uriloader/handler-service;1", + "nsIHandlerService" +); + +XPCOMUtils.defineLazyGetter(this, "gOurBinary", () => { + let file = Services.dirsvc.get("XREExeF", Ci.nsIFile); + // Make sure to get the .app on macOS + if (AppConstants.platform == "macosx") { + while (file) { + if (/\.app\/?$/i.test(file.leafName)) { + break; + } + file = file.parent; + } + } + return file; +}); function getBoolPref(pref, def) { try { @@ -326,7 +351,7 @@ class ChromeActions { Ci.nsILoadContext ); this.extListener = extHelperAppSvc.doContent( - data.isAttachment ? "application/octet-stream" : "application/pdf", + data.isAttachment ? "application/octet-stream" : PDF_CONTENT_TYPE, aRequest, loadContext, false @@ -1010,7 +1035,86 @@ PdfStreamConverter.prototype = { this.listener = aListener; }, + _usableHandler(handlerInfo) { + let { preferredApplicationHandler } = handlerInfo; + if ( + !preferredApplicationHandler || + !(preferredApplicationHandler instanceof Ci.nsILocalHandlerApp) + ) { + return false; + } + preferredApplicationHandler.QueryInterface(Ci.nsILocalHandlerApp); + // We have an app, grab the executable + let { executable } = preferredApplicationHandler; + if (!executable) { + return false; + } + return !executable.equals(gOurBinary); + }, + + /* + * Check if the user wants to use PDF.js. Returns true if PDF.js should + * handle PDFs, and false if not. Will always return true on non-parent + * processes. + * + * If the user has selected to open PDFs with a helper app, and we are that + * helper app, or if the user has selected the OS default, and we are that + * OS default, reset the preference back to pdf.js . + * + */ + _validateAndMaybeUpdatePDFPrefs() { + let { processType, PROCESS_TYPE_DEFAULT } = Services.appinfo; + // If we're not in the parent, or are the default, then just say yes. + if (processType != PROCESS_TYPE_DEFAULT || PdfJs.cachedIsDefault()) { + return true; + } + + // OK, PDF.js might not be the default. Find out if we've misled the user + // into making Firefox an external handler or if we're the OS default and + // Firefox is set to use the OS default: + let mime = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, "pdf"); + // The above might throw errors. We're deliberately letting those bubble + // back up, where they'll tell the stream converter not to use us. + + if (!mime) { + // This shouldn't happen, but we can't fix what isn't there. Assume + // we're OK to handle with PDF.js + return true; + } + + const { saveToDisk, useHelperApp, useSystemDefault } = Ci.nsIHandlerInfo; + let { preferredAction, alwaysAskBeforeHandling } = mime; + // If the user has indicated they want to be asked or want to save to + // disk, we shouldn't render inline immediately: + if (alwaysAskBeforeHandling || preferredAction == saveToDisk) { + return false; + } + // If we have usable helper app info, don't use PDF.js + if (preferredAction == useHelperApp && this._usableHandler(mime)) { + return false; + } + // If we want the OS default and that's not Firefox, don't use PDF.js + if (preferredAction == useSystemDefault && !mime.isCurrentAppOSDefault()) { + return false; + } + // Log that we're doing this to help debug issues if people end up being + // surprised by this behaviour. + Cu.reportError("Found unusable PDF preferences. Fixing back to PDF.js"); + + mime.preferredAction = Ci.nsIHandlerInfo.handleInternally; + mime.alwaysAskBeforeHandling = false; + Svc.handlers.store(mime); + return true; + }, + getConvertedType(aFromType) { + if (!this._validateAndMaybeUpdatePDFPrefs()) { + throw new Components.Exception( + "Can't use PDF.js", + Cr.NS_ERROR_NOT_AVAILABLE + ); + } + return "text/html"; }, diff --git a/browser/extensions/pdfjs/pdfjs.js b/browser/extensions/pdfjs/pdfjs.js index 8114d7df1774..f19ad2227a81 100644 --- a/browser/extensions/pdfjs/pdfjs.js +++ b/browser/extensions/pdfjs/pdfjs.js @@ -25,7 +25,7 @@ ChromeUtils.defineModuleGetter( // Register/unregister a constructor as a factory. function StreamConverterFactory() { - if (Services.cpmm.sharedData.get("pdfjs.enabled")) { + if (!Services.prefs.getBoolPref("pdfjs.disabled", false)) { return new PdfStreamConverter(); } throw Components.Exception("", Cr.NS_ERROR_FACTORY_NOT_REGISTERED); diff --git a/browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js b/browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js index 7095331638a7..48e0d64d7201 100644 --- a/browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js +++ b/browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js @@ -40,9 +40,6 @@ function changeMimeHandler(preferredAction, alwaysAskBeforeHandling) { handlerInfo.preferredAction = preferredAction; handlerService.store(handlerInfo); - Services.obs.notifyObservers(null, "pdfjs:handlerChanged"); - Services.ppmm.sharedData.flush(); - // Refresh data handlerInfo = mimeService.getFromTypeAndExtension("application/pdf", "pdf"); diff --git a/uriloader/exthandler/HandlerService.js b/uriloader/exthandler/HandlerService.js index 304c7ce71c37..62d866e7e09e 100644 --- a/uriloader/exthandler/HandlerService.js +++ b/uriloader/exthandler/HandlerService.js @@ -8,6 +8,8 @@ const { XPCOMUtils } = ChromeUtils.import( ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged"; + ChromeUtils.defineModuleGetter( this, "FileUtils", @@ -443,6 +445,12 @@ HandlerService.prototype = { delete storedHandlerInfo.stubEntry; this._store.saveSoon(); + + // Now notify PDF.js. This is hacky, but a lot better than expecting all + // the consumers to do it... + if (handlerInfo.type == "application/pdf") { + Services.obs.notifyObservers(null, TOPIC_PDFJS_HANDLER_CHANGED); + } }, // nsIHandlerService