Bug 928575 - Overhaul FHR data collection for extensions and plugins for desktop. r=gps

This commit is contained in:
Georg Fritzsche 2014-01-15 16:12:41 +01:00
parent 245ad2433b
commit 41da9eabcd
3 changed files with 279 additions and 35 deletions

View File

@ -537,11 +537,18 @@ version number is incremented.
All measurements are defined alphabetically in the sections below.
org.mozilla.addons.active
org.mozilla.addons.addons
-------------------------
This measurement contains information about the currently-installed add-ons.
Version 2
^^^^^^^^^
This version adds the human-readable fields *name* and *description*, both
coming directly from the Addon instance as most properties in version 1.
Also, all plugin details are now in org.mozilla.addons.plugins.
Version 1
^^^^^^^^^
@ -576,32 +583,99 @@ Example
^^^^^^^
::
"org.mozilla.addons.active": {
"_v": 1,
"SQLiteManager@mrinalkant.blogspot.com": {
"org.mozilla.addons.addons": {
"_v": 2,
"{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}": {
"userDisabled": false,
"appDisabled": false,
"version": "0.7.7",
"name": "Adblock Plus",
"version": "2.4.1",
"type": "extension",
"scope": 1,
"description": "Ads were yesterday!",
"foreignInstall": false,
"hasBinaryComponents": false,
"installDay": 15196,
"updateDay": 15307
"installDay": 16093,
"updateDay": 16093
},
"testpilot@labs.mozilla.com": {
"userDisabled": false,
"{e4a8a97b-f2ed-450b-b12d-ee082ba24781}": {
"userDisabled": true,
"appDisabled": false,
"version": "1.2.2",
"name": "Greasemonkey",
"version": "1.14",
"type": "extension",
"scope": 1,
"description": "A User Script Manager for Firefox",
"foreignInstall": false,
"hasBinaryComponents": false,
"installDay": 15176,
"updateDay": 15595
"installDay": 16093,
"updateDay": 16093
}
}
org.mozilla.addons.plugins
-------------------------
This measurement contains information about the currently-installed plugins.
Version 1
^^^^^^^^^
The measurement object is a mapping of plugin IDs to objects containing
plugin metadata.
The plugin ID is constructed of the plugins filename, name, version and
description. Every plugin has at least a filename and a name.
Each plugin contains the following properties:
* name
* version
* description
* blocklisted
* disabled
* clicktoplay
* mimeTypes
* updateDay
With the exception of *updateDay* and *mimeTypes*, all these properties come
directly from ``nsIPluginTag`` via ``nsIPluginHost``.
*updateDay* is the number of days since UNIX epoch of the plugins last modified
time.
*mimeTypes* is the list of mimetypes the plugin supports, see
``nsIPluginTag.getMimeTypes()`.
Example
^^^^^^^
::
"org.mozilla.addons.plugins": {
"_v": 1,
"Flash Player.plugin:Shockwave Flash:12.0.0.38:Shockwave Flash 12.0 r0": {
"mimeTypes": [
"application/x-shockwave-flash",
"application/futuresplash"
],
"name": "Shockwave Flash",
"version": "12.0.0.38",
"description": "Shockwave Flash 12.0 r0",
"blocklisted": false,
"disabled": false,
"clicktoplay": false
},
"Default Browser.plugin:Default Browser Helper:537:Provides information about the default web browser": {
"mimeTypes": [
"application/apple-default-browser"
],
"name": "Default Browser Helper",
"version": "537",
"description": "Provides information about the default web browser",
"blocklisted": false,
"disabled": true,
"clicktoplay": false
}
}
org.mozilla.addons.counts
-------------------------

View File

@ -696,8 +696,8 @@ function ActiveAddonsMeasurement() {
ActiveAddonsMeasurement.prototype = Object.freeze({
__proto__: Metrics.Measurement.prototype,
name: "active",
version: 1,
name: "addons",
version: 2,
fields: {
addons: LAST_TEXT_FIELD,
@ -705,7 +705,7 @@ ActiveAddonsMeasurement.prototype = Object.freeze({
_serializeJSONSingular: function (data) {
if (!data.has("addons")) {
this._log.warn("Don't have active addons info. Weird.");
this._log.warn("Don't have addons info. Weird.");
return null;
}
@ -716,6 +716,45 @@ ActiveAddonsMeasurement.prototype = Object.freeze({
},
});
/**
* Stores the set of active plugins in storage.
*
* This stores the data in a JSON blob in a text field similar to the
* ActiveAddonsMeasurement.
*/
function ActivePluginsMeasurement() {
Metrics.Measurement.call(this);
this._serializers = {};
this._serializers[this.SERIALIZE_JSON] = {
singular: this._serializeJSONSingular.bind(this),
// We don't need a daily serializer because we have none of this data.
};
}
ActivePluginsMeasurement.prototype = Object.freeze({
__proto__: Metrics.Measurement.prototype,
name: "plugins",
version: 1,
fields: {
plugins: LAST_TEXT_FIELD,
},
_serializeJSONSingular: function (data) {
if (!data.has("plugins")) {
this._log.warn("Don't have plugins info. Weird.");
return null;
}
// Exceptions are caught in the caller.
let result = JSON.parse(data.get("plugins")[1]);
result._v = this.version;
return result;
},
});
function AddonCountsMeasurement() {
Metrics.Measurement.call(this);
@ -783,7 +822,6 @@ AddonsProvider.prototype = Object.freeze({
// Add-on types for which full details are uploaded in the
// ActiveAddonsMeasurement. All other types are ignored.
FULL_DETAIL_TYPES: [
"plugin",
"extension",
"service",
],
@ -792,6 +830,7 @@ AddonsProvider.prototype = Object.freeze({
measurementTypes: [
ActiveAddonsMeasurement,
ActivePluginsMeasurement,
AddonCountsMeasurement1,
AddonCountsMeasurement,
],
@ -826,9 +865,11 @@ AddonsProvider.prototype = Object.freeze({
AddonManager.getAllAddons(function onAllAddons(addons) {
let data;
let addonsField;
let pluginsField;
try {
data = this._createDataStructure(addons);
addonsField = JSON.stringify(data.addons);
pluginsField = JSON.stringify(data.plugins);
} catch (ex) {
this._log.warn("Exception when populating add-ons data structure: " +
CommonUtils.exceptionStr(ex));
@ -837,7 +878,8 @@ AddonsProvider.prototype = Object.freeze({
}
let now = new Date();
let active = this.getMeasurement("active", 1);
let addons = this.getMeasurement("addons", 2);
let plugins = this.getMeasurement("plugins", 1);
let counts = this.getMeasurement(AddonCountsMeasurement.prototype.name,
AddonCountsMeasurement.prototype.version);
@ -853,8 +895,13 @@ AddonsProvider.prototype = Object.freeze({
counts.setDailyLastNumeric(type, data.counts[type], now);
}
return active.setLastText("addons", addonsField).then(
function onSuccess() { deferred.resolve(); },
return addons.setLastText("addons", addonsField).then(
function onSuccess() {
return plugins.setLastText("plugins", pluginsField).then(
function onSuccess() { deferred.resolve(); },
function onError(error) { deferred.reject(error); }
);
},
function onError(error) { deferred.reject(error); }
);
}.bind(this));
@ -863,21 +910,41 @@ AddonsProvider.prototype = Object.freeze({
return deferred.promise;
},
COPY_FIELDS: [
COPY_ADDON_FIELDS: [
"userDisabled",
"appDisabled",
"name",
"version",
"type",
"scope",
"description",
"foreignInstall",
"hasBinaryComponents",
],
COPY_PLUGIN_FIELDS: [
"name",
"version",
"description",
"blocklisted",
"disabled",
"clicktoplay",
],
_createDataStructure: function (addons) {
let data = {addons: {}, counts: {}};
let data = {
addons: {},
plugins: {},
counts: {}
};
for (let addon of addons) {
let type = addon.type;
// We count plugins separately below.
if (addon.type == "plugin")
continue;
data.counts[type] = (data.counts[type] || 0) + 1;
if (this.FULL_DETAIL_TYPES.indexOf(addon.type) == -1) {
@ -885,7 +952,7 @@ AddonsProvider.prototype = Object.freeze({
}
let obj = {};
for (let field of this.COPY_FIELDS) {
for (let field of this.COPY_ADDON_FIELDS) {
obj[field] = addon[field];
}
@ -898,9 +965,29 @@ AddonsProvider.prototype = Object.freeze({
}
data.addons[addon.id] = obj;
}
let pluginTags = Cc["@mozilla.org/plugin/host;1"].
getService(Ci.nsIPluginHost).
getPluginTags({});
for (let tag of pluginTags) {
let obj = {
mimeTypes: tag.getMimeTypes({}),
};
for (let field of this.COPY_PLUGIN_FIELDS) {
obj[field] = tag[field];
}
// Plugins need to have a filename and a name, so this can't be empty.
let id = tag.filename + ":" + tag.name + ":" + tag.version + ":"
+ tag.description;
data.plugins[id] = obj;
}
data.counts["plugin"] = pluginTags.length;
return data;
},
});

View File

@ -3,7 +3,7 @@
"use strict";
const {utils: Cu} = Components;
const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
Cu.import("resource://gre/modules/Metrics.jsm");
@ -15,7 +15,7 @@ Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
let gGlobalScope = this;
function loadAddonManager() {
let ns = {};
Components.utils.import("resource://gre/modules/Services.jsm", ns);
Cu.import("resource://gre/modules/Services.jsm", ns);
let head = "../../../../toolkit/mozapps/extensions/test/xpcshell/head_addons.js";
let file = do_get_file(head);
let uri = ns.Services.io.newFileURI(file);
@ -64,7 +64,7 @@ add_task(function test_collect() {
let now = new Date();
// FUTURE install add-on via AddonManager and don't use monkeypatching.
let addons = [
let testAddons = [
{
id: "addon0",
userDisabled: false,
@ -77,6 +77,7 @@ add_task(function test_collect() {
installDate: now,
updateDate: now,
},
// This plugin entry should get ignored.
{
id: "addon1",
userDisabled: false,
@ -113,15 +114,54 @@ add_task(function test_collect() {
hasBinaryComponents: false,
installDate: now,
updateDate: now,
description: "addon3 description"
},
];
monkeypatchAddons(provider, addons);
monkeypatchAddons(provider, testAddons);
let testPlugins = {
"Test Plug-in":
{
"version": "1.0.0.0",
"description": "Plug-in for testing purposes.™ (हिन्दी 中文 العربية)",
"blocklisted": false,
"disabled": false,
"clicktoplay": false,
"mimeTypes":[
"application/x-test"
],
},
"Second Test Plug-in":
{
"version": "1.0.0.0",
"description": "Second plug-in for testing purposes.",
"blocklisted": false,
"disabled": false,
"clicktoplay": false,
"mimeTypes":[
"application/x-second-test"
],
},
};
let pluginTags = Cc["@mozilla.org/plugin/host;1"]
.getService(Ci.nsIPluginHost)
.getPluginTags({});
for (let tag of pluginTags) {
if (tag.name in testPlugins) {
let p = testPlugins[tag.name];
p.id = tag.filename+":"+tag.name+":"+p.version+":"+p.description;
}
}
yield provider.collectConstantData();
let active = provider.getMeasurement("active", 1);
let data = yield active.getValues();
// Test addons measurement.
let addons = provider.getMeasurement("addons", 2);
let data = yield addons.getValues();
do_check_eq(data.days.size, 0);
do_check_eq(data.singular.size, 1);
@ -130,20 +170,63 @@ add_task(function test_collect() {
let json = data.singular.get("addons")[1];
let value = JSON.parse(json);
do_check_eq(typeof(value), "object");
do_check_eq(Object.keys(value).length, 3);
do_check_eq(Object.keys(value).length, 2);
do_check_true("addon0" in value);
do_check_true("addon1" in value);
do_check_true(!("addon1" in value));
do_check_true(!("addon2" in value));
do_check_true("addon3" in value);
let serializer = active.serializer(active.SERIALIZE_JSON);
let serializer = addons.serializer(addons.SERIALIZE_JSON);
let serialized = serializer.singular(data.singular);
do_check_eq(typeof(serialized), "object");
do_check_eq(Object.keys(serialized).length, 4); // Our three keys, plus _v.
do_check_eq(Object.keys(serialized).length, 3); // Our entries, plus _v.
do_check_true("addon0" in serialized);
do_check_true("addon1" in serialized);
do_check_true("addon3" in serialized);
do_check_eq(serialized._v, 2);
// Test plugins measurement.
let plugins = provider.getMeasurement("plugins", 1);
data = yield plugins.getValues();
do_check_eq(data.days.size, 0);
do_check_eq(data.singular.size, 1);
do_check_true(data.singular.has("plugins"));
json = data.singular.get("plugins")[1];
value = JSON.parse(json);
do_check_eq(typeof(value), "object");
do_check_eq(Object.keys(value).length, 2);
do_check_true(testPlugins["Test Plug-in"].id in value);
do_check_true(testPlugins["Second Test Plug-in"].id in value);
for (let id in value) {
let item = value[id];
let testData = testPlugins[item.name];
for (let prop in testData) {
if (prop == "mimeTypes" || prop == "id") {
continue;
}
do_check_eq(testData[prop], item[prop]);
}
for (let mime of testData.mimeTypes) {
do_check_true(item.mimeTypes.indexOf(mime) != -1);
}
}
serializer = plugins.serializer(plugins.SERIALIZE_JSON);
serialized = serializer.singular(data.singular);
do_check_eq(typeof(serialized), "object");
do_check_eq(Object.keys(serialized).length, 3); // Our entries, plus _v.
for (let name in testPlugins) {
do_check_true(testPlugins[name].id in serialized);
}
do_check_eq(serialized._v, 1);
// Test counts measurement.
let counts = provider.getMeasurement("counts", 2);
data = yield counts.getValues();
do_check_eq(data.days.size, 1);
@ -153,7 +236,7 @@ add_task(function test_collect() {
value = data.days.getDay(now);
do_check_eq(value.size, 4);
do_check_eq(value.get("extension"), 1);
do_check_eq(value.get("plugin"), 1);
do_check_eq(value.get("plugin"), 2);
do_check_eq(value.get("theme"), 1);
do_check_eq(value.get("service"), 1);