Bug 787767 - Implement runtime performance warnings for Worker API abuse. r=felipe

This commit is contained in:
Jared Wein 2012-10-18 18:02:42 -07:00
parent 7ef6e8b80f
commit faf749e67e
6 changed files with 167 additions and 1 deletions

View File

@ -275,6 +275,7 @@ _BROWSER_FILES = \
browser_social_mozSocial_API.js \
browser_social_isVisible.js \
browser_social_chatwindow.js \
browser_social_usageMonitor.js \
social_panel.html \
social_share_image.png \
social_sidebar.html \

View File

@ -0,0 +1,121 @@
/* 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/. */
// A mock notifications server. Based on:
// dom/tests/mochitest/notification/notification_common.js
const FAKE_CID = Cc["@mozilla.org/uuid-generator;1"].
getService(Ci.nsIUUIDGenerator).generateUUID();
const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
const ALERTS_SERVICE_CID = Components.ID(Cc[ALERTS_SERVICE_CONTRACT_ID].number);
function MockAlertsService() {}
MockAlertsService.prototype = {
showAlertNotification: function(imageUrl, title, text, textClickable,
cookie, alertListener, name) {
let obData = JSON.stringify({
imageUrl: imageUrl,
title: title,
text: text,
textClickable: textClickable,
cookie: cookie,
name: name
});
Services.obs.notifyObservers(null, "social-test:notification-alert", obData);
},
QueryInterface: function(aIID) {
if (aIID.equals(Ci.nsISupports) ||
aIID.equals(Ci.nsIAlertsService))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
var factory = {
createInstance: function(aOuter, aIID) {
if (aOuter != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return new MockAlertsService().QueryInterface(aIID);
}
};
function replacePromptService() {
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(FAKE_CID, "",
ALERTS_SERVICE_CONTRACT_ID,
factory)
}
function restorePromptService() {
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(ALERTS_SERVICE_CID, "",
ALERTS_SERVICE_CONTRACT_ID,
null);
}
// end of alerts service mock.
function test() {
waitForExplicitFinish();
let manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social_sidebar.html",
workerURL: "https://example.com/browser/browser/base/content/test/social_worker.js",
iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
};
Services.prefs.setBoolPref("social.debug.monitorUsage", true);
Services.prefs.setIntPref("social.debug.monitorUsageTimeLimitMS", 1000);
replacePromptService();
registerCleanupFunction(function() {
Services.prefs.clearUserPref("social.debug.monitorUsage");
Services.prefs.clearUserPref("social.debug.monitorUsageTimeLimitMS");
restorePromptService();
});
runSocialTestWithProvider(manifest, function (finishcb) {
runSocialTests(tests, undefined, undefined, finishcb);
});
}
var tests = {
testWorkerAPIAbuse: function(next) {
let port = Social.provider.getWorkerPort();
ok(port, "provider has a port");
Services.obs.addObserver(function abuseObserver(subject, topic, data) {
Services.obs.removeObserver(abuseObserver, "social-test:notification-alert");
data = JSON.parse(data);
is(data.title, "provider 1", "Abusive provider name should match");
is(data.text,
"Social API performance warning: More than 10 calls to social.cookies-get in less than 10 seconds.",
"Usage warning should mention social.cookies-get");
next();
}, "social-test:notification-alert", false);
for (let i = 0; i < 15; i++)
port.postMessage({topic: "test-worker-spam-message"});
},
testTimeBetweenFirstAndLastMoreThanLimit: function(next) {
let port = Social.provider.getWorkerPort();
ok(port, "provider has a port");
Services.obs.addObserver(function abuseObserver(subject, topic, data) {
Services.obs.removeObserver(abuseObserver, "social-test:notification-alert");
data = JSON.parse(data);
is(data.title, "provider 1", "Abusive provider name should match");
is(data.text,
"Social API performance warning: More than 10 calls to social.cookies-get in less than 10 seconds.",
"Usage warning should mention social.cookies-get");
next();
}, "social-test:notification-alert", false);
port.postMessage({topic: "test-worker-spam-message"});
setTimeout(function() {
for (let i = 0; i < 15; i++)
port.postMessage({topic: "test-worker-spam-message"});
}, 2000);
}
}

View File

@ -76,6 +76,10 @@ onconnect = function(e) {
case "test-worker-chat":
apiPort.postMessage({topic: "social.request-chat", data: event.data.data });
break;
case "test-worker-spam-message":
// Just use a random api message, but one that has little side-effects.
apiPort.postMessage({topic: "social.cookies-get"});
break;
case "social.initialize":
// This is the workerAPI port, respond and set up a notification icon.
apiPort = port;

View File

@ -3780,6 +3780,8 @@ pref("memory.low_memory_notification_interval_ms", 10000);
pref("memory.ghost_window_timeout_seconds", 60);
pref("social.enabled", false);
pref("social.debug.monitorUsage", false);
pref("social.debug.monitorUsageTimeThresholdMS", 10000);
// Disable idle observer fuzz, because only privileged content can access idle
// observers (bug 780507).

View File

@ -22,6 +22,9 @@ function WorkerAPI(provider, port) {
this._provider = provider;
this._port = port;
this._port.onmessage = this._handleMessage.bind(this);
this._usageMonitor = Services.prefs.getBoolPref("social.debug.monitorUsage") ?
new WorkerAPIUsageMonitor(provider) :
null;
// Send an "intro" message so the worker knows this is the port
// used for the api.
@ -42,6 +45,8 @@ WorkerAPI.prototype = {
return;
}
try {
if (this._usageMonitor)
this._usageMonitor.logMessage(topic);
handler.call(this, data);
} catch (ex) {
Cu.reportError("WorkerAPI: failed to handle message '" + topic + "': " + ex);
@ -69,7 +74,7 @@ WorkerAPI.prototype = {
cookies.forEach(function(aCookie) {
let [name, value] = aCookie.split("=");
results.push({name: unescape(name.trim()),
value: unescape(value.trim())});
value: value ? unescape(value.trim()) : ""});
});
this._port.postMessage({topic: "social.cookies-get-response",
data: results});
@ -130,3 +135,35 @@ WorkerAPI.prototype = {
},
}
}
function WorkerAPIUsageMonitor(provider) {
if (!provider)
throw new Error("Can't initialize WorkerAPIUsageMonitor with a null provider");
this._providerName = provider.name;
this.TIME_THRESHOLD_MS = Services.prefs.getIntPref("social.debug.monitorUsageTimeThresholdMS");
this._messages = {};
}
WorkerAPIUsageMonitor.prototype = {
logMessage: function WorkerAPIUsage_logMessage(aMessage) {
if (!(aMessage in this._messages)) {
this._messages[aMessage] = [];
}
let messageList = this._messages[aMessage];
messageList.push(Date.now());
if (messageList.length > 10) {
if (messageList[9] - messageList[0] < this.TIME_THRESHOLD_MS) {
let alertsService = Cc["@mozilla.org/alerts-service;1"]
.getService(Ci.nsIAlertsService);
const SOCIAL_BUNDLE = "chrome://global/locale/social.properties";
let socialBundle = Services.strings.createBundle(SOCIAL_BUNDLE);
let seconds = (this.TIME_THRESHOLD_MS / 1000).toString();
let text = socialBundle.formatStringFromName("social.usageAbuse",
[aMessage, seconds], 2);
alertsService.showAlertNotification("chrome://branding/content/icon48.png",
this._providerName, text);
}
messageList.shift();
}
}
};

View File

@ -58,6 +58,7 @@
locale/@AB_CD@/global/printProgress.dtd (%chrome/global/printProgress.dtd)
locale/@AB_CD@/global/regionNames.properties (%chrome/global/regionNames.properties)
locale/@AB_CD@/global/resetProfile.dtd (%chrome/global/resetProfile.dtd)
locale/@AB_CD@/global/social.properties (%chrome/global/social.properties)
locale/@AB_CD@/global/dialog.properties (%chrome/global/dialog.properties)
locale/@AB_CD@/global/tree.dtd (%chrome/global/tree.dtd)
locale/@AB_CD@/global/textcontext.dtd (%chrome/global/textcontext.dtd)