Move WebExtensions enabled Add-ons Manager

This commit is contained in:
NTD 2018-02-10 02:49:12 -05:00 • committed by Roy Tam
commit 358cd0404a
1100 changed files with 133 additions and 106 deletions

View file

@ -0,0 +1,39 @@
/* 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";
Components.utils.import("resource://gre/modules/ExtensionManagement.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
var namespace;
var resource;
var resProto;
function install(data, reason) {
}
function startup(data, reason) {
namespace = data.id.replace(/@.*/, "");
resource = `extension-${namespace}-api`;
resProto = Services.io.getProtocolHandler("resource")
.QueryInterface(Components.interfaces.nsIResProtocolHandler);
resProto.setSubstitution(resource, data.resourceURI);
ExtensionManagement.registerAPI(
namespace,
`resource://${resource}/schema.json`,
`resource://${resource}/api.js`);
}
function shutdown(data, reason) {
resProto.setSubstitution(resource, null);
ExtensionManagement.unregisterAPI(namespace);
}
function uninstall(data, reason) {
}

View file

@ -0,0 +1,31 @@
/* 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 = [ "ADDON_SIGNING", "REQUIRE_SIGNING" ];
// Make these non-changable properties so they can't be manipulated from other
// code in the app.
Object.defineProperty(this, "ADDON_SIGNING", {
configurable: false,
enumerable: false,
writable: false,
#ifdef MOZ_ADDON_SIGNING
value: true,
#else
value: false,
#endif
});
Object.defineProperty(this, "REQUIRE_SIGNING", {
configurable: false,
enumerable: false,
writable: false,
#ifdef MOZ_REQUIRE_SIGNING
value: true,
#else
value: false,
#endif
});

View file

@ -0,0 +1,192 @@
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const KEY_PROFILEDIR = "ProfD";
const FILE_EXTENSIONS_LOG = "extensions.log";
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
const LOGGER_FILE_PERM = parseInt("666", 8);
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
Components.utils.import("resource://gre/modules/FileUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = [ "LogManager" ];
var gDebugLogEnabled = false;
function formatLogMessage(aType, aName, aStr, aException) {
let message = aType.toUpperCase() + " " + aName + ": " + aStr;
if (aException) {
if (typeof aException == "number")
return message + ": " + Components.Exception("", aException).name;
message = message + ": " + aException;
// instanceOf doesn't work here, let's duck type
if (aException.fileName)
message = message + " (" + aException.fileName + ":" + aException.lineNumber + ")";
if (aException.message == "too much recursion")
dump(message + "\n" + aException.stack + "\n");
}
return message;
}
function getStackDetails(aException) {
// Defensively wrap all this to ensure that failing to get the message source
// doesn't stop the message from being logged
try {
if (aException) {
if (aException instanceof Ci.nsIException) {
return {
sourceName: aException.filename,
lineNumber: aException.lineNumber
};
}
if (typeof aException == "object") {
return {
sourceName: aException.fileName,
lineNumber: aException.lineNumber
};
}
}
let stackFrame = Components.stack.caller.caller.caller;
return {
sourceName: stackFrame.filename,
lineNumber: stackFrame.lineNumber
};
}
catch (e) {
return {
sourceName: null,
lineNumber: 0
};
}
}
function AddonLogger(aName) {
this.name = aName;
}
AddonLogger.prototype = {
name: null,
error: function(aStr, aException) {
let message = formatLogMessage("error", this.name, aStr, aException);
let stack = getStackDetails(aException);
let consoleMessage = Cc["@mozilla.org/scripterror;1"].
createInstance(Ci.nsIScriptError);
consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
Ci.nsIScriptError.errorFlag, "component javascript");
Services.console.logMessage(consoleMessage);
// Always dump errors, in case the Console Service isn't listening yet
dump("*** " + message + "\n");
function formatTimestamp(date) {
// Format timestamp as: "%Y-%m-%d %H:%M:%S"
let year = String(date.getFullYear());
let month = String(date.getMonth() + 1).padStart(2, "0");
let day = String(date.getDate()).padStart(2, "0");
let hours = String(date.getHours()).padStart(2, "0");
let minutes = String(date.getMinutes()).padStart(2, "0");
let seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
try {
var tstamp = new Date();
var logfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_LOG]);
var stream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
stream.init(logfile, 0x02 | 0x08 | 0x10, LOGGER_FILE_PERM, 0); // write, create, append
var writer = Cc["@mozilla.org/intl/converter-output-stream;1"].
createInstance(Ci.nsIConverterOutputStream);
writer.init(stream, "UTF-8", 0, 0x0000);
writer.writeString(formatTimestamp(tstamp) + " " +
message + " at " + stack.sourceName + ":" +
stack.lineNumber + "\n");
writer.close();
}
catch (e) { }
},
warn: function(aStr, aException) {
let message = formatLogMessage("warn", this.name, aStr, aException);
let stack = getStackDetails(aException);
let consoleMessage = Cc["@mozilla.org/scripterror;1"].
createInstance(Ci.nsIScriptError);
consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
Ci.nsIScriptError.warningFlag, "component javascript");
Services.console.logMessage(consoleMessage);
if (gDebugLogEnabled)
dump("*** " + message + "\n");
},
log: function(aStr, aException) {
if (gDebugLogEnabled) {
let message = formatLogMessage("log", this.name, aStr, aException);
dump("*** " + message + "\n");
Services.console.logStringMessage(message);
}
}
};
this.LogManager = {
getLogger: function(aName, aTarget) {
let logger = new AddonLogger(aName);
if (aTarget) {
["error", "warn", "log"].forEach(function(name) {
let fname = name.toUpperCase();
delete aTarget[fname];
aTarget[fname] = function(aStr, aException) {
logger[name](aStr, aException);
};
});
}
return logger;
}
};
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) {
try {
gDebugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED);
}
catch (e) {
gDebugLogEnabled = false;
}
}
}
};
PrefObserver.init();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,522 @@
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
/* globals AddonManagerPrivate*/
Cu.import("resource://gre/modules/FileUtils.jsm");
const KEY_PROFILEDIR = "ProfD";
const FILE_DATABASE = "addons.sqlite";
const LAST_DB_SCHEMA = 4;
// Add-on properties present in the columns of the database
const PROP_SINGLE = ["id", "type", "name", "version", "creator", "description",
"fullDescription", "developerComments", "eula",
"homepageURL", "supportURL", "contributionURL",
"contributionAmount", "averageRating", "reviewCount",
"reviewURL", "totalDownloads", "weeklyDownloads",
"dailyUsers", "sourceURI", "repositoryStatus", "size",
"updateDate"];
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.repository.sqlmigrator";
// Create a new logger for use by the Addons Repository SQL Migrator
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);
this.EXPORTED_SYMBOLS = ["AddonRepository_SQLiteMigrator"];
this.AddonRepository_SQLiteMigrator = {
/**
* Migrates data from a previous SQLite version of the
* database to the JSON version.
*
* @param structFunctions an object that contains functions
* to create the various objects used
* in the new JSON format
* @param aCallback A callback to be called when migration
* finishes, with the results in an array
* @returns bool True if a migration will happen (DB was
* found and succesfully opened)
*/
migrate: function(aCallback) {
if (!this._openConnection()) {
this._closeConnection();
aCallback([]);
return false;
}
logger.debug("Importing addon repository from previous " + FILE_DATABASE + " storage.");
this._retrieveStoredData((results) => {
this._closeConnection();
let resultArray = Object.keys(results).map(k => results[k]);
logger.debug(resultArray.length + " addons imported.")
aCallback(resultArray);
});
return true;
},
/**
* Synchronously opens a new connection to the database file.
*
* @return bool Whether the DB was opened successfully.
*/
_openConnection: function() {
delete this.connection;
let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
if (!dbfile.exists())
return false;
try {
this.connection = Services.storage.openUnsharedDatabase(dbfile);
} catch (e) {
return false;
}
this.connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
// Any errors in here should rollback
try {
this.connection.beginTransaction();
switch (this.connection.schemaVersion) {
case 0:
return false;
case 1:
logger.debug("Upgrading database schema to version 2");
this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN width INTEGER");
this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN height INTEGER");
this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailWidth INTEGER");
this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailHeight INTEGER");
case 2:
logger.debug("Upgrading database schema to version 3");
this.connection.createTable("compatibility_override",
"addon_internal_id INTEGER, " +
"num INTEGER, " +
"type TEXT, " +
"minVersion TEXT, " +
"maxVersion TEXT, " +
"appID TEXT, " +
"appMinVersion TEXT, " +
"appMaxVersion TEXT, " +
"PRIMARY KEY (addon_internal_id, num)");
case 3:
logger.debug("Upgrading database schema to version 4");
this.connection.createTable("icon",
"addon_internal_id INTEGER, " +
"size INTEGER, " +
"url TEXT, " +
"PRIMARY KEY (addon_internal_id, size)");
this._createIndices();
this._createTriggers();
this.connection.schemaVersion = LAST_DB_SCHEMA;
case LAST_DB_SCHEMA:
break;
default:
return false;
}
this.connection.commitTransaction();
} catch (e) {
logger.error("Failed to open " + FILE_DATABASE + ". Data import will not happen.", e);
this.logSQLError(this.connection.lastError, this.connection.lastErrorString);
this.connection.rollbackTransaction();
return false;
}
return true;
},
_closeConnection: function() {
for (let key in this.asyncStatementsCache) {
let stmt = this.asyncStatementsCache[key];
stmt.finalize();
}
this.asyncStatementsCache = {};
if (this.connection)
this.connection.asyncClose();
delete this.connection;
},
/**
* Asynchronously retrieve all add-ons from the database, and pass it
* to the specified callback
*
* @param aCallback
* The callback to pass the add-ons back to
*/
_retrieveStoredData: function(aCallback) {
let addons = {};
// Retrieve all data from the addon table
let getAllAddons = () => {
this.getAsyncStatement("getAllAddons").executeAsync({
handleResult: aResults => {
let row = null;
while ((row = aResults.getNextRow())) {
let internal_id = row.getResultByName("internal_id");
addons[internal_id] = this._makeAddonFromAsyncRow(row);
}
},
handleError: this.asyncErrorLogger,
handleCompletion: function(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving add-ons from database. Returning empty results");
aCallback({});
return;
}
getAllDevelopers();
}
});
}
// Retrieve all data from the developer table
let getAllDevelopers = () => {
this.getAsyncStatement("getAllDevelopers").executeAsync({
handleResult: aResults => {
let row = null;
while ((row = aResults.getNextRow())) {
let addon_internal_id = row.getResultByName("addon_internal_id");
if (!(addon_internal_id in addons)) {
logger.warn("Found a developer not linked to an add-on in database");
continue;
}
let addon = addons[addon_internal_id];
if (!addon.developers)
addon.developers = [];
addon.developers.push(this._makeDeveloperFromAsyncRow(row));
}
},
handleError: this.asyncErrorLogger,
handleCompletion: function(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving developers from database. Returning empty results");
aCallback({});
return;
}
getAllScreenshots();
}
});
}
// Retrieve all data from the screenshot table
let getAllScreenshots = () => {
this.getAsyncStatement("getAllScreenshots").executeAsync({
handleResult: aResults => {
let row = null;
while ((row = aResults.getNextRow())) {
let addon_internal_id = row.getResultByName("addon_internal_id");
if (!(addon_internal_id in addons)) {
logger.warn("Found a screenshot not linked to an add-on in database");
continue;
}
let addon = addons[addon_internal_id];
if (!addon.screenshots)
addon.screenshots = [];
addon.screenshots.push(this._makeScreenshotFromAsyncRow(row));
}
},
handleError: this.asyncErrorLogger,
handleCompletion: function(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving screenshots from database. Returning empty results");
aCallback({});
return;
}
getAllCompatOverrides();
}
});
}
let getAllCompatOverrides = () => {
this.getAsyncStatement("getAllCompatOverrides").executeAsync({
handleResult: aResults => {
let row = null;
while ((row = aResults.getNextRow())) {
let addon_internal_id = row.getResultByName("addon_internal_id");
if (!(addon_internal_id in addons)) {
logger.warn("Found a compatibility override not linked to an add-on in database");
continue;
}
let addon = addons[addon_internal_id];
if (!addon.compatibilityOverrides)
addon.compatibilityOverrides = [];
addon.compatibilityOverrides.push(this._makeCompatOverrideFromAsyncRow(row));
}
},
handleError: this.asyncErrorLogger,
handleCompletion: function(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving compatibility overrides from database. Returning empty results");
aCallback({});
return;
}
getAllIcons();
}
});
}
let getAllIcons = () => {
this.getAsyncStatement("getAllIcons").executeAsync({
handleResult: aResults => {
let row = null;
while ((row = aResults.getNextRow())) {
let addon_internal_id = row.getResultByName("addon_internal_id");
if (!(addon_internal_id in addons)) {
logger.warn("Found an icon not linked to an add-on in database");
continue;
}
let addon = addons[addon_internal_id];
let { size, url } = this._makeIconFromAsyncRow(row);
addon.icons[size] = url;
if (size == 32)
addon.iconURL = url;
}
},
handleError: this.asyncErrorLogger,
handleCompletion: function(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving icons from database. Returning empty results");
aCallback({});
return;
}
let returnedAddons = {};
for (let id in addons) {
let addon = addons[id];
returnedAddons[addon.id] = addon;
}
aCallback(returnedAddons);
}
});
}
// Begin asynchronous process
getAllAddons();
},
// A cache of statements that are used and need to be finalized on shutdown
asyncStatementsCache: {},
/**
* Gets a cached async statement or creates a new statement if it doesn't
* already exist.
*
* @param aKey
* A unique key to reference the statement
* @return a mozIStorageAsyncStatement for the SQL corresponding to the
* unique key
*/
getAsyncStatement: function(aKey) {
if (aKey in this.asyncStatementsCache)
return this.asyncStatementsCache[aKey];
let sql = this.queries[aKey];
try {
return this.asyncStatementsCache[aKey] = this.connection.createAsyncStatement(sql);
} catch (e) {
logger.error("Error creating statement " + aKey + " (" + sql + ")");
throw Components.Exception("Error creating statement " + aKey + " (" + sql + "): " + e,
e.result);
}
},
// The queries used by the database
queries: {
getAllAddons: "SELECT internal_id, id, type, name, version, " +
"creator, creatorURL, description, fullDescription, " +
"developerComments, eula, homepageURL, supportURL, " +
"contributionURL, contributionAmount, averageRating, " +
"reviewCount, reviewURL, totalDownloads, weeklyDownloads, " +
"dailyUsers, sourceURI, repositoryStatus, size, updateDate " +
"FROM addon",
getAllDevelopers: "SELECT addon_internal_id, name, url FROM developer " +
"ORDER BY addon_internal_id, num",
getAllScreenshots: "SELECT addon_internal_id, url, width, height, " +
"thumbnailURL, thumbnailWidth, thumbnailHeight, caption " +
"FROM screenshot ORDER BY addon_internal_id, num",
getAllCompatOverrides: "SELECT addon_internal_id, type, minVersion, " +
"maxVersion, appID, appMinVersion, appMaxVersion " +
"FROM compatibility_override " +
"ORDER BY addon_internal_id, num",
getAllIcons: "SELECT addon_internal_id, size, url FROM icon " +
"ORDER BY addon_internal_id, size",
},
/**
* Make add-on structure from an asynchronous row.
*
* @param aRow
* The asynchronous row to use
* @return The created add-on
*/
_makeAddonFromAsyncRow: function(aRow) {
// This is intentionally not an AddonSearchResult object in order
// to allow AddonDatabase._parseAddon to parse it, same as if it
// was read from the JSON database.
let addon = { icons: {} };
for (let prop of PROP_SINGLE) {
addon[prop] = aRow.getResultByName(prop)
}
return addon;
},
/**
* Make a developer from an asynchronous row
*
* @param aRow
* The asynchronous row to use
* @return The created developer
*/
_makeDeveloperFromAsyncRow: function(aRow) {
let name = aRow.getResultByName("name");
let url = aRow.getResultByName("url")
return new AddonManagerPrivate.AddonAuthor(name, url);
},
/**
* Make a screenshot from an asynchronous row
*
* @param aRow
* The asynchronous row to use
* @return The created screenshot
*/
_makeScreenshotFromAsyncRow: function(aRow) {
let url = aRow.getResultByName("url");
let width = aRow.getResultByName("width");
let height = aRow.getResultByName("height");
let thumbnailURL = aRow.getResultByName("thumbnailURL");
let thumbnailWidth = aRow.getResultByName("thumbnailWidth");
let thumbnailHeight = aRow.getResultByName("thumbnailHeight");
let caption = aRow.getResultByName("caption");
return new AddonManagerPrivate.AddonScreenshot(url, width, height, thumbnailURL,
thumbnailWidth, thumbnailHeight, caption);
},
/**
* Make a CompatibilityOverride from an asynchronous row
*
* @param aRow
* The asynchronous row to use
* @return The created CompatibilityOverride
*/
_makeCompatOverrideFromAsyncRow: function(aRow) {
let type = aRow.getResultByName("type");
let minVersion = aRow.getResultByName("minVersion");
let maxVersion = aRow.getResultByName("maxVersion");
let appID = aRow.getResultByName("appID");
let appMinVersion = aRow.getResultByName("appMinVersion");
let appMaxVersion = aRow.getResultByName("appMaxVersion");
return new AddonManagerPrivate.AddonCompatibilityOverride(type,
minVersion,
maxVersion,
appID,
appMinVersion,
appMaxVersion);
},
/**
* Make an icon from an asynchronous row
*
* @param aRow
* The asynchronous row to use
* @return An object containing the size and URL of the icon
*/
_makeIconFromAsyncRow: function(aRow) {
let size = aRow.getResultByName("size");
let url = aRow.getResultByName("url");
return { size: size, url: url };
},
/**
* A helper function to log an SQL error.
*
* @param aError
* The storage error code associated with the error
* @param aErrorString
* An error message
*/
logSQLError: function(aError, aErrorString) {
logger.error("SQL error " + aError + ": " + aErrorString);
},
/**
* A helper function to log any errors that occur during async statements.
*
* @param aError
* A mozIStorageError to log
*/
asyncErrorLogger: function(aError) {
logger.error("Async SQL error " + aError.result + ": " + aError.message);
},
/**
* Synchronously creates the triggers in the database.
*/
_createTriggers: function() {
this.connection.executeSimpleSQL("DROP TRIGGER IF EXISTS delete_addon");
this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " +
"ON addon BEGIN " +
"DELETE FROM developer WHERE addon_internal_id=old.internal_id; " +
"DELETE FROM screenshot WHERE addon_internal_id=old.internal_id; " +
"DELETE FROM compatibility_override WHERE addon_internal_id=old.internal_id; " +
"DELETE FROM icon WHERE addon_internal_id=old.internal_id; " +
"END");
},
/**
* Synchronously creates the indices in the database.
*/
_createIndices: function() {
this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS developer_idx " +
"ON developer (addon_internal_id)");
this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS screenshot_idx " +
"ON screenshot (addon_internal_id)");
this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS compatibility_override_idx " +
"ON compatibility_override (addon_internal_id)");
this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS icon_idx " +
"ON icon (addon_internal_id)");
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,934 @@
/* 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/. */
/**
* The AddonUpdateChecker is responsible for retrieving the update information
* from an add-on's remote update manifest.
*/
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "AddonUpdateChecker" ];
const TIMEOUT = 60 * 1000;
const PREFIX_NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
const PREFIX_ITEM = "urn:mozilla:item:";
const PREFIX_EXTENSION = "urn:mozilla:extension:";
const PREFIX_THEME = "urn:mozilla:theme:";
const TOOLKIT_ID = "toolkit@mozilla.org"
const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts";
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
"resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
"resource://gre/modules/ServiceRequest.jsm");
// Shared code for suppressing bad cert dialogs.
XPCOMUtils.defineLazyGetter(this, "CertUtils", function() {
let certUtils = {};
Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
return certUtils;
});
var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
getService(Ci.nsIRDFService);
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.update-checker";
// Create a new logger for use by the Addons Update Checker
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);
/**
* A serialisation method for RDF data that produces an identical string
* for matching RDF graphs.
* The serialisation is not complete, only assertions stemming from a given
* resource are included, multiple references to the same resource are not
* permitted, and the RDF prolog and epilog are not included.
* RDF Blob and Date literals are not supported.
*/
function RDFSerializer() {
this.cUtils = Cc["@mozilla.org/rdf/container-utils;1"].
getService(Ci.nsIRDFContainerUtils);
this.resources = [];
}
RDFSerializer.prototype = {
INDENT: " ", // The indent used for pretty-printing
resources: null, // Array of the resources that have been found
/**
* Escapes characters from a string that should not appear in XML.
*
* @param aString
* The string to be escaped
* @return a string with all characters invalid in XML character data
* converted to entity references.
*/
escapeEntities: function(aString) {
aString = aString.replace(/&/g, "&");
aString = aString.replace(/</g, "&lt;");
aString = aString.replace(/>/g, "&gt;");
return aString.replace(/"/g, "&quot;");
},
/**
* Serializes all the elements of an RDF container.
*
* @param aDs
* The RDF datasource
* @param aContainer
* The RDF container to output the child elements of
* @param aIndent
* The current level of indent for pretty-printing
* @return a string containing the serialized elements.
*/
serializeContainerItems: function(aDs, aContainer, aIndent) {
var result = "";
var items = aContainer.GetElements();
while (items.hasMoreElements()) {
var item = items.getNext().QueryInterface(Ci.nsIRDFResource);
result += aIndent + "<RDF:li>\n"
result += this.serializeResource(aDs, item, aIndent + this.INDENT);
result += aIndent + "</RDF:li>\n"
}
return result;
},
/**
* Serializes all em:* (see EM_NS) properties of an RDF resource except for
* the em:signature property. As this serialization is to be compared against
* the manifest signature it cannot contain the em:signature property itself.
*
* @param aDs
* The RDF datasource
* @param aResource
* The RDF resource that contains the properties to serialize
* @param aIndent
* The current level of indent for pretty-printing
* @return a string containing the serialized properties.
* @throws if the resource contains a property that cannot be serialized
*/
serializeResourceProperties: function(aDs, aResource, aIndent) {
var result = "";
var items = [];
var arcs = aDs.ArcLabelsOut(aResource);
while (arcs.hasMoreElements()) {
var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource);
if (arc.ValueUTF8.substring(0, PREFIX_NS_EM.length) != PREFIX_NS_EM)
continue;
var prop = arc.ValueUTF8.substring(PREFIX_NS_EM.length);
if (prop == "signature")
continue;
var targets = aDs.GetTargets(aResource, arc, true);
while (targets.hasMoreElements()) {
var target = targets.getNext();
if (target instanceof Ci.nsIRDFResource) {
var item = aIndent + "<em:" + prop + ">\n";
item += this.serializeResource(aDs, target, aIndent + this.INDENT);
item += aIndent + "</em:" + prop + ">\n";
items.push(item);
}
else if (target instanceof Ci.nsIRDFLiteral) {
items.push(aIndent + "<em:" + prop + ">" +
this.escapeEntities(target.Value) + "</em:" + prop + ">\n");
}
else if (target instanceof Ci.nsIRDFInt) {
items.push(aIndent + "<em:" + prop + " NC:parseType=\"Integer\">" +
target.Value + "</em:" + prop + ">\n");
}
else {
throw Components.Exception("Cannot serialize unknown literal type");
}
}
}
items.sort();
result += items.join("");
return result;
},
/**
* Recursively serializes an RDF resource and all resources it links to.
* This will only output EM_NS properties and will ignore any em:signature
* property.
*
* @param aDs
* The RDF datasource
* @param aResource
* The RDF resource to serialize
* @param aIndent
* The current level of indent for pretty-printing. If undefined no
* indent will be added
* @return a string containing the serialized resource.
* @throws if the RDF data contains multiple references to the same resource.
*/
serializeResource: function(aDs, aResource, aIndent) {
if (this.resources.indexOf(aResource) != -1 ) {
// We cannot output multiple references to the same resource.
throw Components.Exception("Cannot serialize multiple references to " + aResource.Value);
}
if (aIndent === undefined)
aIndent = "";
this.resources.push(aResource);
var container = null;
var type = "Description";
if (this.cUtils.IsSeq(aDs, aResource)) {
type = "Seq";
container = this.cUtils.MakeSeq(aDs, aResource);
}
else if (this.cUtils.IsAlt(aDs, aResource)) {
type = "Alt";
container = this.cUtils.MakeAlt(aDs, aResource);
}
else if (this.cUtils.IsBag(aDs, aResource)) {
type = "Bag";
container = this.cUtils.MakeBag(aDs, aResource);
}
var result = aIndent + "<RDF:" + type;
if (!gRDF.IsAnonymousResource(aResource))
result += " about=\"" + this.escapeEntities(aResource.ValueUTF8) + "\"";
result += ">\n";
if (container)
result += this.serializeContainerItems(aDs, container, aIndent + this.INDENT);
result += this.serializeResourceProperties(aDs, aResource, aIndent + this.INDENT);
result += aIndent + "</RDF:" + type + ">\n";
return result;
}
}
/**
* Sanitizes the update URL in an update item, as returned by
* parseRDFManifest and parseJSONManifest. Ensures that:
*
* - The URL is secure, or secured by a strong enough hash.
* - The security principal of the update manifest has permission to
* load the URL.
*
* @param aUpdate
* The update item to sanitize.
* @param aRequest
* The XMLHttpRequest used to load the manifest.
* @param aHashPattern
* The regular expression used to validate the update hash.
* @param aHashString
* The human-readable string specifying which hash functions
* are accepted.
*/
function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) {
if (aUpdate.updateURL) {
let scriptSecurity = Services.scriptSecurityManager;
let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel);
try {
// This logs an error on failure, so no need to log it a second time
scriptSecurity.checkLoadURIStrWithPrincipal(principal, aUpdate.updateURL,
scriptSecurity.DISALLOW_SCRIPT);
} catch (e) {
delete aUpdate.updateURL;
return;
}
if (AddonManager.checkUpdateSecurity &&
!aUpdate.updateURL.startsWith("https:") &&
!aHashPattern.test(aUpdate.updateHash)) {
logger.warn(`Update link ${aUpdate.updateURL} is not secure and is not verified ` +
`by a strong enough hash (needs to be ${aHashString}).`);
delete aUpdate.updateURL;
delete aUpdate.updateHash;
}
}
}
/**
* Parses an RDF style update manifest into an array of update objects.
*
* @param aId
* The ID of the add-on being checked for updates
* @param aUpdateKey
* An optional update key for the add-on
* @param aRequest
* The XMLHttpRequest that has retrieved the update manifest
* @param aManifestData
* The pre-parsed manifest, as a bare XML DOM document
* @return an array of update objects
* @throws if the update manifest is invalid in any way
*/
function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) {
if (aManifestData.documentElement.namespaceURI != PREFIX_NS_RDF) {
throw Components.Exception("Update manifest had an unrecognised namespace: " +
aManifestData.documentElement.namespaceURI);
}
function EM_R(aProp) {
return gRDF.GetResource(PREFIX_NS_EM + aProp);
}
function getValue(aLiteral) {
if (aLiteral instanceof Ci.nsIRDFLiteral)
return aLiteral.Value;
if (aLiteral instanceof Ci.nsIRDFResource)
return aLiteral.Value;
if (aLiteral instanceof Ci.nsIRDFInt)
return aLiteral.Value;
return null;
}
function getProperty(aDs, aSource, aProperty) {
return getValue(aDs.GetTarget(aSource, EM_R(aProperty), true));
}
function getBooleanProperty(aDs, aSource, aProperty) {
let propValue = aDs.GetTarget(aSource, EM_R(aProperty), true);
if (!propValue)
return undefined;
return getValue(propValue) == "true";
}
function getRequiredProperty(aDs, aSource, aProperty) {
let value = getProperty(aDs, aSource, aProperty);
if (!value)
throw Components.Exception("Update manifest is missing a required " + aProperty + " property.");
return value;
}
let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
createInstance(Ci.nsIRDFXMLParser);
let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
createInstance(Ci.nsIRDFDataSource);
rdfParser.parseString(ds, aRequest.channel.URI, aRequest.responseText);
// Differentiating between add-on types is deprecated
let extensionRes = gRDF.GetResource(PREFIX_EXTENSION + aId);
let themeRes = gRDF.GetResource(PREFIX_THEME + aId);
let itemRes = gRDF.GetResource(PREFIX_ITEM + aId);
let addonRes;
if (ds.ArcLabelsOut(extensionRes).hasMoreElements())
addonRes = extensionRes;
else if (ds.ArcLabelsOut(themeRes).hasMoreElements())
addonRes = themeRes;
else
addonRes = itemRes;
// If we have an update key then the update manifest must be signed
if (aUpdateKey) {
let signature = getProperty(ds, addonRes, "signature");
if (!signature)
throw Components.Exception("Update manifest for " + aId + " does not contain a required signature");
let serializer = new RDFSerializer();
let updateString = null;
try {
updateString = serializer.serializeResource(ds, addonRes);
}
catch (e) {
throw Components.Exception("Failed to generate signed string for " + aId + ". Serializer threw " + e,
e.result);
}
let result = false;
try {
let verifier = Cc["@mozilla.org/security/datasignatureverifier;1"].
getService(Ci.nsIDataSignatureVerifier);
result = verifier.verifyData(updateString, signature, aUpdateKey);
}
catch (e) {
throw Components.Exception("The signature or updateKey for " + aId + " is malformed." +
"Verifier threw " + e, e.result);
}
if (!result)
throw Components.Exception("The signature for " + aId + " was not created by the add-on's updateKey");
}
let updates = ds.GetTarget(addonRes, EM_R("updates"), true);
// A missing updates property doesn't count as a failure, just as no avialable
// update information
if (!updates) {
logger.warn("Update manifest for " + aId + " did not contain an updates property");
return [];
}
if (!(updates instanceof Ci.nsIRDFResource))
throw Components.Exception("Missing updates property for " + addonRes.Value);
let cu = Cc["@mozilla.org/rdf/container-utils;1"].
getService(Ci.nsIRDFContainerUtils);
if (!cu.IsContainer(ds, updates))
throw Components.Exception("Updates property was not an RDF container");
let results = [];
let ctr = Cc["@mozilla.org/rdf/container;1"].
createInstance(Ci.nsIRDFContainer);
ctr.Init(ds, updates);
let items = ctr.GetElements();
while (items.hasMoreElements()) {
let item = items.getNext().QueryInterface(Ci.nsIRDFResource);
let version = getProperty(ds, item, "version");
if (!version) {
logger.warn("Update manifest is missing a required version property.");
continue;
}
logger.debug("Found an update entry for " + aId + " version " + version);
let targetApps = ds.GetTargets(item, EM_R("targetApplication"), true);
while (targetApps.hasMoreElements()) {
let targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource);
let appEntry = {};
try {
appEntry.id = getRequiredProperty(ds, targetApp, "id");
appEntry.minVersion = getRequiredProperty(ds, targetApp, "minVersion");
appEntry.maxVersion = getRequiredProperty(ds, targetApp, "maxVersion");
}
catch (e) {
logger.warn(e);
continue;
}
let result = {
id: aId,
version: version,
multiprocessCompatible: getBooleanProperty(ds, item, "multiprocessCompatible"),
updateURL: getProperty(ds, targetApp, "updateLink"),
updateHash: getProperty(ds, targetApp, "updateHash"),
updateInfoURL: getProperty(ds, targetApp, "updateInfoURL"),
strictCompatibility: !!getBooleanProperty(ds, targetApp, "strictCompatibility"),
targetApplications: [appEntry]
};
// The JSON update protocol requires an SHA-2 hash. RDF still
// supports SHA-1, for compatibility reasons.
sanitizeUpdateURL(result, aRequest, /^sha/, "sha1 or stronger");
results.push(result);
}
}
return results;
}
/**
* Parses an JSON update manifest into an array of update objects.
*
* @param aId
* The ID of the add-on being checked for updates
* @param aUpdateKey
* An optional update key for the add-on
* @param aRequest
* The XMLHttpRequest that has retrieved the update manifest
* @param aManifestData
* The pre-parsed manifest, as a JSON object tree
* @return an array of update objects
* @throws if the update manifest is invalid in any way
*/
function parseJSONManifest(aId, aUpdateKey, aRequest, aManifestData) {
if (aUpdateKey)
throw Components.Exception("Update keys are not supported for JSON update manifests");
let TYPE_CHECK = {
"array": val => Array.isArray(val),
"object": val => val && typeof val == "object" && !Array.isArray(val),
};
function getProperty(aObj, aProperty, aType, aDefault = undefined) {
if (!(aProperty in aObj))
return aDefault;
let value = aObj[aProperty];
let matchesType = aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType;
if (!matchesType)
throw Components.Exception(`Update manifest property '${aProperty}' has incorrect type (expected ${aType})`);
return value;
}
function getRequiredProperty(aObj, aProperty, aType) {
let value = getProperty(aObj, aProperty, aType);
if (value === undefined)
throw Components.Exception(`Update manifest is missing a required ${aProperty} property.`);
return value;
}
let manifest = aManifestData;
if (!TYPE_CHECK["object"](manifest))
throw Components.Exception("Root element of update manifest must be a JSON object literal");
// The set of add-ons this manifest has updates for
let addons = getRequiredProperty(manifest, "addons", "object");
// The entry for this particular add-on
let addon = getProperty(addons, aId, "object");
// A missing entry doesn't count as a failure, just as no avialable update
// information
if (!addon) {
logger.warn("Update manifest did not contain an entry for " + aId);
return [];
}
// The list of available updates
let updates = getProperty(addon, "updates", "array", []);
let results = [];
for (let update of updates) {
let version = getRequiredProperty(update, "version", "string");
logger.debug(`Found an update entry for ${aId} version ${version}`);
let applications = getProperty(update, "applications", "object",
{ gecko: {} });
// "gecko" is currently the only supported application entry. If
// it's missing, skip this update.
if (!("gecko" in applications)) {
logger.debug("gecko not in application entry, skipping update of ${addon}")
continue;
}
let app = getProperty(applications, "gecko", "object");
let appEntry = {
id: TOOLKIT_ID,
minVersion: getProperty(app, "strict_min_version", "string",
AddonManagerPrivate.webExtensionsMinPlatformVersion),
maxVersion: "*",
};
let result = {
id: aId,
version: version,
multiprocessCompatible: getProperty(update, "multiprocess_compatible", "boolean", true),
updateURL: getProperty(update, "update_link", "string"),
updateHash: getProperty(update, "update_hash", "string"),
updateInfoURL: getProperty(update, "update_info_url", "string"),
strictCompatibility: false,
targetApplications: [appEntry],
};
if ("strict_max_version" in app) {
if ("advisory_max_version" in app) {
logger.warn("Ignoring 'advisory_max_version' update manifest property for " +
aId + " property since 'strict_max_version' also present");
}
appEntry.maxVersion = getProperty(app, "strict_max_version", "string");
result.strictCompatibility = appEntry.maxVersion != "*";
} else if ("advisory_max_version" in app) {
appEntry.maxVersion = getProperty(app, "advisory_max_version", "string");
}
// The JSON update protocol requires an SHA-2 hash. RDF still
// supports SHA-1, for compatibility reasons.
sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512");
results.push(result);
}
return results;
}
/**
* Starts downloading an update manifest and then passes it to an appropriate
* parser to convert to an array of update objects
*
* @param aId
* The ID of the add-on being checked for updates
* @param aUpdateKey
* An optional update key for the add-on
* @param aUrl
* The URL of the update manifest
* @param aObserver
* An observer to pass results to
*/
function UpdateParser(aId, aUpdateKey, aUrl, aObserver) {
this.id = aId;
this.updateKey = aUpdateKey;
this.observer = aObserver;
this.url = aUrl;
let requireBuiltIn = true;
try {
requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS);
}
catch (e) {
}
logger.debug("Requesting " + aUrl);
try {
this.request = new ServiceRequest();
this.request.open("GET", this.url, true);
this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn);
this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
// Prevent the request from writing to cache.
this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
this.request.overrideMimeType("text/plain");
this.request.setRequestHeader("Moz-XPI-Update", "1", true);
this.request.timeout = TIMEOUT;
this.request.addEventListener("load", () => this.onLoad(), false);
this.request.addEventListener("error", () => this.onError(), false);
this.request.addEventListener("timeout", () => this.onTimeout(), false);
this.request.send(null);
}
catch (e) {
logger.error("Failed to request update manifest", e);
}
}
UpdateParser.prototype = {
id: null,
updateKey: null,
observer: null,
request: null,
url: null,
/**
* Called when the manifest has been successfully loaded.
*/
onLoad: function() {
let request = this.request;
this.request = null;
this._doneAt = new Error("place holder");
let requireBuiltIn = true;
try {
requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS);
}
catch (e) {
}
try {
CertUtils.checkCert(request.channel, !requireBuiltIn);
}
catch (e) {
logger.warn("Request failed: " + this.url + " - " + e);
this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
return;
}
if (!Components.isSuccessCode(request.status)) {
logger.warn("Request failed: " + this.url + " - " + request.status);
this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
return;
}
let channel = request.channel;
if (channel instanceof Ci.nsIHttpChannel && !channel.requestSucceeded) {
logger.warn("Request failed: " + this.url + " - " + channel.responseStatus +
": " + channel.responseStatusText);
this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
return;
}
// Detect the manifest type by first attempting to parse it as
// JSON, and falling back to parsing it as XML if that fails.
let parser;
try {
try {
let json = JSON.parse(request.responseText);
parser = () => parseJSONManifest(this.id, this.updateKey, request, json);
} catch (e) {
if (!(e instanceof SyntaxError))
throw e;
let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
let xml = domParser.parseFromString(request.responseText, "text/xml");
if (xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR)
throw new Error("Update manifest was not valid XML or JSON");
parser = () => parseRDFManifest(this.id, this.updateKey, request, xml);
}
} catch (e) {
logger.warn("onUpdateCheckComplete failed to determine manifest type");
this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT);
return;
}
let results;
try {
results = parser();
}
catch (e) {
logger.warn("onUpdateCheckComplete failed to parse update manifest", e);
this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
return;
}
if ("onUpdateCheckComplete" in this.observer) {
try {
this.observer.onUpdateCheckComplete(results);
}
catch (e) {
logger.warn("onUpdateCheckComplete notification failed", e);
}
}
else {
logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker"));
}
},
/**
* Called when the request times out
*/
onTimeout: function() {
this.request = null;
this._doneAt = new Error("Timed out");
logger.warn("Request for " + this.url + " timed out");
this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
},
/**
* Called when the manifest failed to load.
*/
onError: function() {
if (!Components.isSuccessCode(this.request.status)) {
logger.warn("Request failed: " + this.url + " - " + this.request.status);
}
else if (this.request.channel instanceof Ci.nsIHttpChannel) {
try {
if (this.request.channel.requestSucceeded) {
logger.warn("Request failed: " + this.url + " - " +
this.request.channel.responseStatus + ": " +
this.request.channel.responseStatusText);
}
}
catch (e) {
logger.warn("HTTP Request failed for an unknown reason");
}
}
else {
logger.warn("Request failed for an unknown reason");
}
this.request = null;
this._doneAt = new Error("UP_onError");
this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
},
/**
* Helper method to notify the observer that an error occured.
*/
notifyError: function(aStatus) {
if ("onUpdateCheckError" in this.observer) {
try {
this.observer.onUpdateCheckError(aStatus);
}
catch (e) {
logger.warn("onUpdateCheckError notification failed", e);
}
}
},
/**
* Called to cancel an in-progress update check.
*/
cancel: function() {
if (!this.request) {
logger.error("Trying to cancel already-complete request", this._doneAt);
return;
}
this.request.abort();
this.request = null;
this._doneAt = new Error("UP_cancel");
this.notifyError(AddonUpdateChecker.ERROR_CANCELLED);
}
};
/**
* Tests if an update matches a version of the application or platform
*
* @param aUpdate
* The available update
* @param aAppVersion
* The application version to use
* @param aPlatformVersion
* The platform version to use
* @param aIgnoreMaxVersion
* Ignore maxVersion when testing if an update matches. Optional.
* @param aIgnoreStrictCompat
* Ignore strictCompatibility when testing if an update matches. Optional.
* @param aCompatOverrides
* AddonCompatibilityOverride objects to match against. Optional.
* @return true if the update is compatible with the application/platform
*/
function matchesVersions(aUpdate, aAppVersion, aPlatformVersion,
aIgnoreMaxVersion, aIgnoreStrictCompat,
aCompatOverrides) {
if (aCompatOverrides) {
let override = AddonRepository.findMatchingCompatOverride(aUpdate.version,
aCompatOverrides,
aAppVersion,
aPlatformVersion);
if (override && override.type == "incompatible")
return false;
}
if (aUpdate.strictCompatibility && !aIgnoreStrictCompat)
aIgnoreMaxVersion = false;
let result = false;
for (let app of aUpdate.targetApplications) {
if (app.id == Services.appinfo.ID) {
return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) &&
(aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0));
}
if (app.id == TOOLKIT_ID) {
result = (Services.vc.compare(aPlatformVersion, app.minVersion) >= 0) &&
(aIgnoreMaxVersion || (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0));
}
}
return result;
}
this.AddonUpdateChecker = {
// These must be kept in sync with AddonManager
// The update check timed out
ERROR_TIMEOUT: -1,
// There was an error while downloading the update information.
ERROR_DOWNLOAD_ERROR: -2,
// The update information was malformed in some way.
ERROR_PARSE_ERROR: -3,
// The update information was not in any known format.
ERROR_UNKNOWN_FORMAT: -4,
// The update information was not correctly signed or there was an SSL error.
ERROR_SECURITY_ERROR: -5,
// The update was cancelled
ERROR_CANCELLED: -6,
/**
* Retrieves the best matching compatibility update for the application from
* a list of available update objects.
*
* @param aUpdates
* An array of update objects
* @param aVersion
* The version of the add-on to get new compatibility information for
* @param aIgnoreCompatibility
* An optional parameter to get the first compatibility update that
* is compatible with any version of the application or toolkit
* @param aAppVersion
* The version of the application or null to use the current version
* @param aPlatformVersion
* The version of the platform or null to use the current version
* @param aIgnoreMaxVersion
* Ignore maxVersion when testing if an update matches. Optional.
* @param aIgnoreStrictCompat
* Ignore strictCompatibility when testing if an update matches. Optional.
* @return an update object if one matches or null if not
*/
getCompatibilityUpdate: function(aUpdates, aVersion, aIgnoreCompatibility,
aAppVersion, aPlatformVersion,
aIgnoreMaxVersion, aIgnoreStrictCompat) {
if (!aAppVersion)
aAppVersion = Services.appinfo.version;
if (!aPlatformVersion)
aPlatformVersion = Services.appinfo.platformVersion;
for (let update of aUpdates) {
if (Services.vc.compare(update.version, aVersion) == 0) {
if (aIgnoreCompatibility) {
for (let targetApp of update.targetApplications) {
let id = targetApp.id;
if (id == Services.appinfo.ID || id == TOOLKIT_ID)
return update;
}
}
else if (matchesVersions(update, aAppVersion, aPlatformVersion,
aIgnoreMaxVersion, aIgnoreStrictCompat)) {
return update;
}
}
}
return null;
},
/**
* Returns the newest available update from a list of update objects.
*
* @param aUpdates
* An array of update objects
* @param aAppVersion
* The version of the application or null to use the current version
* @param aPlatformVersion
* The version of the platform or null to use the current version
* @param aIgnoreMaxVersion
* When determining compatible updates, ignore maxVersion. Optional.
* @param aIgnoreStrictCompat
* When determining compatible updates, ignore strictCompatibility. Optional.
* @param aCompatOverrides
* Array of AddonCompatibilityOverride to take into account. Optional.
* @return an update object if one matches or null if not
*/
getNewestCompatibleUpdate: function(aUpdates, aAppVersion, aPlatformVersion,
aIgnoreMaxVersion, aIgnoreStrictCompat,
aCompatOverrides) {
if (!aAppVersion)
aAppVersion = Services.appinfo.version;
if (!aPlatformVersion)
aPlatformVersion = Services.appinfo.platformVersion;
let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
let newest = null;
for (let update of aUpdates) {
if (!update.updateURL)
continue;
let state = blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
continue;
if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) &&
matchesVersions(update, aAppVersion, aPlatformVersion,
aIgnoreMaxVersion, aIgnoreStrictCompat,
aCompatOverrides)) {
newest = update;
}
}
return newest;
},
/**
* Starts an update check.
*
* @param aId
* The ID of the add-on being checked for updates
* @param aUpdateKey
* An optional update key for the add-on
* @param aUrl
* The URL of the add-on's update manifest
* @param aObserver
* An observer to notify of results
* @return UpdateParser so that the caller can use UpdateParser.cancel() to shut
* down in-progress update requests
*/
checkForUpdates: function(aId, aUpdateKey, aUrl, aObserver) {
return new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
}
};

View file

@ -0,0 +1,38 @@
/* 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/. */
/* globals addMessageListener*/
"use strict";
(function() {
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
var {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
var nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
"initWithPath");
const MSG_JAR_FLUSH = "AddonJarFlush";
const MSG_MESSAGE_MANAGER_CACHES_FLUSH = "AddonMessageManagerCachesFlush";
try {
if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
// Propagate JAR cache flush notifications across process boundaries.
addMessageListener(MSG_JAR_FLUSH, function(message) {
let file = new nsIFile(message.data);
Services.obs.notifyObservers(file, "flush-cache-entry", null);
});
// Propagate message manager caches flush notifications across processes.
addMessageListener(MSG_MESSAGE_MANAGER_CACHES_FLUSH, function() {
Services.obs.notifyObservers(null, "message-manager-flush-caches", null);
});
}
} catch (e) {
Cu.reportError(e);
}
})();

View file

@ -0,0 +1,982 @@
/* 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 = [ "isAddonPartOfE10SRollout" ];
const Cu = Components.utils;
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const PREF_E10S_ADDON_BLOCKLIST = "extensions.e10s.rollout.blocklist";
const PREF_E10S_ADDON_POLICY = "extensions.e10s.rollout.policy";
const ADDONS = {
"Greasemonkey": { // Greasemonkey
id: "{e4a8a97b-f2ed-450b-b12d-ee082ba24781}", minVersion: "3.8",
},
"DYTV": { // Download YouTube Videos as MP4
id: "{b9bfaf1c-a63f-47cd-8b9a-29526ced9060}", minVersion: "1.8.7",
},
"VDH": { // Video Download Helper
id: "{b9db16a4-6edc-47ec-a1f4-b86292ed211d}", minVersion: "5.6.1",
},
"Lightbeam": { // Lightbeam
id: "jid1-F9UJ2thwoAm5gQ@jetpack", minVersion: "1.3.0.1",
},
"ABP": { // Adblock Plus
id: "{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}", minVersion: "2.7.3",
},
"uBlockOrigin": { // uBlock Origin
id: "uBlock0@raymondhill.net", minVersion: "1.7.6",
},
"Emoji": { // Emoji Cheatsheet
id: "jid1-Xo5SuA6qc1DFpw@jetpack", minVersion: "1.1.1",
},
"ASP": { // Awesome Screenshot Plus
id: "jid0-GXjLLfbCoAx0LcltEdFrEkQdQPI@jetpack", minVersion: "3.0.10",
},
"PersonasPlus": { // PersonasPlus
id: "personas@christopher.beard", minVersion: "1.8.0",
},
"ACR": { // Add-on Compatibility Reporter
id: "compatibility@addons.mozilla.org", minVersion: "2.2.0",
},
// Add-ons used for testing
"test1": {
id: "bootstrap1@tests.mozilla.org", minVersion: "1.0",
},
"test2": {
id: "bootstrap2@tests.mozilla.org", minVersion: "1.0",
},
};
// NOTE: Do not modify sets or policies after they have already been
// published to users. They must remain unchanged to provide valid data.
// Set 2 used during 48 Beta cycle. Kept here for historical reasons.
const set2 = [ADDONS.Greasemonkey,
ADDONS.DYTV,
ADDONS.VDH,
ADDONS.Lightbeam,
ADDONS.ABP,
ADDONS.uBlockOrigin,
ADDONS.Emoji,
ADDONS.ASP,
ADDONS.PersonasPlus];
const set49Release = [
ADDONS.Greasemonkey,
ADDONS.DYTV,
ADDONS.VDH,
ADDONS.Lightbeam,
ADDONS.ABP,
ADDONS.uBlockOrigin,
ADDONS.Emoji,
ADDONS.ASP,
ADDONS.PersonasPlus,
ADDONS.ACR
];
// These are only the add-ons in the Add-Ons Manager Discovery
// pane. This set is here in case we need to reduce add-ons
// exposure live on Release.
const set49PaneOnly = [
ADDONS.ABP,
ADDONS.VDH,
ADDONS.Emoji,
ADDONS.ASP,
ADDONS.ACR
]
// ================== ADDONS FOR 51 RELEASE ==================
//
// During the 51 beta cycle, we tested e10s with all addons
// except those explicitly marked as being incompatible.
// For release, instead of opening this up, we assembled
// the lists below with all addons that were seen on beta
// and had over 50 installs.
//
// This list is in a new format to allow fast access and also
// to allow controlling by the number of addons installed.
const set51Release = {
"_65Members_@download.fromdoctopdf.com": {minVersion: "7.102.10.4221", installs: 32092},
"light_plugin_ACF0E80077C511E59DED005056C00008@kaspersky.com": {minVersion: "4.6.3-15", installs: 27758},
"_ceMembers_@free.easypdfcombine.com": {minVersion: "7.102.10.4117", installs: 17797},
"caa1-aDOiCAxFFMOVIX@jetpack": {minVersion: "0.1.7", installs: 13150},
"{4ED1F68A-5463-4931-9384-8FFF5ED91D92}": {minVersion: "5.0.248.0", installs: 12774},
"_dbMembers_@free.getformsonline.com": {minVersion: "7.102.10.4251", installs: 11909},
"_4zMembers_@www.videodownloadconverter.com": {minVersion: "7.102.10.5033", installs: 11612},
"light_plugin_F6F079488B53499DB99380A7E11A93F6@kaspersky.com": {minVersion: "5.0.141-4-20161031140250", installs: 10944},
"YoutubeDownloader@PeterOlayev.com": {minVersion: "2.4.1", installs: 10722},
"{82AF8DCA-6DE9-405D-BD5E-43525BDAD38A}": {minVersion: "8.0.0.9103", installs: 8856},
"client@anonymox.net": {minVersion: "2.5.2", installs: 8225},
"_8hMembers_@download.allin1convert.com": {minVersion: "7.102.10.3584", installs: 7681},
"light_plugin_D772DC8D6FAF43A29B25C4EBAA5AD1DE@kaspersky.com": {minVersion: "4.6.2-42-20160922074409", installs: 7177},
"_dzMembers_@www.pconverter.com": {minVersion: "7.102.10.4851", installs: 7115},
"fxdevtools-adapters@mozilla.org": {minVersion: "0.3.5", installs: 6926},
"_9pMembers_@free.onlinemapfinder.com": {minVersion: "7.102.10.4836", installs: 6583},
"@DownloadManager": {minVersion: "0.2.1", installs: 6412},
"ar1er-ewrgfdgomusix@jetpack": {minVersion: "1.0.6", installs: 5975},
"_agMembers_@free.premierdownloadmanager.com": {minVersion: "7.102.10.4846", installs: 5605},
"_paMembers_@www.filmfanatic.com": {minVersion: "7.102.10.4163", installs: 5448},
"_gtMembers_@free.gamingwonderland.com": {minVersion: "7.102.10.4263", installs: 5241},
"LVD-SAE@iacsearchandmedia.com": {minVersion: "8.5", installs: 4694},
"_fsMembers_@free.pdfconverterhq.com": {minVersion: "7.102.10.4849", installs: 4526},
"_6xMembers_@www.readingfanatic.com": {minVersion: "7.102.10.4914", installs: 4417},
"@mysmartprice-ff": {minVersion: "0.0.6", installs: 4381},
"jid1-YcMV6ngYmQRA2w@jetpack": {minVersion: "1.37.9", installs: 3899},
"{58d735b4-9d6c-4e37-b146-7b9f7e79e318}": {minVersion: "1.6", installs: 3733},
"anttoolbar@ant.com": {minVersion: "2.4.7.47", installs: 3720},
"adblockpopups@jessehakanen.net": {minVersion: "0.9.2.1-signed.1-signed", installs: 3602},
"ERAIL.IN.FFPLUGIN@jetpack": {minVersion: "6.0.rev142", installs: 3545},
"WebProtection@360safe.com": {minVersion: "5.0.0.1005", installs: 3475},
"yasearch@yandex.ru": {minVersion: "8.20.4", installs: 3299},
"{19503e42-ca3c-4c27-b1e2-9cdb2170ee34}": {minVersion: "1.5.6.14", installs: 3106},
"{C1A2A613-35F1-4FCF-B27F-2840527B6556}": {minVersion: "2016.8.1.9", installs: 3083},
"_b7Members_@free.mytransitguide.com": {minVersion: "7.102.10.4812", installs: 3011},
"_9tMembers_@free.internetspeedtracker.com": {minVersion: "7.102.10.4339", installs: 2828},
"_64Members_@www.televisionfanatic.com": {minVersion: "7.102.10.4968", installs: 2821},
"info@youtube-mp3.org": {minVersion: "1.0.9.1-signed.1-signed", installs: 2717},
"ffext_basicvideoext@startpage24": {minVersion: "1.97.37.1-signed.1-signed", installs: 2663},
"MUB-SAE@iacsearchandmedia.com": {minVersion: "8.7", installs: 2650},
"_4jMembers_@www.radiorage.com": {minVersion: "7.102.10.4916", installs: 2631},
"@Email": {minVersion: "4.0.12", installs: 2583},
"_gcMembers_@www.weatherblink.com": {minVersion: "7.38.8.56523", installs: 2519},
"_dqMembers_@www.downspeedtest.com": {minVersion: "7.102.10.3827", installs: 2445},
"translator@zoli.bod": {minVersion: "2.1.0.5.1.1-signed", installs: 2310},
"{a38384b3-2d1d-4f36-bc22-0f7ae402bcd7}": {minVersion: "1.0.0.51", installs: 2190},
"_1eMembers_@www.videoscavenger.com": {minVersion: "7.38.8.45273", installs: 2185},
"tvplusnewtab-the-extension1@mozilla.com": {minVersion: "0.1.5", installs: 2155},
"homepage@mail.ru": {minVersion: "1.0.2", installs: 2124},
"search@mail.ru": {minVersion: "1.0.7", installs: 2038},
"_69Members_@www.packagetracer.com": {minVersion: "7.102.10.4831", installs: 2036},
"{7b8a500a-a464-4624-bd4f-73eaafe0f766}": {minVersion: "3", installs: 2027},
"paulsaintuzb@gmail.com": {minVersion: "8.2.1", installs: 2005},
"k7srff_enUS@k7computing.com": {minVersion: "2.4", installs: 1929},
"_e5Members_@www.productivityboss.com": {minVersion: "7.38.8.46590", installs: 1892},
"vdpure@link64": {minVersion: "1.97.43", installs: 1860},
"_9tMembers_@download.internetspeedtracker.com": {minVersion: "7.38.8.56171", installs: 1824},
"_g3Members_@free.easyphotoedit.com": {minVersion: "7.102.10.4108", installs: 1822},
"_64Members_@download.televisionfanatic.com": {minVersion: "7.38.9.3004", installs: 1730},
"_8iMembers_@download.audiotoaudio.com": {minVersion: "7.102.10.3585", installs: 1704},
"adblockultimate@adblockultimate.net": {minVersion: "2.25", installs: 1648},
"eagleget_ffext@eagleget.com": {minVersion: "3.8", installs: 1640},
"_9eMembers_@free.findmefreebies.com": {minVersion: "7.102.10.4193", installs: 1638},
"content_blocker_663BE8@kaspersky.com": {minVersion: "4.5.4.19.1", installs: 1625},
"virtual_keyboard_074028@kaspersky.com": {minVersion: "4.5.4.19.1", installs: 1624},
"browsec@browsec.com": {minVersion: "2.0.3", installs: 1610},
"@Maps": {minVersion: "4.0.0", installs: 1587},
"_exMembers_@free.easydocmerge.com": {minVersion: "7.102.10.4137", installs: 1493},
"{635abd67-4fe9-1b23-4f01-e679fa7484c1}": {minVersion: "5.0.2", installs: 1490},
"abb@amazon.com": {minVersion: "10.1612.1.304", installs: 1463},
"{1BC9BA34-1EED-42ca-A505-6D2F1A935BBB}": {minVersion: "6.2.18.1", installs: 1436},
"mp4downloader@jeff.net": {minVersion: "1.3.3.1-signed.1-signed", installs: 1410},
"jid1-16aeif9OQIRKxA@jetpack": {minVersion: "1.1.4", installs: 1399},
"{c45c406e-ab73-11d8-be73-000a95be3b12}": {minVersion: "1.2.11", installs: 1367},
"online_banking_08806E@kaspersky.com": {minVersion: "4.5.4.19.1", installs: 1356},
"_ewMembers_@free.mergedocsonline.com": {minVersion: "7.102.10.4710", installs: 1337},
"@DiscreteSearch": {minVersion: "0.2.1", installs: 1306},
"{6AC85730-7D0F-4de0-B3FA-21142DD85326}": {minVersion: "2.8.2", installs: 1286},
"{063DA41A-2561-401B-91FA-AC75E460F4EB}": {minVersion: "1.0.7.1", installs: 1280},
"netvideohunter@netvideohunter.com": {minVersion: "1.2", installs: 1260},
"_8eMembers_@download.howtosimplified.com": {minVersion: "7.102.10.4285", installs: 1230},
"FGZ-SAE@iacsearchandmedia.com": {minVersion: "8.5", installs: 1220},
"adguardadblocker@adguard.com": {minVersion: "2.4.14", installs: 1172},
"_39Members_@www.mapsgalaxy.com": {minVersion: "7.102.10.4730", installs: 1171},
"_euMembers_@free.filesendsuite.com": {minVersion: "7.102.10.4154", installs: 1166},
"_brMembers_@free.yourtemplatefinder.com": {minVersion: "7.102.10.5047", installs: 1159},
"_8jMembers_@download.myimageconverter.com": {minVersion: "7.102.10.4778", installs: 1150},
"_12Members_@free.myscrapnook.com": {minVersion: "7.102.10.4739", installs: 1113},
"_7eMembers_@www.homeworksimplified.com": {minVersion: "7.102.10.4290", installs: 1109},
"{fe272bd1-5f76-4ea4-8501-a05d35d823fc}": {minVersion: "2.1.9.1-signed.1-let-fixed.1-signed", installs: 1108},
"_frMembers_@free.testforspeed.com": {minVersion: "7.102.10.4993", installs: 1107},
"{068e178c-61a9-4a63-b74f-87404a6f5ea1}": {minVersion: "2", installs: 1104},
"@Package": {minVersion: "0.2.0", installs: 1092},
"6asa42dfa4784fsf368g@youtubeconverter.me": {minVersion: "0.1", installs: 1071},
"_diMembers_@www.free.easymaillogin.com": {minVersion: "7.102.10.4112", installs: 1043},
"_v4Members_@www.dictionaryboss.com": {minVersion: "7.102.10.3797", installs: 1035},
"colorPicker@colorPicker": {minVersion: "3.0.1-signed.1-signed", installs: 1023},
"hotspot-shield@anchorfree.com": {minVersion: "1.2.87", installs: 1000},
"manishjain9@hotmail.com_easiestyoutube": {minVersion: "7.2.1-signed.1-let-fixed.1-signed", installs: 993},
"{cd617375-6743-4ee8-bac4-fbf10f35729e}": {minVersion: "2.9.6", installs: 987},
"@Converter": {minVersion: "4.1.0", installs: 986},
"{dd3d7613-0246-469d-bc65-2a3cc1668adc}": {minVersion: "1.1.8.1-signed.1-signed", installs: 983},
"ubufox@ubuntu.com": {minVersion: "3.2", installs: 950},
"jid1-lpoiffmusixlib@jetpack": {minVersion: "0.1.9", installs: 945},
"_5aMembers_@download.mywebface.com": {minVersion: "7.102.10.4837", installs: 930},
"leethax@leethax.net": {minVersion: "2016.12.02", installs: 930},
"{1A2D0EC4-75F5-4c91-89C4-3656F6E44B68}": {minVersion: "0.6.3.1-signed.1-signed", installs: 885},
"{64161300-e22b-11db-8314-0800200c9a66}": {minVersion: "0.9.6.18", installs: 875},
"_bfMembers_@free.snapmyscreen.com": {minVersion: "7.102.10.4951", installs: 827},
"uriloader@pdf.js": {minVersion: "1.0.277.1-signed.1-signed", installs: 815},
"{e968fc70-8f95-4ab9-9e79-304de2a71ee1}": {minVersion: "0.7.3.1-signed.1-signed", installs: 805},
"save-as-pdf-ff@pdfcrowd.com": {minVersion: "1.5.1-signed.1-signed", installs: 804},
"{75CEEE46-9B64-46f8-94BF-54012DE155F0}": {minVersion: "0.4.15", installs: 794},
"safesearchplus2@avira.com": {minVersion: "1.4.1.371", installs: 786},
"easyscreenshot@mozillaonline.com": {minVersion: "1.2.8", installs: 785},
"_eeMembers_@download.freeradiocast.com": {minVersion: "7.38.8.46366", installs: 783},
"_89Members_@download.safepcrepair.com": {minVersion: "7.39.8.51080", installs: 777},
"{a3a5c777-f583-4fef-9380-ab4add1bc2a5}": {minVersion: "2.4.2.1-signed", installs: 771},
"content_blocker@kaspersky.com": {minVersion: "4.0.10.15", installs: 770},
"safesearch@avira.com": {minVersion: "1.4.1.371", installs: 767},
"youtube2mp3@mondayx.de": {minVersion: "1.2.3.1-signed.1-signed", installs: 748},
"2020Player_IKEA@2020Technologies.com": {minVersion: "5.0.94.1", installs: 736},
"_edMembers_@free.myradioaccess.com": {minVersion: "7.102.10.4797", installs: 734},
"_dmMembers_@free.gounzip.com": {minVersion: "7.102.10.4277", installs: 733},
"Media-Newtab-the-extension1@mozilla.com": {minVersion: "0.1.6", installs: 732},
"foxmarks@kei.com": {minVersion: "4.3.19", installs: 728},
"{e8deb9e5-5688-4655-838a-b7a121a9f16e}": {minVersion: "48.4", installs: 726},
"{195A3098-0BD5-4e90-AE22-BA1C540AFD1E}": {minVersion: "4.1.0.1-signed.1-signed", installs: 722},
"jid1-4P0kohSJxU1qGg@jetpack": {minVersion: "1.22.550", installs: 719},
"DailymotionVideoDownloader@PeterOlayev.com": {minVersion: "1.0.6.1-signed.1-signed", installs: 717},
"jid1-P34HaABBBpOerQ@jetpack": {minVersion: "0.2.1-signed.1-signed", installs: 715},
"SQLiteManager@mrinalkant.blogspot.com": {minVersion: "0.8.3.1-signed.1-signed", installs: 700},
"2.0@disconnect.me": {minVersion: "3.15.3.1-signed.1-signed", installs: 693},
"multifox@hultmann": {minVersion: "3.2.3", installs: 690},
"_5mMembers_@download.myfuncards.com": {minVersion: "7.102.10.4783", installs: 679},
"_btMembers_@free.catsandcatapults.com": {minVersion: "7.102.10.3677", installs: 673},
"pavel.sherbakov@gmail.com": {minVersion: "19.1.1", installs: 666},
"_fbMembers_@free.smarterpassword.com": {minVersion: "7.102.10.4936", installs: 644},
"jid2-l8SPBzHJWBIiHQ@jetpack": {minVersion: "3.1", installs: 639},
"{B17C1C5A-04B1-11DB-9804-B622A1EF5492}": {minVersion: "1.3.2", installs: 633},
"myplaycitycom@gametab": {minVersion: "1.6", installs: 616},
"{ad0d925d-88f8-47f1-85ea-8463569e756e}": {minVersion: "2.0.5", installs: 604},
"{37964A3C-4EE8-47b1-8321-34DE2C39BA4D}": {minVersion: "2.5.4.174", installs: 603},
"youtubemp3podcaster@jeremy.d.gregorio.com": {minVersion: "3.9.0", installs: 601},
"caa1-aDOiCAxFFPRIVATE@jetpack": {minVersion: "0.2.0", installs: 598},
"_f5Members_@free.typingfanatic.com": {minVersion: "7.102.10.5014", installs: 595},
"_94Members_@www.motitags.com": {minVersion: "7.102.10.4744", installs: 594},
"{888d99e7-e8b5-46a3-851e-1ec45da1e644}": {minVersion: "45.0.0", installs: 581},
"_1cMembers_@www.bringmesports.com": {minVersion: "7.102.10.3646", installs: 580},
"{a6fd85ed-e919-4a43-a5af-8da18bda539f}": {minVersion: "2.9.1.1-signed", installs: 572},
"{0fc22c4c-93ed-48ea-ad12-dc8039cf3795}": {minVersion: "1.3", installs: 568},
"homeutil@yandex.ru": {minVersion: "1.0.13", installs: 565},
"_doMembers_@free.convertanyfile.com": {minVersion: "7.38.8.45860", installs: 563},
"SocialNewPages-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 561},
"wappalyzer@crunchlabz.com": {minVersion: "3.2.7", installs: 557},
"_5qMembers_@www.zwinky.com": {minVersion: "7.38.8.45270", installs: 551},
"{0545b830-f0aa-4d7e-8820-50a4629a56fe}": {minVersion: "31.0.9", installs: 531},
"vk@sergeykolosov.mp": {minVersion: "0.3.9.5", installs: 522},
"{77b819fa-95ad-4f2c-ac7c-486b356188a9}": {minVersion: "4.0.20130422.1-signed.1-signed", installs: 505},
"@true-key": {minVersion: "1.23.0.2433", installs: 501},
"_1pMembers_@www.referenceboss.com": {minVersion: "7.102.10.4932", installs: 499},
"{C7AE725D-FA5C-4027-BB4C-787EF9F8248A}": {minVersion: "1.0.0.4", installs: 494},
"alx-ffdeveloper@amazon.com": {minVersion: "3.0.2", installs: 493},
"{3d7eb24f-2740-49df-8937-200b1cc08f8a}": {minVersion: "1.5.20", installs: 491},
"_1gMembers_@www.inboxace.com": {minVersion: "7.38.8.56535", installs: 488},
"{7DD78D43-0962-4d9b-BC76-ABF13B3B2ED1}": {minVersion: "3.5.0.1428", installs: 484},
"imageblock@hemantvats.com": {minVersion: "3.1", installs: 472},
"online_banking@kaspersky.com": {minVersion: "4.0.10.15", installs: 463},
"virtual_keyboard@kaspersky.com": {minVersion: "4.0.10.15", installs: 463},
"button@scholar.google.com": {minVersion: "1.1.1-signed.1-signed", installs: 463},
"anti_banner@kaspersky.com": {minVersion: "4.0.10.15", installs: 462},
"url_advisor@kaspersky.com": {minVersion: "4.0.10.15", installs: 461},
"{6d96bb5e-1175-4ebf-8ab5-5f56f1c79f65}": {minVersion: "0.9.8", installs: 457},
"_14Members_@download.totalrecipesearch.com": {minVersion: "7.102.10.4983", installs: 456},
"{394DCBA4-1F92-4f8e-8EC9-8D2CB90CB69B}": {minVersion: "5.1.1", installs: 447},
"_57Members_@free.marineaquariumfree.com": {minVersion: "7.102.10.4716", installs: 446},
"e67f8350-7edf-11e3-baa7-0800200c9a66@fri-gate.org": {minVersion: "2.2.1.1-signed", installs: 446},
"FireXPath@pierre.tholence.com": {minVersion: "0.9.7.1.1-signed.1-signed", installs: 442},
"@youtube_downloader": {minVersion: "0.0.9", installs: 435},
"ff_hpset@jetpack": {minVersion: "1.0.8", installs: 428},
"{d0bfdcce-52c7-4b32-bb45-948f62db8d3f}": {minVersion: "49.1", installs: 406},
"_j2Members_@www.soccerinferno.com": {minVersion: "7.102.10.4948", installs: 405},
"autoform@olifozzy": {minVersion: "1.2.4.1-signed.1-signed", installs: 405},
"FunSafeTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 405},
"testpilot@labs.mozilla.com": {minVersion: "1.2.3.1-signed", installs: 405},
"vwof@drev.com": {minVersion: "3.1.2", installs: 401},
"_ftMembers_@free.mytelevisionhq.com": {minVersion: "7.102.10.4817", installs: 397},
"{e001c731-5e37-4538-a5cb-8168736a2360}": {minVersion: "0.9.9.152", installs: 396},
"{95E84BD3-3604-4AAC-B2CA-D9AC3E55B64B}": {minVersion: "2.0.0.78", installs: 393},
"_8lMembers_@free.filesharefanatic.com": {minVersion: "7.102.10.4171", installs: 389},
"clipconverter@clipconverter.cc": {minVersion: "1.5.2", installs: 387},
"_7jMembers_@download.gardeningenthusiast.com": {minVersion: "7.102.10.4260", installs: 383},
"antmark@ant.com": {minVersion: "1.1.14", installs: 383},
"_flMembers_@free.myformsfinder.com": {minVersion: "7.102.10.4784", installs: 381},
"{c36177c0-224a-11da-8cd6-0800200c9a91}": {minVersion: "3.9.85.1-signed.1-signed", installs: 375},
"@searchincognito": {minVersion: "0.1.0", installs: 375},
"{f13b157f-b174-47e7-a34d-4815ddfdfeb8}": {minVersion: "0.9.89.1-signed.1-signed", installs: 373},
"_5eMembers_@www.translationbuddy.com": {minVersion: "7.38.8.45962", installs: 372},
"{9c51bd27-6ed8-4000-a2bf-36cb95c0c947}": {minVersion: "11.0.1.1-signed.1-signed", installs: 370},
"clickclean@hotcleaner.com": {minVersion: "4.1.1-signed.1-signed", installs: 366},
"jid1-xKH0EoS44u1a2w@jetpack": {minVersion: "0.1.1-signed.1-signed", installs: 366},
"{c2056674-a37f-4b29-9300-2004759d74fe}": {minVersion: "2.0.0.1090", installs: 361},
"newtab-tv-the-extension1@mozilla.com": {minVersion: "0.1.5", installs: 359},
"ascsurfingprotectionnew@iobit.com": {minVersion: "2.1.3", installs: 355},
"FunTabSafe-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 353},
"d.lehr@chello.at": {minVersion: "1.2", installs: 350},
"anticontainer@downthemall.net": {minVersion: "1.5", installs: 348},
"{F8A55C97-3DB6-4961-A81D-0DE0080E53CB}": {minVersion: "1.0.10", installs: 347},
"@FormsApp": {minVersion: "0.2.0", installs: 346},
"multilinksplus@hugsmile.eu": {minVersion: "3.9.3", installs: 343},
"jid1-KWFaW5zc0EbtBQ@jetpack": {minVersion: "0.2.0", installs: 335},
"{e8f509f0-b677-11de-8a39-0800200c9a66}": {minVersion: "1.12.1-signed.1-let-fixed.1-signed", installs: 334},
"{37E4D8EA-8BDA-4831-8EA1-89053939A250}": {minVersion: "3.0.0.2.1-signed.1-signed", installs: 333},
"{c8d3bc80-0810-4d21-a2c2-be5f2b2832ac}": {minVersion: "0.98", installs: 332},
"{cb40da56-497a-4add-955d-3377cae4c33b}": {minVersion: "10.2.0.271", installs: 331},
"{5546F97E-11A5-46b0-9082-32AD74AAA920}": {minVersion: "0.76.1-signed.1-signed", installs: 325},
"_14Members_@www.totalrecipesearch.com": {minVersion: "7.38.8.45925", installs: 324},
"info@mp3it.eu": {minVersion: "1.4.1.1-signed.1-signed", installs: 324},
"firefox-autofill@googlegroups.com": {minVersion: "3.6.1-signed.1-signed", installs: 317},
"jid1-TQvJxTBYHA8qXg@jetpack": {minVersion: "0.4.1-signed.1-signed", installs: 315},
"{8f8fe09b-0bd3-4470-bc1b-8cad42b8203a}": {minVersion: "0.17.1-signed.1-signed", installs: 311},
"{D4DD63FA-01E4-46a7-B6B1-EDAB7D6AD389}": {minVersion: "0.9.10.1-signed.1-signed", installs: 311},
"{d7f46ca0-899d-11da-a72b-0800200c9a65}": {minVersion: "0.1.2.1-signed.1-signed", installs: 311},
"twoo@twoo.com": {minVersion: "1.6.0.1-signed", installs: 303},
"_29Members_@www.headlinealley.com": {minVersion: "7.38.8.56537", installs: 302},
"_e2Members_@free.coolpopulargames.com": {minVersion: "7.38.8.45873", installs: 300},
"TopTVTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 300},
"tmbepff@trendmicro.com": {minVersion: "9.2.0.1026", installs: 293},
"_2vMembers_@www.dailybibleguide.com": {minVersion: "7.38.8.52880", installs: 289},
"{54e46280-0211-11e3-b778-0800200c9a66}": {minVersion: "0.3", installs: 285},
"_49Members_@www.utilitychest.com": {minVersion: "7.38.8.45977", installs: 284},
"amcontextmenu@loucypher": {minVersion: "0.4.2.1-signed.1-signed", installs: 284},
"jid1-r1tDuNiNb4SEww@jetpack": {minVersion: "1.1.2673", installs: 283},
"_erMembers_@free.getvideoconvert.com": {minVersion: "7.102.10.5038", installs: 281},
"{b1df372d-8b32-4c7d-b6b4-9c5b78cf6fb1}": {minVersion: "0.87.1-signed.1-signed", installs: 281},
"jid1-cHKBMlArKdIVEg@jetpack": {minVersion: "1.24.1-signed.1-signed", installs: 281},
"@90B817C8-8A5C-413B-9DDD-B2C61ED6E79A": {minVersion: "1.09", installs: 278},
"smarterwiki@wikiatic.com": {minVersion: "5.2.1.1-signed.1-signed", installs: 278},
"whatsapppanel@alejandrobrizuela.com.ar": {minVersion: "1.1.1.1-signed.1-signed", installs: 277},
"lazarus@interclue.com": {minVersion: "2.3.1-signed.1-signed", installs: 275},
"{DEDA1132-B316-11DD-8BC1-4E5D56D89593}": {minVersion: "0.18", installs: 274},
"_h2Members_@free.calendarspark.com": {minVersion: "7.102.10.3641", installs: 273},
"@youtubedownloadere": {minVersion: "0.0.1", installs: 273},
"multirevenue@googlemail.com": {minVersion: "6.1.1", installs: 272},
"_d9Members_@www.everydaylookup.com": {minVersion: "7.102.10.4140", installs: 271},
"belgiumeid@eid.belgium.be": {minVersion: "1.0.21", installs: 271},
"{62DD0A97-FDD4-421b-94A5-D1A9434450C7}": {minVersion: "3.1", installs: 270},
"the-addon-bar@GeekInTraining-GiT": {minVersion: "3.2.9-compat-fixed-4", installs: 264},
"@phextension": {minVersion: "6.0.2", installs: 262},
"FunMediaTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 262},
"{7f57cf46-4467-4c2d-adfa-0cba7c507e54}": {minVersion: "4.0.1", installs: 259},
"safefacebook@bkav": {minVersion: "1.0.4", installs: 255},
"content_blocker_6418E0D362104DADA084DC312DFA8ABC@kaspersky.com": {minVersion: "4.5.3.8", installs: 254},
"virtual_keyboard_294FF26A1D5B455495946778FDE7CEDB@kaspersky.com": {minVersion: "4.5.3.8", installs: 254},
"{B821BF60-5C2D-41EB-92DC-3E4CCD3A22E4}": {minVersion: "4.3.1.10", installs: 252},
"@E9438230-A7DF-4D1F-8F2D-CA1D0F0F7924": {minVersion: "1.08.8.66", installs: 252},
"jid1-6MGm94JnyY2VkA@jetpack": {minVersion: "2.1.8", installs: 250},
"{20a82645-c095-46ed-80e3-08825760534b}": {minVersion: "1.3.1.1-signed", installs: 246},
"{a192bf54-089f-4325-ac25-7eafcd17a342}": {minVersion: "3.2", installs: 246},
"e389d8c2-5554-4ba2-a36e-ac7a57093130@gmail.com": {minVersion: "1.44.275", installs: 244},
"yslow@yahoo-inc.com": {minVersion: "3.1.8.1-signed.1-signed", installs: 244},
"avg@safeguard": {minVersion: "19.6.0.592", installs: 243},
"@windscribeff": {minVersion: "0.1.43", installs: 242},
"jid1-PBNne26X1Kn6hQ@jetpack": {minVersion: "3.3.3", installs: 240},
"{53A03D43-5363-4669-8190-99061B2DEBA5}": {minVersion: "1.5.14", installs: 239},
"@offersolymp": {minVersion: "0.0.2", installs: 238},
"firefox@dotvpn.com": {minVersion: "1.0.2", installs: 238},
"{62760FD6-B943-48C9-AB09-F99C6FE96088}": {minVersion: "4.2.9", installs: 236},
"jid1-sNL73VCI4UB0Fw@jetpack": {minVersion: "2.1.4", installs: 236},
"low_quality_flash@pie2k.com": {minVersion: "0.2.1-signed.1-signed", installs: 236},
"jid1-l6VQSR2FeKnliQ@jetpack": {minVersion: "2.0.1-signed", installs: 235},
"_5zMembers_@www.couponxplorer.com": {minVersion: "7.102.10.3738", installs: 234},
"adonis.cuhk@gmail.com": {minVersion: "1.8.9.1-signed.1-signed", installs: 234},
"_e1Members_@free.actionclassicgames.com": {minVersion: "7.38.8.45834", installs: 232},
"{ea4637dc-e014-4c17-9c2c-879322d23268}": {minVersion: "2.1.1-signed.1-signed", installs: 229},
"{4DC70064-89E2-4a55-8FC6-E8CDEAE3618C}": {minVersion: "0.7.7.1-signed.1-signed", installs: 228},
"odyssey_crypto_control@odysseytec.com": {minVersion: "3.5", installs: 228},
"seostatus@rubyweb": {minVersion: "1.5.9.1-signed.1-signed", installs: 228},
"_apMembers_@free.puzzlegamesdaily.com": {minVersion: "7.102.10.4865", installs: 227},
"@safesearchincognito": {minVersion: "0.1.8", installs: 226},
"jid1-HfCj61J5q2gaGQ@jetpack": {minVersion: "1.0.3", installs: 224},
"@stopads": {minVersion: "0.0.4", installs: 224},
"dta3noaero@vano": {minVersion: "1.0.1", installs: 224},
"rainbow@colors.org": {minVersion: "1.6.1-signed.1-signed", installs: 223},
"{146f1820-2b0d-49ef-acbf-d85a6986e10c}": {minVersion: "0.1.9.3.1-signed.1-signed", installs: 222},
"{b2bfe60c-eef8-4e20-8334-c53afdc1ffdd}": {minVersion: "3.2", installs: 222},
"{b7870b41-bfb3-44cd-8cc2-e392e51b0874}": {minVersion: "3.8", installs: 222},
"printPages2Pdf@reinhold.ripper": {minVersion: "0.1.9.3.1-signed", installs: 221},
"YouTubetoALL@ALLPlayer.org": {minVersion: "0.8.5.1-signed.1-signed", installs: 221},
"{7a526449-3a92-426f-8ca4-47439918f2b1}": {minVersion: "3.2", installs: 219},
"jdwimqhayu@yahoo.com": {minVersion: "0.0.0.6", installs: 219},
"{54FBE89E-C878-46bb-A064-AB327EE26EBC}": {minVersion: "3.8", installs: 214},
"modernDownloadManager@teo.pl": {minVersion: "0.2.2", installs: 214},
"{eb8fff7e-1dce-4f3f-a51d-d9513ed6bab4}": {minVersion: "3.8", installs: 211},
"jid0-YQz0l1jthOIz179ehuitYAOdBEs@jetpack": {minVersion: "2.0.2", installs: 211},
"{7e80e173-7e63-464e-8252-fe170b15c15a}": {minVersion: "2.3", installs: 210},
"{35d6291e-1d4b-f9b4-c52f-77e6410d1326}": {minVersion: "4.11.1.0", installs: 209},
"{3c59c791-aeec-44bb-af60-ff112eea18e3}": {minVersion: "3.2", installs: 209},
"{90477448-b59c-48cd-98af-6a298cbc15d2}": {minVersion: "3.8", installs: 209},
"{24d26487-6274-48b1-b500-22f24884f971}": {minVersion: "2.3", installs: 208},
"{b7389dbc-6646-412f-bbd5-53168ee68a98}": {minVersion: "49", installs: 208},
"{22181a4d-af90-4ca3-a569-faed9118d6bc}": {minVersion: "11.0.0.1181", installs: 207},
"printpdf@pavlov.net": {minVersion: "0.76.1-signed.1-signed", installs: 207},
"@com.virtualjame.disableads": {minVersion: "0.1.0", installs: 206},
"{9AA46F4F-4DC7-4c06-97AF-6665170634FE}": {minVersion: "1.11.6.1-signed.1-signed", installs: 205},
"tinyjsdebugger@enigmail.net": {minVersion: "1.1.5", installs: 204},
"_foMembers_@free.flightsearchapp.com": {minVersion: "7.102.10.4176", installs: 202},
"jid1-rs90nxQtPi3Asg@jetpack": {minVersion: "1.8.1-signed.1-signed", installs: 201},
"vlcplaylist@helgatauscher.de": {minVersion: "0.8.1-signed.1-signed", installs: 201},
"jid1-G80Ec8LLEbK5fQ@jetpack": {minVersion: "1.3.8", installs: 200},
"_gpMembers_@free.mymapswizard.com": {minVersion: "7.102.10.4775", installs: 199},
"BestMediaTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 199},
"info@convert2mp3.net": {minVersion: "2.5.1-signed.1-signed", installs: 199},
"partnerdefaults@mozilla.com": {minVersion: "1.0.1", installs: 199},
"qwantcomforfirefox@jetpack": {minVersion: "3.0.28", installs: 199},
"{65e41d20-f092-41b7-bb83-c6e8a9ab0f57}": {minVersion: "1.2.6", installs: 198},
"amznUWL2@amazon.com": {minVersion: "1.11", installs: 197},
"{1b80ae74-4912-44fc-9f27-30f9252a5ad7}": {minVersion: "2.3", installs: 197},
"{c9b4cd26-6f0e-4972-a9e0-8b77e811aa8f}": {minVersion: "2.3", installs: 197},
"shopcbtoolbar2@befrugal.com": {minVersion: "2013.3.23.1", installs: 197},
"trafficlight@bitdefender.com": {minVersion: "0.2.23.1-signed.1-signed", installs: 197},
"webrank-toolbar@probcomp.com": {minVersion: "4.4.1.1-signed.1-signed", installs: 197},
"_4lMembers_@www.bibletriviatime.com": {minVersion: "7.102.10.4330", installs: 196},
"xthunder@lshai.com": {minVersion: "1.3.4.1-signed.1-signed", installs: 196},
"extension@hidemyass.com": {minVersion: "1.3.2", installs: 195},
"jid1-MIAJd5BiK7V4Pw@jetpack": {minVersion: "0.9.1-signed.1-signed", installs: 195},
"{51aa69f8-8825-4def-916a-a766c5e3c0fd}": {minVersion: "3.8", installs: 194},
"{2bc72c53-9bde-4db2-8479-eda9a5e71f4e}": {minVersion: "3.2", installs: 193},
"{a95d8332-e4b4-6e7f-98ac-20b733364387}": {minVersion: "1.0.5", installs: 191},
"ocr@babylon.com": {minVersion: "1.1", installs: 191},
"{d3b9472c-f8b1-4a10-935b-1087bac8417f}": {minVersion: "3.8", installs: 189},
"windowpromo@dm73.net": {minVersion: "1.6", installs: 188},
"alldownloader@link64": {minVersion: "1.00.17.1-signed.1-signed", installs: 187},
"{3e0e7d2a-070f-4a47-b019-91fe5385ba79}": {minVersion: "3.6.5.2", installs: 185},
"jid1-vFmnfCkyf5VeSA@jetpack": {minVersion: "0.4.0", installs: 185},
"@greatdealz": {minVersion: "0.0.3", installs: 184},
"superstart@enjoyfreeware.org": {minVersion: "7.4.0.1-signed", installs: 183},
"{c2fc3c2b-a65a-453c-bf95-101fde56ed1d}": {minVersion: "2.3", installs: 182},
"{53152e75-fd90-472f-9d30-5cba3679eab9}": {minVersion: "48.3", installs: 180},
"jid0-raWjElI57dRa4jx9CCiYm5qZUQU@jetpack": {minVersion: "3.0.12.1.1-signed.1-signed", installs: 180},
"_ivMembers_@free.simplepictureedit.com": {minVersion: "7.102.10.14166", installs: 179},
"jid1-wKRSK9TpFpr9Hw@jetpack": {minVersion: "0.92", installs: 179},
"emailExtractor@penzil.com": {minVersion: "1.3.1-signed.1-signed", installs: 178},
"{60B7679C-BED9-11E5-998D-8526BB8E7F8B}": {minVersion: "6.3", installs: 177},
"@pdfit": {minVersion: "0.1.9", installs: 177},
"jid1-6AyZ1PQXsR9LgQ@jetpack": {minVersion: "0.2.1", installs: 177},
"_6oMembers_@free.heroicplay.com": {minVersion: "7.38.8.46626", installs: 175},
"{4BBDD651-70CF-4821-84F8-2B918CF89CA3}": {minVersion: "8.9.3.1", installs: 173},
"jid1-GeRCnsiDhZiTvA@jetpack": {minVersion: "1.0.3", installs: 172},
"jid0-zs24wecdcQo0Lp18D7QOV4WSZFo@jetpack": {minVersion: "0.2.1-signed.1-signed", installs: 171},
"{c50ca3c4-5656-43c2-a061-13e717f73fc8}": {minVersion: "5.0.1.48.1-signed.1-signed", installs: 170},
"selenium_ide_buttons@egarracingteam.com.ar": {minVersion: "1.2.0.1-signed.1-signed", installs: 170},
"WSVCU@Wondershare.com": {minVersion: "7.1.0", installs: 169},
"{4cc4a13b-94a6-7568-370d-5f9de54a9c7f}": {minVersion: "2.7.1-signed.1-signed", installs: 168},
"{aa84ce40-4253-a00a-8cd6-0800200f9a67}": {minVersion: "3.12.0", installs: 168},
"FasterFox_Lite@BigRedBrent": {minVersion: "3.9.9Lite.1-signed.1-signed", installs: 167},
"{6cc0f0f7-a6e2-4834-9682-24de2229b51e}": {minVersion: "23.6", installs: 166},
"{b749fc7c-e949-447f-926c-3f4eed6accfe}": {minVersion: "0.7.1.1.1-signed.1-signed", installs: 166},
"@mendeleyimporter": {minVersion: "1.6.8", installs: 166},
"ALone-live@ya.ru": {minVersion: "1.4.11", installs: 166},
"{4093c4de-454a-4329-8aff-c6b0b123c386}": {minVersion: "0.8.14.1-signed.1-signed", installs: 165},
"cookiemgr@jayapal.com": {minVersion: "5.12", installs: 164},
"touchenex@raon.co.kr": {minVersion: "1.0.1.11", installs: 163},
"{b0e1b4a6-2c6f-4e99-94f2-8e625d7ae255}": {minVersion: "3.5.0.1-signed.1-signed", installs: 162},
"isreaditlater@ideashower.com": {minVersion: "3.0.6.1-signed", installs: 161},
"safesearchplus@avira.com": {minVersion: "1.4.1.371", installs: 161},
"_e0Members_@www.downshotfree.com": {minVersion: "7.102.10.3833", installs: 159},
"LDSI_plashcor@gmail.com": {minVersion: "1.1.0.3", installs: 159},
"jid1-9ETkKdBARv7Iww@jetpack": {minVersion: "0.20.1-signed.1-signed", installs: 157},
"jid1-CGxMej0nDJTjwQ@jetpack": {minVersion: "1.0.1-signed.1-signed", installs: 157},
"{00f7ab9f-62f4-4145-b2f9-38d579d639f6}": {minVersion: "49", installs: 156},
"googledictionary@toptip.ca": {minVersion: "7.5", installs: 156},
"shopearn@prodege.com": {minVersion: "219", installs: 156},
"fvdmedia@gmail.com": {minVersion: "11.0.1", installs: 155},
"magicplayer_unlisted@acestream.org": {minVersion: "1.1.42", installs: 155},
"{0538E3E3-7E9B-4d49-8831-A227C80A7AD3}": {minVersion: "2.2.2.1-signed.1-let-fixed.1-signed", installs: 154},
"{73007fef-a6e0-47d3-b4e7-dfc116ed6f65}": {minVersion: "1.15.1-signed.1-signed", installs: 153},
"{cd617372-6743-4ee4-bac4-fbf60f35719e}": {minVersion: "2.0.1-signed.1-signed", installs: 152},
"TopSecurityTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 152},
"jid1-hDf2iQXGiUjzGQ@jetpack": {minVersion: "2.5.0", installs: 151},
"_dnMembers_@www.free.webmailworld.com": {minVersion: "7.102.10.5052", installs: 149},
"jid1-rrMTK7JqsxNOeQ@jetpack": {minVersion: "2.1.0", installs: 149},
"jid1-XgC5trUcILmXBw@jetpack": {minVersion: "2.0.3", installs: 149},
"online_banking_69A4E213815F42BD863D889007201D82@kaspersky.com": {minVersion: "4.5.3.8", installs: 148},
"jid1-AXn9cXcB4fD1QQ@jetpack": {minVersion: "0.7.4", installs: 148},
"feedly@devhd": {minVersion: "16.0.528.1-signed.1-signed", installs: 147},
"{6E727987-C8EA-44DA-8749-310C0FBE3C3E}": {minVersion: "2.0.0.11", installs: 146},
"{1082eb84-f0f2-11e5-8e18-9bb85ab7992e}": {minVersion: "1.07", installs: 146},
"{c151d79e-e61b-4a90-a887-5a46d38fba99}": {minVersion: "2.8.8", installs: 146},
"public.proartex@gmail.com": {minVersion: "1.1.3", installs: 145},
"jid1-8J7ayxTha4KqKQ@jetpack": {minVersion: "1.1.1-signed.1-signed", installs: 144},
"stealthyextension@gmail.com": {minVersion: "3.0.1.1-signed", installs: 144},
"_beMembers_@free.dailylocalguide.com": {minVersion: "7.38.9.7920", installs: 143},
"mytube@ashishmishra.in": {minVersion: "0.979.1-signed.1-signed", installs: 142},
"@A3592ADB-854A-443A-854E-EB92130D470D": {minVersion: "1.08.8.88", installs: 139},
"FunkyTVTabs-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 139},
"jid1-QpHD8URtZWJC2A@jetpack": {minVersion: "4.3.0", installs: 138},
"savedeo-video-downloader@fczbkk.com": {minVersion: "0.4.1.1-signed.1-signed", installs: 137},
"toolbar@shopathome.com": {minVersion: "8.20.3.1", installs: 137},
"_dyMembers_@www.dezipper.com": {minVersion: "7.102.10.3775", installs: 135},
"jid0-zXo3XFGyiDalgkeEO4UYJTUwo2I@jetpack": {minVersion: "1.0.0", installs: 134},
"{d57c9ff1-6389-48fc-b770-f78bd89b6e8a}": {minVersion: "1.46.1-signed.1-signed", installs: 133},
"@searchlock-fx": {minVersion: "1.1.6", installs: 133},
"dm@jetpack": {minVersion: "0.0.2", installs: 133},
"proxyselector@mozilla.org": {minVersion: "1.31.1-signed.1-signed", installs: 133},
"{065829BC-17B5-4C0B-9429-3173C361092E}": {minVersion: "1.0.8", installs: 132},
"{ada4b710-8346-4b82-8199-5de2b400a6ae}": {minVersion: "2.1.5.5.3", installs: 132},
"readable@evernote.com": {minVersion: "10.2.1.7.1-signed", installs: 131},
"{d48a39ba-8f80-4fce-8ee1-bc710561c55d}": {minVersion: "3.1.0.1-signed.1-signed", installs: 131},
"autorefresh@plugin": {minVersion: "1.0.2.1-signed.1-signed", installs: 130},
"SafeBrowseSearch-the-extension1@mozilla.com": {minVersion: "0.1.2", installs: 130},
"browsermodulecorp@browcorporation.org": {minVersion: "2.3", installs: 129},
"wisestamp@wisestamp.com": {minVersion: "4.14.20", installs: 127},
"_63Members_@www.aplusgamer.com": {minVersion: "7.38.8.45832", installs: 126},
"bestproxyswitcher@bestproxyswitcher.com": {minVersion: "3.4.6.1-signed.1-signed", installs: 126},
"jid1-AVgCeF1zoVzMjA@jetpack": {minVersion: "0.9.5.6", installs: 126},
"{ce7e73df-6a44-4028-8079-5927a588c948}": {minVersion: "1.1.4", installs: 125},
"{E71B541F-5E72-5555-A47C-E47863195841}": {minVersion: "3.0.3", installs: 125},
"{F5DDF39C-9293-4d5e-9AA8-E04E6DD5E9B4}": {minVersion: "1.6.3.1-signed.1-signed", installs: 125},
"@simplepopupblocker": {minVersion: "1.2.1", installs: 125},
"commonfix@mozillaonline.com": {minVersion: "0.13", installs: 125},
"searchme@mybrowserbar.com": {minVersion: "2.8", installs: 125},
"_4wMembers_@www.retrogamer.com": {minVersion: "7.38.8.46604", installs: 124},
"{71328583-3CA7-4809-B4BA-570A85818FBB}": {minVersion: "0.8.6.3.1-let-fixed", installs: 123},
"dmremote@westbyte.com": {minVersion: "1.9.3", installs: 123},
"@google-translate-menu": {minVersion: "1.0.1", installs: 122},
"_aaMembers_@free.eliteunzip.com": {minVersion: "7.39.8.50909", installs: 121},
"{8620c15f-30dc-4dba-a131-7c5d20cf4a29}": {minVersion: "3.9", installs: 121},
"{eb4b28c8-7f2d-4327-a00c-40de4299ba44}": {minVersion: "1.7", installs: 121},
"flashlight@stephennolan.com.au": {minVersion: "1.2.1-signed.1-signed", installs: 121},
"useragentoverrider@qixinglu.com": {minVersion: "0.4.1", installs: 121},
"{1B33E42F-EF14-4cd3-B6DC-174571C4349C}": {minVersion: "4.7", installs: 120},
"_dxMembers_@www.download-freemaps.com": {minVersion: "7.38.8.46371", installs: 120},
"{95ab36d4-fb6f-47b0-8b8d-e5f3bd547953}": {minVersion: "4.20.13.1-signed.1-signed", installs: 120},
"FirefoxAddon@similarWeb.com": {minVersion: "4.0.6", installs: 120},
"flashstopper@byo.co.il": {minVersion: "1.4.2", installs: 120},
"{15e67a59-bd3d-49ae-90dd-b3d3fd14c2ed}": {minVersion: "1.0.3.1-signed.1-signed", installs: 119},
"{c37bac34-849a-4d28-be41-549b2c76c64e}": {minVersion: "2.6", installs: 119},
"{03B08592-E5B4-45ff-A0BE-C1D975458688}": {minVersion: "1.1.1-signed.1-signed", installs: 118},
"newtabgoogle@graememcc.co.uk": {minVersion: "1.0.2.1-signed.1-signed", installs: 118},
"SocialNewtabs-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 118},
"@kikikokicicidada": {minVersion: "2.1.2", installs: 117},
"{9D6218B8-03C7-4b91-AA43-680B305DD35C}": {minVersion: "4.0.5", installs: 116},
"extension@one-tab.com": {minVersion: "1.17.0", installs: 116},
"{22119944-ED35-4ab1-910B-E619EA06A115}": {minVersion: "7.9.21.5", installs: 115},
"admin@hide-my-ip.org": {minVersion: "9.6.3", installs: 115},
"bdwteffv19@bitdefender.com": {minVersion: "2.2.1", installs: 115},
"exif_viewer@mozilla.doslash.org": {minVersion: "2.00.1-signed.1-signed", installs: 115},
"MyStartab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 115},
"coralietab@mozdev.org": {minVersion: "2.04.20110724.1-signed.1-signed", installs: 113},
"gaurangnshah@gmail.com": {minVersion: "1.3.2.1-signed.1-signed", installs: 113},
"ImagePicker@topolog.org": {minVersion: "1.9.4", installs: 113},
"{d49a148e-817e-4025-bee3-5d541376de3b}": {minVersion: "3.1.1-signed.1-signed", installs: 112},
"firebug@tools.sitepoint.com": {minVersion: "1.6.1-signed.1-signed", installs: 111},
"add-to-searchbox@maltekraus.de": {minVersion: "2.9", installs: 110},
"captiondownloader@hiephm.com": {minVersion: "2.3.1-signed.1-signed", installs: 110},
"jid1-LYopfl0r00ZV5k@jetpack": {minVersion: "1.0.1-signed.1-signed", installs: 110},
"{7CA9CF31-1C73-46CD-8377-85AB71EA771F}": {minVersion: "5.0.12", installs: 109},
"jid1-HdwPLukcGQeOSh@jetpack": {minVersion: "1.2.3", installs: 108},
"{0AA9101C-D3C1-4129-A9B7-D778C6A17F82}": {minVersion: "2.09.1-signed", installs: 107},
"CookiesIE@yahoo.com": {minVersion: "1.0.1-signed.1-signed", installs: 107},
"selenium-expert_selenium-ide@Samit.Badle": {minVersion: "0.25.1-signed.1-signed", installs: 107},
"{19EB90DC-A456-458b-8AAC-616D91AAFCE1}": {minVersion: "1.0.1-signed", installs: 105},
"application@itineraire.info": {minVersion: "4.5.0", installs: 105},
"rest-easy@quickmediasolutions.com": {minVersion: "0.3.1.1-signed", installs: 105},
"TopSocialHub-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 105},
"{7affbfae-c4e2-4915-8c0f-00fa3ec610a1}": {minVersion: "6.36.32", installs: 104},
"azhang@cloudacl.com": {minVersion: "0.19.6.9.1-signed.1-signed", installs: 104},
"FunCyberTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 104},
"SkipScreen@SkipScreen": {minVersion: "0.7.2.1-signed.1-signed", installs: 104},
"toolbar@seomoz.org": {minVersion: "3.1.18", installs: 104},
"{8b86149f-01fb-4842-9dd8-4d7eb02fd055}": {minVersion: "0.26.1-signed.1-signed", installs: 103},
"fbp@fbpurity.com": {minVersion: "9.3.2.1-signed", installs: 103},
"jid1-V8ev2melBDV3qQ@jetpack": {minVersion: "1.0.12.1-signed.1-signed", installs: 103},
"_fvMembers_@free.directionsace.com": {minVersion: "7.102.10.3790", installs: 102},
"{b6b1a201-b252-484f-b9fe-68efbb273fbd}": {minVersion: "1.10.1-signed.1-signed", installs: 102},
"flashfirebug@o-minds.com": {minVersion: "4.9.1", installs: 102},
"_ebMembers_@download.metrohotspot.com": {minVersion: "7.102.10.4735", installs: 101},
"{2e17e2b2-b8d4-4a67-8d7b-fafa6cc9d1d0}": {minVersion: "1.2.7.0.1-signed.1-signed", installs: 101},
"{ea61041c-1e22-4400-99a0-aea461e69d04}": {minVersion: "0.2.4.1-signed.1-signed", installs: 101},
"rapportive@rapportive.com": {minVersion: "1.4.0.1.1-signed.1-signed", installs: 101},
"_dvMembers_@www.testinetspeed.com": {minVersion: "7.38.8.45918", installs: 100},
"{9aad3da6-6c46-4ef0-9109-6df5eaaf597c}": {minVersion: "1.4.1.1-signed.1-signed", installs: 100},
"{c2b1f3ae-5cd5-49b7-8a0c-2c3bcbbbb294}": {minVersion: "1.1.1-signed.1-signed", installs: 100},
"jid0-w1UVmoLd6VGudaIERuRJCPQx1dQ@jetpack": {minVersion: "1.6.8.1-signed", installs: 100},
"_cxMembers_@www.autopcbackup.com": {minVersion: "7.102.10.3597", installs: 99},
"vpn@hide-my-ip.org": {minVersion: "10.6.2", installs: 99},
"{1a5dabbd-0e74-41da-b532-a364bb552cab}": {minVersion: "1.0.9.1-signed", installs: 98},
"FirePHPExtension-Build@firephp.org": {minVersion: "0.7.4.1-signed.1-signed", installs: 98},
"jid1-UXDr6c69BeyPVw@jetpack": {minVersion: "0.8.2", installs: 98},
"TopSafeTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 98},
"{3b56bcc7-54e5-44a2-9b44-66c3ef58c13e}": {minVersion: "0.9.7.4", installs: 97},
"autoreload@yz.com": {minVersion: "1.21", installs: 97},
"manish.p05@gmail.com": {minVersion: "12.9", installs: 97},
"videoresumer@jetpack": {minVersion: "1.1.4", installs: 97},
"@Radio": {minVersion: "0.2.0", installs: 96},
"_hfMembers_@free.everydaymanuals.com": {minVersion: "7.102.10.4142", installs: 96},
"jid0-jJRRRBMgoShUhb07IvnxTBAl29w@jetpack": {minVersion: "2.0.4", installs: 96},
"rikaichan-jpen@polarcloud.com": {minVersion: "2.01.160101", installs: 96},
"{7c6cdf7c-8ea8-4be7-ae5a-0b3effe14d66}": {minVersion: "49.1", installs: 95},
"{FDBAD97E-A258-4fe3-9CF6-60CF386C4422}": {minVersion: "2.0.1.6", installs: 95},
"intgcal@egarracingteam.com.ar": {minVersion: "1.5.1", installs: 95},
"MediaNewTab-the-extension1@mozilla.com": {minVersion: "0.1.6", installs: 95},
"{9EB34849-81D3-4841-939D-666D522B889A}": {minVersion: "2.4.0.157", installs: 94},
"{158d7cb3-7039-4a75-8e0b-3bd0a464edd2}": {minVersion: "2.7.1-signed.1-signed", installs: 94},
"jid1-ach2kaGSshPJCg@jetpack": {minVersion: "0.1.1-signed.1-signed", installs: 94},
"jid1-cwbvBTE216jjpg@jetpack": {minVersion: "2.1.0.1-signed.1-signed", installs: 94},
"{f36c6cd1-da73-491d-b290-8fc9115bfa55}": {minVersion: "3.0.9.1-signed.1-let-fixed.1-signed", installs: 93},
"dmpluginff@westbyte.com": {minVersion: "1.4.12", installs: 93},
"firefox@serptrends.com": {minVersion: "0.8.14", installs: 93},
"panel-plugin@effectivemeasure.com": {minVersion: "4.0.0", installs: 93},
"_evMembers_@www.free.bestbackground.com": {minVersion: "7.102.10.3607", installs: 92},
"canitbecheaper@trafficbroker.co.uk": {minVersion: "3.9.78", installs: 92},
"favorites_selenium-ide@Samit.Badle": {minVersion: "2.0.1-signed.1-signed", installs: 92},
"{5F590AA2-1221-4113-A6F4-A4BB62414FAC}": {minVersion: "0.45.8.20130519.3.1-signed.1-signed", installs: 90},
"{3e9bb2a7-62ca-4efa-a4e6-f6f6168a652d}": {minVersion: "2.7.7.1-signed.1-signed", installs: 90},
"{ab4b5718-3998-4a2c-91ae-18a7c2db513e}": {minVersion: "1.2.0.1-signed.1-signed", installs: 90},
"2020Player_WEB@2020Technologies.com": {minVersion: "5.0.94.0", installs: 90},
"translator@dontfollowme.net": {minVersion: "2.0.5", installs: 90},
"YouTubeAutoReplay@arikv.com": {minVersion: "3.3.1-signed.1-signed", installs: 90},
"{a949831f-d9c0-45ae-8c60-91c2a86fbfb6}": {minVersion: "0.2.1-signed.1-signed", installs: 89},
"@vpn-unlimited-secure-proxy": {minVersion: "4.4", installs: 89},
"jid1-JcGokIiQyjoBAQ@jetpack": {minVersion: "0.6.1-signed.1-signed", installs: 89},
"_73Members_@www.easyhomedecorating.com": {minVersion: "7.102.10.4129", installs: 88},
"{065ee92a-ad57-42a2-b6d5-466b6fd8e24d}": {minVersion: "0.11.6.1-signed.1-signed", installs: 88},
"{455D905A-D37C-4643-A9E2-F6FEFAA0424A}": {minVersion: "0.8.17.1-signed.1-signed", installs: 88},
"{7eb3f691-25b4-4a85-9038-9e57e2bcd537}": {minVersion: "0.4.4.1-signed.1-signed", installs: 88},
"FunSocialTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 88},
"Lucifox@lucidor.org": {minVersion: "0.9.13", installs: 88},
"YourMediaTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 88},
"youtube-video-player@lejenome.me": {minVersion: "0.2.38.1-signed.1-signed", installs: 88},
"_hgMembers_@free.atozmanuals.com": {minVersion: "7.102.10.3604", installs: 87},
"abb-acer@amazon.com": {minVersion: "10.161.13.1002", installs: 87},
"gmail_panel@alejandrobrizuela.com.ar": {minVersion: "1.2.0", installs: 87},
"izer@camelcamelcamel.com": {minVersion: "2.8.2", installs: 87},
"tvnewtab-the-extension1@mozilla.com": {minVersion: "0.1.5", installs: 87},
"vlc_shortcut@kosan.kosan": {minVersion: "0.8.3.0", installs: 87},
"youtubeunblocker@unblocker.yt": {minVersion: "0.6.20", installs: 86},
"email@jetpack": {minVersion: "0.0.16", installs: 86},
"extensions@gismeteo.com": {minVersion: "5.1.0.2", installs: 86},
"idaremote@westbyte.com": {minVersion: "1.6.3", installs: 86},
"{725fc0a6-1f6b-4cf9-ae17-748d111dc16d}": {minVersion: "1.1.0", installs: 85},
"jid1-461B0PwxL3oTt1@jetpack": {minVersion: "0.2.1-signed.1-signed", installs: 85},
"webdavlauncher@benryan.com": {minVersion: "1.1.0", installs: 85},
"jid1-ZM3BerwS6FsQAg@jetpack": {minVersion: "0.4.1-signed", installs: 84},
"_fwMembers_@free.howtosuite.com": {minVersion: "7.102.10.4280", installs: 84},
"{023e9ca0-63f3-47b1-bcb2-9badf9d9ef28}": {minVersion: "4.4.3.1-signed.1-signed", installs: 84},
"{25A1388B-6B18-46c3-BEBA-A81915D0DE8F}": {minVersion: "1.7.8.5.1-signed.1-signed", installs: 84},
"{75493B06-1504-4976-9A55-B6FE240FF0BF}": {minVersion: "3.4.0.0", installs: 84},
"facepaste.firefox.addon@azabani.com": {minVersion: "2.91", installs: 84},
"jid1-cplLTTY501TB2Q@jetpack": {minVersion: "0.5.1", installs: 84},
"_d1Members_@free.mysocialshortcut.com": {minVersion: "7.102.10.4792", installs: 83},
"{761a54f1-8ccf-4112-9e48-dbf72adf6244}": {minVersion: "2.3.1-signed.1-signed", installs: 83},
"{BBB77B49-9FF4-4d5c-8FE2-92B1D6CD696C}": {minVersion: "2.0.0.1083", installs: 83},
"{a3a5c777-f583-4fef-9380-ab4add1bc2a2}": {minVersion: "2.1.4", installs: 82},
"{eb80b076-a444-444c-a590-5aee5d977d80}": {minVersion: "2.6.18", installs: 82},
"KVAllmytube@KeepVid.com": {minVersion: "4.10.0", installs: 82},
"lumerias-instagram@lumerias.com": {minVersion: "1.3", installs: 82},
"omnibar@ajitk.com": {minVersion: "0.7.28.20141004.1-signed.1-signed", installs: 81},
"@autofillanyforms-easytatkal": {minVersion: "7.51.0", installs: 81},
"@youtuberightclick": {minVersion: "0.0.3", installs: 81},
"autoproxy@autoproxy.org": {minVersion: "0.4b2.2013051811.1-signed.1-let-fixed.1-signed", installs: 80},
"{e33788ea-0bb9-4502-9c77-bdc551afc8ad}": {minVersion: "1.0.4", installs: 80},
"dmmm@westbyte.com": {minVersion: "1.3.4", installs: 80},
"easycopy@smokyink.com": {minVersion: "2.7.0", installs: 80},
"jid1-LelsJ0Oz0rt71A@jetpack": {minVersion: "2.0.0", installs: 80},
"_f7Members_@download.smsfrombrowser.com": {minVersion: "7.38.8.45917", installs: 79},
"{6614d11d-d21d-b211-ae23-815234e1ebb5}": {minVersion: "3.9.13", installs: 79},
"FunTvTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 79},
"{4204c864-50bf-467a-95b3-0912b7f15869}": {minVersion: "1.2.00.1-signed.1-signed", installs: 78},
"{987311C6-B504-4aa2-90BF-60CC49808D42}": {minVersion: "3.1-signed.1-signed", installs: 78},
"uploader@adblockfilters.mozdev.org": {minVersion: "2.1.1-signed.1-let-fixed.1-signed", installs: 77},
"PageRank@addonfactory.in": {minVersion: "2.0.1-signed.1-signed", installs: 77},
"restartbutton@strk.jp": {minVersion: "0.1.5.1-signed.1-signed", installs: 77},
"text2voice@vik.josh": {minVersion: "1.15", installs: 77},
"_dpMembers_@free.findyourmaps.com": {minVersion: "7.102.10.4185", installs: 76},
"53ffxtbr@www.dailyfitnesscenter.com": {minVersion: "7.36.8.15623", installs: 76},
"gary@builtwith.com": {minVersion: "1.9.6.1-signed.1-signed", installs: 76},
"idamm@westbyte.com": {minVersion: "1.3.2", installs: 76},
"jid1-3gu11JeYBiIuJA@jetpack": {minVersion: "3.1.1", installs: 76},
"jid1-zV8eHYwTDNUtwQ@jetpack": {minVersion: "1.0.4", installs: 76},
"nst@neiron.ru": {minVersion: "7.3.0.2", installs: 76},
"service@touchpdf.com": {minVersion: "1.15.1-signed.1-signed", installs: 76},
"{02450954-cdd9-410f-b1da-db804e18c671}": {minVersion: "0.96.3.1-signed.1-signed", installs: 75},
"{4176DFF4-4698-11DE-BEEB-45DA55D89593}": {minVersion: "0.8.50.1-signed.1-signed", installs: 75},
"{DAD0F81A-CF67-4eed-98D6-26F6E47274CA}": {minVersion: "1.8.1-signed.1-signed", installs: 75},
"dealxplorermysites770@yahoo.com": {minVersion: "0.0.0.1", installs: 75},
"firefox@online-convert.com": {minVersion: "1.4.1-signed.1-signed", installs: 75},
"jid1-zmgYgiQPXJtjNA@jetpack": {minVersion: "1.23", installs: 75},
"_evMembers_@www.bestbackground.com": {minVersion: "7.38.9.7654", installs: 74},
"dmbarff@westbyte.com": {minVersion: "1.5.11", installs: 74},
"inf@youtube-mp3.video": {minVersion: "0.1", installs: 74},
"jid1-e7w0SHT82Gv9qA@jetpack": {minVersion: "1.2", installs: 74},
"NewTabTVCool-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 74},
"{E173B749-DB5B-4fd2-BA0E-94ECEA0CA55B}": {minVersion: "7.4.1-signed", installs: 73},
"{9BAE5926-8513-417d-8E47-774955A7C60D}": {minVersion: "1.1.1d.1-signed.1-signed", installs: 73},
"{3cc6c6ba-654c-417e-a8af-6997ac388ae1}": {minVersion: "49", installs: 72},
"{daf44bf7-a45e-4450-979c-91cf07434c3d}": {minVersion: "2.0.5", installs: 72},
"{dbac9680-d559-4cd4-9765-059879e8c467}": {minVersion: "5.0.5", installs: 72},
"application@recettes.net": {minVersion: "4.5.0", installs: 72},
"idapluginff@westbyte.com": {minVersion: "1.5.9", installs: 71},
"imgflashblocker@shimon.chohen": {minVersion: "0.7.1-signed.1-signed", installs: 71},
"inspector@mozilla.org": {minVersion: "2.0.16.1-signed", installs: 71},
"jid1-ReWlW1efOwaQJQ@jetpack": {minVersion: "1.1.2", installs: 71},
"youtubedownloader@trafficterminal.com": {minVersion: "1.0.1.1-signed.1-signed", installs: 71},
"FavIconReloader@mozilla.org": {minVersion: "0.8.1-signed", installs: 70},
"_2bMembers_@www.bettercareersearch.com": {minVersion: "7.38.8.45828", installs: 70},
"{5e594888-3e8e-47da-b2c6-b0b545112f84}": {minVersion: "1.3.18", installs: 70},
"@greatdealzu": {minVersion: "0.0.3", installs: 70},
"86ffxtbr@download.yourvideochat.com": {minVersion: "7.36.8.15938", installs: 70},
"google@hitachi.com": {minVersion: "0.3.1-signed.1-signed", installs: 70},
"{6e84150a-d526-41f1-a480-a67d3fed910d}": {minVersion: "1.5.6.1-signed.1-signed", installs: 69},
"firepicker@thedarkone": {minVersion: "1.4.3.1-signed.1-signed", installs: 69},
"jid0-AocRXUCRsLTCYvn6bgJERnwfuqw@jetpack": {minVersion: "2.8.3.1-signed.1-signed", installs: 69},
"nortonsecurity@symantec.com": {minVersion: "7.2.0f90", installs: 69},
"{ef4e370e-d9f0-4e00-b93e-a4f274cfdd5a}": {minVersion: "1.4.10.1-signed", installs: 68},
"{d4e0dc9c-c356-438e-afbe-dca439f4399d}": {minVersion: "49.1", installs: 68},
"{E6C93316-271E-4b3d-8D7E-FE11B4350AEB}": {minVersion: "2.1.25.1-signed.1-signed", installs: 68},
"{fa8476cf-a98c-4e08-99b4-65a69cb4b7d4}": {minVersion: "1.7.6.1", installs: 68},
"simplesiteblocker@example.com": {minVersion: "1.1.1-signed.1-signed", installs: 68},
"_fpMembers_@free.passwordlogic.com": {minVersion: "7.102.10.4853", installs: 67},
"{6e764c17-863a-450f-bdd0-6772bd5aaa18}": {minVersion: "1.0.3.1-signed.1-signed", installs: 67},
"adbeaver@adbeaver.org": {minVersion: "0.7.2.9", installs: 67},
"application2@allo-pages.fr": {minVersion: "4.5.0", installs: 67},
"arf3@getcartt.com": {minVersion: "1.2.3", installs: 67},
"clearcache@michel.de.almeida": {minVersion: "2.0.1.1-signed.1-signed", installs: 67},
"fbmessengerpanel@alejandrobrizuela.com.ar": {minVersion: "1.0.3.1-signed.1-signed", installs: 67},
"tilt@mozilla.com": {minVersion: "1.0.1.1-signed.1-signed", installs: 67},
"toolbar_AVIRA-V7@apn.ask.com": {minVersion: "127.25", installs: 67},
"{524B8EF8-C312-11DB-8039-536F56D89593}": {minVersion: "4.91.0.0", installs: 66},
"{9d1f059c-cada-4111-9696-41a62d64e3ba}": {minVersion: "0.17.0.1", installs: 66},
"{B068AC18-0121-4e67-9A7E-6386F93F4F7A}": {minVersion: "2.4", installs: 66},
"@lottadealsun": {minVersion: "0.0.1", installs: 66},
"@thebestyoutubedownloader": {minVersion: "1.0.0.7", installs: 66},
"TopSocialTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 66},
"{11483926-db67-4190-91b1-ef20fcec5f33}": {minVersion: "0.4.9.1", installs: 65},
"{8AA36F4F-6DC7-4c06-77AF-5035170634FE}": {minVersion: "2016.9.16", installs: 65},
"dam@tensons.com": {minVersion: "5.0.7", installs: 65},
"jid1-D7momAzRw417Ag@jetpack": {minVersion: "4.5.13", installs: 65},
"support@videoadd.ru": {minVersion: "2.8.1.1-signed.1-signed", installs: 65},
"{95322c08-05ff-4f3c-85fd-8ceb821988dd}": {minVersion: "49", installs: 64},
"AllMyTube@Wondershare.com": {minVersion: "4.9.1", installs: 64},
"azan-times@hamid.net": {minVersion: "1.2.3.1-signed.1-signed", installs: 64},
"check-compatibility@dactyl.googlecode.com": {minVersion: "1.3.1-signed.1-signed", installs: 64},
"ifamebook@stormvision.it": {minVersion: "4.03.1-signed", installs: 64},
"jid1-vRJA7N8VwBoiXw@jetpack": {minVersion: "1.1.1.1-signed", installs: 64},
"{04426594-bce6-4705-b811-bcdba2fd9c7b}": {minVersion: "1.7.1-signed.1-signed", installs: 63},
"{f3f219f9-cbce-467e-b8fe-6e076d29665c}": {minVersion: "50", installs: 63},
"fireforce@scrt.ch": {minVersion: "2.2.1-signed.1-signed", installs: 63},
"FunkyMediaTab-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 63},
"TopTabTV-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 63},
"{018f3160-1a6f-4650-84fd-aad8c13609c8}": {minVersion: "0.1.1-signed.1-signed", installs: 62},
"{2e710e6b-5e9d-44ba-8f4e-09a040978b49}": {minVersion: "1.7", installs: 62},
"{c1970c0d-dbe6-4d91-804f-c9c0de643a57}": {minVersion: "1.3.2.13.1-signed.1-signed", installs: 62},
"{c9b4529a-eeba-4e48-976e-f3d3f9026e04}": {minVersion: "1.1.1-signed.1-signed", installs: 62},
"{df4e4df5-5cb7-46b0-9aef-6c784c3249f8}": {minVersion: "1.3.0.1-signed.1-signed", installs: 62},
"@stremio": {minVersion: "1.0.2", installs: 62},
"application@les-pages.com": {minVersion: "4.4.0", installs: 62},
"ffvkontaktemusic@chupakabr.ru": {minVersion: "2.2.1-signed.1-signed", installs: 62},
"firefinder@robertnyman.com": {minVersion: "1.4.1-signed.1-signed", installs: 62},
"formhistory@yahoo.com": {minVersion: "1.4.0.6", installs: 62},
"fxclickonce@rushyo.com": {minVersion: "0.1.1-signed.1-signed", installs: 62},
"gmail@borsosfisoft.com": {minVersion: "1.0.1.1-signed.1-signed", installs: 62},
"HighlightedTextToFile@bobbyrne01.org": {minVersion: "2.7.1", installs: 62},
"jid1-n85lxPv1NAWVTQ@jetpack": {minVersion: "0.96.1.1-signed", installs: 62},
"ramback@pavlov.net": {minVersion: "1.0.1-signed.1-signed", installs: 62},
"VacuumPlacesImproved@lultimouomo-gmail.com": {minVersion: "1.2.1-signed.1-signed", installs: 62},
"@News": {minVersion: "0.2.0", installs: 61},
"{45d8ff86-d909-11db-9705-005056c00008}": {minVersion: "1.3.4.8", installs: 61},
"{686fc9c5-c339-43db-b93a-5181a217f9a6}": {minVersion: "1.11", installs: 61},
"{ea2b95c2-9be8-48ed-bdd1-5fcd2ad0ff99}": {minVersion: "0.3.8.1.1-signed.1-signed", installs: 61},
"@chomikuj": {minVersion: "1.2.0", installs: 61},
"avg@wtu3": {minVersion: "3.7.0.0", installs: 61},
"jid1-f7dnBeTj8ElpWQ@jetpack": {minVersion: "1.34.1-signed.1-signed", installs: 61},
"jid1-OY8Xu5BsKZQa6A@jetpack": {minVersion: "2.0.21", installs: 61},
"jid1-u9RbFp9JcoEGGw@jetpack": {minVersion: "1.2.2.1-signed.1-signed", installs: 61},
"plugin@okta.com": {minVersion: "5.8.0", installs: 61},
"showpassword@pratikpoddar": {minVersion: "1.7.1-signed.1-signed", installs: 61},
"IGF.F3@igufei.com": {minVersion: "3.2.11", installs: 60},
"{12b6fdcd-4423-4276-82a3-73fdbff5f7e4}": {minVersion: "50", installs: 60},
"{8F6A6FD9-0619-459f-B9D0-81DE065D4E21}": {minVersion: "1.13", installs: 60},
"jid1-mW7iuA66Ny8Ziw@jetpack": {minVersion: "0.9.1-signed.1-signed", installs: 60},
"nishan.naseer.googimagesearch@gmail.com": {minVersion: "0.5.1-signed.1-signed", installs: 60},
"quicksearch@yandex.ru": {minVersion: "1.0.13", installs: 60},
"{902D2C4A-457A-4EF9-AD43-7014562929FF}": {minVersion: "0.6.4", installs: 59},
"@yset": {minVersion: "0.0.10", installs: 59},
"csscoverage@spaghetticoder.org": {minVersion: "0.3.4.1-signed.1-signed", installs: 59},
"dgnria2@nuance.com": {minVersion: "15.00.000.058", installs: 59},
"firequery@binaryage.com": {minVersion: "2.0.4", installs: 59},
"IBM-cck@firefox-extensions.ibm.com": {minVersion: "2.3.0", installs: 59},
"trackmenot@mrl.nyu.edu": {minVersion: "0.9.2", installs: 59},
"_chMembers_@free.discoverancestry.com": {minVersion: "7.102.10.3818", installs: 58},
"{338e0b96-2285-4424-b4c8-e25560750fa3}": {minVersion: "3.1-signed.1-signed", installs: 58},
"{8b5bea8c-6194-4c7c-a440-d5ca181480c3}": {minVersion: "1.500.000.11", installs: 58},
"{e30e9060-21d5-11e3-8224-0800200c9a66}": {minVersion: "1.2.12", installs: 58},
"LDshowpicture_plashcor@gmail.com": {minVersion: "3.2", installs: 58},
"open.about.permissions@jasnapaka.com": {minVersion: "1.2.1-signed.1-signed", installs: 58},
"sqlime@security.compass": {minVersion: "0.4.7.1-signed.1-signed", installs: 58},
"@jetpack-easy-turism2": {minVersion: "7.1.0", installs: 57},
"check4change-owner@mozdev.org": {minVersion: "1.9.8.1", installs: 57},
"jid1-SDFC9fEAZRW7ab@jetpack": {minVersion: "0.1.3.1-signed.1-signed", installs: 57},
"linkgopher@oooninja.com": {minVersion: "1.3.3.1-signed.1-signed", installs: 57},
"pixelperfectplugin@openhouseconcepts.com": {minVersion: "2.0.14", installs: 57},
"YoutubeDownloader@huangho.net76.net": {minVersion: "1.6.5.1-signed.1-signed", installs: 57},
"lwthemes-manager@loucypher": {minVersion: "0.2.1-signed.1-let-fixed.1-signed", installs: 56},
"_eiMembers_@www.100sofrecipes.com": {minVersion: "7.102.10.3580", installs: 56},
"{068c594c-1a69-4f51-888d-1e231eac59a3}": {minVersion: "1", installs: 56},
"{139C4B80-60ED-11E4-80EC-84041E5D46B0}": {minVersion: "1.3", installs: 56},
"{4c7097f7-08f2-4ef2-9b9f-f95fa4cbb064}": {minVersion: "1.1", installs: 56},
"{776f38cb-6255-4b92-b5cf-e5c71ff2b688}": {minVersion: "1.6", installs: 56},
"{79c50f9a-2ffe-4ee0-8a37-fae4f5dacd4f}": {minVersion: "5.1.3", installs: 56},
"{8BCA0E8A-E57B-425b-A05B-CD3868EB577E}": {minVersion: "1.4.1-signed.1-signed", installs: 56},
"easycopypaste@everhelper.me": {minVersion: "1.1.0.1-signed.1-signed", installs: 56},
"NoiaFoxoption@davidvincent.tld": {minVersion: "3.0.2.1-signed", installs: 56},
"r2d2b2g@mozilla.org": {minVersion: "4.0.4.1-signed", installs: 56},
"TFToolbarX@torrent-finder": {minVersion: "1.3.1.1-signed.1-signed", installs: 56},
"{E4091D66-127C-11DB-903A-DE80D2EFDFE8}": {minVersion: "1.6.5.5.1-signed.1-signed", installs: 55},
"downintab@max.max": {minVersion: "1.00.1-signed.1-signed", installs: 55},
"flv2mp3@hotger.com": {minVersion: "2.3.2-signed", installs: 55},
"ISVCU@iSkysoft.com": {minVersion: "5.1.0", installs: 55},
"jid1-n5ARdBzHkUEdAA@jetpack": {minVersion: "3.8.7", installs: 55},
"rpnetdownloadhelper@gmail.com": {minVersion: "3.0.1-signed.1-signed", installs: 55},
"shpassword@shpassword.fr": {minVersion: "0.3.1-signed.1-signed", installs: 55},
"snt@simplenewtab.com": {minVersion: "1.3", installs: 55},
"admin@djamol.com": {minVersion: "4.31.1-signed.1-signed", installs: 54},
"{22870005-adef-4c9d-ae36-d0e1f2f27e5a}": {minVersion: "0.4.0.9.1.1-signed.1-signed", installs: 54},
"{DBBB3167-6E81-400f-BBFD-BD8921726F52}": {minVersion: "7125.2016.0115.2213", installs: 54},
"{e4f94d1e-2f53-401e-8885-681602c0ddd8}": {minVersion: "1.0.1-signed.1-signed", installs: 54},
"{FBF6D7FB-F305-4445-BB3D-FEF66579A033}": {minVersion: "6", installs: 54},
"5aa55fd5-6e61-4896-b186-fdc6f298ec92@mozilla": {minVersion: "0.1.2.1-signed", installs: 54},
"fireml@sirma.bg": {minVersion: "1.1.11.1-signed.1-signed", installs: 54},
"info@priceblink.com": {minVersion: "4.8", installs: 54},
"jid0-f82gosWvE8oeGQt6WDCGRF1Dy7Q@jetpack": {minVersion: "1.0.003", installs: 54},
"jid0-hyjN250ZzTOOX3evFwwAQBxE4ik@jetpack": {minVersion: "6.0.1-signed.1-signed", installs: 54},
"restart@restart.org": {minVersion: "0.5.1-signed.1-signed", installs: 54},
"webmaster@keep-tube.com": {minVersion: "1.2.1-signed.1-signed", installs: 54},
"eliteproxyswitcher@my-proxy.com": {minVersion: "1.2.0.2.1-signed.1-signed", installs: 53},
"foxfilter@inspiredeffect.net": {minVersion: "7.7.1-signed.1-signed", installs: 53},
"searchprivacy@searchprivacy.co": {minVersion: "1.15", installs: 53},
"SignPlugin@pekao.pl": {minVersion: "1.4.0.73", installs: 53},
"{15fe27f3-e5ab-2d59-4c5c-dadc7945bdbd}": {minVersion: "2.1.1.1-signed.1-signed", installs: 52},
"ipfuck@p4ul.info": {minVersion: "1.2.1.1-signed.1-signed", installs: 52},
"jyboy.yy@gmail.com": {minVersion: "1.0.4.1-signed.1-signed", installs: 52},
"MySafeTabs-the-extension1@mozilla.com": {minVersion: "0.1.9", installs: 52},
"saiful.neo@gmail.com": {minVersion: "3.0.1-signed.1-signed", installs: 52},
"sendtokindle@amazon.com": {minVersion: "1.0.2.76", installs: 52},
"smile1Button@amazon.com": {minVersion: "1.0.1-signed.1-signed", installs: 52},
"whodeletedme@deleted.io": {minVersion: "0.3.3", installs: 52},
"{C0CB8BA3-6C1B-47e8-A6AB-1FAB889562D9}": {minVersion: "0.7.6", installs: 51},
"@irctctatkal": {minVersion: "2.0.0", installs: 51},
"antgroup@antdownloadmanager.com": {minVersion: "0.1.7", installs: 51},
"downloadplan@firefoxmania.uci.cu": {minVersion: "1.3.1-signed.1-signed", installs: 51},
"jid1-AoXeeOB4j7kFdA@jetpack": {minVersion: "8.1", installs: 51},
"memoryrestart@teamextension.com": {minVersion: "1.18.1-signed.1-signed", installs: 51},
"multifox-toolbar-button@rbaldwin": {minVersion: "4.28.1-signed.1-signed", installs: 51},
"soaclient@santoso": {minVersion: "0.2.1-signed.1-signed", installs: 51},
"speeddns@gmail.com": {minVersion: "0.2.1-signed.1-signed", installs: 51},
"windowandtablimiter@weintraut.net": {minVersion: "4.28.1-signed.1-signed", installs: 51},
"@Recipes": {minVersion: "0.2.0", installs: 50},
"{e6a9a96e-4a08-4719-b9bd-0e91c35aaabc}": {minVersion: "1.3.1.1-signed.1-signed", installs: 50},
"autopager@mozilla.org": {minVersion: "0.8.0.10.1-signed.1-signed", installs: 50},
"btpersonas@brandthunder.com": {minVersion: "2.0.4.7", installs: 50},
"gdrivepanel@alejandrobrizuela.com.ar": {minVersion: "1.0.2.1-signed.1-signed", installs: 50},
"jid1-m3kqTBs1zKXXaA@jetpack": {minVersion: "0.2.6.1-signed.1-signed", installs: 50},
"qqmail_plugin_for_firefox@tencent.com": {minVersion: "1.0.0.22", installs: 50},
"savefileto@mozdev.org": {minVersion: "2.5.5", installs: 50},
"seodoctor@prelovac.com": {minVersion: "1.6.5.1-signed.1-signed", installs: 50},
"support@todoist.com": {minVersion: "4.0.5", installs: 50},
"toolbar_TeoMediaTB@apn.ask.com": {minVersion: "135.3", installs: 50},
"txftn@tencent.com": {minVersion: "1.0.0.7", installs: 50},
};
// ================== END OF LIST FOR 51 ======================
// We use these named policies to correlate the telemetry
// data with them, in order to understand how each set
// is behaving in the wild.
const RolloutPolicy = {
// Used during 48 Beta cycle
"2a": { addons: set2, webextensions: true },
"2b": { addons: set2, webextensions: false },
// Set agreed for Release 49
"49a": { addons: set49Release, webextensions: true },
"49b": { addons: set49Release, webextensions: false },
// Smaller set that can be used for Release 49
"49limiteda": { addons: set49PaneOnly, webextensions: true },
"49limitedb": { addons: set49PaneOnly, webextensions: false },
// Beta testing on 50
"50allmpc": { addons: [], webextensions: true, mpc: true },
// Beta testing on 51
"51alladdons": { addons: [], webextensions: true, alladdons: true },
// 51 release
"51set1": { addonsv2: set51Release, installs: 50, webextensions: true, mpc: true },
"51set2": { addonsv2: set51Release, installs: 100, webextensions: true, mpc: true },
"51set3": { addonsv2: set51Release, installs: 300, webextensions: true, mpc: true },
"51set4": { addonsv2: set51Release, installs: 1000, webextensions: true, mpc: true },
// ESR
"esrA": { addons: [], mpc: true, webextensions: true },
"esrB": { addons: [], mpc: true, webextensions: false },
"esrC": { addons: [], mpc: false, webextensions: true },
"xpcshell-test": { addons: [ADDONS.test1, ADDONS.test2], webextensions: false },
};
Object.defineProperty(this, "isAddonPartOfE10SRollout", {
configurable: false,
enumerable: false,
writable: false,
value: function isAddonPartOfE10SRollout(aAddon) {
let blocklist = Preferences.get(PREF_E10S_ADDON_BLOCKLIST, "");
let policyId = Preferences.get(PREF_E10S_ADDON_POLICY, "");
if (!policyId || !RolloutPolicy.hasOwnProperty(policyId)) {
return false;
}
if (blocklist && blocklist.indexOf(aAddon.id) > -1) {
return false;
}
let policy = RolloutPolicy[policyId];
if (aAddon.mpcOptedOut == true) {
return false;
}
if (policy.alladdons) {
return true;
}
if (policy.webextensions && aAddon.type == "webextension") {
return true;
}
if (policy.mpc && aAddon.multiprocessCompatible) {
return true;
}
if (policy.addonsv2) {
if (aAddon.id in policy.addonsv2) {
let rolloutAddon = policy.addonsv2[aAddon.id];
if (rolloutAddon.installs >= policy.installs &&
Services.vc.compare(aAddon.version, rolloutAddon.minVersion) >= 0) {
return true;
}
}
return false;
}
for (let rolloutAddon of policy.addons) {
if (aAddon.id == rolloutAddon.id &&
Services.vc.compare(aAddon.version, rolloutAddon.minVersion) >= 0) {
return true;
}
}
return false;
},
});

View file

@ -0,0 +1,699 @@
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = [];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
/* globals AddonManagerPrivate*/
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
/* globals OS*/
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/GMPUtils.jsm");
/* globals EME_ADOBE_ID, GMP_PLUGIN_IDS, GMPPrefs, GMPUtils, OPEN_H264_ID, WIDEVINE_ID */
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(
this, "GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm");
XPCOMUtils.defineLazyModuleGetter(
this, "setTimeout", "resource://gre/modules/Timer.jsm");
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
const SEC_IN_A_DAY = 24 * 60 * 60;
// How long to wait after a user enabled EME before attempting to download CDMs.
const GMP_CHECK_DELAY = 10 * 1000; // milliseconds
const NS_GRE_DIR = "GreD";
const CLEARKEY_PLUGIN_ID = "gmp-clearkey";
const CLEARKEY_VERSION = "0.1";
const GMP_LICENSE_INFO = "gmp_license_info";
const GMP_PRIVACY_INFO = "gmp_privacy_info";
const GMP_LEARN_MORE = "learn_more_label";
const GMP_PLUGINS = [
{
id: OPEN_H264_ID,
name: "openH264_name",
description: "openH264_description2",
// The following licenseURL is part of an awful hack to include the OpenH264
// license without having bug 624602 fixed yet, and intentionally ignores
// localisation.
licenseURL: "chrome://mozapps/content/extensions/OpenH264-license.txt",
homepageURL: "http://www.openh264.org/",
optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul",
},
{
id: EME_ADOBE_ID,
name: "eme-adobe_name",
description: "eme-adobe_description",
// The following learnMoreURL is another hack to be able to support a SUMO page for this
// feature.
get learnMoreURL() {
return Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
},
licenseURL: "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM_EULA/index.html",
homepageURL: "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM",
optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul",
isEME: true,
},
{
id: WIDEVINE_ID,
name: "widevine_description",
// Describe the purpose of both CDMs in the same way.
description: "eme-adobe_description",
licenseURL: "https://www.google.com/policies/privacy/",
homepageURL: "https://www.widevine.com/",
optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul",
isEME: true
}];
XPCOMUtils.defineConstant(this, "GMP_PLUGINS", GMP_PLUGINS);
XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
() => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
XPCOMUtils.defineLazyGetter(this, "gmpService",
() => Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(Ci.mozIGeckoMediaPluginChromeService));
var messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
var gLogger;
var gLogAppenderDump = null;
function configureLogging() {
if (!gLogger) {
gLogger = Log.repository.getLogger("Toolkit.GMP");
gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
}
gLogger.level = GMPPrefs.get(GMPPrefs.KEY_LOGGING_LEVEL, Log.Level.Warn);
let logDumping = GMPPrefs.get(GMPPrefs.KEY_LOGGING_DUMP, false);
if (logDumping != !!gLogAppenderDump) {
if (logDumping) {
gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
gLogger.addAppender(gLogAppenderDump);
} else {
gLogger.removeAppender(gLogAppenderDump);
gLogAppenderDump = null;
}
}
}
/**
* The GMPWrapper provides the info for the various GMP plugins to public
* callers through the API.
*/
function GMPWrapper(aPluginInfo) {
this._plugin = aPluginInfo;
this._log =
Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
"GMPWrapper(" +
this._plugin.id + ") ");
Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
this._plugin.id),
this.onPrefEnabledChanged, this);
Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
this._plugin.id),
this.onPrefVersionChanged, this);
if (this._plugin.isEME) {
Preferences.observe(GMPPrefs.KEY_EME_ENABLED,
this.onPrefEMEGlobalEnabledChanged, this);
messageManager.addMessageListener("EMEVideo:ContentMediaKeysRequest", this);
}
}
GMPWrapper.prototype = {
// An active task that checks for plugin updates and installs them.
_updateTask: null,
_gmpPath: null,
_isUpdateCheckPending: false,
optionsType: AddonManager.OPTIONS_TYPE_INLINE,
get optionsURL() { return this._plugin.optionsURL; },
set gmpPath(aPath) { this._gmpPath = aPath; },
get gmpPath() {
if (!this._gmpPath && this.isInstalled) {
this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
this._plugin.id,
GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
null, this._plugin.id));
}
return this._gmpPath;
},
get id() { return this._plugin.id; },
get type() { return "plugin"; },
get isGMPlugin() { return true; },
get name() { return this._plugin.name; },
get creator() { return null; },
get homepageURL() { return this._plugin.homepageURL; },
get description() { return this._plugin.description; },
get fullDescription() { return this._plugin.fullDescription; },
get version() { return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, null,
this._plugin.id); },
get isActive() {
return !this.appDisabled &&
!this.userDisabled &&
!GMPUtils.isPluginHidden(this._plugin);
},
get appDisabled() {
if (this._plugin.isEME && !GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
// If "media.eme.enabled" is false, all EME plugins are disabled.
return true;
}
return false;
},
get userDisabled() {
return !GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ENABLED, true, this._plugin.id);
},
set userDisabled(aVal) { GMPPrefs.set(GMPPrefs.KEY_PLUGIN_ENABLED,
aVal === false,
this._plugin.id); },
get blocklistState() { return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; },
get size() { return 0; },
get scope() { return AddonManager.SCOPE_APPLICATION; },
get pendingOperations() { return AddonManager.PENDING_NONE; },
get operationsRequiringRestart() { return AddonManager.OP_NEEDS_RESTART_NONE },
get permissions() {
let permissions = 0;
if (!this.appDisabled) {
permissions |= AddonManager.PERM_CAN_UPGRADE;
permissions |= this.userDisabled ? AddonManager.PERM_CAN_ENABLE :
AddonManager.PERM_CAN_DISABLE;
}
return permissions;
},
get updateDate() {
let time = Number(GMPPrefs.get(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, null,
this._plugin.id));
if (!isNaN(time) && this.isInstalled) {
return new Date(time * 1000)
}
return null;
},
get isCompatible() {
return true;
},
get isPlatformCompatible() {
return true;
},
get providesUpdatesSecurely() {
return true;
},
get foreignInstall() {
return false;
},
isCompatibleWith: function(aAppVersion, aPlatformVersion) {
return true;
},
get applyBackgroundUpdates() {
if (!GMPPrefs.isSet(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id)) {
return AddonManager.AUTOUPDATE_DEFAULT;
}
return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id) ?
AddonManager.AUTOUPDATE_ENABLE : AddonManager.AUTOUPDATE_DISABLE;
},
set applyBackgroundUpdates(aVal) {
if (aVal == AddonManager.AUTOUPDATE_DEFAULT) {
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id);
} else if (aVal == AddonManager.AUTOUPDATE_ENABLE) {
GMPPrefs.set(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id);
} else if (aVal == AddonManager.AUTOUPDATE_DISABLE) {
GMPPrefs.set(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, false, this._plugin.id);
}
},
findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
this._log.trace("findUpdates() - " + this._plugin.id + " - reason=" +
aReason);
AddonManagerPrivate.callNoUpdateListeners(this, aListener);
if (aReason === AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
if (!AddonManager.shouldAutoUpdate(this)) {
this._log.trace("findUpdates() - " + this._plugin.id +
" - no autoupdate");
return Promise.resolve(false);
}
let secSinceLastCheck =
Date.now() / 1000 - Preferences.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
if (secSinceLastCheck <= SEC_IN_A_DAY) {
this._log.trace("findUpdates() - " + this._plugin.id +
" - last check was less then a day ago");
return Promise.resolve(false);
}
} else if (aReason !== AddonManager.UPDATE_WHEN_USER_REQUESTED) {
this._log.trace("findUpdates() - " + this._plugin.id +
" - the given reason to update is not supported");
return Promise.resolve(false);
}
if (this._updateTask !== null) {
this._log.trace("findUpdates() - " + this._plugin.id +
" - update task already running");
return this._updateTask;
}
this._updateTask = Task.spawn(function*() {
this._log.trace("findUpdates() - updateTask");
try {
let installManager = new GMPInstallManager();
let res = yield installManager.checkForAddons();
let update = res.gmpAddons.find(addon => addon.id === this._plugin.id);
if (update && update.isValid && !update.isInstalled) {
this._log.trace("findUpdates() - found update for " +
this._plugin.id + ", installing");
yield installManager.installAddon(update);
} else {
this._log.trace("findUpdates() - no updates for " + this._plugin.id);
}
this._log.info("findUpdates() - updateTask succeeded for " +
this._plugin.id);
} catch (e) {
this._log.error("findUpdates() - updateTask for " + this._plugin.id +
" threw", e);
throw e;
} finally {
this._updateTask = null;
return true;
}
}.bind(this));
return this._updateTask;
},
get pluginMimeTypes() { return []; },
get pluginLibraries() {
if (this.isInstalled) {
let path = this.version;
return [path];
}
return [];
},
get pluginFullpath() {
if (this.isInstalled) {
let path = OS.Path.join(OS.Constants.Path.profileDir,
this._plugin.id,
this.version);
return [path];
}
return [];
},
get isInstalled() {
return this.version && this.version.length > 0;
},
_handleEnabledChanged: function() {
this._log.info("_handleEnabledChanged() id=" +
this._plugin.id + " isActive=" + this.isActive);
AddonManagerPrivate.callAddonListeners(this.isActive ?
"onEnabling" : "onDisabling",
this, false);
if (this._gmpPath) {
if (this.isActive) {
this._log.info("onPrefEnabledChanged() - adding gmp directory " +
this._gmpPath);
gmpService.addPluginDirectory(this._gmpPath);
} else {
this._log.info("onPrefEnabledChanged() - removing gmp directory " +
this._gmpPath);
gmpService.removePluginDirectory(this._gmpPath);
}
}
AddonManagerPrivate.callAddonListeners(this.isActive ?
"onEnabled" : "onDisabled",
this);
},
onPrefEMEGlobalEnabledChanged: function() {
this._log.info("onPrefEMEGlobalEnabledChanged() id=" + this._plugin.id +
" appDisabled=" + this.appDisabled + " isActive=" + this.isActive +
" hidden=" + GMPUtils.isPluginHidden(this._plugin));
AddonManagerPrivate.callAddonListeners("onPropertyChanged", this,
["appDisabled"]);
// If EME or the GMP itself are disabled, uninstall the GMP.
// Otherwise, check for updates, so we download and install the GMP.
if (this.appDisabled) {
this.uninstallPlugin();
} else if (!GMPUtils.isPluginHidden(this._plugin)) {
AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
null, false);
AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
AddonManagerPrivate.callAddonListeners("onInstalled", this);
this.checkForUpdates(GMP_CHECK_DELAY);
}
if (!this.userDisabled) {
this._handleEnabledChanged();
}
},
checkForUpdates: function(delay) {
if (this._isUpdateCheckPending) {
return;
}
this._isUpdateCheckPending = true;
GMPPrefs.reset(GMPPrefs.KEY_UPDATE_LAST_CHECK, null);
// Delay this in case the user changes his mind and doesn't want to
// enable EME after all.
setTimeout(() => {
if (!this.appDisabled) {
let gmpInstallManager = new GMPInstallManager();
// We don't really care about the results, if someone is interested
// they can check the log.
gmpInstallManager.simpleCheckAndInstall().then(null, () => {});
}
this._isUpdateCheckPending = false;
}, delay);
},
receiveMessage: function({target: browser, data: data}) {
this._log.trace("receiveMessage() data=" + data);
let parsedData;
try {
parsedData = JSON.parse(data);
} catch (ex) {
this._log.error("Malformed EME video message with data: " + data);
return;
}
let {status: status, keySystem: keySystem} = parsedData;
if (status == "cdm-not-installed") {
this.checkForUpdates(0);
}
},
onPrefEnabledChanged: function() {
if (!this._plugin.isEME || !this.appDisabled) {
this._handleEnabledChanged();
}
},
onPrefVersionChanged: function() {
AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
if (this._gmpPath) {
this._log.info("onPrefVersionChanged() - unregistering gmp directory " +
this._gmpPath);
gmpService.removeAndDeletePluginDirectory(this._gmpPath, true /* can defer */);
}
AddonManagerPrivate.callAddonListeners("onUninstalled", this);
AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
null, false);
AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
this._gmpPath = null;
if (this.isInstalled) {
this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
this._plugin.id,
GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
null, this._plugin.id));
}
if (this._gmpPath && this.isActive) {
this._log.info("onPrefVersionChanged() - registering gmp directory " +
this._gmpPath);
gmpService.addPluginDirectory(this._gmpPath);
}
AddonManagerPrivate.callAddonListeners("onInstalled", this);
},
uninstallPlugin: function() {
AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
if (this.gmpPath) {
this._log.info("uninstallPlugin() - unregistering gmp directory " +
this.gmpPath);
gmpService.removeAndDeletePluginDirectory(this.gmpPath);
}
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_VERSION, this.id);
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_ABI, this.id);
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, this.id);
AddonManagerPrivate.callAddonListeners("onUninstalled", this);
},
shutdown: function() {
Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
this._plugin.id),
this.onPrefEnabledChanged, this);
Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
this._plugin.id),
this.onPrefVersionChanged, this);
if (this._plugin.isEME) {
Preferences.ignore(GMPPrefs.KEY_EME_ENABLED,
this.onPrefEMEGlobalEnabledChanged, this);
messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", this);
}
return this._updateTask;
},
_arePluginFilesOnDisk: function() {
let fileExists = function(aGmpPath, aFileName) {
let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
let path = OS.Path.join(aGmpPath, aFileName);
f.initWithPath(path);
return f.exists();
};
let id = this._plugin.id.substring(4);
let libName = AppConstants.DLL_PREFIX + id + AppConstants.DLL_SUFFIX;
let infoName;
if (this._plugin.id == WIDEVINE_ID) {
infoName = "manifest.json";
} else {
infoName = id + ".info";
}
return fileExists(this.gmpPath, libName) &&
fileExists(this.gmpPath, infoName) &&
(this._plugin.id != EME_ADOBE_ID || fileExists(this.gmpPath, id + ".voucher"));
},
validate: function() {
if (!this.isInstalled) {
// Not installed -> Valid.
return {
installed: false,
valid: true
};
}
let abi = GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ABI, UpdateUtils.ABI, this._plugin.id);
if (abi != UpdateUtils.ABI) {
// ABI doesn't match. Possibly this is a profile migrated across platforms
// or from 32 -> 64 bit.
return {
installed: true,
mismatchedABI: true,
valid: false
};
}
// Installed -> Check if files are missing.
let filesOnDisk = this._arePluginFilesOnDisk();
return {
installed: true,
valid: filesOnDisk
};
},
};
var GMPProvider = {
get name() { return "GMPProvider"; },
_plugins: null,
startup: function() {
configureLogging();
this._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
"GMPProvider.");
this.buildPluginList();
this.ensureProperCDMInstallState();
Preferences.observe(GMPPrefs.KEY_LOG_BASE, configureLogging);
for (let [id, plugin] of this._plugins) {
let wrapper = plugin.wrapper;
let gmpPath = wrapper.gmpPath;
let isEnabled = wrapper.isActive;
this._log.trace("startup - enabled=" + isEnabled + ", gmpPath=" +
gmpPath);
if (gmpPath && isEnabled) {
let validation = wrapper.validate();
if (validation.mismatchedABI) {
this._log.info("startup - gmp " + plugin.id +
" mismatched ABI, uninstalling");
wrapper.uninstallPlugin();
continue;
}
if (!validation.valid) {
this._log.info("startup - gmp " + plugin.id +
" invalid, uninstalling");
wrapper.uninstallPlugin();
continue;
}
this._log.info("startup - adding gmp directory " + gmpPath);
try {
gmpService.addPluginDirectory(gmpPath);
} catch (e) {
if (e.name != 'NS_ERROR_NOT_AVAILABLE')
throw e;
this._log.warn("startup - adding gmp directory failed with " +
e.name + " - sandboxing not available?", e);
}
}
}
try {
let greDir = Services.dirsvc.get(NS_GRE_DIR,
Ci.nsILocalFile);
let clearkeyPath = OS.Path.join(greDir.path,
CLEARKEY_PLUGIN_ID,
CLEARKEY_VERSION);
this._log.info("startup - adding clearkey CDM directory " +
clearkeyPath);
gmpService.addPluginDirectory(clearkeyPath);
} catch (e) {
this._log.warn("startup - adding clearkey CDM failed", e);
}
},
shutdown: function() {
this._log.trace("shutdown");
Preferences.ignore(GMPPrefs.KEY_LOG_BASE, configureLogging);
let shutdownTask = Task.spawn(function*() {
this._log.trace("shutdown - shutdownTask");
let shutdownSucceeded = true;
for (let plugin of this._plugins.values()) {
try {
yield plugin.wrapper.shutdown();
} catch (e) {
shutdownSucceeded = false;
}
}
this._plugins = null;
if (!shutdownSucceeded) {
throw new Error("Shutdown failed");
}
}.bind(this));
return shutdownTask;
},
getAddonByID: function(aId, aCallback) {
if (!this.isEnabled) {
aCallback(null);
return;
}
let plugin = this._plugins.get(aId);
if (plugin && !GMPUtils.isPluginHidden(plugin)) {
aCallback(plugin.wrapper);
} else {
aCallback(null);
}
},
getAddonsByTypes: function(aTypes, aCallback) {
if (!this.isEnabled ||
(aTypes && aTypes.indexOf("plugin") < 0)) {
aCallback([]);
return;
}
let results = Array.from(this._plugins.values())
.filter(p => !GMPUtils.isPluginHidden(p))
.map(p => p.wrapper);
aCallback(results);
},
get isEnabled() {
return GMPPrefs.get(GMPPrefs.KEY_PROVIDER_ENABLED, false);
},
generateFullDescription: function(aPlugin) {
let rv = [];
for (let [urlProp, labelId] of [["learnMoreURL", GMP_LEARN_MORE],
["licenseURL", aPlugin.id == WIDEVINE_ID ?
GMP_PRIVACY_INFO : GMP_LICENSE_INFO]]) {
if (aPlugin[urlProp]) {
let label = pluginsBundle.GetStringFromName(labelId);
rv.push(`<xhtml:a href="${aPlugin[urlProp]}" target="_blank">${label}</xhtml:a>.`);
}
}
return rv.length ? rv.join("<xhtml:br /><xhtml:br />") : undefined;
},
buildPluginList: function() {
this._plugins = new Map();
for (let aPlugin of GMP_PLUGINS) {
let plugin = {
id: aPlugin.id,
name: pluginsBundle.GetStringFromName(aPlugin.name),
description: pluginsBundle.GetStringFromName(aPlugin.description),
homepageURL: aPlugin.homepageURL,
optionsURL: aPlugin.optionsURL,
wrapper: null,
isEME: aPlugin.isEME,
};
plugin.fullDescription = this.generateFullDescription(aPlugin);
plugin.wrapper = new GMPWrapper(plugin);
this._plugins.set(plugin.id, plugin);
}
},
ensureProperCDMInstallState: function() {
if (!GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
for (let [id, plugin] of this._plugins) {
if (plugin.isEME && plugin.wrapper.isInstalled) {
gmpService.addPluginDirectory(plugin.wrapper.gmpPath);
plugin.wrapper.uninstallPlugin();
}
}
}
},
};
AddonManagerPrivate.registerProvider(GMPProvider, [
new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 6000,
AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
]);

View file

@ -0,0 +1,180 @@
/* 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 = ["LightweightThemeImageOptimizer"];
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
const ORIGIN_TOP_RIGHT = 1;
const ORIGIN_BOTTOM_LEFT = 2;
this.LightweightThemeImageOptimizer = {
optimize: function(aThemeData, aScreen) {
let data = Object.assign({}, aThemeData);
if (!data.headerURL) {
return data;
}
data.headerURL = ImageCropper.getCroppedImageURL(
data.headerURL, aScreen, ORIGIN_TOP_RIGHT);
if (data.footerURL) {
data.footerURL = ImageCropper.getCroppedImageURL(
data.footerURL, aScreen, ORIGIN_BOTTOM_LEFT);
}
return data;
},
purge: function() {
let dir = FileUtils.getDir("ProfD", ["lwtheme"]);
dir.followLinks = false;
try {
dir.remove(true);
} catch (e) {}
}
};
Object.freeze(LightweightThemeImageOptimizer);
var ImageCropper = {
_inProgress: {},
getCroppedImageURL: function(aImageURL, aScreen, aOrigin) {
// We can crop local files, only.
if (!aImageURL.startsWith("file://")) {
return aImageURL;
}
// Generate the cropped image's file name using its
// base name and the current screen size.
let uri = Services.io.newURI(aImageURL, null, null);
let file = uri.QueryInterface(Ci.nsIFileURL).file;
// Make sure the source file exists.
if (!file.exists()) {
return aImageURL;
}
let fileName = file.leafName + "-" + aScreen.width + "x" + aScreen.height;
let croppedFile = FileUtils.getFile("ProfD", ["lwtheme", fileName]);
// If we have a local file that is not in progress, return it.
if (croppedFile.exists() && !(croppedFile.path in this._inProgress)) {
let fileURI = Services.io.newFileURI(croppedFile);
// Copy the query part to avoid wrong caching.
fileURI.QueryInterface(Ci.nsIURL).query = uri.query;
return fileURI.spec;
}
// Crop the given image in the background.
this._crop(uri, croppedFile, aScreen, aOrigin);
// Return the original image while we're waiting for the cropped version
// to be written to disk.
return aImageURL;
},
_crop: function(aURI, aTargetFile, aScreen, aOrigin) {
let inProgress = this._inProgress;
inProgress[aTargetFile.path] = true;
function resetInProgress() {
delete inProgress[aTargetFile.path];
}
ImageFile.read(aURI, function(aInputStream, aContentType) {
if (aInputStream && aContentType) {
let image = ImageTools.decode(aInputStream, aContentType);
if (image && image.width && image.height) {
let stream = ImageTools.encode(image, aScreen, aOrigin, aContentType);
if (stream) {
ImageFile.write(aTargetFile, stream, resetInProgress);
return;
}
}
}
resetInProgress();
});
}
};
var ImageFile = {
read: function(aURI, aCallback) {
this._netUtil.asyncFetch({
uri: aURI,
loadUsingSystemPrincipal: true,
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
}, function(aInputStream, aStatus, aRequest) {
if (Components.isSuccessCode(aStatus) && aRequest instanceof Ci.nsIChannel) {
let channel = aRequest.QueryInterface(Ci.nsIChannel);
aCallback(aInputStream, channel.contentType);
} else {
aCallback();
}
});
},
write: function(aFile, aInputStream, aCallback) {
let fos = FileUtils.openSafeFileOutputStream(aFile);
this._netUtil.asyncCopy(aInputStream, fos, function(aResult) {
FileUtils.closeSafeFileOutputStream(fos);
// Remove the file if writing was not successful.
if (!Components.isSuccessCode(aResult)) {
try {
aFile.remove(false);
} catch (e) {}
}
aCallback();
});
}
};
XPCOMUtils.defineLazyModuleGetter(ImageFile, "_netUtil",
"resource://gre/modules/NetUtil.jsm", "NetUtil");
var ImageTools = {
decode: function(aInputStream, aContentType) {
let outParam = {value: null};
try {
this._imgTools.decodeImageData(aInputStream, aContentType, outParam);
} catch (e) {}
return outParam.value;
},
encode: function(aImage, aScreen, aOrigin, aContentType) {
let stream;
let width = Math.min(aImage.width, aScreen.width);
let height = Math.min(aImage.height, aScreen.height);
let x = aOrigin == ORIGIN_TOP_RIGHT ? aImage.width - width : 0;
try {
stream = this._imgTools.encodeCroppedImage(aImage, aContentType, x, 0,
width, height);
} catch (e) {}
return stream;
}
};
XPCOMUtils.defineLazyServiceGetter(ImageTools, "_imgTools",
"@mozilla.org/image/tools;1", "imgITools");

View file

@ -0,0 +1,600 @@
/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = [];
Cu.import("resource://gre/modules/AddonManager.jsm");
/* globals AddonManagerPrivate*/
Cu.import("resource://gre/modules/Services.jsm");
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
const LIST_UPDATED_TOPIC = "plugins-list-updated";
const FLASH_MIME_TYPE = "application/x-shockwave-flash";
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.plugins";
// Create a new logger for use by the Addons Plugin Provider
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);
function getIDHashForString(aStr) {
// return the two-digit hexadecimal code for a byte
let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2);
let hasher = Cc["@mozilla.org/security/hash;1"].
createInstance(Ci.nsICryptoHash);
hasher.init(Ci.nsICryptoHash.MD5);
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
stringStream.data = aStr ? aStr : "null";
hasher.updateFromStream(stringStream, -1);
// convert the binary hash data to a hex string.
let binary = hasher.finish(false);
let hash = Array.from(binary, c => toHexString(c.charCodeAt(0)));
hash = hash.join("").toLowerCase();
return "{" + hash.substr(0, 8) + "-" +
hash.substr(8, 4) + "-" +
hash.substr(12, 4) + "-" +
hash.substr(16, 4) + "-" +
hash.substr(20) + "}";
}
var PluginProvider = {
get name() {
return "PluginProvider";
},
// A dictionary mapping IDs to names and descriptions
plugins: null,
startup: function() {
Services.obs.addObserver(this, LIST_UPDATED_TOPIC, false);
Services.obs.addObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, false);
},
/**
* Called when the application is shutting down. Only necessary for tests
* to be able to simulate a shutdown.
*/
shutdown: function() {
this.plugins = null;
Services.obs.removeObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
Services.obs.removeObserver(this, LIST_UPDATED_TOPIC);
},
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case AddonManager.OPTIONS_NOTIFICATION_DISPLAYED:
this.getAddonByID(aData, function(plugin) {
if (!plugin)
return;
let libLabel = aSubject.getElementById("pluginLibraries");
libLabel.textContent = plugin.pluginLibraries.join(", ");
let typeLabel = aSubject.getElementById("pluginMimeTypes"), types = [];
for (let type of plugin.pluginMimeTypes) {
let extras = [type.description.trim(), type.suffixes].
filter(x => x).join(": ");
types.push(type.type + (extras ? " (" + extras + ")" : ""));
}
typeLabel.textContent = types.join(",\n");
let showProtectedModePref = canDisableFlashProtectedMode(plugin);
aSubject.getElementById("pluginEnableProtectedMode")
.setAttribute("collapsed", showProtectedModePref ? "" : "true");
});
break;
case LIST_UPDATED_TOPIC:
if (this.plugins)
this.updatePluginList();
break;
}
},
/**
* Creates a PluginWrapper for a plugin object.
*/
buildWrapper: function(aPlugin) {
return new PluginWrapper(aPlugin.id,
aPlugin.name,
aPlugin.description,
aPlugin.tags);
},
/**
* Called to get an Addon with a particular ID.
*
* @param aId
* The ID of the add-on to retrieve
* @param aCallback
* A callback to pass the Addon to
*/
getAddonByID: function(aId, aCallback) {
if (!this.plugins)
this.buildPluginList();
if (aId in this.plugins)
aCallback(this.buildWrapper(this.plugins[aId]));
else
aCallback(null);
},
/**
* Called to get Addons of a particular type.
*
* @param aTypes
* An array of types to fetch. Can be null to get all types.
* @param callback
* A callback to pass an array of Addons to
*/
getAddonsByTypes: function(aTypes, aCallback) {
if (aTypes && aTypes.indexOf("plugin") < 0) {
aCallback([]);
return;
}
if (!this.plugins)
this.buildPluginList();
let results = [];
for (let id in this.plugins)
this.getAddonByID(id, (addon) => results.push(addon));
aCallback(results);
},
/**
* Called to get Addons that have pending operations.
*
* @param aTypes
* An array of types to fetch. Can be null to get all types
* @param aCallback
* A callback to pass an array of Addons to
*/
getAddonsWithOperationsByTypes: function(aTypes, aCallback) {
aCallback([]);
},
/**
* Called to get the current AddonInstalls, optionally restricting by type.
*
* @param aTypes
* An array of types or null to get all types
* @param aCallback
* A callback to pass the array of AddonInstalls to
*/
getInstallsByTypes: function(aTypes, aCallback) {
aCallback([]);
},
/**
* Builds a list of the current plugins reported by the plugin host
*
* @return a dictionary of plugins indexed by our generated ID
*/
getPluginList: function() {
let tags = Cc["@mozilla.org/plugin/host;1"].
getService(Ci.nsIPluginHost).
getPluginTags({});
let list = {};
let seenPlugins = {};
for (let tag of tags) {
if (!(tag.name in seenPlugins))
seenPlugins[tag.name] = {};
if (!(tag.description in seenPlugins[tag.name])) {
let plugin = {
id: getIDHashForString(tag.name + tag.description),
name: tag.name,
description: tag.description,
tags: [tag]
};
seenPlugins[tag.name][tag.description] = plugin;
list[plugin.id] = plugin;
}
else {
seenPlugins[tag.name][tag.description].tags.push(tag);
}
}
return list;
},
/**
* Builds the list of known plugins from the plugin host
*/
buildPluginList: function() {
this.plugins = this.getPluginList();
},
/**
* Updates the plugins from the plugin host by comparing the current plugins
* to the last known list sending out any necessary API notifications for
* changes.
*/
updatePluginList: function() {
let newList = this.getPluginList();
let lostPlugins = Object.keys(this.plugins).filter(id => !(id in newList)).
map(id => this.buildWrapper(this.plugins[id]));
let newPlugins = Object.keys(newList).filter(id => !(id in this.plugins)).
map(id => this.buildWrapper(newList[id]));
let matchedIDs = Object.keys(newList).filter(id => id in this.plugins);
// The plugin host generates new tags for every plugin after a scan and
// if the plugin's filename has changed then the disabled state won't have
// been carried across, send out notifications for anything that has
// changed (see bug 830267).
let changedWrappers = [];
for (let id of matchedIDs) {
let oldWrapper = this.buildWrapper(this.plugins[id]);
let newWrapper = this.buildWrapper(newList[id]);
if (newWrapper.isActive != oldWrapper.isActive) {
AddonManagerPrivate.callAddonListeners(newWrapper.isActive ?
"onEnabling" : "onDisabling",
newWrapper, false);
changedWrappers.push(newWrapper);
}
}
// Notify about new installs
for (let plugin of newPlugins) {
AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
plugin, null, false);
AddonManagerPrivate.callAddonListeners("onInstalling", plugin, false);
}
// Notify for any plugins that have vanished.
for (let plugin of lostPlugins)
AddonManagerPrivate.callAddonListeners("onUninstalling", plugin, false);
this.plugins = newList;
// Signal that new installs are complete
for (let plugin of newPlugins)
AddonManagerPrivate.callAddonListeners("onInstalled", plugin);
// Signal that enables/disables are complete
for (let wrapper of changedWrappers) {
AddonManagerPrivate.callAddonListeners(wrapper.isActive ?
"onEnabled" : "onDisabled",
wrapper);
}
// Signal that uninstalls are complete
for (let plugin of lostPlugins)
AddonManagerPrivate.callAddonListeners("onUninstalled", plugin);
}
};
function isFlashPlugin(aPlugin) {
for (let type of aPlugin.pluginMimeTypes) {
if (type.type == FLASH_MIME_TYPE) {
return true;
}
}
return false;
}
// Protected mode is win32-only, not win64
function canDisableFlashProtectedMode(aPlugin) {
return isFlashPlugin(aPlugin) && Services.appinfo.XPCOMABI == "x86-msvc";
}
const wrapperMap = new WeakMap();
let pluginFor = wrapper => wrapperMap.get(wrapper);
/**
* The PluginWrapper wraps a set of nsIPluginTags to provide the data visible to
* public callers through the API.
*/
function PluginWrapper(id, name, description, tags) {
wrapperMap.set(this, { id, name, description, tags });
}
PluginWrapper.prototype = {
get id() {
return pluginFor(this).id;
},
get type() {
return "plugin";
},
get name() {
return pluginFor(this).name;
},
get creator() {
return null;
},
get description() {
return pluginFor(this).description.replace(/<\/?[a-z][^>]*>/gi, " ");
},
get version() {
let { tags: [tag] } = pluginFor(this);
return tag.version;
},
get homepageURL() {
let { description } = pluginFor(this);
if (/<A\s+HREF=[^>]*>/i.test(description))
return /<A\s+HREF=["']?([^>"'\s]*)/i.exec(description)[1];
return null;
},
get isActive() {
let { tags: [tag] } = pluginFor(this);
return !tag.blocklisted && !tag.disabled;
},
get appDisabled() {
let { tags: [tag] } = pluginFor(this);
return tag.blocklisted;
},
get userDisabled() {
let { tags: [tag] } = pluginFor(this);
if (tag.disabled)
return true;
if ((Services.prefs.getBoolPref("plugins.click_to_play") && tag.clicktoplay) ||
this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE ||
this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE)
return AddonManager.STATE_ASK_TO_ACTIVATE;
return false;
},
set userDisabled(val) {
let previousVal = this.userDisabled;
if (val === previousVal)
return val;
let { tags } = pluginFor(this);
for (let tag of tags) {
if (val === true)
tag.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
else if (val === false)
tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
else if (val == AddonManager.STATE_ASK_TO_ACTIVATE)
tag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
}
// If 'userDisabled' was 'true' and we're going to a state that's not
// that, we're enabling, so call those listeners.
if (previousVal === true && val !== true) {
AddonManagerPrivate.callAddonListeners("onEnabling", this, false);
AddonManagerPrivate.callAddonListeners("onEnabled", this);
}
// If 'userDisabled' was not 'true' and we're going to a state where
// it is, we're disabling, so call those listeners.
if (previousVal !== true && val === true) {
AddonManagerPrivate.callAddonListeners("onDisabling", this, false);
AddonManagerPrivate.callAddonListeners("onDisabled", this);
}
// If the 'userDisabled' value involved AddonManager.STATE_ASK_TO_ACTIVATE,
// call the onPropertyChanged listeners.
if (previousVal == AddonManager.STATE_ASK_TO_ACTIVATE ||
val == AddonManager.STATE_ASK_TO_ACTIVATE) {
AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["userDisabled"]);
}
return val;
},
get blocklistState() {
let { tags: [tag] } = pluginFor(this);
let bs = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
return bs.getPluginBlocklistState(tag);
},
get blocklistURL() {
let { tags: [tag] } = pluginFor(this);
let bs = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
return bs.getPluginBlocklistURL(tag);
},
get size() {
function getDirectorySize(aFile) {
let size = 0;
let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
let entry;
while ((entry = entries.nextFile)) {
if (entry.isSymlink() || !entry.isDirectory())
size += entry.fileSize;
else
size += getDirectorySize(entry);
}
entries.close();
return size;
}
let size = 0;
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
for (let tag of pluginFor(this).tags) {
file.initWithPath(tag.fullpath);
if (file.isDirectory())
size += getDirectorySize(file);
else
size += file.fileSize;
}
return size;
},
get pluginLibraries() {
let libs = [];
for (let tag of pluginFor(this).tags)
libs.push(tag.filename);
return libs;
},
get pluginFullpath() {
let paths = [];
for (let tag of pluginFor(this).tags)
paths.push(tag.fullpath);
return paths;
},
get pluginMimeTypes() {
let types = [];
for (let tag of pluginFor(this).tags) {
let mimeTypes = tag.getMimeTypes({});
let mimeDescriptions = tag.getMimeDescriptions({});
let extensions = tag.getExtensions({});
for (let i = 0; i < mimeTypes.length; i++) {
let type = {};
type.type = mimeTypes[i];
type.description = mimeDescriptions[i];
type.suffixes = extensions[i];
types.push(type);
}
}
return types;
},
get installDate() {
let date = 0;
for (let tag of pluginFor(this).tags) {
date = Math.max(date, tag.lastModifiedTime);
}
return new Date(date);
},
get scope() {
let { tags: [tag] } = pluginFor(this);
let path = tag.fullpath;
// Plugins inside the application directory are in the application scope
let dir = Services.dirsvc.get("APlugns", Ci.nsIFile);
if (path.startsWith(dir.path))
return AddonManager.SCOPE_APPLICATION;
// Plugins inside the profile directory are in the profile scope
dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
if (path.startsWith(dir.path))
return AddonManager.SCOPE_PROFILE;
// Plugins anywhere else in the user's home are in the user scope,
// but not all platforms have a home directory.
try {
dir = Services.dirsvc.get("Home", Ci.nsIFile);
if (path.startsWith(dir.path))
return AddonManager.SCOPE_USER;
} catch (e) {
if (!e.result || e.result != Components.results.NS_ERROR_FAILURE)
throw e;
// Do nothing: missing "Home".
}
// Any other locations are system scope
return AddonManager.SCOPE_SYSTEM;
},
get pendingOperations() {
return AddonManager.PENDING_NONE;
},
get operationsRequiringRestart() {
return AddonManager.OP_NEEDS_RESTART_NONE;
},
get permissions() {
let { tags: [tag] } = pluginFor(this);
let permissions = 0;
if (tag.isEnabledStateLocked) {
return permissions;
}
if (!this.appDisabled) {
if (this.userDisabled !== true)
permissions |= AddonManager.PERM_CAN_DISABLE;
let blocklistState = this.blocklistState;
let isCTPBlocklisted =
(blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE ||
blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
if (this.userDisabled !== AddonManager.STATE_ASK_TO_ACTIVATE &&
(Services.prefs.getBoolPref("plugins.click_to_play") ||
isCTPBlocklisted)) {
permissions |= AddonManager.PERM_CAN_ASK_TO_ACTIVATE;
}
if (this.userDisabled !== false && !isCTPBlocklisted) {
permissions |= AddonManager.PERM_CAN_ENABLE;
}
}
return permissions;
},
get optionsType() {
if (canDisableFlashProtectedMode(this)) {
return AddonManager.OPTIONS_TYPE_INLINE;
}
return AddonManager.OPTIONS_TYPE_INLINE_INFO;
},
get optionsURL() {
return "chrome://mozapps/content/extensions/pluginPrefs.xul";
},
get updateDate() {
return this.installDate;
},
get isCompatible() {
return true;
},
get isPlatformCompatible() {
return true;
},
get providesUpdatesSecurely() {
return true;
},
get foreignInstall() {
return true;
},
isCompatibleWith: function(aAppVersion, aPlatformVersion) {
return true;
},
findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
if ("onNoCompatibilityUpdateAvailable" in aListener)
aListener.onNoCompatibilityUpdateAvailable(this);
if ("onNoUpdateAvailable" in aListener)
aListener.onNoUpdateAvailable(this);
if ("onUpdateFinished" in aListener)
aListener.onUpdateFinished(this);
}
};
AddonManagerPrivate.registerProvider(PluginProvider, [
new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 6000,
AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
]);

View file

@ -0,0 +1,467 @@
/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const LOCAL_EME_SOURCES = [{
"id": "gmp-eme-adobe",
"src": "chrome://global/content/gmp-sources/eme-adobe.json"
}, {
"id": "gmp-gmpopenh264",
"src": "chrome://global/content/gmp-sources/openh264.json"
}, {
"id": "gmp-widevinecdm",
"src": "chrome://global/content/gmp-sources/widevinecdm.json"
}];
this.EXPORTED_SYMBOLS = [ "ProductAddonChecker" ];
Cu.importGlobalProperties(["XMLHttpRequest"]);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/CertUtils.jsm");
/* globals checkCert, BadCertHandler*/
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
/* globals GMPPrefs */
XPCOMUtils.defineLazyModuleGetter(this, "GMPPrefs",
"resource://gre/modules/GMPUtils.jsm");
/* globals OS */
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
"resource://gre/modules/ServiceRequest.jsm");
// This exists so that tests can override the XHR behaviour for downloading
// the addon update XML file.
var CreateXHR = function() {
return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsISupports);
}
var logger = Log.repository.getLogger("addons.productaddons");
/**
* Number of milliseconds after which we need to cancel `downloadXML`.
*
* Bug 1087674 suggests that the XHR we use in `downloadXML` may
* never terminate in presence of network nuisances (e.g. strange
* antivirus behavior). This timeout is a defensive measure to ensure
* that we fail cleanly in such case.
*/
const TIMEOUT_DELAY_MS = 20000;
// Chunk size for the incremental downloader
const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
// Incremental downloader interval
const DOWNLOAD_INTERVAL = 0;
// How much of a file to read into memory at a time for hashing
const HASH_CHUNK_SIZE = 8192;
/**
* Gets the status of an XMLHttpRequest either directly or from its underlying
* channel.
*
* @param request
* The XMLHttpRequest.
* @return an integer status value.
*/
function getRequestStatus(request) {
let status = null;
try {
status = request.status;
}
catch (e) {
}
if (status != null) {
return status;
}
return request.channel.QueryInterface(Ci.nsIRequest).status;
}
/**
* Downloads an XML document from a URL optionally testing the SSL certificate
* for certain attributes.
*
* @param url
* The url to download from.
* @param allowNonBuiltIn
* Whether to trust SSL certificates without a built-in CA issuer.
* @param allowedCerts
* The list of certificate attributes to match the SSL certificate
* against or null to skip checks.
* @return a promise that resolves to the DOM document downloaded or rejects
* with a JS exception in case of error.
*/
function downloadXML(url, allowNonBuiltIn = false, allowedCerts = null) {
return new Promise((resolve, reject) => {
let request = CreateXHR();
// This is here to let unit test code override XHR
if (request.wrappedJSObject) {
request = request.wrappedJSObject;
}
request.open("GET", url, true);
request.channel.notificationCallbacks = new BadCertHandler(allowNonBuiltIn);
// Prevent the request from reading from the cache.
request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
// Prevent the request from writing to the cache.
request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
// Use conservative TLS settings. See bug 1325501.
// TODO move to ServiceRequest.
if (request.channel instanceof Ci.nsIHttpChannelInternal) {
request.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
}
request.timeout = TIMEOUT_DELAY_MS;
request.overrideMimeType("text/xml");
// The Cache-Control header is only interpreted by proxies and the
// final destination. It does not help if a resource is already
// cached locally.
request.setRequestHeader("Cache-Control", "no-cache");
// HTTP/1.0 servers might not implement Cache-Control and
// might only implement Pragma: no-cache
request.setRequestHeader("Pragma", "no-cache");
let fail = (event) => {
let request = event.target;
let status = getRequestStatus(request);
let message = "Failed downloading XML, status: " + status + ", reason: " + event.type;
logger.warn(message);
let ex = new Error(message);
ex.status = status;
reject(ex);
};
let success = (event) => {
logger.info("Completed downloading document");
let request = event.target;
try {
checkCert(request.channel, allowNonBuiltIn, allowedCerts);
} catch (ex) {
logger.error("Request failed certificate checks: " + ex);
ex.status = getRequestStatus(request);
reject(ex);
return;
}
resolve(request.responseXML);
};
request.addEventListener("error", fail, false);
request.addEventListener("abort", fail, false);
request.addEventListener("timeout", fail, false);
request.addEventListener("load", success, false);
logger.info("sending request to: " + url);
request.send(null);
});
}
function downloadJSON(uri) {
logger.info("fetching config from: " + uri);
return new Promise((resolve, reject) => {
let xmlHttp = new ServiceRequest({mozAnon: true});
xmlHttp.onload = function(aResponse) {
resolve(JSON.parse(this.responseText));
};
xmlHttp.onerror = function(e) {
reject("Fetching " + uri + " results in error code: " + e.target.status);
};
xmlHttp.open("GET", uri);
xmlHttp.overrideMimeType("application/json");
xmlHttp.send();
});
}
/**
* Parses a list of add-ons from a DOM document.
*
* @param document
* The DOM document to parse.
* @return null if there is no <addons> element otherwise an object containing
* an array of the addons listed and a field notifying whether the
* fallback was used.
*/
function parseXML(document) {
// Check that the root element is correct
if (document.documentElement.localName != "updates") {
throw new Error("got node name: " + document.documentElement.localName +
", expected: updates");
}
// Check if there are any addons elements in the updates element
let addons = document.querySelector("updates:root > addons");
if (!addons) {
return null;
}
let results = [];
let addonList = document.querySelectorAll("updates:root > addons > addon");
for (let addonElement of addonList) {
let addon = {};
for (let name of ["id", "URL", "hashFunction", "hashValue", "version", "size"]) {
if (addonElement.hasAttribute(name)) {
addon[name] = addonElement.getAttribute(name);
}
}
addon.size = Number(addon.size) || undefined;
results.push(addon);
}
return {
usedFallback: false,
gmpAddons: results
};
}
/**
* If downloading from the network fails (AUS server is down),
* load the sources from local build configuration.
*/
function downloadLocalConfig() {
if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
return Promise.resolve({usedFallback: true, gmpAddons: []});
}
return Promise.all(LOCAL_EME_SOURCES.map(conf => {
return downloadJSON(conf.src).then(addons => {
let platforms = addons.vendors[conf.id].platforms;
let target = Services.appinfo.OS + "_" + UpdateUtils.ABI;
let details = null;
while (!details) {
if (!(target in platforms)) {
// There was no matching platform so return false, this addon
// will be filtered from the results below
logger.info("no details found for: " + target);
return false;
}
// Field either has the details of the binary or is an alias
// to another build target key that does
if (platforms[target].alias) {
target = platforms[target].alias;
} else {
details = platforms[target];
}
}
logger.info("found plugin: " + conf.id);
return {
"id": conf.id,
"URL": details.fileUrl,
"hashFunction": addons.hashFunction,
"hashValue": details.hashValue,
"version": addons.vendors[conf.id].version,
"size": details.filesize
};
});
})).then(addons => {
// Some filters may not match this platform so
// filter those out
addons = addons.filter(x => x !== false);
return {
usedFallback: true,
gmpAddons: addons
};
});
}
/**
* Downloads file from a URL using XHR.
*
* @param url
* The url to download from.
* @return a promise that resolves to the path of a temporary file or rejects
* with a JS exception in case of error.
*/
function downloadFile(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.onload = function(response) {
logger.info("downloadXHR File download. status=" + xhr.status);
if (xhr.status != 200 && xhr.status != 206) {
reject(Components.Exception("File download failed", xhr.status));
return;
}
Task.spawn(function* () {
let f = yield OS.File.openUnique(OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon"));
let path = f.path;
logger.info(`Downloaded file will be saved to ${path}`);
yield f.file.close();
yield OS.File.writeAtomic(path, new Uint8Array(xhr.response));
return path;
}).then(resolve, reject);
};
let fail = (event) => {
let request = event.target;
let status = getRequestStatus(request);
let message = "Failed downloading via XHR, status: " + status + ", reason: " + event.type;
logger.warn(message);
let ex = new Error(message);
ex.status = status;
reject(ex);
};
xhr.addEventListener("error", fail);
xhr.addEventListener("abort", fail);
xhr.responseType = "arraybuffer";
try {
xhr.open("GET", url);
// Use conservative TLS settings. See bug 1325501.
// TODO move to ServiceRequest.
if (xhr.channel instanceof Ci.nsIHttpChannelInternal) {
xhr.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
}
xhr.send(null);
} catch (ex) {
reject(ex);
}
});
}
/**
* Convert a string containing binary values to hex.
*/
function binaryToHex(input) {
let result = "";
for (let i = 0; i < input.length; ++i) {
let hex = input.charCodeAt(i).toString(16);
if (hex.length == 1) {
hex = "0" + hex;
}
result += hex;
}
return result;
}
/**
* Calculates the hash of a file.
*
* @param hashFunction
* The type of hash function to use, must be supported by nsICryptoHash.
* @param path
* The path of the file to hash.
* @return a promise that resolves to hash of the file or rejects with a JS
* exception in case of error.
*/
var computeHash = Task.async(function*(hashFunction, path) {
let file = yield OS.File.open(path, { existing: true, read: true });
try {
let hasher = Cc["@mozilla.org/security/hash;1"].
createInstance(Ci.nsICryptoHash);
hasher.initWithString(hashFunction);
let bytes;
do {
bytes = yield file.read(HASH_CHUNK_SIZE);
hasher.update(bytes, bytes.length);
} while (bytes.length == HASH_CHUNK_SIZE);
return binaryToHex(hasher.finish(false));
}
finally {
yield file.close();
}
});
/**
* Verifies that a downloaded file matches what was expected.
*
* @param properties
* The properties to check, `size` and `hashFunction` with `hashValue`
* are supported. Any properties missing won't be checked.
* @param path
* The path of the file to check.
* @return a promise that resolves if the file matched or rejects with a JS
* exception in case of error.
*/
var verifyFile = Task.async(function*(properties, path) {
if (properties.size !== undefined) {
let stat = yield OS.File.stat(path);
if (stat.size != properties.size) {
throw new Error("Downloaded file was " + stat.size + " bytes but expected " + properties.size + " bytes.");
}
}
if (properties.hashFunction !== undefined) {
let expectedDigest = properties.hashValue.toLowerCase();
let digest = yield computeHash(properties.hashFunction, path);
if (digest != expectedDigest) {
throw new Error("Hash was `" + digest + "` but expected `" + expectedDigest + "`.");
}
}
});
const ProductAddonChecker = {
/**
* Downloads a list of add-ons from a URL optionally testing the SSL
* certificate for certain attributes.
*
* @param url
* The url to download from.
* @param allowNonBuiltIn
* Whether to trust SSL certificates without a built-in CA issuer.
* @param allowedCerts
* The list of certificate attributes to match the SSL certificate
* against or null to skip checks.
* @return a promise that resolves to an object containing the list of add-ons
* and whether the local fallback was used, or rejects with a JS
* exception in case of error.
*/
getProductAddonList: function(url, allowNonBuiltIn = false, allowedCerts = null) {
if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
return Promise.resolve({usedFallback: true, gmpAddons: []});
}
return downloadXML(url, allowNonBuiltIn, allowedCerts)
.then(parseXML)
.catch(downloadLocalConfig);
},
/**
* Downloads an add-on to a local file and checks that it matches the expected
* file. The caller is responsible for deleting the temporary file returned.
*
* @param addon
* The addon to download.
* @return a promise that resolves to the temporary file downloaded or rejects
* with a JS exception in case of error.
*/
downloadAddon: Task.async(function*(addon) {
let path = yield downloadFile(addon.URL);
try {
yield verifyFile(addon, path);
return path;
}
catch (e) {
yield OS.File.remove(path);
throw e;
}
})
}

View file

@ -0,0 +1,17 @@
/* 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/. */
var hunspell, dir;
function startup(data) {
hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
.getService(Components.interfaces.mozISpellCheckingEngine);
dir = data.installPath.clone();
dir.append("dictionaries");
hunspell.addDirectory(dir);
}
function shutdown() {
hunspell.removeDirectory(dir);
}

View file

@ -0,0 +1,39 @@
/* 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";
Components.utils.import("resource://gre/modules/Extension.jsm");
var extension;
const BOOTSTRAP_REASON_TO_STRING_MAP = {
1: "APP_STARTUP",
2: "APP_SHUTDOWN",
3: "ADDON_ENABLE",
4: "ADDON_DISABLE",
5: "ADDON_INSTALL",
6: "ADDON_UNINSTALL",
7: "ADDON_UPGRADE",
8: "ADDON_DOWNGRADE",
}
function install(data, reason)
{
}
function startup(data, reason)
{
extension = new Extension(data, BOOTSTRAP_REASON_TO_STRING_MAP[reason]);
extension.startup();
}
function shutdown(data, reason)
{
extension.shutdown();
}
function uninstall(data, reason)
{
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,36 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXTRA_JS_MODULES.addons += [
'AddonLogging.jsm',
'AddonRepository.jsm',
'AddonRepository_SQLiteMigrator.jsm',
'AddonUpdateChecker.jsm',
'APIExtensionBootstrap.js',
'Content.js',
'E10SAddonsRollout.jsm',
'GMPProvider.jsm',
'LightweightThemeImageOptimizer.jsm',
'ProductAddonChecker.jsm',
'SpellCheckDictionaryBootstrap.js',
'WebExtensionBootstrap.js',
'XPIProvider.jsm',
'XPIProviderUtils.js',
]
TESTING_JS_MODULES += [
'AddonTestUtils.jsm',
]
# Don't ship unused providers on Android
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
EXTRA_JS_MODULES.addons += [
'PluginProvider.jsm',
]
EXTRA_PP_JS_MODULES.addons += [
'AddonConstants.jsm',
]