/* Copyright 2012 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* jshint esnext:true */ /* globals Components, Services, XPCOMUtils */ 'use strict'; var EXPORTED_SYMBOLS = ['PdfjsChromeUtils']; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; const PREF_PREFIX = 'pdfjs'; const PDF_CONTENT_TYPE = 'application/pdf'; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); var Svc = {}; XPCOMUtils.defineLazyServiceGetter(Svc, 'mime', '@mozilla.org/mime;1', 'nsIMIMEService'); var DEFAULT_PREFERENCES = { "showPreviousViewOnLoad": true, "defaultZoomValue": "", "sidebarViewOnLoad": 0, "enableHandToolOnLoad": false, "enableWebGL": false, "pdfBugEnabled": false, "disableRange": false, "disableStream": false, "disableAutoFetch": false, "disableFontFace": false, "disableTextLayer": false, "useOnlyCssZoom": false, "externalLinkTarget": 0, "enhanceTextSelection": false, "renderInteractiveForms": false, "disablePageLabels": false } var PdfjsChromeUtils = { // For security purposes when running remote, we restrict preferences // content can access. _allowedPrefNames: Object.keys(DEFAULT_PREFERENCES), _ppmm: null, _mmg: null, /* * Public API */ init: function () { this._browsers = new WeakSet(); if (!this._ppmm) { // global parent process message manager (PPMM) this._ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']. getService(Ci.nsIMessageBroadcaster); this._ppmm.addMessageListener('PDFJS:Parent:clearUserPref', this); this._ppmm.addMessageListener('PDFJS:Parent:setIntPref', this); this._ppmm.addMessageListener('PDFJS:Parent:setBoolPref', this); this._ppmm.addMessageListener('PDFJS:Parent:setCharPref', this); this._ppmm.addMessageListener('PDFJS:Parent:setStringPref', this); this._ppmm.addMessageListener('PDFJS:Parent:isDefaultHandlerApp', this); // global dom message manager (MMg) this._mmg = Cc['@mozilla.org/globalmessagemanager;1']. getService(Ci.nsIMessageListenerManager); this._mmg.addMessageListener('PDFJS:Parent:displayWarning', this); this._mmg.addMessageListener('PDFJS:Parent:addEventListener', this); this._mmg.addMessageListener('PDFJS:Parent:removeEventListener', this); this._mmg.addMessageListener('PDFJS:Parent:updateControlState', this); // observer to handle shutdown Services.obs.addObserver(this, 'quit-application', false); } }, uninit: function () { if (this._ppmm) { this._ppmm.removeMessageListener('PDFJS:Parent:clearUserPref', this); this._ppmm.removeMessageListener('PDFJS:Parent:setIntPref', this); this._ppmm.removeMessageListener('PDFJS:Parent:setBoolPref', this); this._ppmm.removeMessageListener('PDFJS:Parent:setCharPref', this); this._ppmm.removeMessageListener('PDFJS:Parent:setStringPref', this); this._ppmm.removeMessageListener('PDFJS:Parent:isDefaultHandlerApp', this); this._mmg.removeMessageListener('PDFJS:Parent:displayWarning', this); this._mmg.removeMessageListener('PDFJS:Parent:addEventListener', this); this._mmg.removeMessageListener('PDFJS:Parent:removeEventListener', this); this._mmg.removeMessageListener('PDFJS:Parent:updateControlState', this); Services.obs.removeObserver(this, 'quit-application', false); this._mmg = null; this._ppmm = null; } }, /* * Called by the main module when preference changes are picked up * in the parent process. Observers don't propagate so we need to * instruct the child to refresh its configuration and (possibly) * the module's registration. */ notifyChildOfSettingsChange: function () { if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT && this._ppmm) { // XXX kinda bad, we want to get the parent process mm associated // with the content process. _ppmm is currently the global process // manager, which means this is going to fire to every child process // we have open. Unfortunately I can't find a way to get at that // process specific mm from js. this._ppmm.broadcastAsyncMessage('PDFJS:Child:refreshSettings', {}); } }, /* * Events */ observe: function(aSubject, aTopic, aData) { if (aTopic === 'quit-application') { this.uninit(); } }, receiveMessage: function (aMsg) { switch (aMsg.name) { case 'PDFJS:Parent:clearUserPref': this._clearUserPref(aMsg.data.name); break; case 'PDFJS:Parent:setIntPref': this._setIntPref(aMsg.data.name, aMsg.data.value); break; case 'PDFJS:Parent:setBoolPref': this._setBoolPref(aMsg.data.name, aMsg.data.value); break; case 'PDFJS:Parent:setCharPref': this._setCharPref(aMsg.data.name, aMsg.data.value); break; case 'PDFJS:Parent:setStringPref': this._setStringPref(aMsg.data.name, aMsg.data.value); break; case 'PDFJS:Parent:isDefaultHandlerApp': return this.isDefaultHandlerApp(); case 'PDFJS:Parent:displayWarning': this._displayWarning(aMsg); break; case 'PDFJS:Parent:updateControlState': return this._updateControlState(aMsg); case 'PDFJS:Parent:addEventListener': return this._addEventListener(aMsg); case 'PDFJS:Parent:removeEventListener': return this._removeEventListener(aMsg); } }, /* * Internal */ _findbarFromMessage: function(aMsg) { let browser = aMsg.target; let tabbrowser = browser.getTabBrowser(); let tab = tabbrowser.getTabForBrowser(browser); return tabbrowser.getFindBar(tab); }, _updateControlState: function (aMsg) { let data = aMsg.data; this._findbarFromMessage(aMsg) .updateControlState(data.result, data.findPrevious); }, handleEvent: function(aEvent) { // To avoid forwarding the message as a CPOW, create a structured cloneable // version of the event for both performance, and ease of usage, reasons. let type = aEvent.type; let detail = { query: aEvent.detail.query, caseSensitive: aEvent.detail.caseSensitive, highlightAll: aEvent.detail.highlightAll, findPrevious: aEvent.detail.findPrevious }; let browser = aEvent.currentTarget.browser; if (!this._browsers.has(browser)) { throw new Error('FindEventManager was not bound ' + 'for the current browser.'); } // Only forward the events if the current browser is a registered browser. let mm = browser.messageManager; mm.sendAsyncMessage('PDFJS:Child:handleEvent', { type: type, detail: detail }); aEvent.preventDefault(); }, _types: ['find', 'findagain', 'findhighlightallchange', 'findcasesensitivitychange'], _addEventListener: function (aMsg) { let browser = aMsg.target; if (this._browsers.has(browser)) { throw new Error('FindEventManager was bound 2nd time ' + 'without unbinding it first.'); } // Since this jsm is global, we need to store all the browsers // we have to forward the messages for. this._browsers.add(browser); // And we need to start listening to find events. for (var i = 0; i < this._types.length; i++) { var type = this._types[i]; this._findbarFromMessage(aMsg) .addEventListener(type, this, true); } }, _removeEventListener: function (aMsg) { let browser = aMsg.target; if (!this._browsers.has(browser)) { throw new Error('FindEventManager was unbound without binding it first.'); } this._browsers.delete(browser); // No reason to listen to find events any longer. for (var i = 0; i < this._types.length; i++) { var type = this._types[i]; this._findbarFromMessage(aMsg) .removeEventListener(type, this, true); } }, _ensurePreferenceAllowed: function (aPrefName) { let unPrefixedName = aPrefName.split(PREF_PREFIX + '.'); if (unPrefixedName[0] !== '' || this._allowedPrefNames.indexOf(unPrefixedName[1]) === -1) { let msg = '"' + aPrefName + '" ' + 'can\'t be accessed from content. See PdfjsChromeUtils.'; throw new Error(msg); } }, _clearUserPref: function (aPrefName) { this._ensurePreferenceAllowed(aPrefName); Services.prefs.clearUserPref(aPrefName); }, _setIntPref: function (aPrefName, aPrefValue) { this._ensurePreferenceAllowed(aPrefName); Services.prefs.setIntPref(aPrefName, aPrefValue); }, _setBoolPref: function (aPrefName, aPrefValue) { this._ensurePreferenceAllowed(aPrefName); Services.prefs.setBoolPref(aPrefName, aPrefValue); }, _setCharPref: function (aPrefName, aPrefValue) { this._ensurePreferenceAllowed(aPrefName); Services.prefs.setCharPref(aPrefName, aPrefValue); }, _setStringPref: function (aPrefName, aPrefValue) { this._ensurePreferenceAllowed(aPrefName); let str = Cc['@mozilla.org/supports-string;1'] .createInstance(Ci.nsISupportsString); str.data = aPrefValue; Services.prefs.setComplexValue(aPrefName, Ci.nsISupportsString, str); }, /* * Svc.mime doesn't have profile information in the child, so * we bounce this pdfjs enabled configuration check over to the * parent. */ isDefaultHandlerApp: function () { var handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, 'pdf'); return (!handlerInfo.alwaysAskBeforeHandling && handlerInfo.preferredAction === Ci.nsIHandlerInfo.handleInternally); }, /* * Display a notification warning when the renderer isn't sure * a pdf displayed correctly. */ _displayWarning: function (aMsg) { let data = aMsg.data; let browser = aMsg.target; let tabbrowser = browser.getTabBrowser(); let notificationBox = tabbrowser.getNotificationBox(browser); // Flag so we don't send the message twice, since if the user clicks // "open with different viewer" both the button callback and // eventCallback will be called. let messageSent = false; function sendMessage(download) { let mm = browser.messageManager; mm.sendAsyncMessage('PDFJS:Child:fallbackDownload', { download: download }); } let buttons = [{ label: data.label, accessKey: data.accessKey, callback: function() { messageSent = true; sendMessage(true); } }]; notificationBox.appendNotification(data.message, 'pdfjs-fallback', null, notificationBox.PRIORITY_INFO_LOW, buttons, function eventsCallback(eventType) { // Currently there is only one event "removed" but if there are any other // added in the future we still only care about removed at the moment. if (eventType !== 'removed') { return; } // Don't send a response again if we already responded when the button was // clicked. if (messageSent) { return; } sendMessage(false); }); } };