zotero-service.js (20330B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2009 Center for History and New Media 5 George Mason University, Fairfax, Virginia, USA 6 http://zotero.org 7 8 This file is part of Zotero. 9 10 Zotero is free software: you can redistribute it and/or modify 11 it under the terms of the GNU Affero General Public License as published by 12 the Free Software Foundation, either version 3 of the License, or 13 (at your option) any later version. 14 15 Zotero is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Affero General Public License for more details. 19 20 You should have received a copy of the GNU Affero General Public License 21 along with Zotero. If not, see <http://www.gnu.org/licenses/>. 22 23 24 Based on nsChromeExtensionHandler example code by Ed Anuff at 25 http://kb.mozillazine.org/Dev_:_Extending_the_Chrome_Protocol 26 27 ***** END LICENSE BLOCK ***** 28 */ 29 30 const Cc = Components.classes; 31 const Ci = Components.interfaces; 32 33 /** XPCOM files to be loaded for all modes **/ 34 const xpcomFilesAll = [ 35 'zotero', 36 'dataDirectory', 37 'date', 38 'debug', 39 'error', 40 'file', 41 'http', 42 'mimeTypeHandler', 43 'openurl', 44 'ipc', 45 'profile', 46 'progressWindow', 47 'proxy', 48 'translation/translate', 49 'translation/translate_firefox', 50 'translation/translator', 51 'translation/tlds', 52 'utilities', 53 'isbn', 54 'utilities_internal', 55 'utilities_translate' 56 ]; 57 58 /** XPCOM files to be loaded only for local translation and DB access **/ 59 const xpcomFilesLocal = [ 60 'libraryTreeView', 61 'collectionTreeView', 62 'collectionTreeRow', 63 'annotate', 64 'api', 65 'attachments', 66 'cite', 67 'cookieSandbox', 68 'data/library', 69 'data/libraries', 70 'data/dataObject', 71 'data/dataObjects', 72 'data/dataObjectUtilities', 73 'data/cachedTypes', 74 'data/notes', 75 'data/item', 76 'data/items', 77 'data/collection', 78 'data/collections', 79 'data/feedItem', 80 'data/feedItems', 81 'data/feed', 82 'data/feeds', 83 'data/creators', 84 'data/group', 85 'data/groups', 86 'data/itemFields', 87 'data/relations', 88 'data/search', 89 'data/searchConditions', 90 'data/searches', 91 'data/tags', 92 'db', 93 'duplicates', 94 'feedReader', 95 'fulltext', 96 'id', 97 'integration', 98 'itemTreeView', 99 'locale', 100 'locateManager', 101 'mime', 102 'notifier', 103 'openPDF', 104 'quickCopy', 105 'recognizePDF', 106 'report', 107 'router', 108 'schema', 109 'server', 110 'streamer', 111 'style', 112 'sync', 113 'sync/syncAPIClient', 114 'sync/syncEngine', 115 'sync/syncExceptions', 116 'sync/syncEventListeners', 117 'sync/syncFullTextEngine', 118 'sync/syncLocal', 119 'sync/syncRunner', 120 'sync/syncUtilities', 121 'storage', 122 'storage/storageEngine', 123 'storage/storageLocal', 124 'storage/storageRequest', 125 'storage/storageResult', 126 'storage/storageUtilities', 127 'storage/streamListener', 128 'storage/zfs', 129 'storage/webdav', 130 'syncedSettings', 131 'timeline', 132 'uri', 133 'users', 134 'translation/translate_item', 135 'translation/translators', 136 'connector/httpIntegrationClient', 137 'connector/server_connector', 138 'connector/server_connectorIntegration', 139 ]; 140 141 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 142 Components.utils.import("resource://gre/modules/Services.jsm"); 143 144 var instanceID = (new Date()).getTime(); 145 var isFirstLoadThisSession = true; 146 var zContext = null; 147 var initCallbacks = []; 148 var zInitOptions = {}; 149 Components.utils.import('resource://zotero/require.js'); 150 151 ZoteroContext = function() {} 152 ZoteroContext.prototype = { 153 require, 154 155 /** 156 * Convenience method to replicate window.alert() 157 **/ 158 // TODO: is this still used? if so, move to zotero.js 159 "alert":function alert(msg){ 160 this.Zotero.debug("alert() is deprecated from Zotero XPCOM"); 161 Cc["@mozilla.org/embedcomp/prompt-service;1"] 162 .getService(Ci.nsIPromptService) 163 .alert(null, "", msg); 164 }, 165 166 /** 167 * Convenience method to replicate window.confirm() 168 **/ 169 // TODO: is this still used? if so, move to zotero.js 170 "confirm":function confirm(msg){ 171 this.Zotero.debug("confirm() is deprecated from Zotero XPCOM"); 172 return Cc["@mozilla.org/embedcomp/prompt-service;1"] 173 .getService(Ci.nsIPromptService) 174 .confirm(null, "", msg); 175 }, 176 177 "Cc":Cc, 178 "Ci":Ci, 179 180 /** 181 * Convenience method to replicate window.setTimeout() 182 **/ 183 "setTimeout":function setTimeout(func, ms){ 184 return this.Zotero.setTimeout(func, ms); 185 }, 186 187 "clearTimeout":function setTimeout(id) { 188 this.Zotero.clearTimeout(id); 189 }, 190 191 /** 192 * Switches in or out of connector mode 193 */ 194 "switchConnectorMode":function(isConnector) { 195 if(isConnector !== this.isConnector) { 196 Services.obs.notifyObservers(zContext.Zotero, "zotero-before-reload", isConnector ? "connector" : "full"); 197 zContext.Zotero.shutdown().then(function() { 198 // create a new zContext 199 makeZoteroContext(isConnector); 200 return zContext.Zotero.init(zInitOptions); 201 }).done(); 202 } 203 204 return zContext; 205 }, 206 207 /** 208 * Shuts down Zotero, calls a callback (that may return a promise), 209 * then reinitializes Zotero. Returns a promise that is resolved 210 * when this process completes. 211 */ 212 "reinit":function(cb, isConnector, options = {}) { 213 Services.obs.notifyObservers(zContext.Zotero, "zotero-before-reload", isConnector ? "connector" : "full"); 214 return zContext.Zotero.shutdown().then(function() { 215 return cb ? cb() : false; 216 }).finally(function() { 217 makeZoteroContext(isConnector); 218 var o = {}; 219 Object.assign(o, zInitOptions); 220 Object.assign(o, options); 221 zContext.Zotero.init(o); 222 }); 223 } 224 }; 225 226 /** 227 * The class from which the Zotero global XPCOM context is constructed 228 * 229 * @constructor 230 * This runs when ZoteroService is first requested to load all applicable scripts and initialize 231 * Zotero. Calls to other XPCOM components must be in here rather than in top-level code, as other 232 * components may not have yet been initialized. 233 */ 234 function makeZoteroContext(isConnector) { 235 if(zContext) { 236 // Swap out old zContext 237 var oldzContext = zContext; 238 // Create new zContext 239 zContext = new ZoteroContext(); 240 // Swap in old Zotero object, so that references don't break, but empty it 241 zContext.Zotero = oldzContext.Zotero; 242 for(var key in zContext.Zotero) delete zContext.Zotero[key]; 243 } else { 244 zContext = new ZoteroContext(); 245 zContext.Zotero = function() {}; 246 } 247 248 var subscriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader); 249 250 // Load zotero.js first 251 subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[0] + ".js", zContext); 252 253 // Load CiteProc into Zotero.CiteProc namespace 254 zContext.Zotero.CiteProc = {"Zotero":zContext.Zotero}; 255 subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/citeproc.js", zContext.Zotero.CiteProc); 256 257 // Load XRegExp object into Zotero.XRegExp 258 const xregexpFiles = [ 259 /**Core functions**/ 260 'xregexp', 261 262 /**Addons**/ 263 'addons/build', //adds ability to "build regular expressions using named subpatterns, for readability and pattern reuse" 264 'addons/matchrecursive', //adds ability to "match recursive constructs using XRegExp pattern strings as left and right delimiters" 265 266 /**Unicode support**/ 267 'addons/unicode/unicode-base', //required for all other unicode packages. Adds \p{Letter} category 268 269 //'addons/unicode/unicode-blocks', //adds support for all Unicode blocks (e.g. InArabic, InCyrillic_Extended_A, etc.) 270 'addons/unicode/unicode-categories', //adds support for all Unicode categories (e.g. Punctuation, Lowercase_Letter, etc.) 271 //'addons/unicode/unicode-properties', //adds Level 1 Unicode properties (e.g. Uppercase, White_Space, etc.) 272 //'addons/unicode/unicode-scripts' //adds support for all Unicode scripts (e.g. Gujarati, Cyrillic, etc.) 273 'addons/unicode/unicode-zotero' //adds support for some Unicode categories used in Zotero 274 ]; 275 for (var i=0; i<xregexpFiles.length; i++) { 276 subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/xregexp/" + xregexpFiles[i] + ".js", zContext); 277 } 278 279 // Load remaining xpcomFiles 280 for (var i=1; i<xpcomFilesAll.length; i++) { 281 try { 282 subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[i] + ".js", zContext); 283 } 284 catch (e) { 285 Components.utils.reportError("Error loading " + xpcomFilesAll[i] + ".js", zContext); 286 throw (e); 287 } 288 } 289 290 // Load xpcomFiles for specific mode 291 for (let xpcomFile of (isConnector ? xpcomFilesConnector : xpcomFilesLocal)) { 292 try { 293 subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFile + ".js", zContext, "UTF-8"); 294 } 295 catch (e) { 296 dump("Error loading " + xpcomFile + ".js\n\n"); 297 dump(e + "\n\n"); 298 Components.utils.reportError("Error loading " + xpcomFile + ".js", zContext); 299 throw (e); 300 } 301 } 302 303 // Load RDF files into Zotero.RDF.AJAW namespace (easier than modifying all of the references) 304 const rdfXpcomFiles = [ 305 'rdf/init', 306 'rdf/uri', 307 'rdf/term', 308 'rdf/identity', 309 'rdf/match', 310 'rdf/n3parser', 311 'rdf/rdfparser', 312 'rdf/serialize' 313 ]; 314 zContext.Zotero.RDF = {Zotero:zContext.Zotero}; 315 for (var i=0; i<rdfXpcomFiles.length; i++) { 316 subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/" + rdfXpcomFiles[i] + ".js", zContext.Zotero.RDF); 317 } 318 319 if(isStandalone()) { 320 // If isStandalone, load standalone.js 321 subscriptLoader.loadSubScript("chrome://zotero/content/xpcom/standalone.js", zContext); 322 } 323 324 // add connector-related properties 325 zContext.Zotero.isConnector = isConnector; 326 zContext.Zotero.instanceID = instanceID; 327 zContext.Zotero.__defineGetter__("isFirstLoadThisSession", function() { return isFirstLoadThisSession; }); 328 }; 329 330 /** 331 * The class representing the Zotero service, and affiliated XPCOM goop 332 */ 333 function ZoteroService() { 334 try { 335 var start = Date.now(); 336 337 if(isFirstLoadThisSession) { 338 makeZoteroContext(false); 339 zContext.Zotero.init(zInitOptions) 340 .catch(function (e) { 341 if (e === "ZOTERO_SHOULD_START_AS_CONNECTOR") { 342 // if Zotero should start as a connector, reload it 343 return zContext.Zotero.shutdown() 344 .then(function() { 345 makeZoteroContext(true); 346 return zContext.Zotero.init(zInitOptions); 347 }) 348 } 349 dump(e + "\n\n"); 350 Components.utils.reportError(e); 351 if (!zContext.Zotero.startupError) { 352 zContext.Zotero.startupError = e.stack || e; 353 } 354 if (!isStandalone()) { 355 throw e; 356 } 357 }) 358 .then(function () { 359 if (isStandalone()) { 360 if (zContext.Zotero.startupErrorHandler || zContext.Zotero.startupError) { 361 if (zContext.Zotero.startupErrorHandler) { 362 zContext.Zotero.startupErrorHandler(); 363 } 364 else if (zContext.Zotero.startupError) { 365 try { 366 zContext.Zotero.startupError = 367 zContext.Zotero.Utilities.Internal.filterStack( 368 zContext.Zotero.startupError 369 ); 370 } 371 catch (e) {} 372 373 let ps = Cc["@mozilla.org/embedcomp/prompt-service;1"] 374 .getService(Ci.nsIPromptService); 375 let buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) 376 + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING); 377 // Get the stringbundle manually 378 let errorStr = "Error"; 379 let quitStr = "Quit"; 380 let checkForUpdateStr = "Check for Update"; 381 try { 382 let src = 'chrome://zotero/locale/zotero.properties'; 383 let stringBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"] 384 .getService(Components.interfaces.nsIStringBundleService); 385 let stringBundle = stringBundleService.createBundle(src); 386 errorStr = stringBundle.GetStringFromName('general.error'); 387 checkForUpdateStr = stringBundle.GetStringFromName('general.checkForUpdate'); 388 quitStr = stringBundle.GetStringFromName('general.quit'); 389 } 390 catch (e) {} 391 let index = ps.confirmEx( 392 null, 393 errorStr, 394 zContext.Zotero.startupError, 395 buttonFlags, 396 checkForUpdateStr, 397 quitStr, 398 null, 399 null, 400 {} 401 ); 402 if (index == 0) { 403 Components.classes["@mozilla.org/embedcomp/window-watcher;1"] 404 .getService(Components.interfaces.nsIWindowWatcher) 405 .openWindow(null, 'chrome://mozapps/content/update/updates.xul', 406 'updateChecker', 'chrome,centerscreen,modal', null); 407 } 408 } 409 zContext.Zotero.Utilities.Internal.quitZotero(); 410 } 411 return; 412 } 413 zContext.Zotero.debug("Initialized in "+(Date.now() - start)+" ms"); 414 isFirstLoadThisSession = false; 415 }); 416 417 let cb; 418 while (cb = initCallbacks.shift()) { 419 cb(zContext.Zotero); 420 } 421 } 422 else { 423 zContext.Zotero.debug("Already initialized"); 424 } 425 this.wrappedJSObject = zContext.Zotero; 426 } catch(e) { 427 var msg = e instanceof Error 428 ? e.name + ': ' + e.message + '\n' + e.fileName + ':' + e.lineNumber + '\n' + e.stack 429 : '' + e; 430 dump(msg + '\n'); 431 Components.utils.reportError(e); 432 throw e; 433 } 434 } 435 436 ZoteroService.prototype = { 437 contractID: '@zotero.org/Zotero;1', 438 classDescription: 'Zotero', 439 classID: Components.ID('{e4c61080-ec2d-11da-8ad9-0800200c9a66}'), 440 QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports, 441 Components.interfaces.nsIProtocolHandler]) 442 } 443 444 function addInitCallback(callback) { 445 if (zContext && zContext.Zotero) { 446 callback(zContext.Zotero); 447 } 448 else { 449 initCallbacks.push(callback); 450 } 451 } 452 453 var _isStandalone = null; 454 /** 455 * Determine whether Zotero Standalone is running 456 */ 457 function isStandalone() { 458 if(_isStandalone === null) { 459 var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]. 460 getService(Components.interfaces.nsIXULAppInfo); 461 _isStandalone = appInfo.ID === 'zotero@chnm.gmu.edu'; 462 } 463 return _isStandalone; 464 } 465 466 function getOS() { 467 return Services.appinfo.OS; 468 } 469 470 function isMac() { 471 return getOS() == "Darwin"; 472 } 473 474 function isWin() { 475 return getOS() == "WINNT"; 476 } 477 478 function isLinux() { 479 return getOS() == "Linux"; 480 } 481 482 /** 483 * The class representing the Zotero command line handler 484 */ 485 function ZoteroCommandLineHandler() {} 486 ZoteroCommandLineHandler.prototype = { 487 /* nsICommandLineHandler */ 488 handle : function(cmdLine) { 489 // Force debug output to window 490 if (cmdLine.handleFlag("ZoteroDebug", false)) { 491 zInitOptions.forceDebugLog = 2; 492 } 493 // Force debug output to text console 494 else if (cmdLine.handleFlag("ZoteroDebugText", false)) { 495 zInitOptions.forceDebugLog = 1; 496 } 497 498 zInitOptions.forceDataDir = cmdLine.handleFlagWithParam("datadir", false); 499 500 // handler to open Zotero pane at startup in Zotero for Firefox 501 if (!isStandalone() && cmdLine.handleFlag("ZoteroPaneOpen", false)) { 502 zInitOptions.openPane = true; 503 } 504 505 if (cmdLine.handleFlag("ZoteroTest", false)) { 506 zInitOptions.test = true; 507 } 508 if (cmdLine.handleFlag("ZoteroAutomatedTest", false)) { 509 zInitOptions.automatedTest = true; 510 } 511 if (cmdLine.handleFlag("ZoteroSkipBundledFiles", false)) { 512 zInitOptions.skipBundledFiles = true; 513 } 514 515 // handler for Zotero integration commands 516 // this is typically used on Windows only, via WM_COPYDATA rather than the command line 517 var agent = cmdLine.handleFlagWithParam("ZoteroIntegrationAgent", false); 518 if(agent) { 519 // Don't open a new window 520 cmdLine.preventDefault = true; 521 522 var command = cmdLine.handleFlagWithParam("ZoteroIntegrationCommand", false); 523 var docId = cmdLine.handleFlagWithParam("ZoteroIntegrationDocument", false); 524 525 zContext.Zotero.Integration.execCommand(agent, command, docId); 526 } 527 528 // handler for Windows IPC commands 529 var ipcParam = cmdLine.handleFlagWithParam("ZoteroIPC", false); 530 if(ipcParam) { 531 // Don't open a new window 532 cmdLine.preventDefault = true; 533 if (!zContext) new ZoteroService(); 534 let Zotero = zContext.Zotero; 535 Zotero.setTimeout(() => Zotero.IPC.parsePipeInput(ipcParam), 0); 536 } 537 538 if(isStandalone()) { 539 var fileToOpen; 540 // Special handler for "zotero" URIs at the command line to prevent them from opening a new window 541 var param = cmdLine.handleFlagWithParam("url", false); 542 if (param) { 543 var uri = cmdLine.resolveURI(param); 544 if(uri.schemeIs("zotero")) { 545 addInitCallback(function (Zotero) { 546 Zotero.uiReadyPromise 547 .then(function () { 548 // Check for existing window and focus it 549 var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] 550 .getService(Components.interfaces.nsIWindowMediator); 551 var win = wm.getMostRecentWindow("navigator:browser"); 552 if (win) { 553 win.focus(); 554 win.ZoteroPane.loadURI(uri.spec) 555 } 556 }); 557 }); 558 } 559 // See below 560 else if (uri.schemeIs("file")) { 561 Components.utils.import("resource://gre/modules/osfile.jsm") 562 fileToOpen = OS.Path.fromFileURI(uri.spec) 563 } 564 else { 565 dump(`Not handling URL: ${uri.spec}\n\n`); 566 } 567 } 568 569 param = cmdLine.handleFlag("debugger", false); 570 if (param) { 571 try { 572 let portOrPath = Services.prefs.getBranch('').getIntPref('devtools.debugger.remote-port'); 573 574 const { devtools } = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); 575 const { DebuggerServer } = devtools.require("devtools/server/main"); 576 577 if (!DebuggerServer.initialized) { 578 dump("Initializing devtools server\n"); 579 DebuggerServer.init(); 580 DebuggerServer.allowChromeProcess = true; 581 DebuggerServer.addBrowserActors(); 582 } 583 584 let listener = DebuggerServer.createListener(); 585 listener.portOrPath = portOrPath; 586 listener.open(); 587 588 dump("Debugger server started on " + portOrPath + "\n\n"); 589 } 590 catch (e) { 591 dump(e + "\n\n"); 592 Components.utils.reportError(e); 593 } 594 } 595 596 // In Fx49-based Mac Standalone, if Zotero is closed, an associated file is launched, and 597 // Zotero hasn't been opened before, a -file parameter is passed and two main windows open. 598 // Subsequent file openings when closed result in -url with file:// URLs (converted above) 599 // and don't result in two windows. Here we prevent the double window. 600 param = fileToOpen; 601 if (!param) { 602 param = cmdLine.handleFlagWithParam("file", false); 603 if (param && isMac()) { 604 cmdLine.preventDefault = true; 605 } 606 } 607 if (param) { 608 addInitCallback(function (Zotero) { 609 // Wait to handle things that require the UI until after it's loaded 610 Zotero.uiReadyPromise 611 .then(function () { 612 var file = Components.classes["@mozilla.org/file/local;1"]. 613 createInstance(Components.interfaces.nsILocalFile); 614 file.initWithPath(param); 615 616 if(file.leafName.substr(-4).toLowerCase() === ".csl" 617 || file.leafName.substr(-8).toLowerCase() === ".csl.txt") { 618 // Install CSL file 619 Zotero.Styles.install({ file: file.path }, file.path); 620 } else { 621 // Ask before importing 622 var checkState = { 623 value: Zotero.Prefs.get('import.createNewCollection.fromFileOpenHandler') 624 }; 625 if (Components.classes["@mozilla.org/embedcomp/prompt-service;1"] 626 .getService(Components.interfaces.nsIPromptService) 627 .confirmCheck(null, Zotero.getString('ingester.importFile.title'), 628 Zotero.getString('ingester.importFile.text', [file.leafName]), 629 Zotero.getString('ingester.importFile.intoNewCollection'), 630 checkState)) { 631 Zotero.Prefs.set( 632 'import.createNewCollection.fromFileOpenHandler', checkState.value 633 ); 634 635 // Perform file import in front window 636 var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] 637 .getService(Components.interfaces.nsIWindowMediator); 638 var browserWindow = wm.getMostRecentWindow("navigator:browser"); 639 browserWindow.Zotero_File_Interface.importFile({ 640 file, 641 createNewCollection: checkState.value 642 }); 643 } 644 } 645 }); 646 }); 647 } 648 } 649 }, 650 651 contractID: "@mozilla.org/commandlinehandler/general-startup;1?type=zotero", 652 classDescription: "Zotero Command Line Handler", 653 classID: Components.ID("{531828f8-a16c-46be-b9aa-14845c3b010f}"), 654 service: true, 655 _xpcom_categories: [{category:"command-line-handler", entry:"m-zotero"}], 656 QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsICommandLineHandler, 657 Components.interfaces.nsISupports]) 658 }; 659 660 var NSGetFactory = XPCOMUtils.generateNSGetFactory([ZoteroService, ZoteroCommandLineHandler]);