mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-31 11:37:30 +09:00
Move WebExtensions enabled Add-ons Manager
This commit is contained in:
parent
d7d16e7fbb
commit
358cd0404a
1100 changed files with 133 additions and 106 deletions
|
|
@ -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) {
|
||||
}
|
||||
31
toolkit/mozapps/webextensions/internal/AddonConstants.jsm
Normal file
31
toolkit/mozapps/webextensions/internal/AddonConstants.jsm
Normal 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
|
||||
});
|
||||
192
toolkit/mozapps/webextensions/internal/AddonLogging.jsm
Normal file
192
toolkit/mozapps/webextensions/internal/AddonLogging.jsm
Normal 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();
|
||||
1988
toolkit/mozapps/webextensions/internal/AddonRepository.jsm
Normal file
1988
toolkit/mozapps/webextensions/internal/AddonRepository.jsm
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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)");
|
||||
}
|
||||
}
|
||||
1231
toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm
Normal file
1231
toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm
Normal file
File diff suppressed because it is too large
Load diff
934
toolkit/mozapps/webextensions/internal/AddonUpdateChecker.jsm
Normal file
934
toolkit/mozapps/webextensions/internal/AddonUpdateChecker.jsm
Normal 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, "<");
|
||||
aString = aString.replace(/>/g, ">");
|
||||
return aString.replace(/"/g, """);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
38
toolkit/mozapps/webextensions/internal/Content.js
Normal file
38
toolkit/mozapps/webextensions/internal/Content.js
Normal 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);
|
||||
}
|
||||
|
||||
})();
|
||||
982
toolkit/mozapps/webextensions/internal/E10SAddonsRollout.jsm
Normal file
982
toolkit/mozapps/webextensions/internal/E10SAddonsRollout.jsm
Normal 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;
|
||||
},
|
||||
});
|
||||
699
toolkit/mozapps/webextensions/internal/GMPProvider.jsm
Normal file
699
toolkit/mozapps/webextensions/internal/GMPProvider.jsm
Normal 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)
|
||||
]);
|
||||
|
|
@ -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");
|
||||
|
||||
600
toolkit/mozapps/webextensions/internal/PluginProvider.jsm
Normal file
600
toolkit/mozapps/webextensions/internal/PluginProvider.jsm
Normal 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)
|
||||
]);
|
||||
467
toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm
Normal file
467
toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm
Normal 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;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
9305
toolkit/mozapps/webextensions/internal/XPIProvider.jsm
Normal file
9305
toolkit/mozapps/webextensions/internal/XPIProvider.jsm
Normal file
File diff suppressed because it is too large
Load diff
2255
toolkit/mozapps/webextensions/internal/XPIProviderUtils.js
Normal file
2255
toolkit/mozapps/webextensions/internal/XPIProviderUtils.js
Normal file
File diff suppressed because it is too large
Load diff
36
toolkit/mozapps/webextensions/internal/moz.build
Normal file
36
toolkit/mozapps/webextensions/internal/moz.build
Normal 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',
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue