browser.js (32267B)
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 code from Greasemonkey and PiggyBank 25 26 ***** END LICENSE BLOCK ***** 27 */ 28 29 // 30 // Zotero Ingester Browser Functions 31 // 32 33 ////////////////////////////////////////////////////////////////////////////// 34 // 35 // Zotero_Browser 36 // 37 ////////////////////////////////////////////////////////////////////////////// 38 39 // Class to interface with the browser when ingesting data 40 41 var Zotero_Browser = new function() { 42 this.init = init; 43 this.annotatePage = annotatePage; 44 this.toggleMode = toggleMode; 45 this.toggleCollapsed = toggleCollapsed; 46 this.chromeLoad = chromeLoad; 47 this.itemUpdated = itemUpdated; 48 this.tabClose = tabClose; 49 this.resize = resize; 50 51 this.tabbrowser = null; 52 this.appcontent = null; 53 this.isScraping = false; 54 55 var _browserData = new WeakMap(); 56 var _attachmentsMap = new WeakMap(); 57 var _detectCallbacks = []; 58 59 var _blacklist = [ 60 "googlesyndication.com", 61 "doubleclick.net", 62 "questionmarket.com", 63 "atdmt.com", 64 "aggregateknowledge.com", 65 "ad.yieldmanager.com" 66 ]; 67 68 var _locationBlacklist = [ 69 "zotero://debug/" 70 ]; 71 72 var tools = { 73 'zotero-annotate-tb-add':{ 74 cursor:"pointer", 75 event:"click", 76 callback:function(e) { _add("annotation", e) } 77 }, 78 'zotero-annotate-tb-highlight':{ 79 cursor:"text", 80 event:"mouseup", 81 callback:function(e) { _add("highlight", e) } 82 }, 83 'zotero-annotate-tb-unhighlight':{ 84 cursor:"text", 85 event:"mouseup", 86 callback:function(e) { _add("unhighlight", e) } 87 } 88 }; 89 90 ////////////////////////////////////////////////////////////////////////////// 91 // 92 // Public Zotero_Browser methods 93 // 94 ////////////////////////////////////////////////////////////////////////////// 95 96 97 /** 98 * Initialize some variables and prepare event listeners for when chrome is done loading 99 */ 100 function init() { 101 // No gBrowser - running in standalone 102 if (!window.hasOwnProperty("gBrowser")) { 103 return; 104 } 105 106 var zoteroInitDone; 107 if (!Zotero || Zotero.skipLoading) { 108 // Zotero either failed to load or is reloading in Connector mode 109 // In case of the latter, listen for the 'zotero-loaded' event (once) and retry 110 var zoteroInitDone_deferred = Zotero.Promise.defer(); 111 var obs = Components.classes["@mozilla.org/observer-service;1"] 112 .getService(Components.interfaces.nsIObserverService); 113 var observer = { 114 "observe":function() { 115 obs.removeObserver(observer, 'zotero-loaded') 116 zoteroInitDone_deferred.resolve(); 117 } 118 }; 119 obs.addObserver(observer, 'zotero-loaded', false); 120 121 zoteroInitDone = zoteroInitDone_deferred.promise; 122 } else { 123 zoteroInitDone = Zotero.Promise.resolve(); 124 } 125 126 var chromeLoaded = Zotero.Promise.defer(); 127 window.addEventListener("load", function(e) { chromeLoaded.resolve() }, false); 128 129 // Wait for Zotero to init and chrome to load before proceeding 130 Zotero.Promise.all([ 131 zoteroInitDone.then(function() { 132 ZoteroPane_Local.addReloadListener(reload); 133 reload(); 134 }), 135 chromeLoaded.promise 136 ]) 137 .then(function() { 138 Zotero_Browser.chromeLoad() 139 }); 140 } 141 142 /** 143 * Called when Zotero is reloaded 144 */ 145 function reload() { 146 // Handles the display of a div showing progress in scraping 147 Zotero_Browser.progress = new Zotero.ProgressWindow(); 148 } 149 150 /** 151 * Saves from current page using translator (called when the capture icon is clicked) 152 * 153 * @param {String} [translator] 154 * @param {Event} [event] 155 * @return {Promise} 156 */ 157 this.scrapeThisPage = Zotero.Promise.coroutine(function* (translator, event) { 158 // Perform translation 159 var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser); 160 var page = tab.getPageObject(); 161 if(page.translators && page.translators.length) { 162 page.translate.setTranslator(translator || page.translators[0]); 163 yield Zotero_Browser.performTranslation(page.translate); 164 } 165 else { 166 yield this.saveAsWebPage( 167 (event && event.shiftKey) ? !Zotero.Prefs.get('automaticSnapshots') : null 168 ); 169 } 170 }); 171 172 /** 173 * Keep in sync with cmd_zotero_newItemFromCurrentPage 174 * 175 * @return {Promise} 176 */ 177 this.saveAsWebPage = function (includeSnapshots) { 178 // DEBUG: Possible to just trigger command directly with event? Assigning it to the 179 // command property of the icon doesn't seem to work, and neither does goDoCommand() 180 // from chrome://global/content/globalOverlay.js. Getting the command by id and 181 // running doCommand() works but doesn't pass the event. 182 return ZoteroPane.addItemFromPage('temporaryPDFHack', includeSnapshots); 183 } 184 185 /* 186 * flags a page for annotation 187 */ 188 function annotatePage(id, browser) { 189 if (browser) { 190 var tab = _getTabObject(browser); 191 } 192 else { 193 var tab = _getTabObject(this.tabbrowser.selectedBrowser); 194 } 195 } 196 197 /* 198 * toggles a tool on/off 199 */ 200 function toggleMode(toggleTool, ignoreOtherTools) { 201 // make sure other tools are turned off 202 if(!ignoreOtherTools) { 203 for(var tool in tools) { 204 if(tool != toggleTool && document.getElementById(tool).getAttribute("tool-active")) { 205 toggleMode(tool, true); 206 } 207 } 208 } 209 210 // make sure annotation action is toggled 211 var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser); 212 var page = tab.getPageObject(); 213 if(page && page.annotations && page.annotations.clearAction) page.annotations.clearAction(); 214 215 if(!toggleTool) return; 216 217 var body = Zotero_Browser.tabbrowser.selectedBrowser.contentDocument.getElementsByTagName("body")[0]; 218 var addElement = document.getElementById(toggleTool); 219 220 if(addElement.getAttribute("tool-active")) { 221 // turn off 222 body.style.cursor = "auto"; 223 addElement.removeAttribute("tool-active"); 224 Zotero_Browser.tabbrowser.selectedBrowser.removeEventListener(tools[toggleTool].event, tools[toggleTool].callback, true); 225 } else { 226 body.style.cursor = tools[toggleTool].cursor; 227 addElement.setAttribute("tool-active", "true"); 228 Zotero_Browser.tabbrowser.selectedBrowser.addEventListener(tools[toggleTool].event, tools[toggleTool].callback, true); 229 } 230 } 231 232 /* 233 * expands all annotations 234 */ 235 function toggleCollapsed() { 236 var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser); 237 tab.getPageObject().annotations.toggleCollapsed(); 238 } 239 240 /* 241 * When chrome loads, register our event handlers with the appropriate interfaces 242 */ 243 function chromeLoad() { 244 this.tabbrowser = gBrowser; 245 this.appcontent = document.getElementById("appcontent"); 246 247 // this gives us onLocationChange, for updating when tabs are switched/created 248 gBrowser.tabContainer.addEventListener("TabClose", 249 function(e) { 250 //Zotero.debug("TabClose"); 251 Zotero_Browser.tabClose(e); 252 }, false); 253 gBrowser.tabContainer.addEventListener("TabSelect", 254 function(e) { 255 //Zotero.debug("TabSelect"); 256 // Note: async 257 Zotero_Browser.updateStatus(); 258 }, false); 259 // this is for pageshow, for updating the status of the book icon 260 this.appcontent.addEventListener("pageshow", contentLoad, true); 261 // this is for turning off the book icon when a user navigates away from a page 262 this.appcontent.addEventListener("pagehide", 263 function(e) { 264 //Zotero.debug("pagehide"); 265 Zotero_Browser.contentHide(e); 266 }, true); 267 268 this.tabbrowser.addEventListener("resize", 269 function(e) { Zotero_Browser.resize(e) }, false); 270 // Resize on text zoom changes 271 272 var reduce = document.getElementById('cmd_fullZoomReduce'); 273 var enlarge = document.getElementById('cmd_fullZoomEnlarge'); 274 var reset = document.getElementById('cmd_fullZoomReset'); 275 276 if(reduce) reduce.addEventListener("command", 277 function(e) { Zotero_Browser.resize(e) }, false); 278 if(enlarge) enlarge.addEventListener("command", 279 function(e) { Zotero_Browser.resize(e) }, false); 280 if(reset) reset.addEventListener("command", 281 function(e) { Zotero_Browser.resize(e) }, false); 282 } 283 284 285 /* 286 * An event handler called when a new document is loaded. Creates a new document 287 * object, and updates the status of the capture icon 288 */ 289 var contentLoad = function (event) { 290 var doc = event.originalTarget; 291 var isHTML = doc instanceof HTMLDocument; 292 var rootDoc = (doc instanceof HTMLDocument ? doc.defaultView.top.document : doc); 293 var browser = Zotero_Browser.tabbrowser.getBrowserForDocument(rootDoc); 294 if(!browser) return; 295 296 if(isHTML) { 297 // ignore blacklisted domains 298 try { 299 if(doc.domain) { 300 for (let i = 0; i < _blacklist.length; i++) { 301 let blacklistedURL = _blacklist[i]; 302 if(doc.domain.substr(doc.domain.length-blacklistedURL.length) == blacklistedURL) { 303 Zotero.debug("Ignoring blacklisted URL "+doc.location); 304 return; 305 } 306 } 307 } 308 } 309 catch (e) {} 310 } 311 312 try { 313 if (_locationBlacklist.indexOf(doc.location.href) != -1) { 314 return; 315 } 316 317 // Ignore TinyMCE popups 318 if (!doc.location.host && doc.location.href.indexOf("tinymce/") != -1) { 319 return; 320 } 321 } 322 catch (e) {} 323 324 // get data object 325 var tab = _getTabObject(browser); 326 327 if(isHTML && !Zotero.isConnector) { 328 var annotationID = Zotero.Annotate.getAnnotationIDFromURL(browser.currentURI.spec); 329 if(annotationID) { 330 if(Zotero.Annotate.isAnnotated(annotationID)) { 331 //window.alert(Zotero.getString("annotations.oneWindowWarning")); 332 } else { 333 var page = tab.getPageObject(); 334 if(!page.annotations) { 335 // enable annotation 336 page.annotations = new Zotero.Annotations(Zotero_Browser, browser, annotationID); 337 var saveAnnotations = function() { 338 page.annotations.save(); 339 page.annotations = undefined; 340 }; 341 browser.contentWindow.addEventListener('beforeunload', saveAnnotations, false); 342 browser.contentWindow.addEventListener('close', saveAnnotations, false); 343 page.annotations.load(); 344 } 345 } 346 } 347 } 348 349 // detect translators 350 tab.detectTranslators(rootDoc, doc); 351 352 // register metadata updated event 353 if(isHTML) { 354 var contentWin = doc.defaultView; 355 if(!contentWin.haveZoteroEventListener) { 356 contentWin.addEventListener("ZoteroItemUpdated", function(event) { itemUpdated(event.originalTarget) }, false); 357 contentWin.haveZoteroEventListener = true; 358 } 359 } 360 }; 361 362 /* 363 * called to unregister Zotero icon, etc. 364 */ 365 this.contentHide = function (event) { 366 var doc = event.originalTarget; 367 if(!(doc instanceof HTMLDocument)) return; 368 369 var rootDoc = (doc instanceof HTMLDocument ? doc.defaultView.top.document : doc); 370 var browser = Zotero_Browser.tabbrowser.getBrowserForDocument(rootDoc); 371 if(!browser) return; 372 373 var tab = _getTabObject(browser); 374 if(!tab) return; 375 376 var page = tab.getPageObject(); 377 if(!page) return; 378 379 if(doc == page.document || doc == rootDoc) { 380 // clear translator only if the page on which the pagehide event was called is 381 // either the page to which the translator corresponded, or the root document 382 // (the second check is probably paranoid, but won't hurt) 383 tab.clear(); 384 } 385 386 // update status 387 if(Zotero_Browser.tabbrowser.selectedBrowser == browser) { 388 // Note: async 389 this.updateStatus(); 390 } 391 } 392 393 /** 394 * Called when item should be updated due to a DOM event 395 */ 396 function itemUpdated(doc) { 397 try { 398 var rootDoc = (doc instanceof HTMLDocument ? doc.defaultView.top.document : doc); 399 var browser = Zotero_Browser.tabbrowser.getBrowserForDocument(rootDoc); 400 var tab = _getTabObject(browser); 401 if(doc == tab.getPageObject().document || doc == rootDoc) tab.clear(); 402 tab.detectTranslators(rootDoc, doc); 403 } catch(e) { 404 Zotero.debug(e); 405 } 406 } 407 408 /* 409 * called when a tab is closed 410 */ 411 function tabClose(event) { 412 // Save annotations when closing a tab, since the browser is already 413 // gone from tabbrowser by the time contentHide() gets called 414 var tab = _getTabObject(event.target); 415 var page = tab.getPageObject(); 416 if(page && page.annotations) page.annotations.save(); 417 tab.clear(); 418 419 // To execute if document object does not exist 420 toggleMode(); 421 } 422 423 424 /* 425 * called when the window is resized 426 */ 427 function resize(event) { 428 var tab = _getTabObject(this.tabbrowser.selectedBrowser); 429 var page = tab.getPageObject(); 430 if(!page.annotations) return; 431 432 page.annotations.refresh(); 433 } 434 435 /* 436 * Updates the status of the capture icon to reflect the scrapability or lack 437 * thereof of the current page 438 */ 439 this.updateStatus = Zotero.Promise.coroutine(function* () { 440 // Wait for translator initialization. This allows detection to still run on a page at startup 441 // once translators have finished loading. 442 if (Zotero.Schema && Zotero.Schema.schemaUpdatePromise.isPending()) { 443 yield Zotero.Schema.schemaUpdatePromise; 444 } 445 446 if (!Zotero_Browser.tabbrowser) return; 447 var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser); 448 449 Components.utils.import("resource:///modules/CustomizableUI.jsm"); 450 var buttons = getSaveButtons(); 451 if (buttons.length) { 452 let state = tab.getCaptureState(); 453 let tooltiptext = tab.getCaptureTooltip(); 454 for (let { button, placement } of buttons) { 455 let inToolbar = placement.area == CustomizableUI.AREA_NAVBAR; 456 button.image = tab.getCaptureIcon(Zotero.hiDPI || !inToolbar); 457 button.tooltipText = tooltiptext; 458 if (state == tab.CAPTURE_STATE_TRANSLATABLE) { 459 button.classList.add('translate'); 460 } 461 else { 462 button.classList.remove('translate'); 463 } 464 button.removeAttribute('disabled'); 465 } 466 } 467 468 // set annotation bar status 469 var page = tab.getPageObject(); 470 if(page.annotations && page.annotations.annotations.length) { 471 document.getElementById('zotero-annotate-tb').hidden = false; 472 toggleMode(); 473 } else { 474 document.getElementById('zotero-annotate-tb').hidden = true; 475 } 476 }); 477 478 this.addDetectCallback = function (func) { 479 _detectCallbacks.push(func); 480 }; 481 482 this.resolveDetectCallbacks = Zotero.Promise.coroutine(function* () { 483 while (_detectCallbacks.length) { 484 let cb = _detectCallbacks.shift(); 485 var res = cb(); 486 if (res && res.then) { 487 yield res.then; 488 } 489 } 490 }); 491 492 function getSaveButtons() { 493 Components.utils.import("resource:///modules/CustomizableUI.jsm"); 494 var buttons = []; 495 496 var placement = CustomizableUI.getPlacementOfWidget("zotero-toolbar-buttons"); 497 if (placement) { 498 let button = document.getElementById("zotero-toolbar-save-button"); 499 if (button) { 500 buttons.push({ 501 button: button, 502 placement: placement 503 }); 504 } 505 } 506 507 placement = CustomizableUI.getPlacementOfWidget("zotero-toolbar-save-button-single"); 508 if (placement) { 509 let button = document.getElementById("zotero-toolbar-save-button-single"); 510 if (button) { 511 buttons.push({ 512 button: button, 513 placement: placement 514 }); 515 } 516 } 517 518 return buttons; 519 } 520 521 /** 522 * Called when status bar icon is right-clicked 523 */ 524 this.onStatusPopupShowing = function(e) { 525 var popup = e.target; 526 while(popup.hasChildNodes()) popup.removeChild(popup.lastChild); 527 528 var tab = _getTabObject(this.tabbrowser.selectedBrowser); 529 var captureState = tab.getCaptureState(); 530 if (captureState == tab.CAPTURE_STATE_TRANSLATABLE) { 531 let translators = tab.getPageObject().translators; 532 for (var i=0, n = translators.length; i < n; i++) { 533 let translator = translators[i]; 534 535 let menuitem = document.createElement("menuitem"); 536 menuitem.setAttribute("label", 537 Zotero.getString("ingester.saveToZoteroUsing", translator.label)); 538 menuitem.setAttribute("image", (translator.itemType === "multiple" 539 ? "chrome://zotero/skin/treesource-collection.png" 540 : Zotero.ItemTypes.getImageSrc(translator.itemType))); 541 menuitem.setAttribute("class", "menuitem-iconic"); 542 menuitem.addEventListener("command", function(e) { 543 Zotero_Browser.scrapeThisPage(translator, e); 544 e.stopPropagation(); 545 }, false); 546 popup.appendChild(menuitem); 547 } 548 } 549 550 let webPageIcon = tab.getWebPageCaptureIcon(Zotero.hiDPI); 551 let menuitem = document.createElement("menuitem"); 552 menuitem.setAttribute("label", Zotero.getString('ingester.saveToZoteroAsWebPageWithSnapshot')); 553 menuitem.setAttribute("image", webPageIcon); 554 menuitem.setAttribute("class", "menuitem-iconic"); 555 menuitem.addEventListener("command", function (event) { 556 Zotero_Browser.saveAsWebPage(true); 557 event.stopPropagation(); 558 }); 559 popup.appendChild(menuitem); 560 561 menuitem = document.createElement("menuitem"); 562 menuitem.setAttribute("label", Zotero.getString('ingester.saveToZoteroAsWebPageWithoutSnapshot')); 563 menuitem.setAttribute("image", webPageIcon); 564 menuitem.setAttribute("class", "menuitem-iconic"); 565 menuitem.addEventListener("command", function (event) { 566 Zotero_Browser.saveAsWebPage(false); 567 event.stopPropagation(); 568 }); 569 popup.appendChild(menuitem); 570 571 if (captureState == tab.CAPTURE_STATE_TRANSLATABLE) { 572 popup.appendChild(document.createElement("menuseparator")); 573 574 let menuitem = document.createElement("menuitem"); 575 menuitem.setAttribute("label", Zotero.getString("locate.libraryLookup.label")); 576 menuitem.setAttribute("tooltiptext", Zotero.getString("locate.libraryLookup.tooltip")); 577 menuitem.setAttribute("image", "chrome://zotero/skin/locate-library-lookup.png"); 578 menuitem.setAttribute("class", "menuitem-iconic"); 579 menuitem.addEventListener("command", _constructLookupFunction(tab, function(event, obj) { 580 var urls = []; 581 for (let i = 0; i < obj.newItems.length; i++) { 582 var url = Zotero.OpenURL.resolve(obj.newItems[i]); 583 if(url) urls.push(url); 584 } 585 ZoteroPane.loadURI(urls, event); 586 }), false); 587 popup.appendChild(menuitem); 588 589 var locateEngines = Zotero.LocateManager.getVisibleEngines(); 590 Zotero_LocateMenu.addLocateEngines(popup, locateEngines, 591 _constructLookupFunction(tab, function(e, obj) { 592 Zotero_LocateMenu.locateItem(e, obj.newItems); 593 }), true); 594 } 595 } 596 597 /** 598 * Translates using the specified translation instance. setTranslator() must already 599 * have been called 600 * @param {Zotero.Translate} translate 601 */ 602 this.performTranslation = Zotero.Promise.coroutine(function* (translate, libraryID, collection) { 603 if (Zotero.locked) { 604 Zotero_Browser.progress.Translation.operationInProgress(); 605 return; 606 } 607 608 if (!Zotero.isConnector && Zotero.DB.inTransaction()) { 609 yield Zotero.DB.waitForTransaction(); 610 } 611 612 Zotero_Browser.progress.show(); 613 Zotero_Browser.isScraping = true; 614 615 // Get libraryID and collectionID 616 if(libraryID === undefined && ZoteroPane && !Zotero.isConnector) { 617 // Save to My Library by default if pane hasn't been opened 618 if (!ZoteroPane.collectionsView || !ZoteroPane.collectionsView.selectedTreeRow) { 619 libraryID = Zotero.Libraries.userLibraryID; 620 } 621 else if (!ZoteroPane.collectionsView.editable) { 622 Zotero_Browser.progress.Translation.cannotEditCollection(); 623 return; 624 } 625 else { 626 libraryID = ZoteroPane.getSelectedLibraryID(); 627 } 628 629 collection = ZoteroPane.getSelectedCollection(); 630 } 631 632 if (!Zotero.isConnector) { 633 if (ZoteroPane.collectionsView 634 && ZoteroPane.collectionsView 635 && ZoteroPane.collectionsView.selectedTreeRow 636 && ZoteroPane.collectionsView.selectedTreeRow.isPublications()) { 637 Zotero_Browser.progress.Translation.cannotAddToPublications(); 638 return; 639 } 640 641 if (Zotero.Feeds.get(libraryID)) { 642 Zotero_Browser.progress.Translation.cannotAddToFeed(); 643 return; 644 } 645 } 646 647 Zotero_Browser.progress.Translation.scrapingTo(libraryID, collection); 648 649 translate.clearHandlers("done"); 650 translate.clearHandlers("itemDone"); 651 translate.clearHandlers("attachmentProgress"); 652 653 var deferred = Zotero.Promise.defer(); 654 655 translate.setHandler("done", function() { 656 Zotero_Browser.progress.Translation.doneHandler.apply(Zotero_Browser.progress.Translation, arguments); 657 Zotero_Browser.isScraping = false; 658 deferred.resolve(); 659 }); 660 661 translate.setHandler("itemDone", function() { 662 let handler = Zotero_Browser.progress.Translation.itemDoneHandler(_attachmentsMap); 663 handler.apply(Zotero_Browser.progress.Translation, arguments); 664 }); 665 666 translate.setHandler("attachmentProgress", function() { 667 let handler = Zotero_Browser.progress.Translation.attachmentProgressHandler(_attachmentsMap); 668 handler.apply(Zotero_Browser.progress.Translation, arguments); 669 }); 670 671 translate.translate({ 672 libraryID, 673 collections: collection ? [collection.id] : false 674 }); 675 676 return deferred.promise; 677 }); 678 679 680 ////////////////////////////////////////////////////////////////////////////// 681 // 682 // Private Zotero_Browser methods 683 // 684 ////////////////////////////////////////////////////////////////////////////// 685 686 function _constructLookupFunction(tab, success) { 687 return function(e) { 688 var page = tab.getPageObject(); 689 page.translate.setTranslator(page.translators[0]); 690 page.translate.clearHandlers("done"); 691 page.translate.clearHandlers("itemDone"); 692 page.translate.setHandler("done", function(obj, status) { 693 if(status) { 694 success(e, obj); 695 Zotero_Browser.progress.close(); 696 } else { 697 Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.lookup.error")); 698 Zotero_Browser.progress.startCloseTimer(8000); 699 } 700 }); 701 702 Zotero_Browser.progress.show(); 703 Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.lookup.performing")); 704 page.translate.translate(false); 705 e.stopPropagation(); 706 } 707 } 708 709 /* 710 * Gets a data object given a browser window object 711 */ 712 function _getTabObject(browser) { 713 if(!browser) return false; 714 var obj = _browserData.get(browser); 715 if(!obj) { 716 obj = new Zotero_Browser.Tab(browser); 717 _browserData.set(browser, obj); 718 } 719 return obj; 720 } 721 722 /** 723 * Adds an annotation 724 */ 725 function _add(type, e) { 726 var tab = _getTabObject(Zotero_Browser.tabbrowser.selectedBrowser); 727 728 if(type == "annotation") { 729 // ignore click if it's on an existing annotation 730 if(e.target.getAttribute("zotero-annotation")) return; 731 732 var annotation = tab.getPageObject().annotations.createAnnotation(); 733 annotation.initWithEvent(e); 734 735 // disable add mode, now that we've used it 736 toggleMode(); 737 } else { 738 try { 739 var selection = Zotero_Browser.tabbrowser.selectedBrowser.contentWindow.getSelection(); 740 } catch(err) { 741 return; 742 } 743 if(selection.isCollapsed) return; 744 745 if(type == "highlight") { 746 tab.getPageObject().annotations.highlight(selection.getRangeAt(0)); 747 } else if(type == "unhighlight") { 748 tab.getPageObject().annotations.unhighlight(selection.getRangeAt(0)); 749 } 750 751 selection.removeAllRanges(); 752 } 753 754 // stop propagation 755 e.stopPropagation(); 756 e.preventDefault(); 757 } 758 } 759 760 761 ////////////////////////////////////////////////////////////////////////////// 762 // 763 // Zotero_Browser.Tab 764 // 765 ////////////////////////////////////////////////////////////////////////////// 766 767 Zotero_Browser.Tab = function(browser) { 768 this.browser = browser; 769 this.wm = new WeakMap(); 770 } 771 772 Zotero_Browser.Tab.prototype.CAPTURE_STATE_DISABLED = 0; 773 Zotero_Browser.Tab.prototype.CAPTURE_STATE_GENERIC = 1; 774 Zotero_Browser.Tab.prototype.CAPTURE_STATE_TRANSLATABLE = 2; 775 776 /** 777 * Gets page-specific information (stored in WeakMap to prevent holding 778 * a reference to translate) 779 */ 780 Zotero_Browser.Tab.prototype.getPageObject = function() { 781 var doc = this.browser.contentWindow; 782 if(!doc) return null; 783 var obj = this.wm.get(doc); 784 if(!obj) { 785 obj = {}; 786 this.wm.set(doc, obj); 787 } 788 return obj; 789 } 790 791 /* 792 * Removes page-specific information from WeakMap 793 */ 794 Zotero_Browser.Tab.prototype.clear = function() { 795 this.wm.delete(this.browser.contentWindow); 796 } 797 798 /* 799 * detects translators for this browser object 800 */ 801 Zotero_Browser.Tab.prototype.detectTranslators = Zotero.Promise.coroutine(function* (rootDoc, doc) { 802 if (Zotero.Schema && Zotero.Schema.schemaUpdatePromise.isPending()) { 803 yield Zotero.Schema.schemaUpdatePromise; 804 } 805 806 // If document no longer exists after waiting for schema updates (probably because another page has 807 // been loaded), bail 808 if (Components.utils.isDeadWrapper(doc)) { 809 return; 810 } 811 812 if (doc instanceof HTMLDocument) { 813 if (doc.documentURI.startsWith("about:")) { 814 return; 815 } 816 817 // get translators 818 var me = this; 819 820 var translate = new Zotero.Translate.Web(); 821 translate.setDocument(doc); 822 translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) }); 823 translate.setHandler("pageModified", function(translate, doc) { Zotero_Browser.itemUpdated(doc) }); 824 translate.getTranslators(true); 825 } else if(doc.documentURI.substr(0, 7) == "file://") { 826 this._attemptLocalFileImport(doc); 827 } 828 }); 829 830 831 /* 832 * searches for a document in all of the frames of a given document 833 */ 834 Zotero_Browser.Tab.prototype._searchFrames = function(rootDoc, searchDoc) { 835 if(rootDoc == searchDoc) return true; 836 var frames = rootDoc.getElementsByTagName("frame"); 837 for (let i = 0; i < frames.length; i++) { 838 let frame = frames[i]; 839 if(frame.contentDocument && 840 (frame.contentDocument == searchDoc || 841 this._searchFrames(frame.contentDocument, searchDoc))) { 842 return true; 843 } 844 } 845 846 var frames = rootDoc.getElementsByTagName("iframe"); 847 for (let i = 0; i < frames.length; i++) { 848 let frame = frames[i]; 849 if(frame.contentDocument && 850 (frame.contentDocument == searchDoc || 851 this._searchFrames(frame.contentDocument, searchDoc))) { 852 return true; 853 } 854 } 855 856 return false; 857 } 858 859 /* 860 * Attempts import of a file; to be run on local files only 861 */ 862 Zotero_Browser.Tab.prototype._attemptLocalFileImport = function(doc) { 863 if(doc.documentURI.match(/\.csl(\.xml|\.txt)?$/i)) { 864 // read CSL string 865 var csl = Zotero.File.getContentsFromURL(doc.documentURI); 866 if(csl.indexOf("http://purl.org/net/xbiblio/csl") != -1) { 867 // looks like a CSL; try to import 868 Zotero.Styles.install(csl, doc.documentURI); 869 } 870 } else { 871 // see if we can import this file 872 var file = Components.classes["@mozilla.org/network/protocol;1?name=file"] 873 .getService(Components.interfaces.nsIFileProtocolHandler) 874 .getFileFromURLSpec(doc.documentURI); 875 876 var me = this; 877 var translate = new Zotero.Translate.Import(); 878 translate.setLocation(file); 879 translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) }); 880 translate.getTranslators(); 881 } 882 } 883 884 885 Zotero_Browser.Tab.prototype.getCaptureState = function () { 886 var page = this.getPageObject(); 887 if (!page.saveEnabled) { 888 return this.CAPTURE_STATE_DISABLED; 889 } 890 if (page.translators && page.translators.length) { 891 return this.CAPTURE_STATE_TRANSLATABLE; 892 } 893 return this.CAPTURE_STATE_GENERIC; 894 } 895 896 /* 897 * returns the URL of the image representing the translator to be called on the 898 * current page, or false if the page cannot be scraped 899 */ 900 Zotero_Browser.Tab.prototype.getCaptureIcon = function (hiDPI) { 901 switch (this.getCaptureState()) { 902 case this.CAPTURE_STATE_TRANSLATABLE: 903 var itemType = this.getPageObject().translators[0].itemType; 904 return (itemType === "multiple" 905 ? "chrome://zotero/skin/treesource-collection" + Zotero.hiDPISuffix + ".png" 906 : Zotero.ItemTypes.getImageSrc(itemType)); 907 908 default: 909 return this.getWebPageCaptureIcon(hiDPI); 910 } 911 } 912 913 // TODO: Show icons for images, PDFs, etc.? 914 Zotero_Browser.Tab.prototype.getWebPageCaptureIcon = function (hiDPI) { 915 return "chrome://zotero/skin/treeitem-webpage" + Zotero.hiDPISuffix + ".png"; 916 } 917 918 Zotero_Browser.Tab.prototype.getCaptureTooltip = function() { 919 switch (this.getCaptureState()) { 920 case this.CAPTURE_STATE_DISABLED: 921 var text = Zotero.getString('ingester.saveToZotero'); 922 break; 923 924 case this.CAPTURE_STATE_TRANSLATABLE: 925 var text = Zotero.getString('ingester.saveToZotero'); 926 var translator = this.getPageObject().translators[0]; 927 if (translator.itemType == 'multiple') { 928 text += '…'; 929 } 930 text += ' (' + translator.label + ')'; 931 break; 932 933 // TODO: Different captions for images, PDFs, etc.? 934 default: 935 var text = Zotero.getString('ingester.saveToZotero') 936 + " (" + Zotero.getString('itemTypes.webpage') + ")"; 937 } 938 939 var key = Zotero.Keys.getKeyForCommand('saveToZotero'); 940 if (key) { 941 // Add RLE mark in RTL mode to make shortcut render the right way 942 text += (Zotero.rtl ? ' \u202B' : ' ') + '(' 943 + (Zotero.isMac ? '⇧⌘' : Zotero.getString('general.keys.ctrlShift')) 944 + key 945 + ')'; 946 } 947 948 return text; 949 } 950 951 Zotero_Browser.Tab.prototype.getCaptureCommand = function () { 952 switch (this.getCaptureState()) { 953 case this.CAPTURE_STATE_DISABLED: 954 return ''; 955 case this.CAPTURE_STATE_TRANSLATABLE: 956 return ''; 957 default: 958 return 'cmd_zotero_newItemFromCurrentPage'; 959 } 960 } 961 962 963 /**********CALLBACKS**********/ 964 965 /* 966 * called when a user is supposed to select items 967 */ 968 Zotero_Browser.Tab.prototype._selectItems = function(obj, itemList, callback) { 969 // this is kinda ugly, mozillazine made me do it! honest! 970 var io = { dataIn:itemList, dataOut:null } 971 var newDialog = window.openDialog("chrome://zotero/content/ingester/selectitems.xul", 972 "_blank","chrome,modal,centerscreen,resizable=yes", io); 973 974 if(!io.dataOut) { // user selected no items, so close the progress indicatior 975 Zotero_Browser.progress.close(); 976 } 977 978 callback(io.dataOut); 979 } 980 981 /* 982 * called when translators are available 983 */ 984 Zotero_Browser.Tab.prototype._translatorsAvailable = Zotero.Promise.coroutine(function* (translate, translators) { 985 var page = this.getPageObject(); 986 if (!page) return; 987 page.saveEnabled = true; 988 989 if(translators && translators.length) { 990 //see if we should keep the previous set of translators 991 if(//we already have a translator for part of this page 992 page.translators && page.translators.length && page.document.location 993 //and the page is still there 994 && page.document.defaultView && !page.document.defaultView.closed 995 //this set of translators is not targeting the same URL as a previous set of translators, 996 // because otherwise we want to use the newer set, 997 // but only if it's not in a subframe of the previous set 998 && (page.document.location.href != translate.document.location.href || 999 Zotero.Utilities.Internal.isIframeOf(translate.document.defaultView, page.document.defaultView)) 1000 //the best translator we had was of higher priority than the new set 1001 && (page.translators[0].priority < translators[0].priority 1002 //or the priority was the same, but... 1003 || (page.translators[0].priority == translators[0].priority 1004 //the previous set of translators targets the top frame or the current one does not either 1005 && (page.document.defaultView == page.document.defaultView.top 1006 || translate.document.defaultView !== page.document.defaultView.top) 1007 )) 1008 ) { 1009 Zotero.debug("Translate: a better translator was already found for this page"); 1010 return; //keep what we had 1011 } else { 1012 this.clear(); //clear URL bar icon 1013 page = this.getPageObject(); 1014 page.saveEnabled = true; 1015 } 1016 1017 Zotero.debug("Translate: found translators for page\n" 1018 + "Best translator: " + translators[0].label + " with priority " + translators[0].priority); 1019 page.translate = translate; 1020 page.translators = translators; 1021 page.document = translate.document; 1022 1023 translate.clearHandlers("select"); 1024 translate.setHandler("select", this._selectItems); 1025 } else if(translate.type != "import" && translate.document.documentURI.length > 7 1026 && translate.document.documentURI.substr(0, 7) == "file://") { 1027 this._attemptLocalFileImport(translate.document); 1028 } 1029 1030 if(!translators || !translators.length) Zotero.debug("Translate: No translators found"); 1031 1032 yield Zotero_Browser.updateStatus(); 1033 yield Zotero_Browser.resolveDetectCallbacks(); 1034 }); 1035 1036 Zotero_Browser.init();