From f7854bfcae35f52d14a123c2b3cb7b9c7b46d616 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 24 Feb 2018 04:50:37 -0500 Subject: [PATCH 01/67] Fx60: Remove versioned JavaScript --- chrome/content/zotero/integration/quickFormat.xul | 4 ++-- chrome/content/zotero/itemPane.xul | 2 +- chrome/content/zotero/preferences/preferences.xul | 2 +- chrome/content/zotero/preferences/preferences_advanced.xul | 2 +- chrome/content/zotero/preferences/preferences_cite.xul | 2 +- chrome/content/zotero/preferences/preferences_export.xul | 2 +- chrome/content/zotero/preferences/preferences_firefox.xul | 2 +- chrome/content/zotero/preferences/preferences_general.xul | 2 +- chrome/content/zotero/preferences/preferences_search.xul | 2 +- chrome/content/zotero/preferences/preferences_sync.xul | 2 +- chrome/content/zotero/test/test.xul | 2 +- chrome/content/zotero/zoteroPane.xul | 5 +++-- test/content/runtests.html | 4 ++-- test/content/runtests.js | 2 +- 14 files changed, 18 insertions(+), 17 deletions(-) diff --git a/chrome/content/zotero/integration/quickFormat.xul b/chrome/content/zotero/integration/quickFormat.xul index 3419f7bb8f..d05cbd2f1f 100644 --- a/chrome/content/zotero/integration/quickFormat.xul +++ b/chrome/content/zotero/integration/quickFormat.xul @@ -41,8 +41,8 @@ onunload="Zotero_QuickFormat.onUnload()"> - - - + + \ No newline at end of file diff --git a/test/content/runtests.js b/test/content/runtests.js index f41a911273..1ab3e5738a 100644 --- a/test/content/runtests.js +++ b/test/content/runtests.js @@ -267,7 +267,7 @@ if(run && ZoteroUnit.tests) { for(var fname of testFiles) { var el = document.createElement("script"); - el.type = "application/javascript;version=1.8"; + el.type = "application/javascript"; el.src = "resource://zotero-unit-tests/"+fname; el.async = false; document.body.appendChild(el); From 78e9db9523ce3843a3c0f451c102315a8b9ad1bf Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 24 Feb 2018 04:54:53 -0500 Subject: [PATCH 02/67] Fx60: Fix issues with JS code modules - The Mozilla CommonJS loader is no longer available, so bundle the Fx52 version of it - Strict mode is enforced - `this` is only defined as a global object in .jsm files, not .js files - `this` can't be converted to a string for BackstagePass test, so check for presence of Components.utils.import instead - The return value from import() is no longer available --- chrome/content/zotero/xpcom/router.js | 2 +- components/zotero-service.js | 2 +- resource/concurrentCaller.js | 4 +- resource/config.js | 4 +- resource/loader.jsm | 1148 +++++++++++++++++++ resource/{pathparser.js => pathparser.jsm} | 14 +- resource/require.js | 2 +- resource/word-processor-plugin-installer.js | 2 +- 8 files changed, 1164 insertions(+), 14 deletions(-) create mode 100644 resource/loader.jsm rename resource/{pathparser.js => pathparser.jsm} (90%) diff --git a/chrome/content/zotero/xpcom/router.js b/chrome/content/zotero/xpcom/router.js index d24c2b84c2..da07026b81 100644 --- a/chrome/content/zotero/xpcom/router.js +++ b/chrome/content/zotero/xpcom/router.js @@ -1,4 +1,4 @@ -Components.utils.import("resource://zotero/pathparser.js", Zotero); +Components.utils.import("resource://zotero/pathparser.jsm", Zotero); Zotero.Router = Zotero.PathParser; delete Zotero.PathParser; diff --git a/components/zotero-service.js b/components/zotero-service.js index 86512f9992..5ecc50ba89 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -161,7 +161,7 @@ Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader) .loadSubScript('resource://zotero/require.js'); -ZoteroContext = function() {} +var ZoteroContext = function() {} ZoteroContext.prototype = { require, diff --git a/resource/concurrentCaller.js b/resource/concurrentCaller.js index 20e4e43ad3..864c1c8276 100644 --- a/resource/concurrentCaller.js +++ b/resource/concurrentCaller.js @@ -23,7 +23,7 @@ ***** END LICENSE BLOCK ***** */ -EXPORTED_SYMBOLS = ["ConcurrentCaller"]; +var EXPORTED_SYMBOLS = ["ConcurrentCaller"]; if (!(typeof process === 'object' && process + '' === '[object process]')) { // Components.utils.import('resource://zotero/require.js'); @@ -69,7 +69,7 @@ if (!(typeof process === 'object' && process + '' === '[object process]')) { * @param {Object} [options.Promise] The Zotero instance of Promise to allow * stubbing/spying in tests */ -ConcurrentCaller = function (options = {}) { +var ConcurrentCaller = function (options = {}) { if (typeof options == 'number') { this._log("ConcurrentCaller now takes an object rather than a number"); options = { diff --git a/resource/config.js b/resource/config.js index 37b40c394d..89293e21a6 100644 --- a/resource/config.js +++ b/resource/config.js @@ -29,5 +29,5 @@ var ZOTERO_CONFIG = { if (typeof process === 'object' && process + '' === '[object process]'){ module.exports = ZOTERO_CONFIG; } else { - EXPORTED_SYMBOLS = ["ZOTERO_CONFIG"]; -} + var EXPORTED_SYMBOLS = ["ZOTERO_CONFIG"]; +} \ No newline at end of file diff --git a/resource/loader.jsm b/resource/loader.jsm new file mode 100644 index 0000000000..e7cc887f1b --- /dev/null +++ b/resource/loader.jsm @@ -0,0 +1,1148 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +;((factory) => { // Module boilerplate :( + if (typeof(require) === 'function') { // CommonJS + require("chrome").Cu.import(module.uri, exports); + } + else if (typeof Components != 'undefined') { // JSM + let module = { uri: __URI__, id: "toolkit/loader", exports: Object.create(null) } + factory(module); + Object.assign(this, module.exports); + this.EXPORTED_SYMBOLS = Object.getOwnPropertyNames(module.exports); + } + else { + throw Error("Loading environment is not supported"); + } +})(module => { + +'use strict'; + +module.metadata = { + "stability": "unstable" +}; + +const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu, + results: Cr, manager: Cm } = Components; +const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); +const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1']. + getService(Ci.mozIJSSubScriptLoader); +const { addObserver, notifyObservers } = Cc['@mozilla.org/observer-service;1']. + getService(Ci.nsIObserverService); +const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); +const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); +const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "resProto", + "@mozilla.org/network/protocol;1?name=resource", + "nsIResProtocolHandler"); +XPCOMUtils.defineLazyServiceGetter(this, "zipCache", + "@mozilla.org/libjar/zip-reader-cache;1", + "nsIZipReaderCache"); + +XPCOMUtils.defineLazyGetter(this, "XulApp", () => { + let xulappURI = module.uri.replace("toolkit/loader.js", + "sdk/system/xul-app.jsm"); + return Cu.import(xulappURI, {}); +}); + +// Define some shortcuts. +const bind = Function.call.bind(Function.bind); +const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const prototypeOf = Object.getPrototypeOf; +const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x), + ...Object.getOwnPropertySymbols(x)]; + +const NODE_MODULES = new Set([ + "assert", + "buffer_ieee754", + "buffer", + "child_process", + "cluster", + "console", + "constants", + "crypto", + "_debugger", + "dgram", + "dns", + "domain", + "events", + "freelist", + "fs", + "http", + "https", + "_linklist", + "module", + "net", + "os", + "path", + "punycode", + "querystring", + "readline", + "repl", + "stream", + "string_decoder", + "sys", + "timers", + "tls", + "tty", + "url", + "util", + "vm", + "zlib", +]); + +const COMPONENT_ERROR = '`Components` is not available in this context.\n' + + 'Functionality provided by Components may be available in an SDK\n' + + 'module: https://developer.mozilla.org/en-US/Add-ons/SDK \n\n' + + 'However, if you still need to import Components, you may use the\n' + + '`chrome` module\'s properties for shortcuts to Component properties:\n\n' + + 'Shortcuts: \n' + + ' Cc = Components' + '.classes \n' + + ' Ci = Components' + '.interfaces \n' + + ' Cu = Components' + '.utils \n' + + ' CC = Components' + '.Constructor \n' + + 'Example: \n' + + ' let { Cc, Ci } = require(\'chrome\');\n'; + +// Workaround for bug 674195. Freezing objects from other compartments fail, +// so we use `Object.freeze` from the same component instead. +function freeze(object) { + if (prototypeOf(object) === null) { + Object.freeze(object); + } + else { + prototypeOf(prototypeOf(object.isPrototypeOf)). + constructor. // `Object` from the owner compartment. + freeze(object); + } + return object; +} + +// Returns map of given `object`-s own property descriptors. +const descriptor = iced(function descriptor(object) { + let value = {}; + getOwnIdentifiers(object).forEach(function(name) { + value[name] = getOwnPropertyDescriptor(object, name) + }); + return value; +}); +Loader.descriptor = descriptor; + +// Freeze important built-ins so they can't be used by untrusted code as a +// message passing channel. +freeze(Object); +freeze(Object.prototype); +freeze(Function); +freeze(Function.prototype); +freeze(Array); +freeze(Array.prototype); +freeze(String); +freeze(String.prototype); + +// This function takes `f` function sets it's `prototype` to undefined and +// freezes it. We need to do this kind of deep freeze with all the exposed +// functions so that untrusted code won't be able to use them a message +// passing channel. +function iced(f) { + if (!Object.isFrozen(f)) { + f.prototype = undefined; + } + return freeze(f); +} + +// Defines own properties of given `properties` object on the given +// target object overriding any existing property with a conflicting name. +// Returns `target` object. Note we only export this function because it's +// useful during loader bootstrap when other util modules can't be used & +// thats only case where this export should be used. +const override = iced(function override(target, source) { + let properties = descriptor(target) + let extension = descriptor(source || {}) + getOwnIdentifiers(extension).forEach(function(name) { + properties[name] = extension[name]; + }); + return Object.defineProperties({}, properties); +}); +Loader.override = override; + +function sourceURI(uri) { return String(uri).split(" -> ").pop(); } +Loader.sourceURI = iced(sourceURI); + +function isntLoaderFrame(frame) { return frame.fileName !== module.uri } + +function parseURI(uri) { return String(uri).split(" -> ").pop(); } +Loader.parseURI = parseURI; + +function parseStack(stack) { + let lines = String(stack).split("\n"); + return lines.reduce(function(frames, line) { + if (line) { + let atIndex = line.indexOf("@"); + let columnIndex = line.lastIndexOf(":"); + let lineIndex = line.lastIndexOf(":", columnIndex - 1); + let fileName = parseURI(line.slice(atIndex + 1, lineIndex)); + let lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex)); + let columnNumber = parseInt(line.slice(columnIndex + 1)); + let name = line.slice(0, atIndex).split("(").shift(); + frames.unshift({ + fileName: fileName, + name: name, + lineNumber: lineNumber, + columnNumber: columnNumber + }); + } + return frames; + }, []); +} +Loader.parseStack = parseStack; + +function serializeStack(frames) { + return frames.reduce(function(stack, frame) { + return frame.name + "@" + + frame.fileName + ":" + + frame.lineNumber + ":" + + frame.columnNumber + "\n" + + stack; + }, ""); +} +Loader.serializeStack = serializeStack; + +class DefaultMap extends Map { + constructor(createItem, items = undefined) { + super(items); + + this.createItem = createItem; + } + + get(key) { + if (!this.has(key)) { + this.set(key, this.createItem(key)); + } + + return super.get(key); + } +} + +const urlCache = { + /** + * Returns a list of fully-qualified URLs for entries within the zip + * file at the given URI which are either directories or files with a + * .js or .json extension. + * + * @param {nsIJARURI} uri + * @param {string} baseURL + * The original base URL, prior to resolution. + * + * @returns {Set} + */ + getZipFileContents(uri, baseURL) { + // Make sure the path has a trailing slash, and strip off the leading + // slash, so that we can easily check whether it is a path prefix. + let basePath = addTrailingSlash(uri.JAREntry).slice(1); + let file = uri.JARFile.QueryInterface(Ci.nsIFileURL).file; + + let enumerator = zipCache.getZip(file).findEntries("(*.js|*.json|*/)"); + + let results = new Set(); + for (let entry of XPCOMUtils.IterStringEnumerator(enumerator)) { + if (entry.startsWith(basePath)) { + let path = entry.slice(basePath.length); + + results.add(baseURL + path); + } + } + + return results; + }, + + zipContentsCache: new DefaultMap(baseURL => { + let uri = NetUtil.newURI(baseURL); + + if (baseURL.startsWith("resource:")) { + uri = NetUtil.newURI(resProto.resolveURI(uri)); + } + + if (uri instanceof Ci.nsIJARURI) { + return urlCache.getZipFileContents(uri, baseURL); + } + + return null; + }), + + filesCache: new DefaultMap(url => { + try { + let uri = NetUtil.newURI(url).QueryInterface(Ci.nsIFileURL); + + return uri.file.exists(); + } catch (e) { + return false; + } + }), + + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]), + + observe() { + // Clear any module resolution caches when the startup cache is flushed, + // since it probably means we're loading new copies of extensions. + this.zipContentsCache.clear(); + this.filesCache.clear(); + }, + + /** + * Returns the base URL for the given URL, if one can be determined. For + * a resource: URL, this is the root of the resource package. For a jar: + * URL, it is the root of the JAR file. Otherwise, null is returned. + * + * @param {string} url + * @returns {string?} + */ + getBaseURL(url) { + // By using simple string matching for the common case of resource: URLs + // backed by jar: URLs, we can avoid creating any nsIURI objects for the + // common case where the JAR contents are already cached. + if (url.startsWith("resource://")) { + return /^resource:\/\/[^\/]+\//.exec(url)[0]; + } + + let uri = NetUtil.newURI(url); + if (uri instanceof Ci.nsIJARURI) { + return `jar:${uri.JARFile.spec}!/`; + } + + return null; + }, + + /** + * Returns true if the target of the given URL exists as a local file, + * or as an entry in a local zip file. + * + * @param {string} url + * @returns {boolean} + */ + exists(url) { + if (!/\.(?:js|json)$/.test(url)) { + url = addTrailingSlash(url); + } + + let baseURL = this.getBaseURL(url); + let scripts = baseURL && this.zipContentsCache.get(baseURL); + if (scripts) { + return scripts.has(url); + } + + return this.filesCache.get(url); + }, +} +addObserver(urlCache, "startupcache-invalidate", true); + +function readURI(uri) { + let nsURI = NetUtil.newURI(uri); + if (nsURI.scheme == "resource") { + // Resolve to a real URI, this will catch any obvious bad paths without + // logging assertions in debug builds, see bug 1135219 + uri = resProto.resolveURI(nsURI); + } + + let stream = NetUtil.newChannel({ + uri: NetUtil.newURI(uri, 'UTF-8'), + loadUsingSystemPrincipal: true} + ).open2(); + let count = stream.available(); + let data = NetUtil.readInputStreamToString(stream, count, { + charset: 'UTF-8' + }); + + stream.close(); + + return data; +} + +// Combines all arguments into a resolved, normalized path +function join(base, ...paths) { + // If this is an absolute URL, we need to normalize only the path portion, + // or we wind up stripping too many slashes and producing invalid URLs. + let match = /^((?:resource|file|chrome)\:\/\/[^\/]*|jar:[^!]+!)(.*)/.exec(base); + if (match) { + return match[1] + normalize(pathJoin(match[2], ...paths)); + } + + return normalize(pathJoin(base, ...paths)); +} +Loader.join = join; + +// Function takes set of options and returns a JS sandbox. Function may be +// passed set of options: +// - `name`: A string value which identifies the sandbox in about:memory. Will +// throw exception if omitted. +// - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to +// system principal. +// - `prototype`: Ancestor for the sandbox that will be created. Defaults to +// `{}`. +// - `wantXrays`: A Boolean value indicating whether code outside the sandbox +// wants X-ray vision with respect to objects inside the sandbox. Defaults +// to `true`. +// - `sandbox`: A sandbox to share JS compartment with. If omitted new +// compartment will be created. +// - `metadata`: A metadata object associated with the sandbox. It should +// be JSON-serializable. +// For more details see: +// https://developer.mozilla.org/en/Components.utils.Sandbox +const Sandbox = iced(function Sandbox(options) { + // Normalize options and rename to match `Cu.Sandbox` expectations. + options = { + // Do not expose `Components` if you really need them (bad idea!) you + // still can expose via prototype. + wantComponents: false, + sandboxName: options.name, + principal: 'principal' in options ? options.principal : systemPrincipal, + wantXrays: 'wantXrays' in options ? options.wantXrays : true, + wantGlobalProperties: 'wantGlobalProperties' in options ? + options.wantGlobalProperties : [], + sandboxPrototype: 'prototype' in options ? options.prototype : {}, + invisibleToDebugger: 'invisibleToDebugger' in options ? + options.invisibleToDebugger : false, + metadata: 'metadata' in options ? options.metadata : {}, + waiveIntereposition: !!options.waiveIntereposition + }; + + if (options.metadata && options.metadata.addonID) { + options.addonId = options.metadata.addonID; + } + + let sandbox = Cu.Sandbox(options.principal, options); + + // Each sandbox at creation gets set of own properties that will be shadowing + // ones from it's prototype. We override delete such `sandbox` properties + // to avoid shadowing. + delete sandbox.Iterator; + delete sandbox.Components; + delete sandbox.importFunction; + delete sandbox.debug; + + return sandbox; +}); +Loader.Sandbox = Sandbox; + +// Evaluates code from the given `uri` into given `sandbox`. If +// `options.source` is passed, then that code is evaluated instead. +// Optionally following options may be given: +// - `options.encoding`: Source encoding, defaults to 'UTF-8'. +// - `options.line`: Line number to start count from for stack traces. +// Defaults to 1. +// - `options.version`: Version of JS used, defaults to '1.8'. +const evaluate = iced(function evaluate(sandbox, uri, options) { + let { source, line, version, encoding } = override({ + encoding: 'UTF-8', + line: 1, + version: '1.8', + source: null + }, options); + + return source ? Cu.evalInSandbox(source, sandbox, version, uri, line) + : loadSubScript(uri, sandbox, encoding); +}); +Loader.evaluate = evaluate; + +// Populates `exports` of the given CommonJS `module` object, in the context +// of the given `loader` by evaluating code associated with it. +const load = iced(function load(loader, module) { + let { sandboxes, globals, loadModuleHook } = loader; + let require = Require(loader, module); + + // We expose set of properties defined by `CommonJS` specification via + // prototype of the sandbox. Also globals are deeper in the prototype + // chain so that each module has access to them as well. + let descriptors = descriptor({ + require: require, + module: module, + exports: module.exports, + get Components() { + // Expose `Components` property to throw error on usage with + // additional information + throw new ReferenceError(COMPONENT_ERROR); + } + }); + + let sandbox; + if ((loader.useSharedGlobalSandbox || isSystemURI(module.uri)) && + loader.sharedGlobalBlocklist.indexOf(module.id) == -1) { + // Create a new object in this sandbox, that will be used as + // the scope object for this particular module + sandbox = new loader.sharedGlobalSandbox.Object(); + // Inject all expected globals in the scope object + getOwnIdentifiers(globals).forEach(function(name) { + descriptors[name] = getOwnPropertyDescriptor(globals, name) + descriptors[name].configurable = true; + }); + Object.defineProperties(sandbox, descriptors); + } + else { + sandbox = Sandbox({ + name: module.uri, + prototype: Object.create(globals, descriptors), + wantXrays: false, + wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [], + invisibleToDebugger: loader.invisibleToDebugger, + metadata: { + addonID: loader.id, + URI: module.uri + } + }); + } + sandboxes[module.uri] = sandbox; + + try { + evaluate(sandbox, module.uri); + } + catch (error) { + let { message, fileName, lineNumber } = error; + let stack = error.stack || Error().stack; + let frames = parseStack(stack).filter(isntLoaderFrame); + let toString = String(error); + let file = sourceURI(fileName); + + // Note that `String(error)` where error is from subscript loader does + // not puts `:` after `"Error"` unlike regular errors thrown by JS code. + // If there is a JS stack then this error has already been handled by an + // inner module load. + if (/^Error opening input stream/.test(String(error))) { + let caller = frames.slice(0).pop(); + fileName = caller.fileName; + lineNumber = caller.lineNumber; + message = "Module `" + module.id + "` is not found at " + module.uri; + toString = message; + } + // Workaround for a Bug 910653. Errors thrown by subscript loader + // do not include `stack` field and above created error won't have + // fileName or lineNumber of the module being loaded, so we ensure + // it does. + else if (frames[frames.length - 1].fileName !== file) { + frames.push({ fileName: file, lineNumber: lineNumber, name: "" }); + } + + let prototype = typeof(error) === "object" ? error.constructor.prototype : + Error.prototype; + + throw Object.create(prototype, { + message: { value: message, writable: true, configurable: true }, + fileName: { value: fileName, writable: true, configurable: true }, + lineNumber: { value: lineNumber, writable: true, configurable: true }, + stack: { value: serializeStack(frames), writable: true, configurable: true }, + toString: { value: () => toString, writable: true, configurable: true }, + }); + } + + if (loadModuleHook) { + module = loadModuleHook(module, require); + } + + if (loader.checkCompatibility) { + let err = XulApp.incompatibility(module); + if (err) { + throw err; + } + } + + if (module.exports && typeof(module.exports) === 'object') + freeze(module.exports); + + return module; +}); +Loader.load = load; + +// Utility function to normalize module `uri`s so they have `.js` extension. +function normalizeExt(uri) { + return isJSURI(uri) ? uri : + isJSONURI(uri) ? uri : + isJSMURI(uri) ? uri : + uri + '.js'; +} + +// Strips `rootURI` from `string` -- used to remove absolute resourceURI +// from a relative path +function stripBase(rootURI, string) { + return string.replace(rootURI, './'); +} + +// Utility function to join paths. In common case `base` is a +// `requirer.uri` but in some cases it may be `baseURI`. In order to +// avoid complexity we require `baseURI` with a trailing `/`. +const resolve = iced(function resolve(id, base) { + if (!isRelative(id)) + return id; + + let baseDir = dirname(base); + if (!baseDir) + return normalize(id); + + let resolved = join(baseDir, id); + + // Joining and normalizing removes the './' from relative files. + // We need to ensure the resolution still has the root + if (isRelative(base)) + resolved = './' + resolved; + + return resolved; +}); +Loader.resolve = resolve; + +// Attempts to load `path` and then `path.js` +// Returns `path` with valid file, or `undefined` otherwise +function resolveAsFile(path) { + // Append '.js' to path name unless it's another support filetype + path = normalizeExt(path); + if (urlCache.exists(path)) { + return path; + } + + return null; +} + +// Attempts to load `path/package.json`'s `main` entry, +// followed by `path/index.js`, or `undefined` otherwise +function resolveAsDirectory(path) { + try { + // If `path/package.json` exists, parse the `main` entry + // and attempt to load that + let manifestPath = addTrailingSlash(path) + 'package.json'; + + let main = (urlCache.exists(manifestPath) && + getManifestMain(JSON.parse(readURI(manifestPath)))); + if (main) { + let found = resolveAsFile(join(path, main)); + if (found) { + return found + } + } + } catch (e) {} + + return resolveAsFile(addTrailingSlash(path) + 'index.js'); +} + +function resolveRelative(rootURI, modulesDir, id) { + let fullId = join(rootURI, modulesDir, id); + + let resolvedPath = (resolveAsFile(fullId) || + resolveAsDirectory(fullId)); + if (resolvedPath) { + return stripBase(rootURI, resolvedPath); + } + + return null; +} + +// From `resolve` module +// https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js +function* getNodeModulePaths(rootURI, start) { + let moduleDir = 'node_modules'; + + let parts = start.split('/'); + while (parts.length) { + let leaf = parts.pop(); + let path = join(...parts, leaf, moduleDir); + if (leaf !== moduleDir && urlCache.exists(join(rootURI, path))) { + yield path; + } + } + + if (urlCache.exists(join(rootURI, moduleDir))) { + yield moduleDir; + } +} + +// Node-style module lookup +// Takes an id and path and attempts to load a file using node's resolving +// algorithm. +// `id` should already be resolved relatively at this point. +// http://nodejs.org/api/modules.html#modules_all_together +const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) { + // Resolve again + id = Loader.resolve(id, requirer); + + // If this is already an absolute URI then there is no resolution to do + if (isAbsoluteURI(id)) { + return null; + } + + // we assume that extensions are correct, i.e., a directory doesnt't have '.js' + // and a js file isn't named 'file.json.js' + let resolvedPath; + + if ((resolvedPath = resolveRelative(rootURI, "", id))) { + return resolvedPath; + } + + // If the requirer is an absolute URI then the node module resolution below + // won't work correctly as we prefix everything with rootURI + if (isAbsoluteURI(requirer)) { + return null; + } + + // If manifest has dependencies, attempt to look up node modules + // in the `dependencies` list + for (let modulesDir of getNodeModulePaths(rootURI, dirname(requirer))) { + if ((resolvedPath = resolveRelative(rootURI, modulesDir, id))) { + return resolvedPath; + } + } + + // We would not find lookup for things like `sdk/tabs`, as that's part of + // the alias mapping. If during `generateMap`, the runtime lookup resolves + // with `resolveURI` -- if during runtime, then `resolve` will throw. + return null; +}); + +Loader.nodeResolve = nodeResolve; + +function addTrailingSlash(path) { + return path.replace(/\/*$/, "/"); +} + +const resolveURI = iced(function resolveURI(id, mapping) { + // Do not resolve if already a resource URI + if (isAbsoluteURI(id)) + return normalizeExt(id); + + for (let [path, uri] of mapping) { + // Strip off any trailing slashes to make comparisons simpler + let stripped = path.replace(/\/+$/, ""); + + // We only want to match path segments explicitly. Examples: + // * "foo/bar" matches for "foo/bar" + // * "foo/bar" matches for "foo/bar/baz" + // * "foo/bar" does not match for "foo/bar-1" + // * "foo/bar/" does not match for "foo/bar" + // * "foo/bar/" matches for "foo/bar/baz" + // + // Check for an empty path, an exact match, or a substring match + // with the next character being a forward slash. + if(stripped === "" || id === stripped || id.startsWith(stripped + "/")) { + return normalizeExt(id.replace(path, uri)); + } + } + return null; +}); +Loader.resolveURI = resolveURI; + +// Creates version of `require` that will be exposed to the given `module` +// in the context of the given `loader`. Each module gets own limited copy +// of `require` that is allowed to load only a modules that are associated +// with it during link time. +const Require = iced(function Require(loader, requirer) { + let { + modules, mapping, resolve: loaderResolve, load, + manifest, rootURI, isNative, requireMap, + requireHook + } = loader; + + if (isSystemURI(requirer.uri)) { + // Built-in modules don't require the expensive module resolution + // algorithm used by SDK add-ons, so give them the more efficient standard + // resolve instead. + isNative = false; + loaderResolve = Loader.resolve; + } + + function require(id) { + if (!id) // Throw if `id` is not passed. + throw Error('You must provide a module name when calling require() from ' + + requirer.id, requirer.uri); + + if (requireHook) { + return requireHook(id, _require); + } + + return _require(id); + } + + function _require(id) { + let { uri, requirement } = getRequirements(id); + let module = null; + // If module is already cached by loader then just use it. + if (uri in modules) { + module = modules[uri]; + } + else if (isJSMURI(uri)) { + module = modules[uri] = Module(requirement, uri); + module.exports = Cu.import(uri, {}); + freeze(module); + } + else if (isJSONURI(uri)) { + let data; + + // First attempt to load and parse json uri + // ex: `test.json` + // If that doesn't exist, check for `test.json.js` + // for node parity + try { + data = JSON.parse(readURI(uri)); + module = modules[uri] = Module(requirement, uri); + module.exports = data; + freeze(module); + } + catch (err) { + // If error thrown from JSON parsing, throw that, do not + // attempt to find .json.js file + if (err && /JSON\.parse/.test(err.message)) + throw err; + uri = uri + '.js'; + } + } + + // If not yet cached, load and cache it. + // We also freeze module to prevent it from further changes + // at runtime. + if (!(uri in modules)) { + // Many of the loader's functionalities are dependent + // on modules[uri] being set before loading, so we set it and + // remove it if we have any errors. + module = modules[uri] = Module(requirement, uri); + try { + freeze(load(loader, module)); + } + catch (e) { + // Clear out modules cache so we can throw on a second invalid require + delete modules[uri]; + // Also clear out the Sandbox that was created + delete loader.sandboxes[uri]; + throw e; + } + } + + return module.exports; + } + + // Resolution function taking a module name/path and + // returning a resourceURI and a `requirement` used by the loader. + // Used by both `require` and `require.resolve`. + function getRequirements(id) { + if (!id) // Throw if `id` is not passed. + throw Error('you must provide a module name when calling require() from ' + + requirer.id, requirer.uri); + + let requirement, uri; + + // TODO should get native Firefox modules before doing node-style lookups + // to save on loading time + if (isNative) { + // If a requireMap is available from `generateMap`, use that to + // immediately resolve the node-style mapping. + // TODO: write more tests for this use case + if (requireMap && requireMap[requirer.id]) + requirement = requireMap[requirer.id][id]; + + let { overrides } = manifest.jetpack; + for (let key in overrides) { + // ignore any overrides using relative keys + if (/^[.\/]/.test(key)) { + continue; + } + + // If the override is for x -> y, + // then using require("x/lib/z") to get reqire("y/lib/z") + // should also work + if (id == key || id.startsWith(key + "/")) { + id = overrides[key] + id.substr(key.length); + id = id.replace(/^[.\/]+/, ""); + } + } + + // For native modules, we want to check if it's a module specified + // in 'modules', like `chrome`, or `@loader` -- if it exists, + // just set the uri to skip resolution + if (!requirement && modules[id]) + uri = requirement = id; + + // If no requireMap was provided, or resolution not found in + // the requireMap, and not a npm dependency, attempt a runtime lookup + if (!requirement && !NODE_MODULES.has(id)) { + // If `isNative` defined, this is using the new, native-style + // loader, not cuddlefish, so lets resolve using node's algorithm + // and get back a path that needs to be resolved via paths mapping + // in `resolveURI` + requirement = loaderResolve(id, requirer.id, { + manifest: manifest, + rootURI: rootURI + }); + } + + // If not found in the map, not a node module, and wasn't able to be + // looked up, it's something + // found in the paths most likely, like `sdk/tabs`, which should + // be resolved relatively if needed using traditional resolve + if (!requirement) { + requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id; + } + } + else if (modules[id]) { + uri = requirement = id; + } + else if (requirer) { + // Resolve `id` to its requirer if it's relative. + requirement = loaderResolve(id, requirer.id); + } + else { + requirement = id; + } + + // Resolves `uri` of module using loaders resolve function. + uri = uri || resolveURI(requirement, mapping); + + // Throw if `uri` can not be resolved. + if (!uri) { + throw Error('Module: Can not resolve "' + id + '" module required by ' + + requirer.id + ' located at ' + requirer.uri, requirer.uri); + } + + return { uri: uri, requirement: requirement }; + } + + // Expose the `resolve` function for this `Require` instance + require.resolve = _require.resolve = function resolve(id) { + let { uri } = getRequirements(id); + return uri; + } + + // This is like webpack's require.context. It returns a new require + // function that prepends the prefix to any requests. + require.context = prefix => { + return id => { + return require(prefix + id); + }; + }; + + // Make `require.main === module` evaluate to true in main module scope. + require.main = loader.main === requirer ? requirer : undefined; + return iced(require); +}); +Loader.Require = Require; + +const main = iced(function main(loader, id) { + // If no main entry provided, and native loader is used, + // read the entry in the manifest + if (!id && loader.isNative) + id = getManifestMain(loader.manifest); + let uri = resolveURI(id, loader.mapping); + let module = loader.main = loader.modules[uri] = Module(id, uri); + return loader.load(loader, module).exports; +}); +Loader.main = main; + +// Makes module object that is made available to CommonJS modules when they +// are evaluated, along with `exports` and `require`. +const Module = iced(function Module(id, uri) { + return Object.create(null, { + id: { enumerable: true, value: id }, + exports: { enumerable: true, writable: true, value: Object.create(null), + configurable: true }, + uri: { value: uri } + }); +}); +Loader.Module = Module; + +// Takes `loader`, and unload `reason` string and notifies all observers that +// they should cleanup after them-self. +const unload = iced(function unload(loader, reason) { + // subject is a unique object created per loader instance. + // This allows any code to cleanup on loader unload regardless of how + // it was loaded. To handle unload for specific loader subject may be + // asserted against loader.destructor or require('@loader/unload') + // Note: We don not destroy loader's module cache or sandboxes map as + // some modules may do cleanup in subsequent turns of event loop. Destroying + // cache may cause module identity problems in such cases. + let subject = { wrappedJSObject: loader.destructor }; + notifyObservers(subject, 'sdk:loader:destroy', reason); +}); +Loader.unload = unload; + +// Function makes new loader that can be used to load CommonJS modules +// described by a given `options.manifest`. Loader takes following options: +// - `globals`: Optional map of globals, that all module scopes will inherit +// from. Map is also exposed under `globals` property of the returned loader +// so it can be extended further later. Defaults to `{}`. +// - `modules` Optional map of built-in module exports mapped by module id. +// These modules will incorporated into module cache. Each module will be +// frozen. +// - `resolve` Optional module `id` resolution function. If given it will be +// used to resolve module URIs, by calling it with require term, requirer +// module object (that has `uri` property) and `baseURI` of the loader. +// If `resolve` does not returns `uri` string exception will be thrown by +// an associated `require` call. +function Loader(options) { + if (options.sharedGlobalBlacklist && !options.sharedGlobalBlocklist) { + options.sharedGlobalBlocklist = options.sharedGlobalBlacklist; + } + let { + modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative, + metadata, sharedGlobal, sharedGlobalBlocklist, checkCompatibility, waiveIntereposition + } = override({ + paths: {}, + modules: {}, + globals: { + get console() { + // Import Console.jsm from here to prevent loading it until someone uses it + let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm"); + let console = new ConsoleAPI({ + consoleID: options.id ? "addon/" + options.id : "" + }); + Object.defineProperty(this, "console", { value: console }); + return this.console; + } + }, + checkCompatibility: false, + resolve: options.isNative ? + // Make the returned resolve function have the same signature + (id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: rootURI }) : + Loader.resolve, + sharedGlobalBlocklist: ["sdk/indexed-db"], + waiveIntereposition: false + }, options); + + // Create overrides defaults, none at the moment + if (typeof manifest != "object" || !manifest) { + manifest = {}; + } + if (typeof manifest.jetpack != "object" || !manifest.jetpack) { + manifest.jetpack = { + overrides: {} + }; + } + if (typeof manifest.jetpack.overrides != "object" || !manifest.jetpack.overrides) { + manifest.jetpack.overrides = {}; + } + + // We create an identity object that will be dispatched on an unload + // event as subject. This way unload listeners will be able to assert + // which loader is unloaded. Please note that we intentionally don't + // use `loader` as subject to prevent a loader access leakage through + // observer notifications. + let destructor = freeze(Object.create(null)); + + // Make mapping array that is sorted from longest path to shortest path. + let mapping = Object.keys(paths) + .sort((a, b) => b.length - a.length) + .map(path => [path, paths[path]]); + + // Define pseudo modules. + modules = override({ + '@loader/unload': destructor, + '@loader/options': options, + 'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm, + CC: bind(CC, Components), components: Components, + // `ChromeWorker` has to be inject in loader global scope. + // It is done by bootstrap.js:loadSandbox for the SDK. + ChromeWorker: ChromeWorker + } + }, modules); + + const builtinModuleExports = modules; + modules = {}; + for (let id of Object.keys(builtinModuleExports)) { + // We resolve `uri` from `id` since modules are cached by `uri`. + let uri = resolveURI(id, mapping); + // In native loader, the mapping will not contain values for + // pseudomodules -- store them as their ID rather than the URI + if (isNative && !uri) + uri = id; + let module = Module(id, uri); + + // Lazily expose built-in modules in order to + // allow them to be loaded lazily. + Object.defineProperty(module, "exports", { + enumerable: true, + get: function() { + return builtinModuleExports[id]; + } + }); + + modules[uri] = freeze(module); + } + + // Create the unique sandbox we will be using for all modules, + // so that we prevent creating a new comportment per module. + // The side effect is that all modules will share the same + // global objects. + let sharedGlobalSandbox = Sandbox({ + name: "Addon-SDK", + wantXrays: false, + wantGlobalProperties: [], + invisibleToDebugger: options.invisibleToDebugger || false, + metadata: { + addonID: options.id, + URI: "Addon-SDK" + }, + prototype: options.sandboxPrototype || {} + }); + + // Loader object is just a representation of a environment + // state. We freeze it and mark make it's properties non-enumerable + // as they are pure implementation detail that no one should rely upon. + let returnObj = { + destructor: { enumerable: false, value: destructor }, + globals: { enumerable: false, value: globals }, + mapping: { enumerable: false, value: mapping }, + // Map of module objects indexed by module URIs. + modules: { enumerable: false, value: modules }, + metadata: { enumerable: false, value: metadata }, + useSharedGlobalSandbox: { enumerable: false, value: !!sharedGlobal }, + sharedGlobalSandbox: { enumerable: false, value: sharedGlobalSandbox }, + sharedGlobalBlocklist: { enumerable: false, value: sharedGlobalBlocklist }, + sharedGlobalBlacklist: { enumerable: false, value: sharedGlobalBlocklist }, + // Map of module sandboxes indexed by module URIs. + sandboxes: { enumerable: false, value: {} }, + resolve: { enumerable: false, value: resolve }, + // ID of the addon, if provided. + id: { enumerable: false, value: options.id }, + // Whether the modules loaded should be ignored by the debugger + invisibleToDebugger: { enumerable: false, + value: options.invisibleToDebugger || false }, + load: { enumerable: false, value: options.load || load }, + checkCompatibility: { enumerable: false, value: checkCompatibility }, + requireHook: { enumerable: false, value: options.requireHook }, + loadModuleHook: { enumerable: false, value: options.loadModuleHook }, + // Main (entry point) module, it can be set only once, since loader + // instance can have only one main module. + main: new function() { + let main; + return { + enumerable: false, + get: function() { return main; }, + // Only set main if it has not being set yet! + set: function(module) { main = main || module; } + } + } + }; + + if (isNative) { + returnObj.isNative = { enumerable: false, value: true }; + returnObj.manifest = { enumerable: false, value: manifest }; + returnObj.requireMap = { enumerable: false, value: requireMap }; + returnObj.rootURI = { enumerable: false, value: addTrailingSlash(rootURI) }; + } + + return freeze(Object.create(null, returnObj)); +}; +Loader.Loader = Loader; + +var isSystemURI = uri => /^resource:\/\/(gre|devtools|testing-common)\//.test(uri); + +var isJSONURI = uri => uri.endsWith('.json'); +var isJSMURI = uri => uri.endsWith('.jsm'); +var isJSURI = uri => uri.endsWith('.js'); +var isAbsoluteURI = uri => uri.startsWith("resource://") || + uri.startsWith("chrome://") || + uri.startsWith("file://"); +var isRelative = id => id.startsWith("."); + +// Default `main` entry to './index.js' and ensure is relative, +// since node allows 'lib/index.js' without relative `./` +function getManifestMain(manifest) { + let main = manifest.main || './index.js'; + return isRelative(main) ? main : './' + main; +} + +module.exports = iced(Loader); +}); + diff --git a/resource/pathparser.js b/resource/pathparser.jsm similarity index 90% rename from resource/pathparser.js rename to resource/pathparser.jsm index a28d97367f..96b4cf0b37 100644 --- a/resource/pathparser.js +++ b/resource/pathparser.jsm @@ -5,7 +5,7 @@ * License: MIT * https://github.com/dstillman/pathparser.js */ -(function (factory) { +(function (root, factory) { // AMD/RequireJS if (typeof define === 'function' && define.amd) { define(factory); @@ -13,14 +13,16 @@ } else if (typeof exports === 'object') { module.exports = factory(); // Mozilla JSM - } else if (~String(this).indexOf('BackstagePass')) { - EXPORTED_SYMBOLS = ["PathParser"]; - PathParser = factory(); + } else if (typeof Components != 'undefined' + && typeof Components.utils != 'undefined' + && typeof Components.utils.import == 'function') { + root.EXPORTED_SYMBOLS = ["PathParser"]; + root.PathParser = factory(); // Browser global } else { - PathParser = factory(); + root.PathParser = factory(); } -}(function () { +}(this, function () { "use strict"; var PathParser = function (params) { diff --git a/resource/require.js b/resource/require.js index 5adf6b52c0..301d6e61a7 100644 --- a/resource/require.js +++ b/resource/require.js @@ -2,7 +2,7 @@ var require = (function() { var win, Zotero; - var { Loader, Require, Module } = Components.utils.import('resource://gre/modules/commonjs/toolkit/loader.js'); + Components.utils.import('resource://zotero/loader.jsm'); var requirer = Module('/', '/'); var _runningTimers = {}; if (typeof window != 'undefined') { diff --git a/resource/word-processor-plugin-installer.js b/resource/word-processor-plugin-installer.js index 76e31a16e7..943784e61d 100644 --- a/resource/word-processor-plugin-installer.js +++ b/resource/word-processor-plugin-installer.js @@ -27,7 +27,7 @@ * installable and available in the cite preferences pane. */ -EXPORTED_SYMBOLS = ["ZoteroPluginInstaller"]; +var EXPORTED_SYMBOLS = ["ZoteroPluginInstaller"]; var Zotero = Components.classes["@zotero.org/Zotero;1"] // Currently uses only nsISupports From 0ad308f5f6909d1ba02b7be7811f4b8e7faa0c91 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 24 Feb 2018 05:04:04 -0500 Subject: [PATCH 03/67] Fx60: Update nsIInputStreamPump init() calls --- chrome/content/zotero/xpcom/ipc.js | 7 ++++++- chrome/content/zotero/xpcom/server.js | 7 ++++++- chrome/content/zotero/xpcom/utilities_internal.js | 14 ++++++++++++-- components/zotero-protocol-handler.js | 7 ++++++- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/chrome/content/zotero/xpcom/ipc.js b/chrome/content/zotero/xpcom/ipc.js index fb356c6e2b..3b4854cecf 100755 --- a/chrome/content/zotero/xpcom/ipc.js +++ b/chrome/content/zotero/xpcom/ipc.js @@ -461,7 +461,12 @@ Zotero.IPC.Pipe.DeferredOpen.prototype = { var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]. createInstance(Components.interfaces.nsIInputStreamPump); - pump.init(fifoStream, -1, -1, 4096, 1, true); + try { + pump.init(fifoStream, 0, 0, true); + } + catch (e) { + pump.init(fifoStream, -1, -1, 4096, 1, true); + } pump.asyncRead(this, null); this._openTime = Date.now(); diff --git a/chrome/content/zotero/xpcom/server.js b/chrome/content/zotero/xpcom/server.js index aad09b4be8..e5363ff20b 100755 --- a/chrome/content/zotero/xpcom/server.js +++ b/chrome/content/zotero/xpcom/server.js @@ -140,7 +140,12 @@ Zotero.Server.SocketListener = new function() { var dataListener = new Zotero.Server.DataListener(iStream, oStream); var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"] .createInstance(Components.interfaces.nsIInputStreamPump); - pump.init(iStream, -1, -1, 0, 0, false); + try { + pump.init(iStream, 0, 0, false); + } + catch (e) { + pump.init(iStream, -1, -1, 0, 0, false); + } pump.asyncRead(dataListener, null); } diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index c68a731014..daccf48a7f 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -283,7 +283,12 @@ Zotero.Utilities.Internal = { // Send input stream to stream converter var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"] .createInstance(Components.interfaces.nsIInputStreamPump); - pump.init(is, -1, -1, 0, 0, true); + try { + pump.init(is, 0, 0, true); + } + catch (e) { + pump.init(is, -1, -1, 0, 0, true); + } pump.asyncRead(converter, null); return deferred.promise; @@ -343,7 +348,12 @@ Zotero.Utilities.Internal = { // Send input stream to stream converter var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"] .createInstance(Components.interfaces.nsIInputStreamPump); - pump.init(bis, -1, -1, 0, 0, true); + try { + pump.init(bis, 0, 0, true); + } + catch (e) { + pump.init(bis, -1, -1, 0, 0, true); + } pump.asyncRead(converter, null); return deferred.promise; diff --git a/components/zotero-protocol-handler.js b/components/zotero-protocol-handler.js index f8c56ace75..33f33acf53 100644 --- a/components/zotero-protocol-handler.js +++ b/components/zotero-protocol-handler.js @@ -1397,7 +1397,12 @@ AsyncChannel.prototype = { //Zotero.debug("AsyncChannel: Got input stream from generator"); var pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(Ci.nsIInputStreamPump); - pump.init(data, -1, -1, 0, 0, true); + try { + pump.init(data, 0, 0, true); + } + catch (e) { + pump.init(data, -1, -1, 0, 0, true); + } pump.asyncRead(listenerWrapper, context); } else if (data instanceof Ci.nsIFile || data instanceof Ci.nsIURI) { From d4b10f1c1ef00bbe083864f13d4105b803cfda45 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 24 Feb 2018 05:04:36 -0500 Subject: [PATCH 04/67] Fx60: Don't try to override toString() on an object --- chrome/content/zotero/xpcom/storage/storageResult.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chrome/content/zotero/xpcom/storage/storageResult.js b/chrome/content/zotero/xpcom/storage/storageResult.js index eaa1f38c19..394a2a025a 100644 --- a/chrome/content/zotero/xpcom/storage/storageResult.js +++ b/chrome/content/zotero/xpcom/storage/storageResult.js @@ -38,10 +38,10 @@ Zotero.Sync.Storage.Result.prototype.updateFromResults = function (results) { } -Zotero.Sync.Storage.Result.prototype.toString = function () { +/*Zotero.Sync.Storage.Result.prototype.toString = function () { var obj = {}; for (let prop of this._props) { obj[prop] = this[prop] || false; } return JSON.stringify(obj, null, " "); -} +}*/ From 331522b106a190e8144721b6bd888a096575ba60 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 24 Feb 2018 05:05:30 -0500 Subject: [PATCH 05/67] Fx60: Update pref handling - getStringPref/setStringPref are now used for strings instead of getComplexValue/setComplexValue - Remove nsIPrefBranch2 reference - If there was a pref failure during initialization, nothing was logged to the terminal --- chrome/content/zotero/xpcom/prefs.js | 42 ++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/chrome/content/zotero/xpcom/prefs.js b/chrome/content/zotero/xpcom/prefs.js index a0df9a3c6b..84eea2e40d 100644 --- a/chrome/content/zotero/xpcom/prefs.js +++ b/chrome/content/zotero/xpcom/prefs.js @@ -98,13 +98,22 @@ Zotero.Prefs = new function(){ case branch.PREF_BOOL: return branch.getBoolPref(pref); case branch.PREF_STRING: - return '' + branch.getComplexValue(pref, Components.interfaces.nsISupportsString); + // Pre-Fx59 + if (!branch.getStringPref) { + return '' + branch.getComplexValue(pref, Components.interfaces.nsISupportsString); + } + return branch.getStringPref(pref); case branch.PREF_INT: return branch.getIntPref(pref); } } - catch (e){ - throw new Error("Invalid preference '" + pref + "'"); + catch (e) { + // If debug system isn't yet initialized, log proper error + if (Zotero.Debug.enabled === undefined) { + dump(e + "\n\n"); + } + Zotero.logError(e); + throw new Error(`Error getting preference '${pref}'`); } } @@ -125,10 +134,14 @@ Zotero.Prefs = new function(){ case branch.PREF_BOOL: return branch.setBoolPref(pref, value); case branch.PREF_STRING: - let str = Cc["@mozilla.org/supports-string;1"] - .createInstance(Ci.nsISupportsString); - str.data = value; - return branch.setComplexValue(pref, Ci.nsISupportsString, str); + // Pre-Fx59 + if (!branch.setStringPref) { + let str = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + str.data = value; + return branch.setComplexValue(pref, Ci.nsISupportsString, str); + } + return branch.setStringPref(pref, value); case branch.PREF_INT: return branch.setIntPref(pref, value); @@ -140,7 +153,14 @@ Zotero.Prefs = new function(){ } if (typeof value == 'string') { Zotero.debug("Creating string pref '" + pref + "'"); - return branch.setCharPref(pref, value); + // Pre-Fx59 + if (!branch.setStringPref) { + let str = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + str.data = value; + return branch.setComplexValue(pref, Ci.nsISupportsString, str); + } + return branch.setStringPref(pref, value); } if (parseInt(value) == value) { Zotero.debug("Creating integer pref '" + pref + "'"); @@ -150,8 +170,12 @@ Zotero.Prefs = new function(){ } } catch (e) { + // If debug system isn't yet initialized, log proper error + if (Zotero.Debug.enabled === undefined) { + dump(e + "\n\n"); + } Zotero.logError(e); - throw new Error("Invalid preference '" + pref + "'"); + throw new Error(`Error setting preference '${pref}'`); } } From 186d2b088382778b5a790b9489d077bb829023db Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 24 Feb 2018 05:08:54 -0500 Subject: [PATCH 06/67] Fx60: nsILocalFile -> nsIFile --- chrome/content/zotero/xpcom/integration.js | 4 +--- chrome/content/zotero/xpcom/ipc.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js index 642c8ce538..403b6e15f5 100644 --- a/chrome/content/zotero/xpcom/integration.js +++ b/chrome/content/zotero/xpcom/integration.js @@ -83,9 +83,7 @@ Zotero.Integration = new function() { // on OS X, first try /Users/Shared for those who can't put pipes in their home // directories var pipe = null; - var sharedDir = Components.classes["@mozilla.org/file/local;1"]. - createInstance(Components.interfaces.nsILocalFile); - sharedDir.initWithPath("/Users/Shared"); + var sharedDir = Zotero.File.pathToFile('/Users/Shared'); if(sharedDir.exists() && sharedDir.isDirectory()) { var logname = Components.classes["@mozilla.org/process/environment;1"]. diff --git a/chrome/content/zotero/xpcom/ipc.js b/chrome/content/zotero/xpcom/ipc.js index 3b4854cecf..245c11f238 100755 --- a/chrome/content/zotero/xpcom/ipc.js +++ b/chrome/content/zotero/xpcom/ipc.js @@ -229,7 +229,7 @@ Zotero.IPC = new function() { if(pipeDir.exists()) { var dirEntries = pipeDir.directoryEntries; while (dirEntries.hasMoreElements()) { - var pipe = dirEntries.getNext().QueryInterface(Ci.nsILocalFile); + var pipe = dirEntries.getNext().QueryInterface(Ci.nsIFile); if(pipe.leafName[0] !== "." && (!_instancePipe || !pipe.equals(_instancePipe))) { pipes.push(pipe); } From 15a7a812f619b94452d6d5dd36d0ddaec7af3497 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 24 Feb 2018 19:19:28 -0500 Subject: [PATCH 07/67] Fx60: += operator no longer works on nsIURI spec --- chrome/content/zotero/bindings/styled-textbox.xml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/chrome/content/zotero/bindings/styled-textbox.xml b/chrome/content/zotero/bindings/styled-textbox.xml index e994d5fe55..d3c7c951ea 100644 --- a/chrome/content/zotero/bindings/styled-textbox.xml +++ b/chrome/content/zotero/bindings/styled-textbox.xml @@ -644,14 +644,12 @@ // for read-only mode var htmlFile = this.mode + (this.getAttribute('readonly') != 'true' ? "" : "view"); - var ios = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); - var uri = ios.newURI("resource://zotero/tinymce/" + htmlFile + ".html", null, null); - - // Pass directionality (LTR/RTL) and locale in URL - uri.spec += "?locale=" + encodeURIComponent(Zotero.locale) + var url = `resource://zotero/tinymce/${htmlFile}.html` + // Pass directionality (LTR/RTL) and locale in URL + + "?locale=" + encodeURIComponent(Zotero.locale) + "&dir=" + Zotero.dir; - + Components.utils.import("resource://gre/modules/Services.jsm"); + var uri = Services.io.newURI(url, null, null); Zotero.debug("Loading " + uri.spec); From 9ca1014f5bc90b6edabb5f04b3f73df09fdb246a Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sat, 24 Feb 2018 22:39:34 -0500 Subject: [PATCH 08/67] Fx60: Update old-style shorthand function definitions --- chrome/content/zotero/bindings/tagsbox.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chrome/content/zotero/bindings/tagsbox.xml b/chrome/content/zotero/bindings/tagsbox.xml index 4db6c61550..0a5edc33cb 100644 --- a/chrome/content/zotero/bindings/tagsbox.xml +++ b/chrome/content/zotero/bindings/tagsbox.xml @@ -139,7 +139,7 @@ val.split("/")[1] == 'tagColors') && this.item) { this.reload(); return; } @@ -227,7 +227,7 @@ // Sort tags alphabetically var collation = Zotero.getLocaleCollation(); - tags.sort(function (a, b) collation.compareString(1, a.tag, b.tag)); + tags.sort((a, b) => collation.compareString(1, a.tag, b.tag)); for (let i=0; i val.trim()); // Modifying existing tag with a single new one if (!isNew && tags.length < 2) { From 8f844251c8ab730e49151c817a790233dac6499c Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 25 Feb 2018 01:15:24 -0500 Subject: [PATCH 09/67] Fx60: Fix access to translator object in sandbox There's almost certainly other sandbox breakage --- chrome/content/zotero/xpcom/translation/translate.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index ef5362df8c..89ce7398f3 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -313,16 +313,6 @@ Zotero.Translate.Sandbox = { * @inner */ var safeTranslator = {}; - safeTranslator.__exposedProps__ = { - "setSearch":"r", - "setDocument":"r", - "setHandler":"r", - "setString":"r", - "setTranslator":"r", - "getTranslators":"r", - "translate":"r", - "getTranslatorObject":"r" - }; safeTranslator.setSearch = function(arg) { if(!Zotero.isBookmarklet) arg = JSON.parse(JSON.stringify(arg)); return translation.setSearch(arg); From 0678f13e965226ec6bf8d85f21eaafa15f88dcfa Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 25 Feb 2018 01:16:36 -0500 Subject: [PATCH 10/67] Use Services for an nsILoginManager initialization --- chrome/content/zotero/xpcom/sync/syncLocal.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/chrome/content/zotero/xpcom/sync/syncLocal.js b/chrome/content/zotero/xpcom/sync/syncLocal.js index bbfbe1c4d2..ab08ce7c6e 100644 --- a/chrome/content/zotero/xpcom/sync/syncLocal.js +++ b/chrome/content/zotero/xpcom/sync/syncLocal.js @@ -381,9 +381,7 @@ Zotero.Sync.Data.Local = { */ _getAPIKeyLoginInfo: function () { try { - var loginManager = Components.classes["@mozilla.org/login-manager;1"] - .getService(Components.interfaces.nsILoginManager); - var logins = loginManager.findLogins( + var logins = Services.logins.findLogins( {}, this._loginManagerHost, null, From c771d9b046bd6e96d28f606912dedaeab3d50d1c Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 25 Feb 2018 04:23:39 -0500 Subject: [PATCH 11/67] Fx60: Fix devtools initialization --- components/zotero-service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/zotero-service.js b/components/zotero-service.js index 5ecc50ba89..eb20e9b290 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -580,8 +580,8 @@ ZoteroCommandLineHandler.prototype = { if (!DebuggerServer.initialized) { dump("Initializing devtools server\n"); DebuggerServer.init(); + DebuggerServer.registerAllActors(); DebuggerServer.allowChromeProcess = true; - DebuggerServer.addBrowserActors(); } let listener = DebuggerServer.createListener(); From c0da6c042798b0b20ec354e9e40f1cf5227821d5 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 25 Feb 2018 04:24:14 -0500 Subject: [PATCH 12/67] Fx60: Add built_in_addons.json file fetch_xulrunner.sh updates the built-in path to point to this file, but it's not currently being read properly for some reason (and I'm not sure it does anything we need). --- chrome/content/zotero/built_in_addons.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 chrome/content/zotero/built_in_addons.json diff --git a/chrome/content/zotero/built_in_addons.json b/chrome/content/zotero/built_in_addons.json new file mode 100644 index 0000000000..8af8c11cba --- /dev/null +++ b/chrome/content/zotero/built_in_addons.json @@ -0,0 +1 @@ +{"system": ["activity-stream@mozilla.org", "aushelper@mozilla.org", "firefox@getpocket.com", "followonsearch@mozilla.com", "formautofill@mozilla.org", "onboarding@mozilla.org", "screenshots@mozilla.org", "shield-recipe-client@mozilla.org", "webcompat@mozilla.org"]} From 6fd879fc16552b3a16a5cbad0a74cb19baf15a35 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 25 Feb 2018 12:55:54 -0500 Subject: [PATCH 13/67] Fx60: Switch to `new XMLHttpRequest()` --- chrome/content/zotero/xpcom/file.js | 3 +-- chrome/content/zotero/xpcom/http.js | 15 +++++---------- chrome/content/zotero/xpcom/openurl.js | 2 +- chrome/content/zotero/xpcom/zotero.js | 3 ++- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js index 72c6647c82..af7feb4813 100644 --- a/chrome/content/zotero/xpcom/file.js +++ b/chrome/content/zotero/xpcom/file.js @@ -333,8 +333,7 @@ Zotero.File = new function(){ * Runs synchronously, so should only be run on local (e.g. chrome) URLs */ function getContentsFromURL(url) { - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); + var xmlhttp = new XMLHttpRequest(); xmlhttp.open('GET', url, false); xmlhttp.overrideMimeType("text/plain"); xmlhttp.send(null); diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js index 56baa55ced..3404621264 100644 --- a/chrome/content/zotero/xpcom/http.js +++ b/chrome/content/zotero/xpcom/http.js @@ -175,8 +175,7 @@ Zotero.HTTP = new function() { var deferred = Zotero.Promise.defer(); if (!this.mock) { - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); + var xmlhttp = new XMLHttpRequest(); } else { var xmlhttp = new this.mock; @@ -456,8 +455,7 @@ Zotero.HTTP = new function() { return false; } - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); + var xmlhttp = new XMLHttpRequest(); // Prevent certificate/authentication dialogs from popping up xmlhttp.mozBackgroundRequest = true; @@ -530,8 +528,7 @@ Zotero.HTTP = new function() { return false; } - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); + var xmlhttp = new XMLHttpRequest(); // Prevent certificate/authentication dialogs from popping up xmlhttp.mozBackgroundRequest = true; xmlhttp.open('POST', url, true); @@ -608,8 +605,7 @@ Zotero.HTTP = new function() { // Workaround for "Accept third-party cookies" being off in Firefox 3.0.1 // https://www.zotero.org/trac/ticket/1070 - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); + var xmlhttp = new XMLHttpRequest(); // Prevent certificate/authentication dialogs from popping up xmlhttp.mozBackgroundRequest = true; xmlhttp.open('HEAD', url, true); @@ -656,8 +652,7 @@ Zotero.HTTP = new function() { return false; } - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); + var xmlhttp = new XMLHttpRequest(); // Prevent certificate/authentication dialogs from popping up xmlhttp.mozBackgroundRequest = true; xmlhttp.open('OPTIONS', uri.spec, true); diff --git a/chrome/content/zotero/xpcom/openurl.js b/chrome/content/zotero/xpcom/openurl.js index a96175ebba..9e6ad0bf78 100644 --- a/chrome/content/zotero/xpcom/openurl.js +++ b/chrome/content/zotero/xpcom/openurl.js @@ -47,7 +47,7 @@ Zotero.OpenURL = new function() { * Queries OCLC's OpenURL resolver registry and returns an address and version */ function discoverResolvers() { - var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(); + var req = new XMLHttpRequest(); req.open("GET", "http://worldcatlibraries.org/registry/lookup?IP=requestor", false); req.send(null); diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index bcbda180f2..c8d2c20b7a 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -24,6 +24,7 @@ */ // Commonly used imports accessible anywhere +Components.utils.importGlobalProperties(["XMLHttpRequest"]); Components.utils.import("resource://zotero/config.js"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); @@ -239,7 +240,7 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); Zotero.browser = "g"; Zotero.Intl.init(); - + Zotero.Prefs.init(); Zotero.Debug.init(options && options.forceDebugLog); From c07379fe3361d11b0bec6e42aba996cd6aeac50a Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 25 Feb 2018 15:09:38 -0500 Subject: [PATCH 14/67] Fx60: Fix "path is undefined" in cookie sandbox getCookiesForURI() seems to now get an nsIURL that won't query to nsIURI, so look for `filePath` in addition to `path`. --- chrome/content/zotero/xpcom/cookieSandbox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/cookieSandbox.js b/chrome/content/zotero/xpcom/cookieSandbox.js index 73bf41492c..87f34b6a3c 100755 --- a/chrome/content/zotero/xpcom/cookieSandbox.js +++ b/chrome/content/zotero/xpcom/cookieSandbox.js @@ -221,7 +221,7 @@ Zotero.CookieSandbox.prototype = { */ "getCookiesForURI": function(uri) { var hostParts = Zotero.CookieSandbox.normalizeHost(uri.host).split('.'), - pathParts = Zotero.CookieSandbox.normalizePath(uri.path).split('/'), + pathParts = Zotero.CookieSandbox.normalizePath(uri.filePath || uri.path).split('/'), cookies = {}, found = false, secure = uri.scheme.toUpperCase() == 'HTTPS'; // Fetch cookies starting from the highest level domain From 8c59df435f1f5d243d1f720c1234bc2ad136aa7b Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 25 Feb 2018 15:07:33 -0500 Subject: [PATCH 15/67] Fx60: Fix snapshot filenames nsIURL doesn't seem to work anymore, so add Zotero.Utilities.parseURL(), which uses the `url` package from NPM and adds fileName, fileExtension, and fileBaseName. --- chrome/content/zotero/xpcom/attachments.js | 34 ++++++---------- chrome/content/zotero/xpcom/utilities.js | 15 +++++++ package-lock.json | 7 +--- package.json | 3 +- scripts/config.js | 7 ++++ test/tests/utilitiesTest.js | 46 ++++++++++++++++++++++ 6 files changed, 84 insertions(+), 28 deletions(-) diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js index 00b203a954..0db4985781 100644 --- a/chrome/content/zotero/xpcom/attachments.js +++ b/chrome/content/zotero/xpcom/attachments.js @@ -2426,53 +2426,43 @@ Zotero.Attachments = new function(){ this._getFileNameFromURL = function(url, contentType) { - var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"] - .createInstance(Components.interfaces.nsIURL); - nsIURL.spec = url; + url = Zotero.Utilities.parseURL(url); - var ext = Zotero.MIME.getPrimaryExtension(contentType, nsIURL.fileExtension); + var fileBaseName = url.fileBaseName; + var fileExt = Zotero.MIME.getPrimaryExtension(contentType, url.fileExtension); - if (!nsIURL.fileName) { - var matches = nsIURL.directory.match(/\/([^\/]+)\/$/); + if (!fileBaseName) { + let matches = url.pathname.match(/\/([^\/]+)\/$/); // If no filename, use the last part of the path if there is one if (matches) { - nsIURL.fileName = matches[1]; + fileBaseName = matches[1]; } // Or just use the host else { - nsIURL.fileName = nsIURL.host; - var tld = nsIURL.fileExtension; + fileBaseName = url.hostname; } } - // If we found a better extension, use that - if (ext && (!nsIURL.fileExtension || nsIURL.fileExtension != ext)) { - nsIURL.fileExtension = ext; - } - - // If we replaced the TLD (which would've been interpreted as the extension), add it back - if (tld && tld != nsIURL.fileExtension) { - nsIURL.fileBaseName = nsIURL.fileBaseName + '.' + tld; - } - // Test unencoding fileBaseName try { - decodeURIComponent(nsIURL.fileBaseName); + decodeURIComponent(fileBaseName); } catch (e) { if (e.name == 'URIError') { // If we got a 'malformed URI sequence' while decoding, // use MD5 of fileBaseName - nsIURL.fileBaseName = Zotero.Utilities.Internal.md5(nsIURL.fileBaseName, false); + fileBaseName = Zotero.Utilities.Internal.md5(fileBaseName, false); } else { throw e; } } + var fileName = fileBaseName + (fileExt ? '.' + fileExt : ''); + // Pass unencoded name to getValidFileName() so that percent-encoded // characters aren't stripped to just numbers - return Zotero.File.getValidFileName(decodeURIComponent(nsIURL.fileName)); + return Zotero.File.getValidFileName(decodeURIComponent(fileName)); } diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js index ce5d85e37c..29a58fd813 100644 --- a/chrome/content/zotero/xpcom/utilities.js +++ b/chrome/content/zotero/xpcom/utilities.js @@ -1913,6 +1913,21 @@ Zotero.Utilities = { } }, + + parseURL: function (url) { + var parts = require('url').parse(url); + // fileName + parts.fileName = parts.pathname.split('/').pop(); + // fileExtension + var pos = parts.fileName.lastIndexOf('.'); + parts.fileExtension = pos == -1 ? '' : parts.fileName.substr(pos + 1); + // fileBaseName + parts.fileBaseName = parts.fileName + // filename up to the period before the file extension, if there is one + .substr(0, parts.fileName.length - (parts.fileExtension ? parts.fileExtension.length + 1 : 0)); + return parts; + }, + /** * Get the real target URL from an intermediate URL */ diff --git a/package-lock.json b/package-lock.json index d5422d1407..237a2ea520 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5398,8 +5398,7 @@ "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystring-es3": { "version": "0.2.1", @@ -6686,7 +6685,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, "requires": { "punycode": "1.3.2", "querystring": "0.2.0" @@ -6695,8 +6693,7 @@ "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" } } }, diff --git a/package.json b/package.json index 4faf8096ae..6154b6130f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6", - "react-intl": "^2.7.2" + "react-intl": "^2.7.2", + "url": "^0.11.0" }, "devDependencies": { "@babel/cli": "^7.2.3", diff --git a/scripts/config.js b/scripts/config.js index 817a31a861..ec64adb9fc 100644 --- a/scripts/config.js +++ b/scripts/config.js @@ -38,6 +38,13 @@ const symlinkFiles = [ // these files will be browserified during the build const browserifyConfigs = [ + { + src: 'node_modules/url/url.js', + dest: 'resource/url.js', + config: { + standalone: 'url' + } + }, { src: 'node_modules/sinon/lib/sinon.js', dest: 'test/resource/sinon.js', diff --git a/test/tests/utilitiesTest.js b/test/tests/utilitiesTest.js index 6d57bd5100..81d035a3fb 100644 --- a/test/tests/utilitiesTest.js +++ b/test/tests/utilitiesTest.js @@ -477,6 +477,52 @@ describe("Zotero.Utilities", function() { }) }); + describe("#parseURL()", function () { + var f; + before(() => { + f = Zotero.Utilities.parseURL; + }); + + describe("#fileName", function () { + it("should contain filename", function () { + assert.propertyVal(f('http://example.com/abc/def.html?foo=bar'), 'fileName', 'def.html'); + }); + + it("should be empty if no filename", function () { + assert.propertyVal(f('http://example.com/abc/'), 'fileName', ''); + }); + }); + + describe("#fileExtension", function () { + it("should contain extension", function () { + assert.propertyVal(f('http://example.com/abc/def.html?foo=bar'), 'fileExtension', 'html'); + }); + + it("should be empty if no extension", function () { + assert.propertyVal(f('http://example.com/abc/def'), 'fileExtension', ''); + }); + + it("should be empty if no filename", function () { + assert.propertyVal(f('http://example.com/abc/'), 'fileExtension', ''); + }); + }); + + describe("#fileBaseName", function () { + it("should contain base name", function () { + assert.propertyVal(f('http://example.com/abc/def.html?foo=bar'), 'fileBaseName', 'def'); + }); + + it("should equal filename if no extension", function () { + assert.propertyVal(f('http://example.com/abc/def'), 'fileBaseName', 'def'); + }); + + it("should be empty if no filename", function () { + assert.propertyVal(f('http://example.com/abc/'), 'fileBaseName', ''); + }); + }); + }); + + describe("#ellipsize()", function () { describe("with wordBoundary", function () { it("should truncate at word boundary", function* () { From e736f07ebcad2f1722d993e47342855f627f841d Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 25 Feb 2018 22:20:40 -0500 Subject: [PATCH 16/67] Update Travis to 59.0b11 unbranded build --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c13e01454..1523c04c02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ env: global: secure: "NxvkbZ7/Op7BTGQRR3C4q8lLoO29f8WtyNN27NSH7AO3H0vBr1Vp5xO8gn+H2qHEug5HvM+YrZ/xAkNXaZVbOInmBmKVMxqVvdpKp9JM1Amf+gzsXWQphfySvs6iqzyP6cwU/jspdvX/WSakgU5v7PWXxtUIaKxdANt6Rw7W+Pc=" matrix: - - FX_VERSION="52.0.3" + - FX_VERSION="59.0b11" matrix: fast_finish: true #allow_failures: @@ -24,8 +24,8 @@ addons: services: - xvfb install: - - if [ $FX_VERSION = "52.0.3" ]; then - wget -O tarball "https://archive.mozilla.org/pub/firefox/tinderbox-builds/mozilla-release-linux64-add-on-devel/1491732920/firefox-52.0.3.en-US.linux-x86_64-add-on-devel.tar.bz2"; + - if [ $FX_VERSION = "59.0b11" ]; then + wget -O tarball "https://queue.taskcluster.net/v1/task/L9FO6qp_TnaLkGB_fFy0Sw/runs/0/artifacts/public/build/target.tar.bz2"; fi - tar xf tarball before_script: From 0165f75f7975072eee44d4d75de19b9e969a12b3 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 16 Aug 2018 18:20:34 -0400 Subject: [PATCH 17/67] Stub Promise.defer() to avoid the Bluebird deprecation warnings We should indeed use `new Zotero.Promise`, but we don't need the warnings for existing code. --- resource/bluebird.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/resource/bluebird.js b/resource/bluebird.js index 06728b4d1d..5c01e08086 100644 --- a/resource/bluebird.js +++ b/resource/bluebird.js @@ -10,6 +10,15 @@ Promise.config({ cancellation: true }); +// Use our own stub to avoid the Bluebird deprecation warnings +Promise.defer = function() { + var deferred = {}; + deferred.promise = new Promise(function(resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + return deferred; +} // TEMP: Only turn on if debug logging enabled? Promise.onPossiblyUnhandledRejection((e, promise) => { if (e.name == 'ZoteroPromiseInterrupt' || e.handledRejection) { From ac456ab9bf9850bc1b33fe5c8fb9e9f96ffefd90 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 16 Aug 2018 18:22:16 -0400 Subject: [PATCH 18/67] Fx60: Additional prefs changes for tests --- test/runtests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/runtests.sh b/test/runtests.sh index 78c8985a88..eb36ed7840 100755 --- a/test/runtests.sh +++ b/test/runtests.sh @@ -147,6 +147,7 @@ cp -R $PDF_TOOLS_CACHE_DIR $PDF_TOOLS_DIR cat < "$PROFILE/prefs.js" user_pref("app.update.enabled", false); user_pref("extensions.autoDisableScopes", 0); +user_pref("browser.dom.window.dump.enabled", true); user_pref("browser.tabs.remote.autostart", false); user_pref("browser.tabs.remote.autostart.2", false); user_pref("browser.uitour.enabled", false); @@ -166,6 +167,7 @@ user_pref("extensions.zotero.httpServer.enabled", true); user_pref("extensions.zotero.backup.numBackups", 0); user_pref("extensions.zotero.sync.autoSync", false); user_pref("xpinstall.signatures.required", false); +user_pref("xpinstall.whitelist.required", false); user_pref("datareporting.healthreport.uploadEnabled", false); user_pref("datareporting.healthreport.service.enabled", false); user_pref("datareporting.healthreport.service.firstRun", false); From 77d1c6cf7421f486a97f47dc690cb59aad8249ef Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 26 Sep 2018 00:19:33 -0400 Subject: [PATCH 19/67] Fx60: Fix CSL locale loading --- chrome/content/zotero/xpcom/cite.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/chrome/content/zotero/xpcom/cite.js b/chrome/content/zotero/xpcom/cite.js index e8d53acc37..3238a83be6 100644 --- a/chrome/content/zotero/xpcom/cite.js +++ b/chrome/content/zotero/xpcom/cite.js @@ -673,23 +673,8 @@ Zotero.Cite.Locale = { if (str) { return str; } - var uri = `chrome://zotero/content/locale/csl/locales-${locale}.xml`; try { - let protHandler = Components.classes["@mozilla.org/network/protocol;1?name=chrome"] - .createInstance(Components.interfaces.nsIProtocolHandler); - let channel = protHandler.newChannel(protHandler.newURI(uri)); - let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Components.interfaces.nsIConverterInputStream); - cstream.init(channel.open(), "UTF-8", 0, 0); - let obj = {}; - let read = 0; - let str = ""; - do { - // Read as much as we can and put it in obj.value - read = cstream.readString(0xffffffff, obj); - str += obj.value; - } while (read != 0); - cstream.close(); + str = Zotero.File.getResource(`chrome://zotero/content/locale/csl/locales-${locale}.xml`); this._cache.set(locale, str); return str; } From 210c2fcea60dd9581a19fa496aa770309cd5b358 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 26 Sep 2018 00:54:11 -0400 Subject: [PATCH 20/67] Fx60: Remove obsolete CSS properties --- chrome/content/zotero-platform/unix/overlay.css | 3 --- chrome/content/zotero-platform/win/overlay.css | 4 ---- chrome/skin/default/zotero/bindings/itembox.css | 4 ---- chrome/skin/default/zotero/overlay.css | 1 - 4 files changed, 12 deletions(-) diff --git a/chrome/content/zotero-platform/unix/overlay.css b/chrome/content/zotero-platform/unix/overlay.css index fba2f7c8b6..cc19958220 100644 --- a/chrome/content/zotero-platform/unix/overlay.css +++ b/chrome/content/zotero-platform/unix/overlay.css @@ -61,9 +61,6 @@ tab { border-top: 1px solid hsla(0, 0%, 0%, 0.2); border-right: 1px solid hsla(0, 0%, 0%, 0.2); border-left: 1px solid hsla(0, 0%, 0%, 0.2); - -moz-border-top-colors: none; - -moz-border-right-colors: none; - -moz-border-left-colors: none; } /* Fixes misc Ubuntu 16.10 rendering issue fixes. */ #zotero-prefs menulist dropmarker{ diff --git a/chrome/content/zotero-platform/win/overlay.css b/chrome/content/zotero-platform/win/overlay.css index 05b28fb467..4d46ca15df 100644 --- a/chrome/content/zotero-platform/win/overlay.css +++ b/chrome/content/zotero-platform/win/overlay.css @@ -105,10 +105,6 @@ border-color: #818790; margin: 0; padding: 0; - -moz-border-top-colors: none; - -moz-border-bottom-colors: none; - -moz-border-right-colors: none; - -moz-border-left-colors: none; } treechildren::-moz-tree-twisty { diff --git a/chrome/skin/default/zotero/bindings/itembox.css b/chrome/skin/default/zotero/bindings/itembox.css index 8b95d3277b..fc75bad93d 100644 --- a/chrome/skin/default/zotero/bindings/itembox.css +++ b/chrome/skin/default/zotero/bindings/itembox.css @@ -111,10 +111,6 @@ row > vbox > description margin: 1px 5px 0 1px !important; max-height: 1.5em !important; border: 1px solid transparent; - -moz-border-top-colors: none; - -moz-border-right-colors: none; - -moz-border-bottom-colors: none; - -moz-border-left-colors: none; background-color: transparent; } diff --git a/chrome/skin/default/zotero/overlay.css b/chrome/skin/default/zotero/overlay.css index ff97637a0d..e7f75e8d71 100644 --- a/chrome/skin/default/zotero/overlay.css +++ b/chrome/skin/default/zotero/overlay.css @@ -644,7 +644,6 @@ font-weight: bold; margin-bottom: 1em; -moz-user-select: text; - user-select: text; cursor: text } From f26b1592a3089d1342176e536c93e6866f154310 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 26 Sep 2018 01:13:19 -0400 Subject: [PATCH 21/67] Fx60: Clear built_in_addons.json file Though it's not being properly read anyway --- chrome/content/zotero/built_in_addons.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/content/zotero/built_in_addons.json b/chrome/content/zotero/built_in_addons.json index 8af8c11cba..835660407f 100644 --- a/chrome/content/zotero/built_in_addons.json +++ b/chrome/content/zotero/built_in_addons.json @@ -1 +1 @@ -{"system": ["activity-stream@mozilla.org", "aushelper@mozilla.org", "firefox@getpocket.com", "followonsearch@mozilla.com", "formautofill@mozilla.org", "onboarding@mozilla.org", "screenshots@mozilla.org", "shield-recipe-client@mozilla.org", "webcompat@mozilla.org"]} +{"system": []} From 8fa4cc9387b5502c51b93552c91136df711ce5b8 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 26 Sep 2018 02:34:42 -0400 Subject: [PATCH 22/67] Fx60: Update Zotero.File.iterateDirectory() signature StopIteration is no longer supported in Firefox 60, so instead of taking a generator function that might throw StopIteration for the second parameter, take a function that is passed to iterator.forEach() that receives an OS.File.DirectoryIterator.Entry for each directory entry. If the function returns a promise, it's waited for. Also update other direct uses of OS.File.DirectoryIterator to remove StopIteration use. --- chrome/content/zotero/fileInterface.js | 23 +-- .../zotero/import/mendeley/mendeleyImport.js | 23 +-- chrome/content/zotero/xpcom/attachments.js | 14 +- chrome/content/zotero/xpcom/dataDirectory.js | 22 +- chrome/content/zotero/xpcom/file.js | 192 ++++++++---------- chrome/content/zotero/xpcom/profile.js | 19 +- chrome/content/zotero/xpcom/zotero.js | 35 ++-- 7 files changed, 142 insertions(+), 186 deletions(-) diff --git a/chrome/content/zotero/fileInterface.js b/chrome/content/zotero/fileInterface.js index 0509f5374e..bbd05957d6 100644 --- a/chrome/content/zotero/fileInterface.js +++ b/chrome/content/zotero/fileInterface.js @@ -238,19 +238,16 @@ var Zotero_File_Interface = new function() { Zotero.debug(`${dir} does not exist`); return dbs; } - await Zotero.File.iterateDirectory(dir, function* (iterator) { - while (true) { - let entry = yield iterator.next(); - if (entry.isDir) continue; - // online.sqlite, counterintuitively, is the default database before you sign in - if (entry.name == 'online.sqlite' || entry.name.endsWith('@www.mendeley.com.sqlite')) { - dbs.push({ - name: entry.name, - path: entry.path, - lastModified: null, - size: null - }); - } + await Zotero.File.iterateDirectory(dir, function (entry) { + if (entry.isDir) return; + // online.sqlite, counterintuitively, is the default database before you sign in + if (entry.name == 'online.sqlite' || entry.name.endsWith('@www.mendeley.com.sqlite')) { + dbs.push({ + name: entry.name, + path: entry.path, + lastModified: null, + size: null + }); } }); for (let i = 0; i < dbs.length; i++) { diff --git a/chrome/content/zotero/import/mendeley/mendeleyImport.js b/chrome/content/zotero/import/mendeley/mendeleyImport.js index 8d693ee6fe..00fff34df5 100644 --- a/chrome/content/zotero/import/mendeley/mendeleyImport.js +++ b/chrome/content/zotero/import/mendeley/mendeleyImport.js @@ -1208,19 +1208,16 @@ Zotero_Import_Mendeley.prototype.deleteNonPrimaryFiles = async function () { let filename = row.path.substr(8); Zotero.debug(`Checking for extra files in ${dir}`); - await Zotero.File.iterateDirectory(dir, function* (iterator) { - while (true) { - let entry = yield iterator.next(); - if (entry.name.startsWith('.zotero') || entry.name == filename) { - continue; - } - Zotero.debug(`Deleting ${entry.path}`); - try { - yield OS.File.remove(entry.path); - } - catch (e) { - Zotero.logError(e); - } + await Zotero.File.iterateDirectory(dir, async function (entry) { + if (entry.name.startsWith('.zotero') || entry.name == filename) { + return; + } + Zotero.debug(`Deleting ${entry.path}`); + try { + await OS.File.remove(entry.path); + } + catch (e) { + Zotero.logError(e); } }); } diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js index 0db4985781..fffce7c66c 100644 --- a/chrome/content/zotero/xpcom/attachments.js +++ b/chrome/content/zotero/xpcom/attachments.js @@ -2113,21 +2113,15 @@ Zotero.Attachments = new function(){ var parent = OS.Path.dirname(path); var iterator = new OS.File.DirectoryIterator(parent); try { - while (true) { - let entry = yield iterator.next(); + yield iterator.forEach((entry) => { if (entry.name.startsWith('.')) { - continue; + return; } numFiles++; if (numFiles > 1) { - break; + iterator.close(); } - } - } - catch (e) { - if (e != StopIteration) { - throw e; - } + }); } finally { iterator.close(); diff --git a/chrome/content/zotero/xpcom/dataDirectory.js b/chrome/content/zotero/xpcom/dataDirectory.js index a845f4bdb6..c0ca29c5b7 100644 --- a/chrome/content/zotero/xpcom/dataDirectory.js +++ b/chrome/content/zotero/xpcom/dataDirectory.js @@ -1012,21 +1012,13 @@ Zotero.DataDirectory = { // Focus the first file/folder in the old directory else if (index == 2) { try { - let it = new OS.File.DirectoryIterator(oldDir); - let entry; - try { - entry = yield it.next(); - } - catch (e) { - if (e != StopIteration) { - throw e; - } - } - finally { - it.close(); - } - if (entry) { - yield Zotero.File.reveal(entry.path); + let firstEntry; + yield Zotero.File.iterateDirectory(oldDir, function (entry, index, iterator) { + firstEntry = entry; + iterator.close(); + }); + if (firstEntry) { + yield Zotero.File.reveal(firstEntry.path); } // Focus the database file in the new directory yield Zotero.File.reveal(OS.Path.join(newDir, this.getDatabaseFilename())); diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js index af7feb4813..ddb4842dd9 100644 --- a/chrome/content/zotero/xpcom/file.js +++ b/chrome/content/zotero/xpcom/file.js @@ -562,51 +562,39 @@ Zotero.File = new function(){ /** * @return {Promise} */ - this.directoryIsEmpty = Zotero.Promise.coroutine(function* (path) { - var it = new OS.File.DirectoryIterator(path); + this.directoryIsEmpty = async function (path) { + var iterator = new OS.File.DirectoryIterator(path); + var empty = true; try { - let entry = yield it.next(); - return false; - } - catch (e) { - if (e != StopIteration) { - throw e; - } + await iterator.forEach(() => { + iterator.close(); + empty = false; + }); } finally { - it.close(); + iterator.close(); } - return true; - }); + return empty; + }; /** - * Run a generator with an OS.File.DirectoryIterator, closing the - * iterator when done. Promises yielded by the generator are awaited. + * Run a function on each entry in a directory * - * The DirectoryIterator is passed as the first parameter to the generator. + * 'entry' is an instance of OS.File.DirectoryIterator.Entry: * - * Zotero.File.iterateDirectory(path, function* (iterator) { - * while (true) { - * let entry = yield iterator.next(); - * let contents = yield Zotero.File.getContentsAsync(entry.path); - * [...] - * } - * }) + * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/OSFile.jsm/OS.File.DirectoryIterator.Entry * * @return {Promise} */ - this.iterateDirectory = function (path, generator) { + this.iterateDirectory = async function (path, onEntry) { var iterator = new OS.File.DirectoryIterator(path); - return Zotero.Promise.coroutine(generator)(iterator) - .catch(function (e) { - if (e != StopIteration) { - throw e; - } - }) - .finally(function () { + try { + await iterator.forEach(onEntry); + } + finally { iterator.close(); - }); + } } @@ -681,85 +669,82 @@ Zotero.File = new function(){ Zotero.debug("Moving files in " + oldDir); - yield Zotero.File.iterateDirectory(oldDir, function* (iterator) { - while (true) { - let entry = yield iterator.next(); - let dest = newDir + entry.path.substr(rootDir.length); - - // entry.isDir can be false for some reason on Travis, causing spurious test failures - if (Zotero.automatedTest && !entry.isDir && (yield OS.File.stat(entry.path)).isDir) { - Zotero.debug("Overriding isDir for " + entry.path); - entry.isDir = true; + yield Zotero.File.iterateDirectory(oldDir, async function (entry) { + var dest = newDir + entry.path.substr(rootDir.length); + + // entry.isDir can be false for some reason on Travis, causing spurious test failures + if (Zotero.automatedTest && !entry.isDir && (await OS.File.stat(entry.path)).isDir) { + Zotero.debug("Overriding isDir for " + entry.path); + entry.isDir = true; + } + + // Move files in directory + if (!entry.isDir) { + try { + await OS.File.move( + entry.path, + dest, + { + noOverwrite: options + && options.noOverwrite + && options.noOverwrite(entry.path) + } + ); } + catch (e) { + checkError(e); + Zotero.debug("Error moving " + entry.path); + addError(e); + } + } + else { + // Move directory with external command if possible and the directory doesn't + // already exist in target + let moved = false; - // Move files in directory - if (!entry.isDir) { + if (useCmd && !(await OS.File.exists(dest))) { + Zotero.debug(`Moving ${entry.path} with ${cmd}`); + let args = [entry.path, dest]; try { - yield OS.File.move( - entry.path, - dest, - { - noOverwrite: options - && options.noOverwrite - && options.noOverwrite(entry.path) - } - ); + await Zotero.Utilities.Internal.exec(cmd, args); + moved = true; } catch (e) { checkError(e); - Zotero.debug("Error moving " + entry.path); - addError(e); + Zotero.debug(e, 1); } } - else { - // Move directory with external command if possible and the directory doesn't - // already exist in target - let moved = false; - - if (useCmd && !(yield OS.File.exists(dest))) { - Zotero.debug(`Moving ${entry.path} with ${cmd}`); - let args = [entry.path, dest]; - try { - yield Zotero.Utilities.Internal.exec(cmd, args); - moved = true; - } - catch (e) { - checkError(e); - Zotero.debug(e, 1); - } + + + // If can't use command, try moving with OS.File.move(). Technically this is + // unsupported for directories, but it works on all platforms as long as noCopy + // is set (and on some platforms regardless) + if (!moved && useFunction) { + Zotero.debug(`Moving ${entry.path} with OS.File`); + try { + await OS.File.move( + entry.path, + dest, + { + noCopy: true + } + ); + moved = true; } - - - // If can't use command, try moving with OS.File.move(). Technically this is - // unsupported for directories, but it works on all platforms as long as noCopy - // is set (and on some platforms regardless) - if (!moved && useFunction) { - Zotero.debug(`Moving ${entry.path} with OS.File`); - try { - yield OS.File.move( - entry.path, - dest, - { - noCopy: true - } - ); - moved = true; - } - catch (e) { - checkError(e); - Zotero.debug(e, 1); - } + catch (e) { + checkError(e); + Zotero.debug(e, 1); } - - // Otherwise, recurse into subdirectories to copy files individually - if (!moved) { - try { - yield moveSubdirs(entry.path, depth - 1); - } - catch (e) { - checkError(e); - addError(e); - } + } + + // Otherwise, recurse into subdirectories to copy files individually + if (!moved) { + try { + await moveSubdirs(entry.path, depth - 1); + } + catch (e) { + checkError(e); + addError(e); } } } @@ -983,11 +968,8 @@ Zotero.File = new function(){ unixMode: 0o755 }); - return this.iterateDirectory(source, function* (iterator) { - while (true) { - let entry = yield iterator.next(); - yield OS.File.copy(entry.path, OS.Path.join(target, entry.name)); - } + return this.iterateDirectory(source, function (entry) { + return OS.File.copy(entry.path, OS.Path.join(target, entry.name)); }) }); diff --git a/chrome/content/zotero/xpcom/profile.js b/chrome/content/zotero/xpcom/profile.js index 4a06a71f7d..136b2f8ff0 100644 --- a/chrome/content/zotero/xpcom/profile.js +++ b/chrome/content/zotero/xpcom/profile.js @@ -283,17 +283,14 @@ Zotero.Profile = { */ _getProfilesInDir: Zotero.Promise.coroutine(function* (profilesDir) { var dirs = []; - yield Zotero.File.iterateDirectory(profilesDir, function* (iterator) { - while (true) { - let entry = yield iterator.next(); - // entry.isDir can be false for some reason on Travis, causing spurious test failures - if (Zotero.automatedTest && !entry.isDir && (yield OS.File.stat(entry.path)).isDir) { - Zotero.debug("Overriding isDir for " + entry.path); - entry.isDir = true; - } - if (entry.isDir && (yield OS.File.exists(OS.Path.join(entry.path, "prefs.js")))) { - dirs.push(entry.path); - } + yield Zotero.File.iterateDirectory(profilesDir, async function (entry) { + // entry.isDir can be false for some reason on Travis, causing spurious test failures + if (Zotero.automatedTest && !entry.isDir && (await OS.File.stat(entry.path)).isDir) { + Zotero.debug("Overriding isDir for " + entry.path); + entry.isDir = true; + } + if (entry.isDir && (await OS.File.exists(OS.Path.join(entry.path, "prefs.js")))) { + dirs.push(entry.path); } }); return dirs; diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index c8d2c20b7a..f8d8ccb3d1 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -499,28 +499,25 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); let lastError; // Delete all files in directory rather than removing directory, in case it's // a symlink - yield Zotero.File.iterateDirectory(dataDir, function* (iterator) { - while (true) { - let entry = yield iterator.next(); - // Don't delete some files - if (entry.name == 'pipes') { - continue; + yield Zotero.File.iterateDirectory(dataDir, async function (entry) { + // Don't delete some files + if (entry.name == 'pipes') { + return; + } + Zotero.debug("Deleting " + entry.path); + try { + if (entry.isDir) { + await OS.File.removeDir(entry.path); } - Zotero.debug("Deleting " + entry.path); - try { - if (entry.isDir) { - yield OS.File.removeDir(entry.path); - } - else { - yield OS.File.remove(entry.path); - } - } - // Keep trying to delete as much as we can - catch (e) { - lastError = e; - Zotero.logError(e); + else { + await OS.File.remove(entry.path); } } + // Keep trying to delete as much as we can + catch (e) { + lastError = e; + Zotero.logError(e); + } }); if (lastError) { throw lastError; From cde5db463a039dbe410c79f25d976dbd7ead22c7 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 26 Sep 2018 02:49:01 -0400 Subject: [PATCH 23/67] Fx60: Restore default prefs in tests Default prefs from legacy extensions don't seem to be used anymore --- test/runtests.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/runtests.sh b/test/runtests.sh index eb36ed7840..8d003db806 100755 --- a/test/runtests.sh +++ b/test/runtests.sh @@ -144,7 +144,10 @@ if [ ! -f "$PDF_TOOLS_CACHE_DIR/$PDF_TOOLS_VERSION" ]; then fi cp -R $PDF_TOOLS_CACHE_DIR $PDF_TOOLS_DIR -cat < "$PROFILE/prefs.js" +# Add default prefs, which are apparently no longer read from extensions in Firefox 60 +cat "$ZOTERO_PATH/defaults/preferences/zotero.js" > "$PROFILE/prefs.js" + +cat <> "$PROFILE/prefs.js" user_pref("app.update.enabled", false); user_pref("extensions.autoDisableScopes", 0); user_pref("browser.dom.window.dump.enabled", true); From 75daaa3b7c2b052ab9ca7fd99813c0189e95db43 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 26 Sep 2018 02:50:05 -0400 Subject: [PATCH 24/67] Fix incorrect 'return' in test --- test/tests/schemaTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/schemaTest.js b/test/tests/schemaTest.js index 06e9f407ca..b9f9ef523d 100644 --- a/test/tests/schemaTest.js +++ b/test/tests/schemaTest.js @@ -15,7 +15,7 @@ describe("Zotero.Schema", function() { describe("#updateSchema()", function () { it("should set last client version", function* () { var sql = "REPLACE INTO settings (setting, key, value) VALUES ('client', 'lastVersion', ?)"; - return Zotero.DB.queryAsync(sql, "5.0old"); + yield Zotero.DB.queryAsync(sql, "5.0old"); yield Zotero.Schema.updateSchema(); From d89fe8e80ab7d16cfc889cadf5d334dbd2696ec5 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 26 Sep 2018 02:50:35 -0400 Subject: [PATCH 25/67] Fx60: Add extensions.legacy.enabled for tests --- test/runtests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.sh b/test/runtests.sh index 8d003db806..c23c3d8eb2 100755 --- a/test/runtests.sh +++ b/test/runtests.sh @@ -169,6 +169,7 @@ user_pref("extensions.zotero.reportTranslationFailure", false); user_pref("extensions.zotero.httpServer.enabled", true); user_pref("extensions.zotero.backup.numBackups", 0); user_pref("extensions.zotero.sync.autoSync", false); +user_pref("extensions.legacy.enabled", true); user_pref("xpinstall.signatures.required", false); user_pref("xpinstall.whitelist.required", false); user_pref("datareporting.healthreport.uploadEnabled", false); From 7504086a5ef4c22f0e50e8d6a97ad88bccc56169 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 26 Sep 2018 02:52:45 -0400 Subject: [PATCH 26/67] Use modified Firefox executable from zotero-standalone-build in tests --- test/runtests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/runtests.sh b/test/runtests.sh index c23c3d8eb2..edc3e1b53a 100755 --- a/test/runtests.sh +++ b/test/runtests.sh @@ -17,9 +17,9 @@ function makePath { if [ -z "$FX_EXECUTABLE" ]; then if [ "`uname`" == "Darwin" ]; then - FX_EXECUTABLE="/Applications/Firefox Unbranded.app/Contents/MacOS/firefox" + FX_EXECUTABLE="$( dirname "$ROOT_DIR" )/zotero-standalone-build/xulrunner/Firefox.app/Contents/MacOS/firefox" else - FX_EXECUTABLE="firefox" + FX_EXECUTABLE="$( dirname "$ROOT_DIR" )/../zotero-standalone-build/xulrunner/firefox-x86_64/firefox" fi fi From a8a4d13b7217422f3bda8e79778225d61d684200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adomas=20Ven=C4=8Dkauskas?= Date: Wed, 26 Sep 2018 14:43:55 +0300 Subject: [PATCH 27/67] Fx60: Fix citation dialog height --- chrome/content/zotero/integration/quickFormat.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/chrome/content/zotero/integration/quickFormat.js b/chrome/content/zotero/integration/quickFormat.js index 3f147adc4d..a9adba4ef1 100644 --- a/chrome/content/zotero/integration/quickFormat.js +++ b/chrome/content/zotero/integration/quickFormat.js @@ -77,6 +77,12 @@ var Zotero_QuickFormat = new function () { qfb.setAttribute("square", "true"); } + // With fx60 and drawintitlebar=true Firefox calculates the minHeight + // as titlebar+maincontent, so we have hack around that here. + if (Zotero.isMac && Zotero.platformMajorVersion >= 60) { + qfb.style.marginBottom = "-22px"; + } + // add labels to popup var locators = Zotero.Cite.labels; var menu = document.getElementById("locator-label"); From 76978aa9124dfc53e3e2235114b604e096e159f6 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Sun, 13 Jan 2019 18:01:08 -0500 Subject: [PATCH 28/67] Fx60: Fix protocol handler These will need to be redone for Electron, but this keeps them working in the meantime. --- chrome/content/zotero/debugViewer.html | 5 ++ chrome/content/zotero/xpcom/report.js | 14 +++- chrome/skin/default/zotero/report/detail.css | 8 ++ components/zotero-protocol-handler.js | 85 ++++++-------------- 4 files changed, 50 insertions(+), 62 deletions(-) diff --git a/chrome/content/zotero/debugViewer.html b/chrome/content/zotero/debugViewer.html index b7c6fa626f..3a3f785d4c 100644 --- a/chrome/content/zotero/debugViewer.html +++ b/chrome/content/zotero/debugViewer.html @@ -9,6 +9,11 @@