/* ***** BEGIN LICENSE BLOCK ***** Copyright © 2009 Center for History and New Media George Mason University, Fairfax, Virginia, USA http://zotero.org This file is part of Zotero. Zotero is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Zotero is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Zotero. If not, see <http://www.gnu.org/licenses/>. Based on nsChromeExtensionHandler example code by Ed Anuff at http://kb.mozillazine.org/Dev_:_Extending_the_Chrome_Protocol ***** END LICENSE BLOCK ***** */ const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; /** XPCOM files to be loaded for all modes **/ const xpcomFilesAll = [ 'zotero', 'intl', 'prefs', 'dataDirectory', 'debug', 'error', 'utilities/date', 'utilities/utilities', 'utilities/utilities_item', 'utilities/openurl', 'utilities/xregexp-all', 'utilities/xregexp-unicode-zotero', 'utilities_internal', 'translate/src/utilities_translate', 'file', 'http', 'mimeTypeHandler', 'pdfWorker/manager', 'ipc', 'profile', 'progressWindow', 'proxy', 'translate/src/translation/translate', 'translate/src/translator', 'translate/src/tlds', 'translation/translate_firefox', 'isbn', 'preferencePanes', ]; /** XPCOM files to be loaded only for local translation and DB access **/ const xpcomFilesLocal = [ 'collectionTreeRow', 'annotations', 'api', 'attachments', 'cite', 'citeprocRsBridge', 'cookieSandbox', 'data/library', 'data/libraries', 'data/dataObject', 'data/dataObjects', 'data/dataObjectUtilities', 'data/cachedTypes', 'data/notes', 'data/item', 'data/items', 'data/collection', 'data/collections', 'data/feedItem', 'data/feedItems', 'data/feed', 'data/feeds', 'data/creators', 'data/group', 'data/groups', 'data/itemFields', 'data/relations', 'data/search', 'data/searchConditions', 'data/searches', 'data/tags', 'db', 'dictionaries', 'duplicates', 'editorInstance', 'feedReader', 'fileDragDataProvider', 'fulltext', 'id', 'integration', 'locale', 'locateManager', 'mime', 'notifier', 'openPDF', 'plugins', 'reader', 'progressQueue', 'progressQueueDialog', 'quickCopy', 'recognizePDF', 'report', 'retractions', 'router', 'schema', 'server', 'session', 'streamer', 'style', 'sync', 'sync/syncAPIClient', 'sync/syncEngine', 'sync/syncExceptions', 'sync/syncEventListeners', 'sync/syncFullTextEngine', 'sync/syncLocal', 'sync/syncRunner', 'sync/syncUtilities', 'storage', 'storage/storageEngine', 'storage/storageLocal', 'storage/storageRequest', 'storage/storageResult', 'storage/storageUtilities', 'storage/streamListener', 'storage/zfs', 'storage/webdav', 'syncedSettings', 'timeline', 'uri', 'users', 'translation/translate_item', 'translation/translators', 'connector/httpIntegrationClient', 'connector/server_connector', 'connector/server_connectorIntegration', ]; Components.utils.import("resource://gre/modules/ComponentUtils.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); var instanceID = (new Date()).getTime(); var isFirstLoadThisSession = true; var zContext = null; var initCallbacks = []; var zInitOptions = {}; // Components.utils.import('resource://zotero/require.js'); // Not using Cu.import here since we don't want the require module to be cached // for includes within ZoteroPane or other code, where we want the window instance available to modules. Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader) .loadSubScript('resource://zotero/require.js'); var ZoteroContext = function() {} ZoteroContext.prototype = { require, /** * Convenience method to replicate window.alert() **/ // TODO: is this still used? if so, move to zotero.js "alert":function alert(msg){ this.Zotero.debug("alert() is deprecated from Zotero XPCOM"); Cc["@mozilla.org/embedcomp/prompt-service;1"] .getService(Ci.nsIPromptService) .alert(null, "", msg); }, /** * Convenience method to replicate window.confirm() **/ // TODO: is this still used? if so, move to zotero.js "confirm":function confirm(msg){ this.Zotero.debug("confirm() is deprecated from Zotero XPCOM"); return Cc["@mozilla.org/embedcomp/prompt-service;1"] .getService(Ci.nsIPromptService) .confirm(null, "", msg); }, "Cc":Cc, "Ci":Ci, /** * Convenience method to replicate window.setTimeout() **/ "setTimeout":function setTimeout(func, ms){ return this.Zotero.setTimeout(func, ms); }, "clearTimeout":function setTimeout(id) { this.Zotero.clearTimeout(id); }, /** * Switches in or out of connector mode */ "switchConnectorMode":function(isConnector) { if(isConnector !== this.isConnector) { Services.obs.notifyObservers(zContext.Zotero, "zotero-before-reload", isConnector ? "connector" : "full"); zContext.Zotero.shutdown().then(function() { // create a new zContext makeZoteroContext(isConnector); return zContext.Zotero.init(zInitOptions); }).done(); } return zContext; }, /** * Shuts down Zotero, calls a callback (that may return a promise), * then reinitializes Zotero. Returns a promise that is resolved * when this process completes. */ "reinit":function(cb, isConnector, options = {}) { Services.obs.notifyObservers(zContext.Zotero, "zotero-before-reload", isConnector ? "connector" : "full"); return zContext.Zotero.shutdown().then(function() { return cb ? cb() : false; }).finally(function() { makeZoteroContext(isConnector); var o = {}; Object.assign(o, zInitOptions); Object.assign(o, options); return zContext.Zotero.init(o); }); } }; /** * The class from which the Zotero global XPCOM context is constructed * * @constructor * This runs when ZoteroService is first requested to load all applicable scripts and initialize * Zotero. Calls to other XPCOM components must be in here rather than in top-level code, as other * components may not have yet been initialized. */ function makeZoteroContext(isConnector) { if(zContext) { // Swap out old zContext var oldzContext = zContext; // Create new zContext zContext = new ZoteroContext(); // Swap in old Zotero object, so that references don't break, but empty it zContext.Zotero = oldzContext.Zotero; for(var key in zContext.Zotero) delete zContext.Zotero[key]; } else { zContext = new ZoteroContext(); zContext.Zotero = function() {}; } var subscriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader); // Load zotero.js first subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[0] + ".js", zContext, 'utf-8'); // Load CiteProc into Zotero.CiteProc namespace zContext.Zotero.CiteProc = {"Zotero":zContext.Zotero}; subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/citeproc.js", zContext.Zotero.CiteProc, 'utf-8'); // Load XRegExp object into Zotero.XRegExp const xregexpFiles = [ /**Core functions**/ 'xregexp-all', 'xregexp-unicode-zotero' //adds support for some Unicode categories used in Zotero ]; for (var i=0; i<xregexpFiles.length; i++) { subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/utilities/" + xregexpFiles[i] + ".js", zContext, 'utf-8'); } // Load remaining xpcomFiles for (var i=1; i<xpcomFilesAll.length; i++) { try { subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[i] + ".js", zContext, 'utf-8'); } catch (e) { Components.utils.reportError("Error loading " + xpcomFilesAll[i] + ".js", zContext); throw (e); } } // Load xpcomFiles for specific mode for (let xpcomFile of (isConnector ? xpcomFilesConnector : xpcomFilesLocal)) { try { subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFile + ".js", zContext, "utf-8"); } catch (e) { dump("Error loading " + xpcomFile + ".js\n\n"); dump(e + "\n\n"); Components.utils.reportError("Error loading " + xpcomFile + ".js"); throw (e); } } // Load RDF files into Zotero.RDF.AJAW namespace (easier than modifying all of the references) const rdfXpcomFiles = [ 'rdf/init', 'rdf/uri', 'rdf/term', 'rdf/identity', 'rdf/n3parser', 'rdf/rdfparser', 'rdf/serialize' ]; zContext.Zotero.RDF = {Zotero:zContext.Zotero}; for (var i=0; i<rdfXpcomFiles.length; i++) { subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/translate/src/" + rdfXpcomFiles[i] + ".js", zContext.Zotero.RDF, 'utf-8'); } if(isStandalone()) { // If isStandalone, load standalone.js subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/standalone.js", zContext, 'utf-8'); } // add connector-related properties zContext.Zotero.isConnector = isConnector; zContext.Zotero.instanceID = instanceID; zContext.Zotero.__defineGetter__("isFirstLoadThisSession", function() { return isFirstLoadThisSession; }); }; /** * The class representing the Zotero service, and affiliated XPCOM goop */ function ZoteroService() { try { var start = Date.now(); if(isFirstLoadThisSession) { makeZoteroContext(false); zContext.Zotero.init(zInitOptions) .catch(function (e) { dump(e + "\n\n"); Components.utils.reportError(e); if (!zContext.Zotero.startupError) { zContext.Zotero.startupError = e.stack || e; } if (!isStandalone()) { throw e; } }) .then(function () { if (isStandalone()) { if (zContext.Zotero.startupErrorHandler || zContext.Zotero.startupError) { if (zContext.Zotero.startupErrorHandler) { zContext.Zotero.startupErrorHandler(); } else if (zContext.Zotero.startupError) { // Try to repair the DB on the next startup, in case it helps resolve // the error try { zContext.Zotero.Schema.setIntegrityCheckRequired(true); } catch (e) {} try { zContext.Zotero.startupError = zContext.Zotero.Utilities.Internal.filterStack( zContext.Zotero.startupError ); } catch (e) {} let ps = Cc["@mozilla.org/embedcomp/prompt-service;1"] .getService(Ci.nsIPromptService); let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING); // Get the stringbundle manually let errorStr = "Error"; let quitStr = "Quit"; let checkForUpdateStr = "Check for Update"; try { let src = 'chrome://zotero/locale/zotero.properties'; let stringBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"] .getService(Components.interfaces.nsIStringBundleService); let stringBundle = stringBundleService.createBundle(src); errorStr = stringBundle.GetStringFromName('general.error'); checkForUpdateStr = stringBundle.GetStringFromName('general.checkForUpdate'); quitStr = stringBundle.GetStringFromName('general.quit'); } catch (e) {} let index = ps.confirmEx( null, errorStr, zContext.Zotero.startupError, buttonFlags, checkForUpdateStr, quitStr, null, null, {} ); if (index == 0) { Components.classes["@mozilla.org/embedcomp/window-watcher;1"] .getService(Components.interfaces.nsIWindowWatcher) .openWindow(null, 'chrome://mozapps/content/update/updates.xul', 'updateChecker', 'chrome,centerscreen,modal', null); } } zContext.Zotero.Utilities.Internal.quitZotero(); } return; } zContext.Zotero.debug("Initialized in "+(Date.now() - start)+" ms"); isFirstLoadThisSession = false; }); let cb; while (cb = initCallbacks.shift()) { cb(zContext.Zotero); } } else { zContext.Zotero.debug("Already initialized"); } this.wrappedJSObject = zContext.Zotero; } catch(e) { var msg = e instanceof Error ? e.name + ': ' + e.message + '\n' + e.fileName + ':' + e.lineNumber + '\n' + e.stack : '' + e; dump(msg + '\n'); Components.utils.reportError(e); throw e; } } ZoteroService.prototype = { classID: Components.ID('{e4c61080-ec2d-11da-8ad9-0800200c9a66}'), QueryInterface: ChromeUtils.generateQI([]) } function addInitCallback(callback) { if (zContext && zContext.Zotero) { callback(zContext.Zotero); } else { initCallbacks.push(callback); } } var _isStandalone = null; /** * Determine whether Zotero Standalone is running */ function isStandalone() { return true; } function getOS() { return Services.appinfo.OS; } function isMac() { return getOS() == "Darwin"; } function isWin() { return getOS() == "WINNT"; } function isLinux() { return getOS() == "Linux"; } /** * The class representing the Zotero command line handler */ function ZoteroCommandLineHandler() {} ZoteroCommandLineHandler.prototype = { /* nsICommandLineHandler */ handle: async function (cmdLine) { // Force debug output to window if (cmdLine.handleFlag("ZoteroDebug", false)) { zInitOptions.forceDebugLog = 2; } // Force debug output to text console else if (cmdLine.handleFlag("ZoteroDebugText", false)) { zInitOptions.forceDebugLog = 1; } // Pressing Ctrl-C via the terminal is interpreted as a crash, and after three crashes // Firefox starts up in automatic safe mode. To avoid this, we clear the crash counter when // using one of the debug-logging flags, which generally imply terminal usage. if (zInitOptions.forceDebugLog) { Services.prefs.getBranch("toolkit.startup.").clearUserPref('recent_crashes'); } zInitOptions.forceDataDir = cmdLine.handleFlagWithParam("datadir", false); // handler to open Zotero pane at startup in Zotero for Firefox if (!isStandalone() && cmdLine.handleFlag("ZoteroPaneOpen", false)) { zInitOptions.openPane = true; } if (cmdLine.handleFlag("ZoteroTest", false)) { zInitOptions.test = true; } if (cmdLine.handleFlag("ZoteroAutomatedTest", false)) { zInitOptions.automatedTest = true; } if (cmdLine.handleFlag("ZoteroSkipBundledFiles", false)) { zInitOptions.skipBundledFiles = true; } // handler for Zotero integration commands // this is typically used on Windows only, via WM_COPYDATA rather than the command line var agent = cmdLine.handleFlagWithParam("ZoteroIntegrationAgent", false); if(agent) { // Don't open a new window cmdLine.preventDefault = true; var command = cmdLine.handleFlagWithParam("ZoteroIntegrationCommand", false); var docId = cmdLine.handleFlagWithParam("ZoteroIntegrationDocument", false); var templateVersion = parseInt(cmdLine.handleFlagWithParam("ZoteroIntegrationTemplateVersion", false)); templateVersion = isNaN(templateVersion) ? 0 : templateVersion; zContext.Zotero.Integration.execCommand(agent, command, docId, templateVersion); } // handler for Windows IPC commands var ipcParam = cmdLine.handleFlagWithParam("ZoteroIPC", false); if(ipcParam) { // Don't open a new window cmdLine.preventDefault = true; if (!zContext) new ZoteroService(); let Zotero = zContext.Zotero; Zotero.setTimeout(() => Zotero.IPC.parsePipeInput(ipcParam), 0); } if(isStandalone()) { var fileToOpen; // Special handler for "zotero" URIs at the command line to prevent them from opening a new window var param = cmdLine.handleFlagWithParam("url", false); if (param) { var uri = cmdLine.resolveURI(param); if(uri.schemeIs("zotero")) { addInitCallback(function (Zotero) { Zotero.uiReadyPromise .then(function () { // Check for existing window and focus it var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); var win = wm.getMostRecentWindow("navigator:browser"); if (win) { win.focus(); win.ZoteroPane.loadURI(uri.spec) } }); }); } // See below else if (uri.schemeIs("file")) { Components.utils.import("resource://gre/modules/osfile.jsm") fileToOpen = OS.Path.fromFileURI(uri.spec) } else { dump(`Not handling URL: ${uri.spec}\n\n`); } } param = cmdLine.handleFlag("debugger", false); if (param) { try { let portOrPath = Services.prefs.getBranch('').getIntPref('devtools.debugger.remote-port'); const { DevToolsLoader } = ChromeUtils.import( "resource://devtools/shared/loader/Loader.jsm" ); const loader = new DevToolsLoader({ freshCompartment: true, }); const { DevToolsServer } = loader.require("devtools/server/devtools-server"); const { SocketListener } = loader.require("devtools/shared/security/socket"); if (DevToolsServer.initialized) { dump("Debugger server already initialized\n\n"); return; } DevToolsServer.init(); DevToolsServer.registerAllActors(); DevToolsServer.allowChromeProcess = true; const socketOptions = { portOrPath }; const listener = new SocketListener(DevToolsServer, socketOptions); await listener.open(); if (!DevToolsServer.listeningSockets) { throw new Error("No listening sockets"); } dump(`Debugger server started on ${portOrPath}\n\n`); } catch (e) { dump(e + "\n\n"); Components.utils.reportError(e); } } // In Fx49-based Mac Standalone, if Zotero is closed, an associated file is launched, and // Zotero hasn't been opened before, a -file parameter is passed and two main windows open. // Subsequent file openings when closed result in -url with file:// URLs (converted above) // and don't result in two windows. Here we prevent the double window. param = fileToOpen; if (!param) { param = cmdLine.handleFlagWithParam("file", false); if (param && isMac()) { cmdLine.preventDefault = true; } } if (param) { addInitCallback(function (Zotero) { // Wait to handle things that require the UI until after it's loaded Zotero.uiReadyPromise .then(function () { var file = Zotero.File.pathToFile(param); if(file.leafName.substr(-4).toLowerCase() === ".csl" || file.leafName.substr(-8).toLowerCase() === ".csl.txt") { // Install CSL file Zotero.Styles.install({ file: file.path }, file.path); } else { // Ask before importing var checkState = { value: Zotero.Prefs.get('import.createNewCollection.fromFileOpenHandler') }; if (Components.classes["@mozilla.org/embedcomp/prompt-service;1"] .getService(Components.interfaces.nsIPromptService) .confirmCheck(null, Zotero.getString('ingester.importFile.title'), Zotero.getString('ingester.importFile.text', [file.leafName]), Zotero.getString('ingester.importFile.intoNewCollection'), checkState)) { Zotero.Prefs.set( 'import.createNewCollection.fromFileOpenHandler', checkState.value ); // Perform file import in front window var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); var browserWindow = wm.getMostRecentWindow("navigator:browser"); browserWindow.Zotero_File_Interface.importFile({ file, createNewCollection: checkState.value }); } } }); }); } } }, classID: Components.ID("{531828f8-a16c-46be-b9aa-14845c3b010f}"), service: true, _xpcom_categories: [{category:"command-line-handler", entry:"m-zotero"}], QueryInterface: ChromeUtils.generateQI([Components.interfaces.nsICommandLineHandler]) }; var NSGetFactory = ComponentUtils.generateNSGetFactory([ZoteroService, ZoteroCommandLineHandler]);