www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit 753252be77276bf8cba9fe63477ce21c298e3732
parent b2152f811a5f01d8c76f339d75fb31859f93de02
Author: Dan Stillman <dstillman@zotero.org>
Date:   Wed, 28 Sep 2016 11:42:58 -0400

Merge pull request #1090 from adomasven/feature/iframe-translator-rules

Changes for #1021 to support targetAll translator property.
Diffstat:
Mchrome/content/zotero/tools/testTranslators/testTranslators.js | 27++++++++++++++++++---------
Mchrome/content/zotero/xpcom/connector/translator.js | 7+++++--
Mchrome/content/zotero/xpcom/translation/translate.js | 9++++++---
Mchrome/content/zotero/xpcom/translation/translator.js | 9++++++---
Mchrome/content/zotero/xpcom/translation/translators.js | 137+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mtest/content/support.js | 12++++++------
Mtest/tests/translateTest.js | 2+-
Atest/tests/translatorsTest.js | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 209 insertions(+), 83 deletions(-)

diff --git a/chrome/content/zotero/tools/testTranslators/testTranslators.js b/chrome/content/zotero/tools/testTranslators/testTranslators.js @@ -550,18 +550,27 @@ function haveTranslators(translators, type) { return a.label.localeCompare(b.label); }); + var promises = []; for(var i in translators) { - var translatorTestView = new TranslatorTestView(); - translatorTestView.initWithTranslatorAndType(translators[i], type); - if(translatorTestView.canRun) { - translatorTestViewsToRun[type].push(translatorTestView); - } + promises.push(translators[i].getCode()); } - translatorTestStats[type].update(); - var ev = document.createEvent('HTMLEvents'); - ev.initEvent('ZoteroHaveTranslators-'+type, true, true); - document.dispatchEvent(ev); + return Promise.all(promises).then(function(codes) { + for(var i in translators) { + // Make sure translator code is cached on the object + translators[i].code = codes[i]; + var translatorTestView = new TranslatorTestView(); + translatorTestView.initWithTranslatorAndType(translators[i], type); + if(translatorTestView.canRun) { + translatorTestViewsToRun[type].push(translatorTestView); + } + } + + translatorTestStats[type].update(); + var ev = document.createEvent('HTMLEvents'); + ev.initEvent('ZoteroHaveTranslators-'+type, true, true); + document.dispatchEvent(ev); + }); } /** diff --git a/chrome/content/zotero/xpcom/connector/translator.js b/chrome/content/zotero/xpcom/connector/translator.js @@ -362,7 +362,7 @@ Zotero.Translators.CodeGetter.prototype.getAll = function () { var TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target", "priority", "lastUpdated"]; var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES - .concat(["browserSupport", "code", "runMode", "itemType"]); + .concat(["targetAll", "browserSupport", "code", "runMode", "itemType"]); var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browserSupport"]); /** * @class Represents an individual translator @@ -425,7 +425,10 @@ Zotero.Translator.prototype.init = function(info) { if(this.translatorType & TRANSLATOR_TYPES["web"]) { // compile web regexp - this.webRegexp = this.target ? new RegExp(this.target, "i") : null; + this.webRegexp = { + root: this.target ? new RegExp(this.target, "i") : null, + all: this.targetAll ? new RegExp(this.targetAll, "i") : null + }; } else if(this.hasOwnProperty("webRegexp")) { delete this.webRegexp; } diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js @@ -1883,7 +1883,8 @@ Zotero.Translate.Web.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBa */ Zotero.Translate.Web.prototype.setDocument = function(doc) { this.document = doc; - this.setLocation(doc.location.href); + this.rootDocument = doc.defaultView.top.document || doc; + this.setLocation(doc.location.href, this.rootDocument.location.href); } /** @@ -1900,9 +1901,11 @@ Zotero.Translate.Web.prototype.setCookieSandbox = function(cookieSandbox) { * Sets the location to operate upon * * @param {String} location The URL of the page to translate + * @param {String} rootLocation The URL of the root page, within which `location` is embedded */ -Zotero.Translate.Web.prototype.setLocation = function(location) { +Zotero.Translate.Web.prototype.setLocation = function(location, rootLocation) { this.location = location; + this.rootLocation = rootLocation || location; this.path = this.location; } @@ -1910,7 +1913,7 @@ Zotero.Translate.Web.prototype.setLocation = function(location) { * Get potential web translators */ Zotero.Translate.Web.prototype._getTranslatorsGetPotentialTranslators = function() { - return Zotero.Translators.getWebTranslatorsForLocation(this.location); + return Zotero.Translators.getWebTranslatorsForLocation(this.location, this.rootLocation); } /** diff --git a/chrome/content/zotero/xpcom/translation/translator.js b/chrome/content/zotero/xpcom/translation/translator.js @@ -27,12 +27,12 @@ var TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target", "priority", "lastUpdated"]; // Properties that are preserved if present -var TRANSLATOR_OPTIONAL_PROPERTIES = ["browserSupport", "minVersion", "maxVersion", +var TRANSLATOR_OPTIONAL_PROPERTIES = ["targetAll", "browserSupport", "minVersion", "maxVersion", "inRepository", "configOptions", "displayOptions", "hiddenPrefs", "itemType"]; // Properties that are passed from background to inject page in connector var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES. - concat(["browserSupport", "code", "runMode", "itemType"]); + concat(["targetAll", "browserSupport", "code", "runMode", "itemType"]); // Properties that are saved in connector if set but not required var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browserSupport"]); @@ -115,7 +115,10 @@ Zotero.Translator.prototype.init = function(info) { if(this.translatorType & TRANSLATOR_TYPES["web"]) { // compile web regexp this.cacheCode |= !this.target; - this.webRegexp = this.target ? new RegExp(this.target, "i") : null; + this.webRegexp = { + root: this.target ? new RegExp(this.target, "i") : null, + all: this.targetAll ? new RegExp(this.targetAll, "i") : null + }; } else if(this.hasOwnProperty("webRegexp")) { delete this.webRegexp; } diff --git a/chrome/content/zotero/xpcom/translation/translators.js b/chrome/content/zotero/xpcom/translation/translators.js @@ -46,7 +46,7 @@ Zotero.Translators = new function() { Zotero.debug("Initializing translators"); var start = new Date; - _cache = {"import":[], "export":[], "web":[], "search":[]}; + _cache = {"import":[], "export":[], "web":[], "webWithTargetAll":[], "search":[]}; _translators = {}; var sql = "SELECT fileName, metadataJSON, lastModifiedTime FROM translatorCache"; @@ -152,6 +152,9 @@ Zotero.Translators = new function() { for (let type in TRANSLATOR_TYPES) { if (translator.translatorType & TRANSLATOR_TYPES[type]) { _cache[type].push(translator); + if ((translator.translatorType & TRANSLATOR_TYPES.web) && translator.targetAll) { + _cache.webWithTargetAll.push(translator); + } } } @@ -267,76 +270,92 @@ Zotero.Translators = new function() { /** * Gets web translators for a specific location * @param {String} uri The URI for which to look for translators + * @param {String} rootUri The root URI of the page, different from `uri` if running in an iframe */ - this.getWebTranslatorsForLocation = function(uri) { - return this.getAllForType("web").then(function(allTranslators) { + this.getWebTranslatorsForLocation = function(URI, rootURI) { + var isFrame = URI !== rootURI; + var type = isFrame ? "webWithTargetAll" : "web"; + + return this.getAllForType(type).then(function(allTranslators) { var potentialTranslators = []; + var translatorConverterFunctions = []; - var properHosts = []; - var proxyHosts = []; + var rootSearchURIs = this.getSearchURIs(rootURI); + var frameSearchURIs = isFrame ? this.getSearchURIs(URI) : rootSearchURIs; - var properURI = Zotero.Proxies.proxyToProper(uri); - var knownProxy = properURI !== uri; - if(knownProxy) { - // if we know this proxy, just use the proper URI for detection - var searchURIs = [properURI]; - } else { - var searchURIs = [uri]; - - // if there is a subdomain that is also a TLD, also test against URI with the domain - // dropped after the TLD - // (i.e., www.nature.com.mutex.gmu.edu => www.nature.com) - var m = /^(https?:\/\/)([^\/]+)/i.exec(uri); - if(m) { - // First, drop the 0- if it exists (this is an III invention) - var host = m[2]; - if(host.substr(0, 2) === "0-") host = host.substr(2); - var hostnames = host.split("."); - for(var i=1; i<hostnames.length-2; i++) { - if(TLDS[hostnames[i].toLowerCase()]) { - var properHost = hostnames.slice(0, i+1).join("."); - searchURIs.push(m[1]+properHost+uri.substr(m[0].length)); - properHosts.push(properHost); - proxyHosts.push(hostnames.slice(i+1).join(".")); - } - } - } - } + Zotero.debug("Translators: Looking for translators for "+Object.keys(frameSearchURIs).join(', ')); - Zotero.debug("Translators: Looking for translators for "+searchURIs.join(", ")); - - var converterFunctions = []; - for(var i=0; i<allTranslators.length; i++) { - for(var j=0; j<searchURIs.length; j++) { - if((!allTranslators[i].webRegexp - && allTranslators[i].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) - || (uri.length < 8192 && allTranslators[i].webRegexp.test(searchURIs[j]))) { - // add translator to list - potentialTranslators.push(allTranslators[i]); - - if(j === 0) { - if(knownProxy) { - converterFunctions.push(Zotero.Proxies.properToProxy); - } else { - converterFunctions.push(null); + for (let translator of allTranslators) { + translatorLoop: + for (let rootSearchURI in rootSearchURIs) { + let isGeneric = (!translator.webRegexp.root && translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER); + if (!isGeneric && !translator.webRegexp.root) { + continue; + } + let rootURIMatches = isGeneric || rootSearchURI.length < 8192 && translator.webRegexp.root.test(rootSearchURI); + if (translator.webRegexp.all && rootURIMatches) { + for (let frameSearchURI in frameSearchURIs) { + let frameURIMatches = frameSearchURI.length < 8192 && translator.webRegexp.all.test(frameSearchURI); + + if (frameURIMatches) { + potentialTranslators.push(translator); + translatorConverterFunctions.push(frameSearchURIs[frameSearchURI]); + // prevent adding the translator multiple times + break translatorLoop; } - } else { - converterFunctions.push(new function() { - var re = new RegExp('^https?://(?:[^/]+\\.)?'+Zotero.Utilities.quotemeta(properHosts[j-1])+'(?=/)', "gi"); - var proxyHost = proxyHosts[j-1].replace(/\$/g, "$$$$"); - return function(uri) { return uri.replace(re, "$&."+proxyHost) }; - }); } - - // don't add translator more than once + } + else if(!isFrame && (isGeneric || rootURIMatches)) { + potentialTranslators.push(translator); + translatorConverterFunctions.push(rootSearchURIs[rootSearchURI]); break; } } } - return [potentialTranslators, converterFunctions]; - }); - } + return [potentialTranslators, translatorConverterFunctions]; + }.bind(this)); + }, + + /** + * Get the array of searchURIs and related proxy converter functions + * + * @param {String} URI to get searchURIs and converterFunctions for + */ + this.getSearchURIs = function(URI) { + var properURI = Zotero.Proxies.proxyToProper(URI); + if (properURI !== URI) { + // if we know this proxy, just use the proper URI for detection + let obj = {}; + obj[properURI] = Zotero.Proxies.properToProxy; + return obj; + } + + var searchURIs = {}; + searchURIs[URI] = null; + + // if there is a subdomain that is also a TLD, also test against URI with the domain + // dropped after the TLD + // (i.e., www.nature.com.mutex.gmu.edu => www.nature.com) + var m = /^(https?:\/\/)([^\/]+)/i.exec(URI); + if (m) { + // First, drop the 0- if it exists (this is an III invention) + var host = m[2]; + if(host.substr(0, 2) === "0-") host = host.substr(2); + var hostnames = host.split("."); + for (var i=1; i<hostnames.length-2; i++) { + if (TLDS[hostnames[i].toLowerCase()]) { + var properHost = hostnames.slice(0, i+1).join("."); + searchURIs[m[1]+properHost+URI.substr(m[0].length)] = new function() { + var re = new RegExp('^https?://(?:[^/]+\\.)?'+Zotero.Utilities.quotemeta(properHost)+'(?=/)', "gi"); + var proxyHost = hostnames.slice(i+1).join(".").replace(/\$/g, "$$$$"); + return function(uri) { return uri.replace(re, "$&."+proxyHost) }; + }; + } + } + } + return searchURIs; + }, /** * Gets import translators for a specific location diff --git a/test/content/support.js b/test/content/support.js @@ -757,10 +757,11 @@ var generateTranslatorExportData = Zotero.Promise.coroutine(function* generateTr /** * Build a dummy translator that can be passed to Zotero.Translate */ -function buildDummyTranslator(translatorType, code, translatorID="dummy-translator") { - let info = { - "translatorID":translatorID, - "translatorType":translatorType, +function buildDummyTranslator(translatorType, code, info={}) { + const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8}; + info = Object.assign({ + "translatorID":"dummy-translator", + "translatorType":TRANSLATOR_TYPES[translatorType], "label":"Dummy Translator", "creator":"Simon Kornblith", "target":"", @@ -768,10 +769,9 @@ function buildDummyTranslator(translatorType, code, translatorID="dummy-translat "browserSupport":"g", "inRepository":false, "lastUpdated":"0000-00-00 00:00:00", - }; + }, info); let translator = new Zotero.Translator(info); translator.code = code; - translator.getCode = function() {return Promise.resolve(code)}; return translator; } diff --git a/test/tests/translateTest.js b/test/tests/translateTest.js @@ -561,7 +561,7 @@ describe("Zotero.Translate", function() { item.title = "The Definitive Guide of Owls"; item.tags = ['owl', 'tag']; item.complete(); - }`, 'child-dummy-translator' + }`, {translatorID: 'child-dummy-translator'} ); sinon.stub(Zotero.Translators, 'get').withArgs('child-dummy-translator').returns(childTranslator); diff --git a/test/tests/translatorsTest.js b/test/tests/translatorsTest.js @@ -0,0 +1,89 @@ +"use strict"; + +describe("Zotero.Translators", function () { + describe("#getWebTranslatorsForLocation()", function () { + var genericTranslator, topLevelTranslator, frameTranslator; + var noMatchURL = 'http://notowls.com/citation/penguin-migration-patterns'; + var topMatchURL = 'http://www.owl.com/owl_page/snowy_owl'; + var frameMatchURL = 'http://iframe.owl.com/citation/owl-migration-patterns'; + + before(function* (){ + genericTranslator = buildDummyTranslator('web', `function doDetect() {}; function doWeb(); {}`, { + translatorID: 'generic-translator' + }); + topLevelTranslator = buildDummyTranslator('web', `function doDetect() {}; function doWeb(); {}`, { + translatorID: 'top-level-translator', + target: "https?://www\\.owl\\.com/(citation|owl_page)/.+" + }); + frameTranslator = buildDummyTranslator('web', `function doDetect() {}; function doWeb(); {}`, { + translatorID: 'frame-translator', + target: "https?://([^.]+\\.)?owl\\.com/(citation|owl_page)/.+", + targetAll: "https?://iframe.owl\\.com/(citation|owl_page)/.+" + }); + + let getAllForType = sinon.stub(Zotero.Translators, 'getAllForType'); + getAllForType.withArgs('web').resolves([genericTranslator, topLevelTranslator, frameTranslator]); + getAllForType.withArgs('webWithTargetAll').resolves([frameTranslator]); + + let regexp = new RegExp(topLevelTranslator.target, 'i'); + assert.isFalse(regexp.test(noMatchURL)); + assert.isTrue(regexp.test(topMatchURL)); + assert.isFalse(regexp.test(frameMatchURL)); + + regexp = new RegExp(frameTranslator.target, 'i'); + assert.isFalse(regexp.test(noMatchURL)); + assert.isTrue(regexp.test(topMatchURL)); + assert.isTrue(regexp.test(frameMatchURL)); + + regexp = new RegExp(frameTranslator.targetAll, 'i'); + assert.isFalse(regexp.test(noMatchURL)); + assert.isFalse(regexp.test(topMatchURL)); + assert.isTrue(regexp.test(frameMatchURL)); + }); + + after(function* (){ + Zotero.Translators.getAllForType.restore(); + }); + + describe("when called from a root document", function() { + it("should return generic translators when not matching any translator `target`", function* () { + var translators = yield Zotero.Translators.getWebTranslatorsForLocation(noMatchURL, noMatchURL); + assert.equal(translators[0].length, 1); + assert.equal(translators[0][0].translatorID, 'generic-translator'); + }); + + it("should return all matching translators without `targetAll` property", function* () { + var translators = yield Zotero.Translators.getWebTranslatorsForLocation(topMatchURL, topMatchURL); + assert.equal(translators[0].length, 2); + assert.equal(translators[0][0].translatorID, 'generic-translator'); + assert.equal(translators[0][1].translatorID, 'top-level-translator'); + }); + + it("should return translators that match both `target` and `targetAll` when both properties present", function* () { + var translators = yield Zotero.Translators.getWebTranslatorsForLocation(frameMatchURL, frameMatchURL); + assert.equal(translators[0].length, 2); + assert.equal(translators[0][0].translatorID, 'generic-translator'); + assert.equal(translators[0][1].translatorID, 'frame-translator'); + }); + + }); + + describe("when called from an iframe", function() { + it("should not return generic translators or translators without `targetAll` property", function* () { + var translators = yield Zotero.Translators.getWebTranslatorsForLocation(frameMatchURL, noMatchURL); + assert.equal(translators[0].length, 0); + }); + + it("should not return translators that match `target` but not `targetAll", function* () { + var translators = yield Zotero.Translators.getWebTranslatorsForLocation(noMatchURL, topMatchURL); + assert.equal(translators[0].length, 0); + }); + + it("should return translators that match both `target` and `targetAll`", function* () { + var translators = yield Zotero.Translators.getWebTranslatorsForLocation(frameMatchURL, topMatchURL); + assert.equal(translators[0].length, 1); + assert.equal(translators[0][0].translatorID, 'frame-translator'); + }); + }); + }); +});