text: /* 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.
   */
  /* globals Components, Services, XPCOMUtils, PdfjsChromeUtils,
             PdfjsContentUtils, PdfStreamConverter */
  
  "use strict";
  
  var EXPORTED_SYMBOLS = ["PdfJs"];
  
  const Cc = Components.classes;
  const Ci = Components.interfaces;
  const Cr = Components.results;
  const Cm = Components.manager;
  const Cu = Components.utils;
  
  const PREF_PREFIX = "pdfjs";
  const PREF_DISABLED = PREF_PREFIX + ".disabled";
  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_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types";
  const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
  const TOPIC_PLUGINS_LIST_UPDATED = "plugins-list-updated";
  const TOPIC_PLUGIN_INFO_UPDATED = "plugin-info-updated";
  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");
  XPCOMUtils.defineLazyServiceGetter(Svc, "pluginHost",
                                     "@mozilla.org/plugin/host;1",
                                     "nsIPluginHost");
  XPCOMUtils.defineLazyModuleGetter(this, "PdfjsChromeUtils",
                                    "resource://pdf.js/PdfjsChromeUtils.jsm");
  XPCOMUtils.defineLazyModuleGetter(this, "PdfjsContentUtils",
                                    "resource://pdf.js/PdfjsContentUtils.jsm");
  
  function getBoolPref(aPref, aDefaultValue) {
    try {
      return Services.prefs.getBoolPref(aPref);
    } catch (ex) {
      return aDefaultValue;
    }
  }
  
  function getIntPref(aPref, aDefaultValue) {
    try {
      return Services.prefs.getIntPref(aPref);
    } catch (ex) {
      return aDefaultValue;
    }
  }
  
  function isDefaultHandler() {
   if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
     return PdfjsContentUtils.isDefaultHandlerApp();
   }
   return PdfjsChromeUtils.isDefaultHandlerApp();
  }
  
  function initializeDefaultPreferences() {
    var DEFAULT_PREFERENCES =
  //#include ../../../web/default_preferences.json
  //#if false
      "end of DEFAULT_PREFERENCES";
  //#endif
  
    var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + ".");
    var defaultValue;
    for (var key in DEFAULT_PREFERENCES) {
      defaultValue = DEFAULT_PREFERENCES[key];
      switch (typeof defaultValue) {
        case "boolean":
          defaultBranch.setBoolPref(key, defaultValue);
          break;
        case "number":
          defaultBranch.setIntPref(key, defaultValue);
          break;
        case "string":
          defaultBranch.setCharPref(key, defaultValue);
          break;
      }
    }
  }
  
  // Register/unregister a constructor as a factory.
  function Factory() {}
  Factory.prototype = {
    register: function register(targetConstructor) {
      var proto = targetConstructor.prototype;
      this._classID = proto.classID;
  
      var factory = XPCOMUtils._getFactory(targetConstructor);
      this._factory = factory;
  
      var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
      registrar.registerFactory(proto.classID, proto.classDescription,
                                proto.contractID, factory);
  
      if (proto.classID2) {
        this._classID2 = proto.classID2;
        registrar.registerFactory(proto.classID2, proto.classDescription,
                                  proto.contractID2, factory);
      }
    },
  
    unregister: function unregister() {
      var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
      registrar.unregisterFactory(this._classID, this._factory);
      if (this._classID2) {
        registrar.unregisterFactory(this._classID2, this._factory);
      }
      this._factory = null;
    }
  };
  
  var PdfJs = {
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
    _registered: false,
    _initialized: false,
  
    init: function init(remote) {
      if (Services.appinfo.processType !==
          Services.appinfo.PROCESS_TYPE_DEFAULT) {
        throw new Error("PdfJs.init should only get called " +
                        "in the parent process.");
      }
      PdfjsChromeUtils.init();
      if (!remote) {
        PdfjsContentUtils.init();
      }
      this.initPrefs();
      this.updateRegistration();
    },
  
    initPrefs: function initPrefs() {
      if (this._initialized) {
        return;
      }
      this._initialized = true;
  
      if (!getBoolPref(PREF_DISABLED, true)) {
        this._migrate();
      }
  
      // Listen for when pdf.js is completely disabled or a different pdf handler
      // is chosen.
      Services.prefs.addObserver(PREF_DISABLED, this);
      Services.prefs.addObserver(PREF_DISABLED_PLUGIN_TYPES, this);
      Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED);
      Services.obs.addObserver(this, TOPIC_PLUGINS_LIST_UPDATED);
      Services.obs.addObserver(this, TOPIC_PLUGIN_INFO_UPDATED);
  
      initializeDefaultPreferences();
    },
  
    updateRegistration: function updateRegistration() {
      if (this.enabled) {
        this.ensureRegistered();
      } else {
        this.ensureUnregistered();
      }
    },
  
    uninit: function uninit() {
      if (this._initialized) {
        Services.prefs.removeObserver(PREF_DISABLED, this);
        Services.prefs.removeObserver(PREF_DISABLED_PLUGIN_TYPES, this);
        Services.obs.removeObserver(this, TOPIC_PDFJS_HANDLER_CHANGED);
        Services.obs.removeObserver(this, TOPIC_PLUGINS_LIST_UPDATED);
        Services.obs.removeObserver(this, TOPIC_PLUGIN_INFO_UPDATED);
        this._initialized = false;
      }
      this.ensureUnregistered();
    },
  
    _migrate: function migrate() {
      const VERSION = 2;
      var currentVersion = getIntPref(PREF_MIGRATION_VERSION, 0);
      if (currentVersion >= VERSION) {
        return;
      }
      // Make pdf.js the default pdf viewer on the first migration.
      if (currentVersion < 1) {
        this._becomeHandler();
      }
      if (currentVersion < 2) {
        // cleaning up of unused database preference (see #3994)
        Services.prefs.clearUserPref(PREF_PREFIX + ".database");
      }
      Services.prefs.setIntPref(PREF_MIGRATION_VERSION, VERSION);
    },
  
    _becomeHandler: function _becomeHandler() {
      let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, "pdf");
      let prefs = Services.prefs;
      if (handlerInfo.preferredAction !== Ci.nsIHandlerInfo.handleInternally &&
          handlerInfo.preferredAction !== false) {
        // Store the previous settings of preferredAction and
        // alwaysAskBeforeHandling in case we need to revert them in a hotfix that
        // would turn pdf.js off.
        prefs.setIntPref(PREF_PREVIOUS_ACTION, handlerInfo.preferredAction);
        prefs.setBoolPref(PREF_PREVIOUS_ASK, handlerInfo.alwaysAskBeforeHandling);
      }
  
      let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"].
                           getService(Ci.nsIHandlerService);
  
      // Change and save mime handler settings.
      handlerInfo.alwaysAskBeforeHandling = false;
      handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
      handlerService.store(handlerInfo);
  
      // Also disable any plugins for pdfs.
      var stringTypes = "";
      var types = [];
      if (prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) {
        stringTypes = prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
      }
      if (stringTypes !== "") {
        types = stringTypes.split(",");
      }
  
      if (types.indexOf(PDF_CONTENT_TYPE) === -1) {
        types.push(PDF_CONTENT_TYPE);
      }
      prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES, types.join(","));
  
      // Update the category manager in case the plugins are already loaded.
      let categoryManager = Cc["@mozilla.org/categorymanager;1"];
      categoryManager.getService(Ci.nsICategoryManager).
                      deleteCategoryEntry("Gecko-Content-Viewers",
                                          PDF_CONTENT_TYPE,
                                          false);
    },
  
    // nsIObserver
    observe: function observe(aSubject, aTopic, aData) {
      this.updateRegistration();
      if (Services.appinfo.processType ===
          Services.appinfo.PROCESS_TYPE_DEFAULT) {
        let jsm = "resource://pdf.js/PdfjsChromeUtils.jsm";
        let PdfjsChromeUtils = Components.utils.import(jsm, {}).PdfjsChromeUtils;
        PdfjsChromeUtils.notifyChildOfSettingsChange();
      }
    },
  
    /**
     * pdf.js is only enabled if it is both selected as the pdf viewer and if the
     * global switch enabling it is true.
     * @return {boolean} Whether or not it's enabled.
     */
    get enabled() {
      var disabled = getBoolPref(PREF_DISABLED, true);
      if (disabled) {
        return false;
      }
  
      // Check if the 'application/pdf' preview handler is configured properly.
      if (!isDefaultHandler()) {
        return false;
      }
  
      // Check if we have disabled plugin handling of 'application/pdf' in prefs
      if (Services.prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) {
        let disabledPluginTypes =
          Services.prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES).split(",");
        if (disabledPluginTypes.indexOf(PDF_CONTENT_TYPE) >= 0) {
          return true;
        }
      }
  
      // Check if there is an enabled pdf plugin.
      // Note: this check is performed last because getPluginTags() triggers
      // costly plugin list initialization (bug 881575)
      let tags = Cc["@mozilla.org/plugin/host;1"].
                    getService(Ci.nsIPluginHost).
                    getPluginTags();
      let enabledPluginFound = tags.some(function(tag) {
        if (tag.disabled) {
          return false;
        }
        let mimeTypes = tag.getMimeTypes();
        return mimeTypes.some(function(mimeType) {
          return mimeType === PDF_CONTENT_TYPE;
        });
      });
  
      // Use pdf.js if pdf plugin is not present or disabled
      return !enabledPluginFound;
    },
  
    ensureRegistered: function ensureRegistered() {
      if (this._registered) {
        return;
      }
      this._pdfStreamConverterFactory = new Factory();
      Cu.import("resource://pdf.js/PdfStreamConverter.jsm");
      this._pdfStreamConverterFactory.register(PdfStreamConverter);
  
      this._registered = true;
    },
  
    ensureUnregistered: function ensureUnregistered() {
      if (!this._registered) {
        return;
      }
      this._pdfStreamConverterFactory.unregister();
      Cu.unload("resource://pdf.js/PdfStreamConverter.jsm");
      delete this._pdfStreamConverterFactory;
  
      this._registered = false;
    }
  };
  