diff options
Diffstat (limited to 'toolkit/mozapps/webextensions')
-rw-r--r-- | toolkit/mozapps/webextensions/ChromeManifestParser.jsm | 157 | ||||
-rw-r--r-- | toolkit/mozapps/webextensions/DeferredSave.jsm | 275 | ||||
-rw-r--r-- | toolkit/mozapps/webextensions/moz.build | 4 |
3 files changed, 2 insertions, 434 deletions
diff --git a/toolkit/mozapps/webextensions/ChromeManifestParser.jsm b/toolkit/mozapps/webextensions/ChromeManifestParser.jsm deleted file mode 100644 index 63f1db785..000000000 --- a/toolkit/mozapps/webextensions/ChromeManifestParser.jsm +++ /dev/null @@ -1,157 +0,0 @@ -/* 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/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = ["ChromeManifestParser"]; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/NetUtil.jsm"); - -const MSG_JAR_FLUSH = "AddonJarFlush"; - - -/** - * Sends local and remote notifications to flush a JAR file cache entry - * - * @param aJarFile - * The ZIP/XPI/JAR file as a nsIFile - */ -function flushJarCache(aJarFile) { - Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null); - Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster) - .broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path); -} - - -/** - * Parses chrome manifest files. - */ -this.ChromeManifestParser = { - - /** - * Reads and parses a chrome manifest file located at a specified URI, and all - * secondary manifests it references. - * - * @param aURI - * A nsIURI pointing to a chrome manifest. - * Typically a file: or jar: URI. - * @return Array of objects describing each manifest instruction, in the form: - * { type: instruction-type, baseURI: string-uri, args: [arguments] } - **/ - parseSync: function(aURI) { - function parseLine(aLine) { - let line = aLine.trim(); - if (line.length == 0 || line.charAt(0) == '#') - return; - let tokens = line.split(/\s+/); - let type = tokens.shift(); - if (type == "manifest") { - let uri = NetUtil.newURI(tokens.shift(), null, aURI); - data = data.concat(this.parseSync(uri)); - } else { - data.push({type: type, baseURI: baseURI, args: tokens}); - } - } - - let contents = ""; - try { - if (aURI.scheme == "jar") - contents = this._readFromJar(aURI); - else - contents = this._readFromFile(aURI); - } catch (e) { - // Silently fail. - } - - if (!contents) - return []; - - let baseURI = NetUtil.newURI(".", null, aURI).spec; - - let data = []; - let lines = contents.split("\n"); - lines.forEach(parseLine.bind(this)); - return data; - }, - - _readFromJar: function(aURI) { - let data = ""; - let entries = []; - let readers = []; - - try { - // Deconstrict URI, which can be nested jar: URIs. - let uri = aURI.clone(); - while (uri instanceof Ci.nsIJARURI) { - entries.push(uri.JAREntry); - uri = uri.JARFile; - } - - // Open the base jar. - let reader = Cc["@mozilla.org/libjar/zip-reader;1"]. - createInstance(Ci.nsIZipReader); - reader.open(uri.QueryInterface(Ci.nsIFileURL).file); - readers.push(reader); - - // Open the nested jars. - for (let i = entries.length - 1; i > 0; i--) { - let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"]. - createInstance(Ci.nsIZipReader); - innerReader.openInner(reader, entries[i]); - readers.push(innerReader); - reader = innerReader; - } - - // First entry is the actual file we want to read. - let zis = reader.getInputStream(entries[0]); - data = NetUtil.readInputStreamToString(zis, zis.available()); - } - finally { - // Close readers in reverse order. - for (let i = readers.length - 1; i >= 0; i--) { - readers[i].close(); - flushJarCache(readers[i].file); - } - } - - return data; - }, - - _readFromFile: function(aURI) { - let file = aURI.QueryInterface(Ci.nsIFileURL).file; - if (!file.exists() || !file.isFile()) - return ""; - - let data = ""; - let fis = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - try { - fis.init(file, -1, -1, false); - data = NetUtil.readInputStreamToString(fis, fis.available()); - } finally { - fis.close(); - } - return data; - }, - - /** - * Detects if there were any instructions of a specified type in a given - * chrome manifest. - * - * @param aManifest - * Manifest data, as returned by ChromeManifestParser.parseSync(). - * @param aType - * Instruction type to filter by. - * @return True if any matching instructions were found in the manifest. - */ - hasType: function(aManifest, aType) { - return aManifest.some(entry => entry.type == aType); - } -}; diff --git a/toolkit/mozapps/webextensions/DeferredSave.jsm b/toolkit/mozapps/webextensions/DeferredSave.jsm deleted file mode 100644 index 89f82b265..000000000 --- a/toolkit/mozapps/webextensions/DeferredSave.jsm +++ /dev/null @@ -1,275 +0,0 @@ -/* 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/. */ - -"use strict"; - -const Cu = Components.utils; -const Cc = Components.classes; -const Ci = Components.interfaces; - -Cu.import("resource://gre/modules/osfile.jsm"); -/* globals OS*/ -Cu.import("resource://gre/modules/Promise.jsm"); - -// Make it possible to mock out timers for testing -var MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - -this.EXPORTED_SYMBOLS = ["DeferredSave"]; - -// If delay parameter is not provided, default is 50 milliseconds. -const DEFAULT_SAVE_DELAY_MS = 50; - -Cu.import("resource://gre/modules/Log.jsm"); -// Configure a logger at the parent 'DeferredSave' level to format -// messages for all the modules under DeferredSave.* -const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave"; -var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID); -parentLogger.level = Log.Level.Warn; -var formatter = new Log.BasicFormatter(); -// Set parent logger (and its children) to append to -// the Javascript section of the Browser Console -parentLogger.addAppender(new Log.ConsoleAppender(formatter)); -// Set parent logger (and its children) to -// also append to standard out -parentLogger.addAppender(new Log.DumpAppender(formatter)); - -// Provide the ability to enable/disable logging -// messages at runtime. -// If the "extensions.logging.enabled" preference is -// missing or 'false', messages at the WARNING and higher -// severity should be logged to the JS console and standard error. -// If "extensions.logging.enabled" is set to 'true', messages -// at DEBUG and higher should go to JS console and standard error. -Cu.import("resource://gre/modules/Services.jsm"); - -const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; -const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; - -/** -* Preference listener which listens for a change in the -* "extensions.logging.enabled" preference and changes the logging level of the -* parent 'addons' level logger accordingly. -*/ -var PrefObserver = { - init: function() { - Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); - Services.obs.addObserver(this, "xpcom-shutdown", false); - this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); - }, - - observe: function(aSubject, aTopic, aData) { - if (aTopic == "xpcom-shutdown") { - Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); - Services.obs.removeObserver(this, "xpcom-shutdown"); - } - else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { - let debugLogEnabled = false; - try { - debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); - } - catch (e) { - } - if (debugLogEnabled) { - parentLogger.level = Log.Level.Debug; - } - else { - parentLogger.level = Log.Level.Warn; - } - } - } -}; - -PrefObserver.init(); - -/** - * A module to manage deferred, asynchronous writing of data files - * to disk. Writing is deferred by waiting for a specified delay after - * a request to save the data, before beginning to write. If more than - * one save request is received during the delay, all requests are - * fulfilled by a single write. - * - * @constructor - * @param aPath - * String representing the full path of the file where the data - * is to be written. - * @param aDataProvider - * Callback function that takes no argument and returns the data to - * be written. If aDataProvider returns an ArrayBufferView, the - * bytes it contains are written to the file as is. - * If aDataProvider returns a String the data are UTF-8 encoded - * and then written to the file. - * @param [optional] aDelay - * The delay in milliseconds between the first saveChanges() call - * that marks the data as needing to be saved, and when the DeferredSave - * begins writing the data to disk. Default 50 milliseconds. - */ -this.DeferredSave = function(aPath, aDataProvider, aDelay) { - // Create a new logger (child of 'DeferredSave' logger) - // for use by this particular instance of DeferredSave object - let leafName = OS.Path.basename(aPath); - let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName; - this.logger = Log.repository.getLogger(logger_id); - - // @type {Deferred|null}, null when no data needs to be written - // @resolves with the result of OS.File.writeAtomic when all writes complete - // @rejects with the error from OS.File.writeAtomic if the write fails, - // or with the error from aDataProvider() if that throws. - this._pending = null; - - // @type {Promise}, completes when the in-progress write (if any) completes, - // kept as a resolved promise at other times to simplify logic. - // Because _deferredSave() always uses _writing.then() to execute - // its next action, we don't need a special case for whether a write - // is in progress - if the previous write is complete (and the _writing - // promise is already resolved/rejected), _writing.then() starts - // the next action immediately. - // - // @resolves with the result of OS.File.writeAtomic - // @rejects with the error from OS.File.writeAtomic - this._writing = Promise.resolve(0); - - // Are we currently waiting for a write to complete - this.writeInProgress = false; - - this._path = aPath; - this._dataProvider = aDataProvider; - - this._timer = null; - - // Some counters for telemetry - // The total number of times the file was written - this.totalSaves = 0; - - // The number of times the data became dirty while - // another save was in progress - this.overlappedSaves = 0; - - // Error returned by the most recent write (if any) - this._lastError = null; - - if (aDelay && (aDelay > 0)) - this._delay = aDelay; - else - this._delay = DEFAULT_SAVE_DELAY_MS; -} - -this.DeferredSave.prototype = { - get dirty() { - return this._pending || this.writeInProgress; - }, - - get lastError() { - return this._lastError; - }, - - // Start the pending timer if data is dirty - _startTimer: function() { - if (!this._pending) { - return; - } - - this.logger.debug("Starting timer"); - if (!this._timer) - this._timer = MakeTimer(); - this._timer.initWithCallback(() => this._deferredSave(), - this._delay, Ci.nsITimer.TYPE_ONE_SHOT); - }, - - /** - * Mark the current stored data dirty, and schedule a flush to disk - * @return A Promise<integer> that will be resolved after the data is written to disk; - * the promise is resolved with the number of bytes written. - */ - saveChanges: function() { - this.logger.debug("Save changes"); - if (!this._pending) { - if (this.writeInProgress) { - this.logger.debug("Data changed while write in progress"); - this.overlappedSaves++; - } - this._pending = Promise.defer(); - // Wait until the most recent write completes or fails (if it hasn't already) - // and then restart our timer - this._writing.then(count => this._startTimer(), error => this._startTimer()); - } - return this._pending.promise; - }, - - _deferredSave: function() { - let pending = this._pending; - this._pending = null; - let writing = this._writing; - this._writing = pending.promise; - - // In either the success or the exception handling case, we don't need to handle - // the error from _writing here; it's already being handled in another then() - let toSave = null; - try { - toSave = this._dataProvider(); - } - catch (e) { - this.logger.error("Deferred save dataProvider failed", e); - writing.then(null, error => {}) - .then(count => { - pending.reject(e); - }); - return; - } - - writing.then(null, error => { return 0; }) - .then(count => { - this.logger.debug("Starting write"); - this.totalSaves++; - this.writeInProgress = true; - - OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"}) - .then( - result => { - this._lastError = null; - this.writeInProgress = false; - this.logger.debug("Write succeeded"); - pending.resolve(result); - }, - error => { - this._lastError = error; - this.writeInProgress = false; - this.logger.warn("Write failed", error); - pending.reject(error); - }); - }); - }, - - /** - * Immediately save the dirty data to disk, skipping - * the delay of normal operation. Note that the write - * still happens asynchronously in the worker - * thread from OS.File. - * - * There are four possible situations: - * 1) Nothing to flush - * 2) Data is not currently being written, in-memory copy is dirty - * 3) Data is currently being written, in-memory copy is clean - * 4) Data is being written and in-memory copy is dirty - * - * @return Promise<integer> that will resolve when all in-memory data - * has finished being flushed, returning the number of bytes - * written. If all in-memory data is clean, completes with the - * result of the most recent write. - */ - flush: function() { - // If we have pending changes, cancel our timer and set up the write - // immediately (_deferredSave queues the write for after the most - // recent write completes, if it hasn't already) - if (this._pending) { - this.logger.debug("Flush called while data is dirty"); - if (this._timer) { - this._timer.cancel(); - this._timer = null; - } - this._deferredSave(); - } - - return this._writing; - } -}; diff --git a/toolkit/mozapps/webextensions/moz.build b/toolkit/mozapps/webextensions/moz.build index d039ac68c..d437bf46d 100644 --- a/toolkit/mozapps/webextensions/moz.build +++ b/toolkit/mozapps/webextensions/moz.build @@ -31,9 +31,9 @@ EXTRA_PP_COMPONENTS += [ ] EXTRA_JS_MODULES += [ + '../extensions/ChromeManifestParser.jsm', + '../extensions/DeferredSave.jsm', 'AddonManager.jsm', - 'ChromeManifestParser.jsm', - 'DeferredSave.jsm', 'LightweightThemeManager.jsm', ] |