Merge mozilla-central into mozilla-inbound

This commit is contained in:
Ehsan Akhgari 2013-01-12 09:27:31 -05:00
commit 46c6a08469
70 changed files with 2148 additions and 1175 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1357164966000">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1357926611000">
<emItems>
<emItem blockID="i58" id="webmaster@buzzzzvideos.info">
<versionRange minVersion="0" maxVersion="*">
@ -588,42 +588,42 @@
</versionRange>
</pluginItem>
<pluginItem blockID="p180">
<match name="filename" exp="JavaAppletPlugin\.plugin" /> <versionRange minVersion="Java 7 Update 07" maxVersion="Java 7 Update 08" severity="0" vulnerabilitystatus="1">
<match name="filename" exp="JavaAppletPlugin\.plugin" /> <versionRange minVersion="Java 7 Update 0" maxVersion="Java 7 Update 10" severity="0" vulnerabilitystatus="2">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="17.0" maxVersion="*" />
</targetApplication>
</versionRange>
</pluginItem>
<pluginItem blockID="p182">
<match name="name" exp="Java\(TM\) Platform SE 7 U[7-8](\s[^\d\._U]|$)" /> <match name="filename" exp="npjp2\.dll" /> <versionRange severity="0" vulnerabilitystatus="1">
<match name="name" exp="Java\(TM\) Platform SE 7 U([0-9]|10)(\s[^\d\._U]|$)" /> <match name="filename" exp="npjp2\.dll" /> <versionRange severity="0" vulnerabilitystatus="2">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="17.0" maxVersion="*" />
</targetApplication>
</versionRange>
</pluginItem>
<pluginItem blockID="p184">
<match name="name" exp="Java\(TM\) Plug-in 1\.7\.0(_0?([7-8]))?([^\d\._]|$)" /> <match name="filename" exp="libnpjp2\.so" /> <versionRange severity="0" vulnerabilitystatus="1">
<match name="name" exp="Java\(TM\) Plug-in 1\.7\.0(_0?([0-9]|10)?)?([^\d\._]|$)" /> <match name="filename" exp="libnpjp2\.so" /> <versionRange severity="0" vulnerabilitystatus="2">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="17.0" maxVersion="*" />
</targetApplication>
</versionRange>
</pluginItem>
<pluginItem blockID="p186">
<match name="name" exp="Java\(TM\) Platform SE 6 U3[3-6](\s[^\d\._U]|$)" /> <match name="filename" exp="npjp2\.dll" /> <versionRange severity="0" vulnerabilitystatus="1">
<match name="name" exp="Java\(TM\) Platform SE 6 U3[1-8](\s[^\d\._U]|$)" /> <match name="filename" exp="npjp2\.dll" /> <versionRange severity="0" vulnerabilitystatus="2">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="17.0" maxVersion="*" />
</targetApplication>
</versionRange>
</pluginItem>
<pluginItem blockID="p188">
<match name="filename" exp="JavaAppletPlugin\.plugin" /> <versionRange minVersion="Java 6 Update 0" maxVersion="Java 6 Update 36" severity="0" vulnerabilitystatus="1">
<match name="filename" exp="JavaAppletPlugin\.plugin" /> <versionRange minVersion="Java 6 Update 0" maxVersion="Java 6 Update 38" severity="0" vulnerabilitystatus="2">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="17.0" maxVersion="*" />
</targetApplication>
</versionRange>
</pluginItem>
<pluginItem blockID="p190">
<match name="name" exp="Java\(TM\) Plug-in 1\.6\.0_3[3-6]([^\d\._]|$)" /> <match name="filename" exp="libnpjp2\.so" /> <versionRange severity="0" vulnerabilitystatus="1">
<match name="name" exp="Java\(TM\) Plug-in 1\.6\.0_3[1-8]([^\d\._]|$)" /> <match name="filename" exp="libnpjp2\.so" /> <versionRange severity="0" vulnerabilitystatus="1">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="17.0" maxVersion="*" />
</targetApplication>

View File

@ -865,6 +865,10 @@ pref("breakpad.reportURL", "http://crash-stats.mozilla.com/report/index/");
pref("toolkit.crashreporter.pluginHangSubmitURL",
"https://hang-reports.mozilla.org/submit");
// URL for "Learn More" for Crash Reporter
pref("toolkit.crashreporter.infoURL",
"http://www.mozilla.com/legal/privacy/firefox.html#crash-reporter");
// base URL for web-based support pages
pref("app.support.baseURL", "http://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/");

View File

@ -0,0 +1,140 @@
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
}
body {
background-color: window;
color: windowtext;
font-family: "Trebuchet MS", "Helvetica";
}
#about-header {
padding: 6px 20px;
min-height: 60px;
border-bottom: 1px solid #999999;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: space-between;
background-image: linear-gradient(to bottom, #E66000, #BB2200);
color: #FFFFFF;
}
#control-container {
display: flex;
align-items: center;
}
#content-line {
display: flex;
justify-content: space-between;
align-items: center;
}
#content {
display: flex;
flex-direction: column;
}
#state-intro {
background-image: linear-gradient(to bottom, #EAEFF2, #D4DDE4);
border: 1px solid #999999;
border-radius: 6px;
margin: 12px;
padding: 12px;
}
#settings-controls {
padding-top: 15px;
}
#control-container {
padding-top: 15px;
}
#content[state="default"] #details-hide,
#content[state="default"] #btn-optin,
#content[state="default"] #intro-disabled {
display: none;
}
#content[state="showDetails"] #details-show,
#content[state="showDetails"] #btn-optin,
#content[state="showDetails"] #intro-disabled {
display: none;
}
#content[state="showReport"] #details-hide,
#content[state="showReport"] #report-show,
#content[state="showReport"] #btn-optin,
#content[state="showReport"] #intro-disabled {
display: none;
}
#content[state="disabled"] #details-hide,
#content[state="disabled"] #details-show,
#content[state="disabled"] #btn-optout,
#content[state="disabled"] #intro-enabled {
display: none;
}
#details-view,
#report-view {
display: none;
}
#data-view {
height: auto;
margin-top: 8px;
align-items: center;
justify-content: center;
border: 1px solid #999999;
border-radius: 6px;
margin: 12px;
}
#remote-report,
#report-view {
width: 100%;
height: 100%;
min-height: 600px;
}
#report-show {
display: flex;
width: 100%;
height: 100%;
min-height: 60px;
font-size: 18px;
font-weight: bold;
background-image: linear-gradient(to bottom, #80BB2E, #547D1C);
color: #ffffff;
border-radius: 6px;
}
#details-view {
border: 1px solid #999999;
border-radius: 6px;
margin: 12px;
padding: 12px;
}
#content[state="showDetails"],
#content[state="showReport"],
#content[state="showDetails"] #details-view,
#content[state="showReport"] #report-view {
display: block;
}
#content[state="showReport"] #report-show {
display: none;
}
#content[state="showReport"] #report-view,
#remote-report {
border: 0;
}

View File

@ -0,0 +1,117 @@
/* 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;
Cu.import("resource://services-common/preferences.js");
const reporter = Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject
.healthReporter;
const policy = Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject.policy;
const prefs = new Preferences("datareporting.healthreport.about.");
function getLocale() {
return Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry)
.getSelectedLocale("global");
}
function init() {
refreshWithDataSubmissionFlag(reporter.willUploadData);
refreshJSONPayload();
document.getElementById("details-link").href = prefs.get("glossaryUrl");
}
/**
* Update the state of the page to reflect the current data submission state.
*
* @param enabled
* (bool) Whether data submission is enabled.
*/
function refreshWithDataSubmissionFlag(enabled) {
if (!enabled)
updateView("disabled");
else
updateView("default");
}
function updateView(state="default") {
let content = document.getElementById("content");
let controlContainer = document.getElementById("control-container");
content.setAttribute("state", state);
controlContainer.setAttribute("state", state);
}
function refreshDataView(data) {
let noData = document.getElementById("data-no-data");
let dataEl = document.getElementById("raw-data");
noData.style.display = data ? "none" : "inline";
dataEl.style.display = data ? "block" : "none";
if (data)
dataEl.innerHTML = JSON.stringify(data, null, 2);
}
/**
* Ensure the page has the latest version of the uploaded JSON payload.
*/
function refreshJSONPayload() {
reporter.getLastPayload().then(refreshDataView);
}
function onOptInClick() {
policy.healthReportUploadEnable = true;
refreshWithDataSubmissionFlag(true);
}
function onOptOutClick() {
let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Ci.nsIPromptService);
let messages = document.getElementById("optout-confirmationPrompt");
let title = messages.getAttribute("confirmationPrompt_title");
let message = messages.getAttribute("confirmationPrompt_message");
if (!prompts.confirm(window, title, message)) {
return;
}
policy.healthReportUploadEnable = false;
let promise = reporter.requestDeleteRemoteData("Clicked opt out button on about page.");
if (promise) {
promise.then(function onDelete() {
refreshWithDataSubmissionFlag(reporter.willUploadData);
});
}
refreshWithDataSubmissionFlag(false);
updateView("disabled");
}
function onShowRawDataClick() {
updateView("showDetails");
refreshJSONPayload();
}
function onHideRawDataClick() {
updateView("default");
}
function onShowReportClick() {
updateView("showReport");
document.getElementById("remote-report").src = prefs.get("reportUrl");
}
function onHideReportClick() {
updateView("default");
document.getElementById("remote-report").src = "";
}

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
%htmlDTD;
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % securityPrefsDTD SYSTEM "chrome://browser/locale/preferences/security.dtd">
%securityPrefsDTD;
<!ENTITY % aboutHealthReportDTD SYSTEM "chrome://browser/locale/aboutHealthReport.dtd">
%aboutHealthReportDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&abouthealth.pagetitle;</title>
<link rel="stylesheet"
href="chrome://browser/content/abouthealthreport/abouthealth.css"
type="text/css" />
<script type="text/javascript;version=1.8"
src="chrome://browser/content/abouthealthreport/abouthealth.js" />
</head>
<body dir="&abouthealth.locale-direction;" class="aboutPageWideContainer" onload="init();">
<div id="about-header">
<h1>&abouthealth.header;</h1>
<img src="chrome://branding/content/icon48.png"/>
</div>
<div id="content">
<div id="state-intro">
<h3>&abouthealth.intro.title;</h3>
<div id="content-line">
<p id="intro-enabled">&abouthealth.intro-enabled;</p>
<p id="intro-disabled">&abouthealth.intro-disabled;</p>
<div id="control-container">
<button id="btn-optin" onclick="onOptInClick();">&abouthealth.optin;</button>
<button id="btn-optout" onclick="onOptOutClick();">&abouthealth.optout;</button>
<button id="details-show" onclick="onShowRawDataClick()">&abouthealth.show-raw-data;</button>
<button id="details-hide" onclick="onHideRawDataClick()">&abouthealth.hide-raw-data;</button>
</div>
</div>
</div>
<div id="details-view">
<p id="details-description">
&abouthealth.details.description-start;
<a id="details-link">&abouthealth.details-link;</a>
&abouthealth.details.description-end;
</p>
<p id="data-no-data">&abouthealth.no-data-available;</p>
<pre id="raw-data" style="display: none"></pre>
</div>
<div id="data-view">
<button id="report-show" onclick="onShowReportClick()">&abouthealth.show-report;</button>
<div id="report-view">
<iframe id="remote-report"/>
</div>
</div>
</div>
<input type="hidden" id="optout-confirmationPrompt"
confirmationPrompt_title="&abouthealth.optout.confirmationPrompt.title;"
confirmationPrompt_message="&abouthealth.optout.confirmationPrompt.message;"
/>
</body>
</html>

View File

@ -52,6 +52,11 @@
#else
/>
#endif
<menuitem id="healthReport"
label="&healthReport.label;"
accesskey="&healthReport.accesskey;"
oncommand="openHealthReport()"
onclick="checkForMiddleClick(this, event);"/>
<menuitem id="troubleShooting"
accesskey="&helpTroubleshootingInfo.accesskey;"
label="&helpTroubleshootingInfo.label;"

View File

@ -378,6 +378,10 @@
label="&appMenuGettingStarted.label;"
oncommand="gBrowser.loadOneTab('http://www.mozilla.com/firefox/central/', {inBackground: false});"
onclick="checkForMiddleClick(this, event);"/>
<menuitem id="appmenu_healthReport"
label="&healthReport.label;"
oncommand="openHealthReport()"
onclick="checkForMiddleClick(this, event);"/>
<menuitem id="appmenu_troubleshootingInfo"
label="&helpTroubleshootingInfo.label;"
oncommand="openTroubleshootingPage()"

View File

@ -0,0 +1,171 @@
/* 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";
Cu.import("resource://services-common/log4moz.js");
/**
* Represents an info bar that shows a data submission notification.
*/
function DataNotificationInfoBar() {
let log4moz = Cu.import("resource://services-common/log4moz.js", {}).Log4Moz;
this._log = log4moz.repository.getLogger("Services.DataReporting.InfoBar");
this._notificationBox = null;
}
DataNotificationInfoBar.prototype = {
_OBSERVERS: [
"datareporting:notify-data-policy:request",
"datareporting:notify-data-policy:close",
],
#ifdef MOZ_TELEMETRY_REPORTING
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
_PREF_TELEMETRY_DISPLAYED: "toolkit.telemetry.notifiedOptOut",
#else
_PREF_TELEMETRY_DISPLAYED: "toolkit.telemetry.prompted",
#endif
_TELEMETRY_DISPLAY_REV: 2,
#endif
init: function() {
window.addEventListener("unload", function onUnload() {
window.removeEventListener("unload", onUnload, false);
for (let o of this._OBSERVERS) {
Services.obs.removeObserver(this, o);
}
}.bind(this), false);
for (let o of this._OBSERVERS) {
Services.obs.addObserver(this, o, true);
}
},
_ensureNotificationBox: function () {
if (this._notificationBox) {
return;
}
let nb = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"notificationbox"
);
nb.id = "data-notification-notify-bar";
nb.setAttribute("flex", "1");
let bottombox = document.getElementById("browser-bottombox");
bottombox.insertBefore(nb, bottombox.firstChild);
this._notificationBox = nb;
},
_displayDataPolicyInfoBar: function (request) {
this._ensureNotificationBox();
if (this._notificationBox.getNotificationWithValue("data-reporting")) {
return;
}
let policy = Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject
.policy;
let brandBundle = document.getElementById("bundle_brand");
let appName = brandBundle.getString("brandShortName");
let vendorName = brandBundle.getString("vendorShortName");
let message = gNavigatorBundle.getFormattedString(
"dataReportingNotification.message",
[appName, vendorName]);
let actionTaken = false;
let buttons = [{
label: gNavigatorBundle.getString("dataReportingNotification.button.label"),
accesskey: gNavigatorBundle.getString("dataReportingNotification.button.accessKey"),
popup: null,
callback: function () {
// Clicking the button to go to the preferences tab constitutes
// acceptance of the data upload policy for Firefox Health Report.
// This will ensure the checkbox is checked. The user has the option of
// unchecking it.
request.onUserAccept("info-bar-button-pressed");
actionTaken = true;
window.openAdvancedPreferences("dataChoicesTab");
},
}];
this._log.info("Creating data reporting policy notification.");
let notification = this._notificationBox.appendNotification(
message,
"data-reporting",
null,
this._notificationBox.PRIORITY_INFO_HIGH,
buttons,
function onEvent(event) {
if (event == "removed") {
if (!actionTaken) {
request.onUserAccept("info-bar-dismissed");
}
Services.obs.notifyObservers(null, "datareporting:notify-data-policy:close", null);
}
}.bind(this)
);
// Keep open until user closes it.
notification.persistence = -1;
// This likely isn't needed in a world where Telemetry and FHR share a
// notification and data reporting policy. It is preserved until traces
// of this pref are wiped from the code base.
Services.prefs.setIntPref(this._PREF_TELEMETRY_DISPLAYED,
this._TELEMETRY_DISPLAY_REV);
// Tell the notification request we have displayed the notification.
request.onUserNotifyComplete();
},
_clearPolicyNotification: function () {
if (!this._notificationBox ||
!this._notificationBox.getNotificationWithValue("data-reporting")) {
return;
}
this._notificationBox.getNotificationWithValue("date-reporting").close();
},
onNotifyDataPolicy: function (request) {
try {
this._displayDataPolicyInfoBar(request);
} catch (ex) {
request.onUserNotifyFailed(ex);
}
},
observe: function(subject, topic, data) {
switch (topic) {
case "datareporting:notify-data-policy:request":
this.onNotifyDataPolicy(subject.wrappedJSObject.object);
break;
case "datareporting:notify-data-policy:close":
this._clearPolicyNotification();
break;
default:
}
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
]),
};

View File

@ -157,6 +157,12 @@ let gInitialPages = [
#include browser-thumbnails.js
#include browser-webrtcUI.js
#ifdef MOZ_DATA_REPORTING
#include browser-data-submission-info-bar.js
let gDataNotificationInfoBar = new DataNotificationInfoBar();
#endif
#ifdef MOZ_SERVICES_SYNC
#include browser-syncui.js
#endif
@ -1395,6 +1401,10 @@ var gBrowserInit = {
gSyncUI.init();
#endif
#ifdef MOZ_DATA_REPORTING
gDataNotificationInfoBar.init();
#endif
gBrowserThumbnails.init();
TabView.init();

View File

@ -529,6 +529,16 @@ function openTroubleshootingPage()
openUILinkIn("about:support", "tab");
}
/**
* Opens the troubleshooting information (about:support) page for this version
* of the application.
*/
function openHealthReport()
{
openUILinkIn("about:healthreport", "tab");
}
/**
* Opens the feedback page for this version of the application.
*/

View File

@ -48,6 +48,11 @@ browser.jar:
content/browser/abouthome/restore@2x.png (content/abouthome/restore@2x.png)
content/browser/abouthome/restore-large@2x.png (content/abouthome/restore-large@2x.png)
content/browser/abouthome/mozilla@2x.png (content/abouthome/mozilla@2x.png)
#endif
#ifdef MOZ_SERVICES_HEALTHREPORT
content/browser/abouthealthreport/abouthealth.xhtml (content/abouthealthreport/abouthealth.xhtml)
content/browser/abouthealthreport/abouthealth.js (content/abouthealthreport/abouthealth.js)
content/browser/abouthealthreport/abouthealth.css (content/abouthealthreport/abouthealth.css)
#endif
content/browser/aboutRobots-icon.png (content/aboutRobots-icon.png)
content/browser/aboutRobots-widget-left.png (content/aboutRobots-widget-left.png)

View File

@ -82,6 +82,10 @@ static RedirEntry kRedirMap[] = {
nsIAboutModule::ALLOW_SCRIPT },
{ "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
nsIAboutModule::ALLOW_SCRIPT },
#ifdef MOZ_SERVICES_HEALTHREPORT
{ "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
#endif
};
static const int kRedirTotal = NS_ARRAY_LENGTH(kRedirMap);

View File

@ -117,6 +117,9 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "permissions", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#ifdef MOZ_SERVICES_HEALTHREPORT
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif
#if defined(XP_WIN)
{ NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
#elif defined(XP_MACOSX)

View File

@ -438,11 +438,6 @@ BrowserGlue.prototype = {
// Show about:rights notification, if needed.
if (this._shouldShowRights()) {
this._showRightsNotification();
#ifdef MOZ_TELEMETRY_REPORTING
} else {
// Only show telemetry notification when about:rights notification is not shown.
this._showTelemetryNotification();
#endif
}
// Show update notification, if needed.
@ -506,7 +501,7 @@ BrowserGlue.prototype = {
var win = this.getMostRecentBrowserWindow();
var brandBundle = win.document.getElementById("bundle_brand");
var shellBundle = win.document.getElementById("bundle_shell");
var brandShortName = brandBundle.getString("brandShortName");
var promptTitle = shellBundle.getString("setDefaultBrowserTitle");
var promptMessage = shellBundle.getFormattedString("setDefaultBrowserMessage",
@ -857,167 +852,6 @@ BrowserGlue.prototype = {
}
},
#ifdef MOZ_TELEMETRY_REPORTING
_showTelemetryNotification: function BG__showTelemetryNotification() {
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabledPreRelease";
const PREF_TELEMETRY_DISPLAYED = "toolkit.telemetry.notifiedOptOut";
#else
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
const PREF_TELEMETRY_DISPLAYED = "toolkit.telemetry.prompted";
#endif
const PREF_TELEMETRY_REJECTED = "toolkit.telemetry.rejected";
const PREF_TELEMETRY_INFOURL = "toolkit.telemetry.infoURL";
const PREF_TELEMETRY_SERVER_OWNER = "toolkit.telemetry.server_owner";
// This is used to reprompt/renotify users when privacy message changes
const TELEMETRY_DISPLAY_REV = @MOZ_TELEMETRY_DISPLAY_REV@;
// Stick notifications onto the selected tab of the active browser window.
var win = this.getMostRecentBrowserWindow();
var tabbrowser = win.gBrowser;
var notifyBox = tabbrowser.getNotificationBox();
var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
var productName = brandBundle.GetStringFromName("brandFullName");
var serverOwner = Services.prefs.getCharPref(PREF_TELEMETRY_SERVER_OWNER);
function appendTelemetryNotification(message, buttons, hideclose) {
let notification = notifyBox.appendNotification(message, "telemetry", null,
notifyBox.PRIORITY_INFO_LOW,
buttons);
if (hideclose)
notification.setAttribute("hideclose", hideclose);
notification.persistence = -1; // Until user closes it
return notification;
}
function appendLearnMoreLink(notification) {
let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let link = notification.ownerDocument.createElementNS(XULNS, "label");
link.className = "text-link telemetry-text-link";
link.setAttribute("value", browserBundle.GetStringFromName("telemetryLinkLabel"));
let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
description.appendChild(link);
return link;
}
/*
* Display an opt-out notification when telemetry is enabled by default,
* an opt-in prompt otherwise.
*
* But do not display this prompt/notification if:
*
* - The last accepted/refused policy (either by accepting the prompt or by
* manually flipping the telemetry preference) is already at version
* TELEMETRY_DISPLAY_REV or higher (to avoid the prompt in tests).
*/
var telemetryDisplayed;
try {
telemetryDisplayed = Services.prefs.getIntPref(PREF_TELEMETRY_DISPLAYED);
} catch(e) {}
if (telemetryDisplayed >= TELEMETRY_DISPLAY_REV)
return;
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
/*
* Additionally, in opt-out builds, don't display the notification if:
*
* - Telemetry is disabled
* - Telemetry was explicitly refused through the UI
* - Opt-in telemetry was already enabled, don't notify the user until next
* policy update. (Do the check only at first run with opt-out builds)
*/
var telemetryEnabled = Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED);
if (!telemetryEnabled)
return;
// If telemetry was explicitly refused through the UI,
// also disable opt-out telemetry and bail out.
var telemetryRejected = false;
try {
telemetryRejected = Services.prefs.getBoolPref(PREF_TELEMETRY_REJECTED);
} catch(e) {}
if (telemetryRejected) {
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, false);
Services.prefs.setIntPref(PREF_TELEMETRY_DISPLAYED, TELEMETRY_DISPLAY_REV);
return;
}
// If opt-in telemetry was enabled and this is the first run with opt-out,
// don't notify the user.
var optInTelemetryEnabled = false;
try {
optInTelemetryEnabled = Services.prefs.getBoolPref("toolkit.telemetry.enabled");
} catch(e) {}
if (optInTelemetryEnabled && telemetryDisplayed === undefined) {
Services.prefs.setBoolPref(PREF_TELEMETRY_REJECTED, false);
Services.prefs.setIntPref(PREF_TELEMETRY_DISPLAYED, TELEMETRY_DISPLAY_REV);
return;
}
var telemetryPrompt = browserBundle.formatStringFromName("telemetryOptOutPrompt",
[productName, serverOwner, productName], 3);
var buttons = null;
var hideCloseButton = false;
function learnModeClickHandler() {
// Open the learn more url in a new tab
var url = Services.urlFormatter.formatURLPref("app.support.baseURL");
url += "how-can-i-help-submitting-performance-data";
tabbrowser.selectedTab = tabbrowser.addTab(url);
// Remove the notification on which the user clicked
notification.parentNode.removeNotification(notification, true);
}
#else
// Clear old prefs and reprompt
Services.prefs.clearUserPref(PREF_TELEMETRY_ENABLED);
Services.prefs.clearUserPref(PREF_TELEMETRY_REJECTED);
var telemetryPrompt = browserBundle.formatStringFromName("telemetryOptInPrompt",
[productName, serverOwner], 2);
var buttons = [
{
label: browserBundle.GetStringFromName("telemetryYesButtonLabel2"),
accessKey: browserBundle.GetStringFromName("telemetryYesButtonAccessKey"),
popup: null,
callback: function(aNotificationBar, aButton) {
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
Services.prefs.setBoolPref(PREF_TELEMETRY_REJECTED, false);
}
},
{
label: browserBundle.GetStringFromName("telemetryNoButtonLabel"),
accessKey: browserBundle.GetStringFromName("telemetryNoButtonAccessKey"),
popup: null,
callback: function(aNotificationBar, aButton) {
Services.prefs.setBoolPref(PREF_TELEMETRY_REJECTED, true);
}
}
];
var hideCloseButton = true;
function learnModeClickHandler() {
// Open the learn more url in a new tab
win.openUILinkIn(Services.prefs.getCharPref(PREF_TELEMETRY_INFOURL), "tab");
// Remove the notification on which the user clicked
notification.parentNode.removeNotification(notification, true);
// Add a new notification to that tab, with no "Learn more" link
notifyBox = tabbrowser.getNotificationBox();
appendTelemetryNotification(telemetryPrompt, buttons, true);
}
#endif
// Set pref to indicate we've shown the notification.
Services.prefs.setIntPref(PREF_TELEMETRY_DISPLAYED, TELEMETRY_DISPLAY_REV);
var notification = appendTelemetryNotification(telemetryPrompt, buttons, hideCloseButton);
var link = appendLearnMoreLink(notification);
link.addEventListener('click', learnModeClickHandler, false);
},
#endif
_showPluginUpdatePage: function BG__showPluginUpdatePage() {
Services.prefs.setBoolPref(PREF_PLUGINS_NOTIFYUSER, false);

View File

@ -48,9 +48,11 @@ var gAdvancedPane = {
#ifdef MOZ_CRASHREPORTER
this.initSubmitCrashes();
#endif
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
this.initTelemetry();
#ifdef MOZ_SERVICES_HEALTHREPORT
this.initSubmitHealthReport();
#endif
this.updateActualCacheSize("disk");
this.updateActualCacheSize("offline");
@ -129,6 +131,36 @@ var gAdvancedPane = {
return checkbox.checked ? (this._storedSpellCheck == 2 ? 2 : 1) : 0;
},
/**
* When the user toggles the layers.acceleration.disabled pref,
* sync its new value to the gfx.direct2d.disabled pref too.
*/
updateHardwareAcceleration: function()
{
#ifdef XP_WIN
var fromPref = document.getElementById("layers.acceleration.disabled");
var toPref = document.getElementById("gfx.direct2d.disabled");
toPref.value = fromPref.value;
#endif
},
// DATA CHOICES TAB
/**
* Set up or hide the Learn More links for various data collection options
*/
_setupLearnMoreLink: function (pref, element) {
// set up the Learn More link with the correct URL
let url = Services.prefs.getCharPref(pref);
let el = document.getElementById(element);
if (url) {
el.setAttribute("href", url);
} else {
el.setAttribute("hidden", "true");
}
},
/**
*
*/
@ -142,6 +174,7 @@ var gAdvancedPane = {
} catch (e) {
checkbox.style.display = "none";
}
this._setupLearnMoreLink("toolkit.crashreporter.infoURL", "crashReporterLearnMore");
},
/**
@ -157,16 +190,19 @@ var gAdvancedPane = {
} catch (e) { }
},
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
/**
* When telemetry is opt-out, verify if the user explicitly rejected the
* telemetry prompt, and if so reflect his choice in the current preference
* value. This doesn't cover the case where the user refused telemetry in the
* prompt but later enabled it in preferences in builds before the fix for
* bug 737600.
*
* In all cases, set up the Learn More link sanely
*/
initTelemetry: function ()
{
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabledPreRelease";
let enabled = Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED);
let rejected = false;
@ -176,8 +212,11 @@ var gAdvancedPane = {
if (enabled && rejected) {
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, false);
}
},
#endif
#ifdef MOZ_TELEMETRY_REPORTING
this._setupLearnMoreLink("toolkit.telemetry.infoURL", "telemetryLearnMore");
#endif
},
/**
* When the user toggles telemetry, update the rejected value as well, so we
@ -191,19 +230,46 @@ var gAdvancedPane = {
displayed.value = @MOZ_TELEMETRY_DISPLAY_REV@;
},
#ifdef MOZ_SERVICES_HEALTHREPORT
/**
* When the user toggles the layers.acceleration.disabled pref,
* sync its new value to the gfx.direct2d.disabled pref too.
* Initialize the health report service reference and checkbox.
*/
updateHardwareAcceleration: function()
{
#ifdef XP_WIN
var fromPref = document.getElementById("layers.acceleration.disabled");
var toPref = document.getElementById("gfx.direct2d.disabled");
toPref.value = fromPref.value;
#endif
initSubmitHealthReport: function () {
this._setupLearnMoreLink("datareporting.healthreport.infoURL", "FHRLearnMore");
let policy = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject
.policy;
let checkbox = document.getElementById("submitHealthReportBox");
if (!policy) {
checkbox.setAttribute("disabled", "true");
return;
}
checkbox.checked = policy.healthReportUploadEnabled;
},
/**
* Update the health report policy acceptance with state from checkbox.
*/
updateSubmitHealthReport: function () {
let policy = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject
.policy;
if (!policy) {
return;
}
let checkbox = document.getElementById("submitHealthReportBox");
policy.healthReportUploadEnabled = checkbox.checked;
},
#endif
// NETWORK TAB
/*

View File

@ -50,6 +50,8 @@
type="bool"/>
#endif
<!-- Data Choices tab -->
#ifdef MOZ_TELEMETRY_REPORTING
<preference id="toolkit.telemetry.enabled"
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
@ -74,12 +76,12 @@
<!-- Network tab -->
<preference id="browser.cache.disk.capacity" name="browser.cache.disk.capacity" type="int"/>
<preference id="browser.offline-apps.notify" name="browser.offline-apps.notify" type="bool"/>
<preference id="browser.cache.disk.smart_size.enabled"
name="browser.cache.disk.smart_size.enabled"
inverted="true"
type="bool"/>
<!-- Update tab -->
#ifdef MOZ_UPDATER
<preference id="app.update.enabled" name="app.update.enabled" type="bool"/>
@ -115,7 +117,7 @@
name="security.disable_button.openDeviceManager"
type="bool"/>
</preferences>
#ifdef HAVE_SHELL_SERVICE
<stringbundle id="bundleShell" src="chrome://browser/locale/shellservice.properties"/>
<stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
@ -128,6 +130,9 @@
<tabs id="tabsElement">
<tab id="generalTab" label="&generalTab.label;" helpTopic="prefs-advanced-general"/>
#ifdef MOZ_DATA_REPORTING
<tab id="dataChoicesTab" label="&dataChoicesTab.label;" helpTopic="prefs-advanced-data-choices"/>
#endif
<tab id="networkTab" label="&networkTab.label;" helpTopic="prefs-advanced-network"/>
<tab id="updateTab" label="&updateTab.label;" helpTopic="prefs-advanced-update"/>
<tab id="encryptionTab" label="&encryptionTab.label;" helpTopic="prefs-advanced-encryption"/>
@ -180,11 +185,11 @@
preference="layout.spellcheckDefault"/>
</groupbox>
#ifdef HAVE_SHELL_SERVICE
<!-- System Defaults -->
<groupbox id="systemDefaultsGroup" orient="vertical">
<caption label="&systemDefaults.label;"/>
#ifdef HAVE_SHELL_SERVICE
<checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
label="&alwaysCheckDefault.label;" accesskey="&alwaysCheckDefault.accesskey;"
flex="1"/>
@ -197,20 +202,64 @@
<description>&isDefault.label;</description>
</deck>
</hbox>
#ifdef MOZ_CRASHREPORTER
<checkbox id="submitCrashesBox" flex="1"
oncommand="gAdvancedPane.updateSubmitCrashes();"
label="&submitCrashes.label;" accesskey="&submitCrashes.accesskey;"/>
#endif
#endif
#ifdef MOZ_TELEMETRY_REPORTING
<checkbox id="submitTelemetryBox" flex="1"
preference="toolkit.telemetry.enabled"
label="&submitTelemetry.label;" accesskey="&submitTelemetry.accesskey;"/>
#endif
</groupbox>
#endif
</tabpanel>
#ifdef MOZ_DATA_REPORTING
<!-- Data Choices -->
<tabpanel id="dataChoicesPanel" orient="vertical">
#ifdef MOZ_TELEMETRY_REPORTING
<groupbox>
<caption label="&telemetrySection.label;"/>
<description>&telemetryDesc.label;</description>
<hbox>
<checkbox id="submitTelemetryBox"
preference="toolkit.telemetry.enabled"
label="&enableTelemetry.label;"
accesskey="&enableTelemetry.accesskey;"/>
<spacer flex="1"/>
<label id="telemetryLearnMore"
class="text-link"
value="&telemetryLearnMore.label;"/>
</hbox>
</groupbox>
#endif
#ifdef MOZ_SERVICES_HEALTHREPORT
<groupbox>
<caption label="&FHRSection.label;"/>
<description>&FHRDesc.label;</description>
<hbox>
<checkbox id="submitHealthReportBox"
oncommand="gAdvancedPane.updateSubmitHealthReport();"
label="&enableFHR.label;"
accesskey="&enableFHR.accesskey;"/>
<spacer flex="1"/>
<label id="FHRLearnMore"
class="text-link"
value="&FHRLearnMore.label;"/>
</hbox>
</groupbox>
#endif
#ifdef MOZ_CRASHREPORTER
<groupbox>
<caption label="&crashReporterSection.label;"/>
<description>&crashReporterDesc.label;</description>
<hbox>
<checkbox id="submitCrashesBox"
oncommand="gAdvancedPane.updateSubmitCrashes();"
label="&enableCrashReporter.label;"
accesskey="&enableCrashReporter.accesskey;"/>
<spacer flex="1"/>
<label id="crashReporterLearnMore"
class="text-link"
value="&crashReporterLearnMore.label;"/>
</hbox>
</groupbox>
#endif
</tabpanel>
#endif
<!-- Network -->
<tabpanel id="networkPanel" orient="vertical">
@ -239,11 +288,11 @@
<checkbox preference="browser.cache.disk.smart_size.enabled"
id="allowSmartSize" flex="1"
onsyncfrompreference="return gAdvancedPane.readSmartSizeEnabled();"
label="&overrideSmartCacheSize.label;"
label="&overrideSmartCacheSize.label;"
accesskey="&overrideSmartCacheSize.accesskey;"/>
<hbox align="center" class="indent">
<label id="useCacheBefore" control="cacheSize"
accesskey="&limitCacheSizeBefore.accesskey;"
accesskey="&limitCacheSizeBefore.accesskey;"
value="&limitCacheSizeBefore.label;"/>
<textbox id="cacheSize" type="number" size="4" max="1024"
preference="browser.cache.disk.capacity"
@ -288,7 +337,7 @@
<button id="offlineAppsListRemove"
disabled="true"
label="&offlineAppsListRemove.label;"
accesskey="&offlineAppsListRemove.accesskey;"
accesskey="&offlineAppsListRemove.accesskey;"
oncommand="gAdvancedPane.removeOfflineApp();"/>
</vbox>
</hbox>
@ -389,9 +438,9 @@
<radiogroup id="certSelection" orient="horizontal" preftype="string"
preference="security.default_personal_cert"
aria-labelledby="CertGroupCaption CertSelectionDesc">
<radio label="&certs.auto;" accesskey="&certs.auto.accesskey;"
<radio label="&certs.auto;" accesskey="&certs.auto.accesskey;"
value="Select Automatically"/>
<radio label="&certs.ask;" accesskey="&certs.ask.accesskey;"
<radio label="&certs.ask;" accesskey="&certs.ask.accesskey;"
value="Ask Every Time"/>
</radiogroup>
@ -402,15 +451,15 @@
#endif
<hbox>
<button id="viewCertificatesButton"
label="&viewCerts.label;" accesskey="&viewCerts.accesskey;"
label="&viewCerts.label;" accesskey="&viewCerts.accesskey;"
oncommand="gAdvancedPane.showCertificates();"
preference="security.disable_button.openCertManager"/>
<button id="viewCRLButton"
label="&viewCRLs.label;" accesskey="&viewCRLs.accesskey;"
label="&viewCRLs.label;" accesskey="&viewCRLs.accesskey;"
oncommand="gAdvancedPane.showCRLs();"
preference="security.OCSP.disable_button.managecrl"/>
<button id="verificationButton"
label="&verify2.label;" accesskey="&verify2.accesskey;"
label="&verify2.label;" accesskey="&verify2.accesskey;"
oncommand="gAdvancedPane.showOCSP();"/>
#ifdef XP_MACOSX
</hbox>

View File

@ -39,8 +39,9 @@ var gAdvancedPane = {
#ifdef MOZ_CRASHREPORTER
this.initSubmitCrashes();
#endif
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
this.initTelemetry();
#ifdef MOZ_SERVICES_HEALTHREPORT
this.initSubmitHealthReport();
#endif
this.updateActualCacheSize("disk");
this.updateActualCacheSize("offline");
@ -117,11 +118,45 @@ var gAdvancedPane = {
return checkbox.checked ? (this._storedSpellCheck == 2 ? 2 : 1) : 0;
},
/**
* When the user toggles the layers.acceleration.disabled pref,
* sync its new value to the gfx.direct2d.disabled pref too.
*/
updateHardwareAcceleration: function()
{
#ifdef XP_WIN
var fromPref = document.getElementById("layers.acceleration.disabled");
var toPref = document.getElementById("gfx.direct2d.disabled");
toPref.value = fromPref.value;
#endif
},
// DATA CHOICES TAB
/**
* Set up or hide the Learn More links for various data collection options
*/
_setupLearnMoreLink: function (pref, element) {
// set up the Learn More link with the correct URL
let url = Services.prefs.getCharPref(pref);
let el = document.getElementById(element);
if (url) {
el.setAttribute("href", url);
} else {
el.setAttribute("hidden", "true");
}
},
/**
*
*/
initSubmitCrashes: function ()
{
this._setupLearnMoreLink("toolkit.crashreporter.infoURL",
"crashReporterLearnMore");
var checkbox = document.getElementById("submitCrashesBox");
try {
var cr = Components.classes["@mozilla.org/toolkit/crash-reporter;1"].
@ -145,16 +180,18 @@ var gAdvancedPane = {
} catch (e) { }
},
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
/**
* When telemetry is opt-out, verify if the user explicitly rejected the
* telemetry prompt, and if so reflect his choice in the current preference
* value. This doesn't cover the case where the user refused telemetry in the
* prompt but later enabled it in preferences in builds before the fix for
* bug 737600.
*
* In all cases, set up the Learn More link sanely.
*/
initTelemetry: function ()
{
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabledPreRelease";
let enabled = Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED);
let rejected = false;
@ -164,8 +201,12 @@ var gAdvancedPane = {
if (enabled && rejected) {
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, false);
}
},
#endif
#ifdef MOZ_TELEMETRY_REPORTING
this._setupLearnMoreLink("toolkit.telemetry.infoURL", "telemetryLearnMore");
#endif
},
/**
* When the user toggles telemetry, update the rejected value as well, so we
@ -179,19 +220,46 @@ var gAdvancedPane = {
displayed.value = @MOZ_TELEMETRY_DISPLAY_REV@;
},
#ifdef MOZ_SERVICES_HEALTHREPORT
/**
* When the user toggles the layers.acceleration.disabled pref,
* sync its new value to the gfx.direct2d.disabled pref too.
* Initialize the health report service reference and checkbox.
*/
updateHardwareAcceleration: function()
{
#ifdef XP_WIN
var fromPref = document.getElementById("layers.acceleration.disabled");
var toPref = document.getElementById("gfx.direct2d.disabled");
toPref.value = fromPref.value;
#endif
initSubmitHealthReport: function () {
this._setupLearnMoreLink("datareporting.healthreport.infoURL", "FHRLearnMore");
let policy = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject
.policy;
let checkbox = document.getElementById("submitHealthReportBox");
if (!policy) {
checkbox.setAttribute("disabled", "true");
return;
}
checkbox.checked = policy.healthReportUploadEnabled;
},
/**
* Update the health report policy acceptance with state from checkbox.
*/
updateSubmitHealthReport: function () {
let policy = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject
.policy;
if (!policy) {
return;
}
let checkbox = document.getElementById("submitHealthReportBox");
policy.healthReportUploadEnabled = checkbox.checked;
},
#endif
// NETWORK TAB
/*

View File

@ -152,6 +152,9 @@
<tabs id="tabsElement">
<tab id="generalTab" label="&generalTab.label;" helpTopic="prefs-advanced-general"/>
#ifdef MOZ_DATA_REPORTING
<tab id="dataChoicesTab" label="&dataChoicesTab.label;" helpTopic="prefs-advanced-data-choices"/>
#endif
<tab id="networkTab" label="&networkTab.label;" helpTopic="prefs-advanced-network"/>
<tab id="updateTab" label="&updateTab.label;" helpTopic="prefs-advanced-update"/>
<tab id="encryptionTab" label="&encryptionTab.label;" helpTopic="prefs-advanced-encryption"/>
@ -201,14 +204,14 @@
onsynctopreference="return gAdvancedPane.writeCheckSpelling();"
preference="layout.spellcheckDefault"/>
</groupbox>
#ifdef HAVE_SHELL_SERVICE
<!-- System Defaults -->
<groupbox id="systemDefaultsGroup" orient="vertical">
<caption label="&systemDefaults.label;"/>
#ifdef HAVE_SHELL_SERVICE
<checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
label="&alwaysCheckDefault.label;" accesskey="&alwaysCheckDefault.accesskey;"
flex="1"/>
label="&alwaysCheckDefault.label;" accesskey="&alwaysCheckDefault.accesskey;"
flex="1"/>
<hbox class="indent">
<deck id="setDefaultPane">
<button id="setDefaultButton"
@ -218,19 +221,63 @@
<description>&isDefault.label;</description>
</deck>
</hbox>
#ifdef MOZ_CRASHREPORTER
<checkbox id="submitCrashesBox" flex="1"
oncommand="gAdvancedPane.updateSubmitCrashes();"
label="&submitCrashes.label;" accesskey="&submitCrashes.accesskey;"/>
#endif
#endif
#ifdef MOZ_TELEMETRY_REPORTING
<checkbox id="submitTelemetryBox" flex="1"
preference="toolkit.telemetry.enabled"
label="&submitTelemetry.label;" accesskey="&submitTelemetry.accesskey;"/>
#endif
</groupbox>
#endif
</tabpanel>
#ifdef MOZ_DATA_REPORTING
<!-- Data Choices -->
<tabpanel id="dataChoicesPanel" orient="vertical">
#ifdef MOZ_TELEMETRY_REPORTING
<groupbox>
<caption label="&telemetrySection.label;"/>
<description>&telemetryDesc.label;</description>
<hbox>
<checkbox id="submitTelemetryBox"
preference="toolkit.telemetry.enabled"
label="&enableTelemetry.label;"
accesskey="&enableTelemetry.accesskey;"/>
<spacer flex="1"/>
<label id="telemetryLearnMore"
class="text-link"
value="&telemetryLearnMore.label;"/>
</hbox>
</groupbox>
#endif
#ifdef MOZ_SERVICES_HEALTHREPORT
<groupbox>
<caption label="&FHRSection.label;"/>
<description>&FHRDesc.label;</description>
<hbox>
<checkbox id="submitHealthReportBox"
oncommand="gAdvancedPane.updateSubmitHealthReport();"
label="&enableFHR.label;"
accesskey="&enableFHR.accesskey;"/>
<spacer flex="1"/>
<label id="FHRLearnMore"
class="text-link"
value="&FHRLearnMore.label;"/>
</hbox>
</groupbox>
#endif
#ifdef MOZ_CRASHREPORTER
<groupbox>
<caption label="&crashReporterSection.label;"/>
<description>&crashReporterDesc.label;</description>
<hbox>
<checkbox id="submitCrashesBox"
oncommand="gAdvancedPane.updateSubmitCrashes();"
label="&enableCrashReporter.label;"
accesskey="&enableCrashReporter.accesskey;"/>
<spacer flex="1"/>
<label id="crashReporterLearnMore"
class="text-link"
value="&crashReporterLearnMore.label;"/>
</hbox>
</groupbox>
#endif
</tabpanel>
#endif
<!-- Network -->
<tabpanel id="networkPanel" orient="vertical">

View File

@ -43,5 +43,9 @@ _BROWSER_FILES += \
$(NULL)
endif
ifdef MOZ_SERVICES_HEALTHREPORT
_BROWSER_FILES += browser_healthreport.js
endif
libs:: $(_BROWSER_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)

View File

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function test() {
waitForExplicitFinish();
resetPreferences();
registerCleanupFunction(resetPreferences);
open_preferences(runTest);
}
function runTest(win) {
let doc = win.document;
win.gotoPref("paneAdvanced");
let advancedPrefs = doc.getElementById("advancedPrefs");
let dataChoicesTab = doc.getElementById("dataChoicesTab");
advancedPrefs.selectedTab = dataChoicesTab;
let policy = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject
.policy;
ok(policy);
is(policy.dataSubmissionPolicyAccepted, false, "Data submission policy not accepted.");
is(policy.healthReportUploadEnabled, true, "Health Report upload enabled on app first run.");
let checkbox = doc.getElementById("submitHealthReportBox");
ok(checkbox);
is(checkbox.checked, true, "Health Report checkbox is checked on app first run.");
checkbox.checked = false;
checkbox.doCommand();
is(policy.healthReportUploadEnabled, false, "Unchecking checkbox opts out of FHR upload.");
checkbox.checked = true;
checkbox.doCommand();
is(policy.healthReportUploadEnabled, true, "Checking checkbox allows FHR upload.");
win.close();
finish();
}
function resetPreferences() {
Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
}

View File

@ -28,8 +28,8 @@ function is_element_hidden(aElement, aMsg) {
function open_preferences(aCallback) {
gBrowser.selectedTab = gBrowser.addTab("about:preferences");
let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
newTabBrowser.addEventListener("load", function () {
newTabBrowser.removeEventListener("load", arguments.callee, true);
newTabBrowser.addEventListener("Initialized", function () {
newTabBrowser.removeEventListener("Initialized", arguments.callee, true);
aCallback(gBrowser.contentWindow);
}, true);
}

View File

@ -43,5 +43,9 @@ _BROWSER_FILES += \
$(NULL)
endif
ifdef MOZ_SERVICES_HEALTHREPORT
_BROWSER_FILES += browser_healthreport.js
endif
libs:: $(_BROWSER_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)

View File

@ -0,0 +1,50 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function test() {
waitForExplicitFinish();
resetPreferences();
registerCleanupFunction(resetPreferences);
function observer(win, topic, data) {
Services.obs.removeObserver(observer, "advanced-pane-loaded");
runTest(win);
}
Services.obs.addObserver(observer, "advanced-pane-loaded", false);
openDialog("chrome://browser/content/preferences/preferences.xul", "Preferences",
"chrome,titlebar,toolbar,centerscreen,dialog=no", "paneAdvanced");
}
function runTest(win) {
let doc = win.document;
let policy = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject
.policy;
ok(policy);
is(policy.dataSubmissionPolicyAccepted, false, "Data submission policy not accepted.");
is(policy.healthReportUploadEnabled, true, "Health Report upload enabled on app first run.");
let checkbox = doc.getElementById("submitHealthReportBox");
ok(checkbox);
is(checkbox.checked, true, "Health Report checkbox is checked on app first run.");
checkbox.checked = false;
checkbox.doCommand();
is(policy.healthReportUploadEnabled, false, "Unchecking checkbox opts out of FHR upload.");
checkbox.checked = true;
checkbox.doCommand();
is(policy.healthReportUploadEnabled, true, "Checking checkbox allows FHR upload.");
win.close();
finish();
}
function resetPreferences() {
Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
}

View File

@ -28,6 +28,11 @@ MOZ_SAFE_BROWSING=1
MOZ_SERVICES_AITC=1
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_CRYPTO=1
if test "$OS_ARCH" != "Linux"; then
MOZ_SERVICES_HEALTHREPORT=1
fi
MOZ_SERVICES_METRICS=1
MOZ_SERVICES_SYNC=1
MOZ_APP_VERSION=$FIREFOX_VERSION

View File

@ -469,9 +469,9 @@
@BINPATH@/components/AitcComponents.manifest
@BINPATH@/components/Aitc.js
#endif
#ifdef MOZ_SERVICES_HEALTHREPORT
@BINPATH@/components/HealthReportComponents.manifest
@BINPATH@/components/HealthReportService.js
#ifdef MOZ_DATA_REPORTING
@BINPATH@/components/DataReporting.manifest
@BINPATH@/components/DataReportingService.js
#endif
#ifdef MOZ_SERVICES_SYNC
@BINPATH@/components/SyncComponents.manifest

View File

@ -899,6 +899,7 @@ xpicleanup@BIN_SUFFIX@
components/GPSDGeolocationProvider.js
components/interfaces.manifest
components/jsconsole-clhandler.js
components/MetricsCollectionService.js
components/NetworkGeolocationProvider.js
components/NotificationsComponents.manifest
components/nsBadCertHandler.js

View File

@ -0,0 +1,27 @@
<!-- metrics.locale-direction instead of the usual local.dir entity, so RTL can skip translating page. -->
<!ENTITY abouthealth.locale-direction "ltr">
<!-- LOCALIZATION NOTE (abouthealth.pagetitle): Firefox Health Report is a proper noun in en-US, please keep this in mind. -->
<!ENTITY abouthealth.pagetitle "&brandShortName; Health Report">
<!ENTITY abouthealth.header "&brandFullName; Health Report">
<!ENTITY abouthealth.intro.title "What is &brandShortName; Health Report?">
<!ENTITY abouthealth.intro-enabled "&brandFullName; collects some data about your computer and usage in order to provide you with a better browser experience.">
<!ENTITY abouthealth.intro-disabled "You are currently not submitting usage data to &vendorShortName;. You can help us make &brandShortName; better by clicking the &quot;&abouthealth.optin;&quot; button.">
<!ENTITY abouthealth.optin "Help make &brandShortName; better">
<!ENTITY abouthealth.optout "Turn Off Reporting">
<!ENTITY abouthealth.optout.confirmationPrompt.title "Stop data submission?">
<!ENTITY abouthealth.optout.confirmationPrompt.message "Are you sure you want to opt out and delete all your anonymous product data stored on &vendorShortName; servers?">
<!ENTITY abouthealth.show-raw-data "Show Details">
<!ENTITY abouthealth.hide-raw-data "Hide Details">
<!ENTITY abouthealth.show-report "Show &brandShortName; Report">
<!ENTITY abouthealth.details.description-start "This is the data &brandFullName; is submitting in order for &brandShortName; Health Report to function. You can ">
<!ENTITY abouthealth.details-link "learn more">
<!ENTITY abouthealth.details.description-end " about what we collect and submit.">
<!ENTITY abouthealth.no-data-available "There is no previous submission to display. Please check back later.">

View File

@ -22,6 +22,9 @@
<!ENTITY helpSafeMode.label "Restart with Add-ons Disabled…">
<!ENTITY helpSafeMode.accesskey "R">
<!ENTITY healthReport.label "&brandShortName; Health Report">
<!ENTITY healthReport.accesskey "e">
<!ENTITY helpTroubleshootingInfo.label "Troubleshooting Information">
<!ENTITY helpTroubleshootingInfo.accesskey "T">

View File

@ -351,16 +351,10 @@ syncPromoNotification.addons.description=You can access your add-ons on all your
# The final space separates this text from the Learn More link.
syncPromoNotification.addons-sync-disabled.description=You can use your %S account to synchronize add-ons across multiple devices.\u0020
# Telemetry prompt
# LOCALIZATION NOTE (telemetryOptInPrompt): %1$S will be replaced by
# brandFullName, and %2$S by the value of the toolkit.telemetry.server_owner
# preference.
telemetryOptInPrompt = Will you help improve %1$S by sending information about performance, hardware, usage, and customizations to %2$S?
telemetryLinkLabel = Learn More
telemetryYesButtonLabel2 = Yes, I want to help
telemetryYesButtonAccessKey = Y
telemetryNoButtonLabel = No
telemetryNoButtonAccessKey = N
# Mozilla data reporting notification (Telemetry, Firefox Health Report, etc)
dataReportingNotification.message = %1$S automatically sends some data to %2$S so that we can improve your experience.
dataReportingNotification.button.label = Choose What I Share
dataReportingNotification.button.accessKey = C
# Keyword.URL reset prompt
# LOCALIZATION NOTE (keywordPrompt.message):
@ -384,11 +378,6 @@ webapps.install.accesskey = I
webapps.requestInstall = Do you want to install "%1$S" from this site (%2$S)?
webapps.install.success = Application Installed
# Telemetry opt-out prompt for Aurora and Nightly
# LOCALIZATION NOTE (telemetryOptOutPrompt): %1$S and %3$S will be replaced by
# brandShortName, and %2$S by the value of the toolkit.telemetry.server_owner preference.
telemetryOptOutPrompt = %1$S sends information about performance, hardware, usage and customizations back to %2$S to help improve %3$S.
# LOCALIZATION NOTE (fullscreen.entered): displayed when we enter HTML5 fullscreen mode, %S is the domain name of the focused website (e.g. mozilla.com).
fullscreen.entered=%S is now fullscreen.
# LOCALIZATION NOTE (fullscreen.rememberDecision): displayed when we enter HTML5 fullscreen mode, %S is the domain name of the focused website (e.g. mozilla.com).

View File

@ -32,10 +32,26 @@
<!ENTITY setDefault.label "Make &brandShortName; the default browser">
<!ENTITY setDefault.accesskey "d">
<!ENTITY isDefault.label "&brandShortName; is currently your default browser">
<!ENTITY submitCrashes.label "Submit crash reports">
<!ENTITY submitCrashes.accesskey "S">
<!ENTITY submitTelemetry.label "Submit performance data">
<!ENTITY submitTelemetry.accesskey "P">
<!ENTITY dataChoicesTab.label "Data Choices">
<!ENTITY FHRSection.label "Firefox Health Report">
<!ENTITY FHRDesc.label "Helps you understand your browser performance and shares data with &vendorShortName; about your browser health">
<!ENTITY enableFHR.label "Enable Firefox Health Report">
<!ENTITY enableFHR.accesskey "F">
<!ENTITY FHRLearnMore.label "Learn More">
<!ENTITY telemetrySection.label "Telemetry">
<!ENTITY telemetryDesc.label "Shares performance, usage, hardware and customization data about your browser with &vendorShortName; to help us make &brandShortName; better">
<!ENTITY enableTelemetry.label "Enable Telemetry">
<!ENTITY enableTelemetry.accesskey "T">
<!ENTITY telemetryLearnMore.label "Learn More">
<!ENTITY crashReporterSection.label "Crash Reporter">
<!ENTITY crashReporterDesc.label "&brandShortName; submits crash reports to help &vendorShortName; make your browser more stable and secure">
<!ENTITY enableCrashReporter.label "Enable Crash Reporter">
<!ENTITY enableCrashReporter.accesskey "C">
<!ENTITY crashReporterLearnMore.label "Learn More">
<!ENTITY networkTab.label "Network">

View File

@ -11,6 +11,9 @@
locale/browser/aboutPrivateBrowsing.dtd (%chrome/browser/aboutPrivateBrowsing.dtd)
locale/browser/aboutRobots.dtd (%chrome/browser/aboutRobots.dtd)
locale/browser/aboutHome.dtd (%chrome/browser/aboutHome.dtd)
#ifdef MOZ_SERVICES_HEALTHREPORT
locale/browser/aboutHealthReport.dtd (%chrome/browser/aboutHealthReport.dtd)
#endif
locale/browser/aboutSessionRestore.dtd (%chrome/browser/aboutSessionRestore.dtd)
#ifdef MOZ_SERVICES_SYNC
locale/browser/syncProgress.dtd (%chrome/browser/syncProgress.dtd)

View File

@ -460,6 +460,9 @@ user_pref("security.turn_off_all_security_so_that_viruses_can_take_over_this_com
// Get network events.
user_pref("network.activity.blipIntervalMilliseconds", 250);
// Don't allow the Data Reporting service to prompt for policy acceptance.
user_pref("datareporting.policy.dataSubmissionPolicyBypassAcceptance", true);
""" % { "server" : self.webServer + ":" + str(self.httpPort) }
prefs.append(part)

View File

@ -8719,6 +8719,15 @@ if test "$MOZ_TELEMETRY_REPORTING"; then
fi
fi
dnl If we have any service that uploads data (and requires data submission
dnl policy alert), set MOZ_DATA_REPORTING.
dnl We need SUBST for build system and DEFINE for xul preprocessor.
if test -n "$MOZ_TELEMETRY_REPORTING" || test -n "$MOZ_SERVICES_HEALTHREPORT" || test -n "MOZ_CRASHREPORTER"; then
MOZ_DATA_REPORTING=1
AC_DEFINE(MOZ_DATA_REPORTING)
AC_SUBST(MOZ_DATA_REPORTING)
fi
dnl win32 options
AC_SUBST(MOZ_MAPINFO)
AC_SUBST(MOZ_BROWSE_INFO)

View File

@ -324,6 +324,7 @@ user_pref("reftest.remote", true);
user_pref("toolkit.telemetry.prompted", 999);
user_pref("toolkit.telemetry.notifiedOptOut", 999);
user_pref("reftest.uri", "%s");
user_pref("datareporting.policy.dataSubmissionPolicyBypassAcceptance", true);
""" % reftestlist)
#workaround for jsreftests.

View File

@ -43,6 +43,10 @@ GARBAGE += greprefs.js
# TODO bug 813259 external files should be defined near their location in the source tree.
grepref_files = $(topsrcdir)/netwerk/base/public/security-prefs.js $(srcdir)/init/all.js
ifdef MOZ_DATA_REPORTING
grepref_files += $(topsrcdir)/services/datareporting/datareporting-prefs.js
endif
ifdef MOZ_SERVICES_HEALTHREPORT
grepref_files += $(topsrcdir)/services/healthreport/healthreport-prefs.js
endif

View File

@ -9,9 +9,10 @@ VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
PARALLEL_DIRS += common
PARALLEL_DIRS += crypto
PARALLEL_DIRS += \
common \
crypto \
$(NULL)
ifdef MOZ_SERVICES_AITC
PARALLEL_DIRS += aitc
@ -21,6 +22,10 @@ ifdef MOZ_SERVICES_HEALTHREPORT
PARALLEL_DIRS += healthreport
endif
ifdef MOZ_DATA_REPORTING
PARALLEL_DIRS += datareporting
endif
ifdef MOZ_SERVICES_METRICS
PARALLEL_DIRS += metrics
endif

View File

@ -0,0 +1,16 @@
# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
# browser: {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
# suite (comm): {92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}
# metro browser: {99bceaaa-e3c6-48c1-b981-ef9b46b67d60}
# The Data Reporting Service drives collection and submission of metrics
# and other useful data to Mozilla. It drives the display of the data
# submission notification info bar and thus is required by Firefox Health
# Report and Telemetry.
component {41f6ae36-a79f-4613-9ac3-915e70f83789} DataReportingService.js
contract @mozilla.org/datareporting/service;1 {41f6ae36-a79f-4613-9ac3-915e70f83789}
category app-startup DataReportingService service,@mozilla.org/datareporting/service;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66}

View File

@ -6,11 +6,16 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/services/datareporting/policy.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://services-common/preferences.js");
const BRANCH = "healthreport.";
const ROOT_BRANCH = "datareporting.";
const POLICY_BRANCH = ROOT_BRANCH + "policy.";
const HEALTHREPORT_BRANCH = ROOT_BRANCH + "healthreport.";
const HEALTHREPORT_LOGGING_BRANCH = HEALTHREPORT_BRANCH + "logging.";
const DEFAULT_LOAD_DELAY_MSEC = 10 * 1000;
/**
@ -27,7 +32,7 @@ const DEFAULT_LOAD_DELAY_MSEC = 10 * 1000;
* let reporter = Cc["@mozilla.org/healthreport/service;1"]
* .getService(Ci.nsISupports)
* .wrappedJSObject
* .reporter;
* .healthReporter;
*
* if (reporter.haveRemoteData) {
* // ...
@ -45,36 +50,77 @@ const DEFAULT_LOAD_DELAY_MSEC = 10 * 1000;
* instance (it registers observers on initialization). See the notes on that
* type for more.
*/
this.HealthReportService = function HealthReportService() {
this.DataReportingService = function () {
this.wrappedJSObject = this;
this._prefs = new Preferences(BRANCH);
this._reporter = null;
this._os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
}
HealthReportService.prototype = {
classID: Components.ID("{e354c59b-b252-4040-b6dd-b71864e3e35c}"),
DataReportingService.prototype = Object.freeze({
classID: Components.ID("{41f6ae36-a79f-4613-9ac3-915e70f83789}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe: function observe(subject, topic, data) {
// If the background service is disabled, don't do anything.
if (!this._prefs.get("service.enabled", true)) {
//---------------------------------------------
// Start of policy listeners.
//---------------------------------------------
/**
* Called when policy requests data upload.
*/
onRequestDataUpload: function (request) {
if (!this.healthReporter) {
return;
}
let os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
this.healthReporter.requestDataUpload(request);
},
onNotifyDataPolicy: function (request) {
Observers.notify("datareporting:notify-data-policy:request", request);
},
onRequestRemoteDelete: function (request) {
if (!this.healthReporter) {
return;
}
this.healthReporter.deleteRemoteData(request);
},
//---------------------------------------------
// End of policy listeners.
//---------------------------------------------
observe: function observe(subject, topic, data) {
switch (topic) {
case "app-startup":
os.addObserver(this, "final-ui-startup", true);
this._os.addObserver(this, "profile-after-change", true);
break;
case "final-ui-startup":
os.removeObserver(this, "final-ui-startup");
case "profile-after-change":
this._os.removeObserver(this, "profile-after-change");
this._os.addObserver(this, "sessionstore-windows-restored", true);
// We can't interact with prefs until after the profile is present.
let policyPrefs = new Preferences(POLICY_BRANCH);
this._prefs = new Preferences(HEALTHREPORT_BRANCH);
this.policy = new DataReportingPolicy(policyPrefs, this._prefs, this);
break;
case "sessionstore-windows-restored":
this._os.removeObserver(this, "sessionstore-windows-restored");
this._os.addObserver(this, "quit-application", false);
this.policy.startPolling();
// Don't initialize Firefox Health Reporter collection and submission
// service unless it is enabled.
if (!this._prefs.get("service.enabled", true)) {
return;
}
let delayInterval = this._prefs.get("service.loadDelayMsec") ||
DEFAULT_LOAD_DELAY_MSEC;
@ -86,12 +132,17 @@ HealthReportService.prototype = {
notify: function notify() {
// Side effect: instantiates the reporter instance if not already
// accessed.
let reporter = this.reporter;
let reporter = this.healthReporter;
delete this.timer;
}.bind(this),
}, delayInterval, this.timer.TYPE_ONE_SHOT);
break;
case "quit-application":
this._os.removeObserver(this, "quit-application");
this.policy.stopPolling();
break;
}
},
@ -102,17 +153,28 @@ HealthReportService.prototype = {
*
* The obtained instance may not be fully initialized.
*/
get reporter() {
get healthReporter() {
if (!this._prefs.get("service.enabled", true)) {
return null;
}
if (this._reporter) {
return this._reporter;
if ("_healthReporter" in this) {
return this._healthReporter;
}
try {
this._loadHealthReporter();
} catch (ex) {
this._healthReporter = null;
}
return this._healthReporter;
},
_loadHealthReporter: function () {
let ns = {};
// Lazy import so application startup isn't adversely affected.
Cu.import("resource://gre/modules/Task.jsm", ns);
Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm", ns);
Cu.import("resource://services-common/log4moz.js", ns);
@ -120,15 +182,16 @@ HealthReportService.prototype = {
// How many times will we rewrite this code before rolling it up into a
// generic module? See also bug 451283.
const LOGGERS = [
"Services.DataReporting",
"Services.HealthReport",
"Services.Metrics",
"Services.BagheeraClient",
"Sqlite.Connection.healthreport",
];
let prefs = new Preferences(BRANCH + "logging.");
if (prefs.get("consoleEnabled", true)) {
let level = prefs.get("consoleLevel", "Warn");
let loggingPrefs = new Preferences(HEALTHREPORT_LOGGING_BRANCH);
if (loggingPrefs.get("consoleEnabled", true)) {
let level = loggingPrefs.get("consoleLevel", "Warn");
let appender = new ns.Log4Moz.ConsoleAppender();
appender.level = ns.Log4Moz.Level[level] || ns.Log4Moz.Level.Warn;
@ -139,13 +202,10 @@ HealthReportService.prototype = {
}
// The reporter initializes in the background.
this._reporter = new ns.HealthReporter(BRANCH);
return this._reporter;
this._healthReporter = new ns.HealthReporter(HEALTHREPORT_BRANCH,
this.policy);
},
};
});
Object.freeze(HealthReportService.prototype);
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HealthReportService]);
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataReportingService]);

View File

@ -0,0 +1,26 @@
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
TEST_DIRS += tests
MODULES_FILES := policy.jsm
MODULES_DEST = $(FINAL_TARGET)/modules/services/datareporting
INSTALL_TARGETS += MODULES
TESTING_JS_MODULES := $(addprefix modules-testing/,mocks.jsm)
TESTING_JS_MODULE_DIR := services/datareporting
EXTRA_COMPONENTS := \
DataReporting.manifest \
DataReportingService.js \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,12 @@
/* 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/. */
pref("datareporting.policy.dataSubmissionEnabled", true);
pref("datareporting.policy.dataSubmissionPolicyAccepted", false);
pref("datareporting.policy.dataSubmissionPolicyBypassAcceptance", false);
pref("datareporting.policy.dataSubmissionPolicyNotifiedTime", "0");
pref("datareporting.policy.dataSubmissionPolicyResponseType", "");
pref("datareporting.policy.dataSubmissionPolicyResponseTime", "0");
pref("datareporting.policy.firstRunTime", "0");

View File

@ -12,7 +12,7 @@ Cu.import("resource://services-common/log4moz.js");
this.MockPolicyListener = function MockPolicyListener() {
this._log = Log4Moz.repository.getLogger("HealthReport.Testing.MockPolicyListener");
this._log = Log4Moz.repository.getLogger("Services.DataReporting.Testing.MockPolicyListener");
this._log.level = Log4Moz.Level["Debug"];
this.requestDataUploadCount = 0;
@ -44,3 +44,4 @@ MockPolicyListener.prototype = {
this.lastNotifyRequest = request;
},
};

View File

@ -2,11 +2,22 @@
* 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/. */
/**
* This file is in transition. It was originally conceived to fulfill the
* needs of only Firefox Health Report. It is slowly being morphed into
* fulfilling the needs of all data reporting facilities in Gecko applications.
* As a result, some things feel a bit weird.
*
* DataReportingPolicy is both a driver for data reporting notification
* (a true policy) and the driver for FHR data submission. The latter should
* eventually be split into its own type and module.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"DataSubmissionRequest", // For test use only.
"HealthReportPolicy",
"DataReportingPolicy",
];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
@ -41,7 +52,7 @@ const OLDEST_ALLOWED_YEAR = 2012;
* signaling explicit user acceptance or rejection of the policy. They do this
* by calling `onUserAccept` or `onUserReject`, respectively. These functions
* are essentially proxies to
* HealthReportPolicy.{recordUserAcceptance,recordUserRejection}.
* DataReportingPolicy.{recordUserAcceptance,recordUserRejection}.
*
* If the user never explicitly accepts or rejects the policy, it will be
* implicitly accepted after a specified duration of time. The notice is
@ -53,7 +64,7 @@ const OLDEST_ALLOWED_YEAR = 2012;
* the exception of the on* functions.
*
* @param policy
* (HealthReportPolicy) The policy instance this request came from.
* (DataReportingPolicy) The policy instance this request came from.
* @param promise
* (deferred) The promise that will be fulfilled when display occurs.
*/
@ -235,15 +246,17 @@ Object.freeze(DataSubmissionRequest.prototype);
* can have different mechanisms by which they notify the user of data
* submission practices.
*
* @param prefs
* @param policyPrefs
* (Preferences) Handle on preferences branch on which state will be
* queried and stored.
* @param healthReportPrefs
* (Preferences) Handle on preferences branch hold Health Report state.
* @param listener
* (object) Object with callbacks that will be invoked at certain key
* events.
*/
this.HealthReportPolicy = function HealthReportPolicy(prefs, listener) {
this._log = Log4Moz.repository.getLogger("Services.HealthReport.Policy");
this.DataReportingPolicy = function (prefs, healthReportPrefs, listener) {
this._log = Log4Moz.repository.getLogger("Services.DataReporting.Policy");
this._log.level = Log4Moz.Level["Debug"];
for (let handler of this.REQUIRED_LISTENERS) {
@ -254,6 +267,7 @@ this.HealthReportPolicy = function HealthReportPolicy(prefs, listener) {
}
this._prefs = prefs;
this._healthReportPrefs = healthReportPrefs;
this._listener = listener;
// If we've never run before, record the current time.
@ -276,7 +290,7 @@ this.HealthReportPolicy = function HealthReportPolicy(prefs, listener) {
this._inProgressSubmissionRequest = null;
}
HealthReportPolicy.prototype = {
DataReportingPolicy.prototype = Object.freeze({
/**
* How long after first run we should notify about data submission.
*/
@ -444,22 +458,6 @@ HealthReportPolicy.prototype = {
this._prefs.set("dataSubmissionEnabled", !!value);
},
/**
* Whether upload of data is allowed.
*
* This is a kill switch for upload. It is meant to reflect a system or
* deployment policy decision. User intent should be reflected in the
* "dataSubmissionPolicy" prefs.
*/
get dataUploadEnabled() {
// Default is true because we are opt-out.
return this._prefs.get("dataUploadEnabled", true);
},
set dataUploadEnabled(value) {
this._prefs.set("dataUploadEnabled", !!value);
},
/**
* Whether the user has accepted that data submission can occur.
*
@ -474,13 +472,17 @@ HealthReportPolicy.prototype = {
this._prefs.set("dataSubmissionPolicyAccepted", !!value);
},
set dataSubmissionPolicyAcceptedVersion(value) {
this._prefs.set("dataSubmissionPolicyAcceptedVersion", value);
},
/**
* The state of user notification of the data policy.
*
* This must be HealthReportPolicy.STATE_NOTIFY_COMPLETE before data
* This must be DataReportingPolicy.STATE_NOTIFY_COMPLETE before data
* submission can occur.
*
* @return HealthReportPolicy.STATE_NOTIFY_* constant.
* @return DataReportingPolicy.STATE_NOTIFY_* constant.
*/
get notifyState() {
if (this.dataSubmissionPolicyResponseDate.getTime()) {
@ -505,13 +507,14 @@ HealthReportPolicy.prototype = {
* on scheduling or run-time behavior.
*/
get lastDataSubmissionRequestedDate() {
return CommonUtils.getDatePref(this._prefs,
return CommonUtils.getDatePref(this._healthReportPrefs,
"lastDataSubmissionRequestedTime", 0,
this._log, OLDEST_ALLOWED_YEAR);
},
set lastDataSubmissionRequestedDate(value) {
CommonUtils.setDatePref(this._prefs, "lastDataSubmissionRequestedTime",
CommonUtils.setDatePref(this._healthReportPrefs,
"lastDataSubmissionRequestedTime",
value, OLDEST_ALLOWED_YEAR);
},
@ -522,13 +525,14 @@ HealthReportPolicy.prototype = {
* actual scheduling.
*/
get lastDataSubmissionSuccessfulDate() {
return CommonUtils.getDatePref(this._prefs,
return CommonUtils.getDatePref(this._healthReportPrefs,
"lastDataSubmissionSuccessfulTime", 0,
this._log, OLDEST_ALLOWED_YEAR);
},
set lastDataSubmissionSuccessfulDate(value) {
CommonUtils.setDatePref(this._prefs, "lastDataSubmissionSuccessfulTime",
CommonUtils.setDatePref(this._healthReportPrefs,
"lastDataSubmissionSuccessfulTime",
value, OLDEST_ALLOWED_YEAR);
},
@ -539,13 +543,15 @@ HealthReportPolicy.prototype = {
* scheduling.
*/
get lastDataSubmissionFailureDate() {
return CommonUtils.getDatePref(this._prefs, "lastDataSubmissionFailureTime",
return CommonUtils.getDatePref(this._healthReportPrefs,
"lastDataSubmissionFailureTime",
0, this._log, OLDEST_ALLOWED_YEAR);
},
set lastDataSubmissionFailureDate(value) {
CommonUtils.setDatePref(this._prefs, "lastDataSubmissionFailureTime", value,
OLDEST_ALLOWED_YEAR);
CommonUtils.setDatePref(this._healthReportPrefs,
"lastDataSubmissionFailureTime",
value, OLDEST_ALLOWED_YEAR);
},
/**
@ -555,12 +561,14 @@ HealthReportPolicy.prototype = {
* mutate this value.
*/
get nextDataSubmissionDate() {
return CommonUtils.getDatePref(this._prefs, "nextDataSubmissionTime", 0,
return CommonUtils.getDatePref(this._healthReportPrefs,
"nextDataSubmissionTime", 0,
this._log, OLDEST_ALLOWED_YEAR);
},
set nextDataSubmissionDate(value) {
CommonUtils.setDatePref(this._prefs, "nextDataSubmissionTime", value,
CommonUtils.setDatePref(this._healthReportPrefs,
"nextDataSubmissionTime", value,
OLDEST_ALLOWED_YEAR);
},
@ -570,7 +578,7 @@ HealthReportPolicy.prototype = {
* This is used to drive backoff and scheduling.
*/
get currentDaySubmissionFailureCount() {
let v = this._prefs.get("currentDaySubmissionFailureCount", 0);
let v = this._healthReportPrefs.get("currentDaySubmissionFailureCount", 0);
if (!Number.isInteger(v)) {
v = 0;
@ -584,7 +592,7 @@ HealthReportPolicy.prototype = {
throw new Error("Value must be integer: " + value);
}
this._prefs.set("currentDaySubmissionFailureCount", value);
this._healthReportPrefs.set("currentDaySubmissionFailureCount", value);
},
/**
@ -595,11 +603,22 @@ HealthReportPolicy.prototype = {
* the remote deletion is fulfilled.
*/
get pendingDeleteRemoteData() {
return !!this._prefs.get("pendingDeleteRemoteData", false);
return !!this._healthReportPrefs.get("pendingDeleteRemoteData", false);
},
set pendingDeleteRemoteData(value) {
this._prefs.set("pendingDeleteRemoteData", !!value);
this._healthReportPrefs.set("pendingDeleteRemoteData", !!value);
},
/**
* Whether upload of Firefox Health Report data is enabled.
*/
get healthReportUploadEnabled() {
return !!this._healthReportPrefs.get("uploadEnabled", true);
},
set healthReportUploadEnabled(value) {
this._healthReportPrefs.set("uploadEnabled", !!value);
},
/**
@ -620,6 +639,7 @@ HealthReportPolicy.prototype = {
this.dataSubmissionPolicyResponseDate = this.now();
this.dataSubmissionPolicyResponseType = "accepted-" + reason;
this.dataSubmissionPolicyAccepted = true;
this.dataSubmissionPolicyAcceptedVersion = 1;
},
/**
@ -753,7 +773,7 @@ HealthReportPolicy.prototype = {
return this._dispatchSubmissionRequest("onRequestRemoteDelete", true);
}
if (!this.dataUploadEnabled) {
if (!this.healthReportUploadEnabled) {
this._log.debug("Data upload is disabled. Doing nothing.");
return;
}
@ -1003,7 +1023,5 @@ HealthReportPolicy.prototype = {
_futureDate: function _futureDate(offset) {
return new Date(this.now().getTime() + offset);
},
};
Object.freeze(HealthReportPolicy.prototype);
});

View File

@ -0,0 +1,16 @@
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
XPCSHELL_TESTS = xpcshell
include $(topsrcdir)/config/rules.mk

View File

@ -6,15 +6,19 @@
const {utils: Cu} = Components;
Cu.import("resource://services-common/preferences.js");
Cu.import("resource://gre/modules/services/healthreport/policy.jsm");
Cu.import("resource://testing-common/services/healthreport/mocks.jsm");
Cu.import("resource://gre/modules/services/datareporting/policy.jsm");
Cu.import("resource://testing-common/services/datareporting/mocks.jsm");
function getPolicy(name) {
let prefs = new Preferences(name);
let listener = new MockPolicyListener();
let branch = "testing.datareporting." + name;
let policyPrefs = new Preferences(branch + ".policy.");
let healthReportPrefs = new Preferences(branch + ".healthreport.");
return [new HealthReportPolicy(prefs, listener), prefs, listener];
let listener = new MockPolicyListener();
let policy = new DataReportingPolicy(policyPrefs, healthReportPrefs, listener);
return [policy, policyPrefs, healthReportPrefs, listener];
}
function defineNow(policy, now) {
@ -32,14 +36,15 @@ function run_test() {
}
add_test(function test_constructor() {
let prefs = new Preferences("foo.bar");
let policyPrefs = new Preferences("foo.bar.policy.");
let hrPrefs = new Preferences("foo.bar.healthreport.");
let listener = {
onRequestDataUpload: function() {},
onRequestRemoteDelete: function() {},
onNotifyDataPolicy: function() {},
};
let policy = new HealthReportPolicy(prefs, listener);
let policy = new DataReportingPolicy(policyPrefs, hrPrefs, listener);
do_check_true(Date.now() - policy.firstRunDate.getTime() < 1000);
let tomorrow = Date.now() + 24 * 60 * 60 * 1000;
@ -51,68 +56,75 @@ add_test(function test_constructor() {
});
add_test(function test_prefs() {
let [policy, prefs, listener] = getPolicy("prefs");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("prefs");
let now = new Date();
let nowT = now.getTime();
policy.firstRunDate = now;
do_check_eq(prefs.get("firstRunTime"), nowT);
do_check_eq(policyPrefs.get("firstRunTime"), nowT);
do_check_eq(policy.firstRunDate.getTime(), nowT);
policy.dataSubmissionPolicyNotifiedDate= now;
do_check_eq(prefs.get("dataSubmissionPolicyNotifiedTime"), nowT);
do_check_eq(policyPrefs.get("dataSubmissionPolicyNotifiedTime"), nowT);
do_check_eq(policy.dataSubmissionPolicyNotifiedDate.getTime(), nowT);
policy.dataSubmissionPolicyResponseDate = now;
do_check_eq(prefs.get("dataSubmissionPolicyResponseTime"), nowT);
do_check_eq(policyPrefs.get("dataSubmissionPolicyResponseTime"), nowT);
do_check_eq(policy.dataSubmissionPolicyResponseDate.getTime(), nowT);
policy.dataSubmissionPolicyResponseType = "type-1";
do_check_eq(prefs.get("dataSubmissionPolicyResponseType"), "type-1");
do_check_eq(policyPrefs.get("dataSubmissionPolicyResponseType"), "type-1");
do_check_eq(policy.dataSubmissionPolicyResponseType, "type-1");
policy.dataSubmissionEnabled = false;
do_check_false(prefs.get("dataSubmissionEnabled", true));
do_check_false(policyPrefs.get("dataSubmissionEnabled", true));
do_check_false(policy.dataSubmissionEnabled);
policy.dataSubmissionPolicyAccepted = false;
do_check_false(prefs.get("dataSubmissionPolicyAccepted", true));
do_check_false(policyPrefs.get("dataSubmissionPolicyAccepted", true));
do_check_false(policy.dataSubmissionPolicyAccepted);
policy.dataSubmissionPolicyAcceptedVersion = 2;
do_check_eq(policyPrefs.get("dataSubmissionPolicyAcceptedVersion"), 2);
do_check_false(policy.dataSubmissionPolicyBypassAcceptance);
prefs.set("dataSubmissionPolicyBypassAcceptance", true);
policyPrefs.set("dataSubmissionPolicyBypassAcceptance", true);
do_check_true(policy.dataSubmissionPolicyBypassAcceptance);
policy.lastDataSubmissionRequestedDate = now;
do_check_eq(prefs.get("lastDataSubmissionRequestedTime"), nowT);
do_check_eq(hrPrefs.get("lastDataSubmissionRequestedTime"), nowT);
do_check_eq(policy.lastDataSubmissionRequestedDate.getTime(), nowT);
policy.lastDataSubmissionSuccessfulDate = now;
do_check_eq(prefs.get("lastDataSubmissionSuccessfulTime"), nowT);
do_check_eq(hrPrefs.get("lastDataSubmissionSuccessfulTime"), nowT);
do_check_eq(policy.lastDataSubmissionSuccessfulDate.getTime(), nowT);
policy.lastDataSubmissionFailureDate = now;
do_check_eq(prefs.get("lastDataSubmissionFailureTime"), nowT);
do_check_eq(hrPrefs.get("lastDataSubmissionFailureTime"), nowT);
do_check_eq(policy.lastDataSubmissionFailureDate.getTime(), nowT);
policy.nextDataSubmissionDate = now;
do_check_eq(prefs.get("nextDataSubmissionTime"), nowT);
do_check_eq(hrPrefs.get("nextDataSubmissionTime"), nowT);
do_check_eq(policy.nextDataSubmissionDate.getTime(), nowT);
policy.currentDaySubmissionFailureCount = 2;
do_check_eq(prefs.get("currentDaySubmissionFailureCount", 0), 2);
do_check_eq(hrPrefs.get("currentDaySubmissionFailureCount", 0), 2);
do_check_eq(policy.currentDaySubmissionFailureCount, 2);
policy.pendingDeleteRemoteData = true;
do_check_true(prefs.get("pendingDeleteRemoteData"));
do_check_true(hrPrefs.get("pendingDeleteRemoteData"));
do_check_true(policy.pendingDeleteRemoteData);
policy.healthReportUploadEnabled = false;
do_check_false(hrPrefs.get("uploadEnabled"));
do_check_false(policy.healthReportUploadEnabled);
run_next_test();
});
add_test(function test_notify_state_prefs() {
let [policy, prefs, listener] = getPolicy("notify_state_prefs");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notify_state_prefs");
do_check_eq(policy.notifyState, policy.STATE_NOTIFY_UNNOTIFIED);
@ -127,7 +139,7 @@ add_test(function test_notify_state_prefs() {
});
add_test(function test_initial_submission_notification() {
let [policy, prefs, listener] = getPolicy("initial_submission_notification");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("initial_submission_notification");
do_check_eq(listener.notifyUserCount, 0);
@ -159,9 +171,9 @@ add_test(function test_initial_submission_notification() {
});
add_test(function test_bypass_acceptance() {
let [policy, prefs, listener] = getPolicy("bypass_acceptance");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("bypass_acceptance");
prefs.set("dataSubmissionPolicyBypassAcceptance", true);
policyPrefs.set("dataSubmissionPolicyBypassAcceptance", true);
do_check_false(policy.dataSubmissionPolicyAccepted);
do_check_true(policy.dataSubmissionPolicyBypassAcceptance);
defineNow(policy, new Date(policy.nextDataSubmissionDate.getTime()));
@ -172,7 +184,7 @@ add_test(function test_bypass_acceptance() {
});
add_test(function test_notification_implicit_acceptance() {
let [policy, prefs, listener] = getPolicy("notification_implicit_acceptance");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notification_implicit_acceptance");
let now = new Date(policy.nextDataSubmissionDate.getTime() -
policy.SUBMISSION_NOTIFY_INTERVAL_MSEC + 1);
@ -202,7 +214,7 @@ add_test(function test_notification_implicit_acceptance() {
add_test(function test_notification_rejected() {
// User notification failed. We should not record it as being presented.
let [policy, prefs, listener] = getPolicy("notification_failed");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notification_failed");
let now = new Date(policy.nextDataSubmissionDate.getTime() -
policy.SUBMISSION_NOTIFY_INTERVAL_MSEC + 1);
@ -218,7 +230,7 @@ add_test(function test_notification_rejected() {
});
add_test(function test_notification_accepted() {
let [policy, prefs, listener] = getPolicy("notification_accepted");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notification_accepted");
let now = new Date(policy.nextDataSubmissionDate.getTime() -
policy.SUBMISSION_NOTIFY_INTERVAL_MSEC + 1);
@ -238,7 +250,7 @@ add_test(function test_notification_accepted() {
});
add_test(function test_notification_rejected() {
let [policy, prefs, listener] = getPolicy("notification_rejected");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notification_rejected");
let now = new Date(policy.nextDataSubmissionDate.getTime() -
policy.SUBMISSION_NOTIFY_INTERVAL_MSEC + 1);
@ -261,11 +273,12 @@ add_test(function test_notification_rejected() {
});
add_test(function test_submission_kill_switch() {
let [policy, prefs, listener] = getPolicy("submission_kill_switch");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_kill_switch");
policy.firstRunDate = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
policy.nextDataSubmissionDate = new Date(Date.now() - 24 * 60 * 60 * 1000);
policy.recordUserAcceptance("accept-old-ack");
do_check_eq(policyPrefs.get("dataSubmissionPolicyAcceptedVersion"), 1);
policy.checkStateAndTrigger();
do_check_eq(listener.requestDataUploadCount, 1);
@ -279,16 +292,16 @@ add_test(function test_submission_kill_switch() {
});
add_test(function test_upload_kill_switch() {
let [policy, prefs, listener] = getPolicy("upload_kill_switch");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("upload_kill_switch");
defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000));
policy.recordUserAcceptance();
defineNow(policy, policy.nextDataSubmissionDate);
policy.dataUploadEnabled = false;
policy.healthReportUploadEnabled = false;
policy.checkStateAndTrigger();
do_check_eq(listener.requestDataUploadCount, 0);
policy.dataUploadEnabled = true;
policy.healthReportUploadEnabled = true;
policy.checkStateAndTrigger();
do_check_eq(listener.requestDataUploadCount, 1);
@ -296,7 +309,7 @@ add_test(function test_upload_kill_switch() {
});
add_test(function test_data_submission_no_data() {
let [policy, prefs, listener] = getPolicy("data_submission_no_data");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("data_submission_no_data");
policy.dataSubmissionPolicyResponseDate = new Date(Date.now() - 24 * 60 * 60 * 1000);
policy.dataSubmissionPolicyAccepted = true;
@ -316,7 +329,7 @@ add_test(function test_data_submission_no_data() {
});
add_test(function test_data_submission_submit_failure_hard() {
let [policy, prefs, listener] = getPolicy("data_submission_submit_failure_hard");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("data_submission_submit_failure_hard");
policy.dataSubmissionPolicyResponseDate = new Date(Date.now() - 24 * 60 * 60 * 1000);
policy.dataSubmissionPolicyAccepted = true;
@ -341,7 +354,7 @@ add_test(function test_data_submission_submit_failure_hard() {
});
add_test(function test_data_submission_submit_try_again() {
let [policy, prefs, listener] = getPolicy("data_submission_failure_soft");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("data_submission_failure_soft");
policy.recordUserAcceptance();
let nextDataSubmissionDate = policy.nextDataSubmissionDate;
@ -356,7 +369,7 @@ add_test(function test_data_submission_submit_try_again() {
});
add_test(function test_submission_daily_scheduling() {
let [policy, prefs, listener] = getPolicy("submission_daily_scheduling");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_daily_scheduling");
policy.dataSubmissionPolicyResponseDate = new Date(Date.now() - 24 * 60 * 60 * 1000);
policy.dataSubmissionPolicyAccepted = true;
@ -396,7 +409,7 @@ add_test(function test_submission_daily_scheduling() {
});
add_test(function test_submission_far_future_scheduling() {
let [policy, prefs, listener] = getPolicy("submission_far_future_scheduling");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_far_future_scheduling");
let now = new Date(Date.now() - 24 * 60 * 60 * 1000);
defineNow(policy, now);
@ -420,7 +433,7 @@ add_test(function test_submission_far_future_scheduling() {
});
add_test(function test_submission_backoff() {
let [policy, prefs, listener] = getPolicy("submission_backoff");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_backoff");
do_check_eq(policy.FAILURE_BACKOFF_INTERVALS.length, 2);
@ -483,7 +496,7 @@ add_test(function test_submission_backoff() {
// Ensure that only one submission request can be active at a time.
add_test(function test_submission_expiring() {
let [policy, prefs, listener] = getPolicy("submission_expiring");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_expiring");
policy.dataSubmissionPolicyResponseDate = new Date(Date.now() - 24 * 60 * 60 * 1000);
policy.dataSubmissionPolicyAccepted = true;
@ -506,7 +519,7 @@ add_test(function test_submission_expiring() {
});
add_test(function test_delete_remote_data() {
let [policy, prefs, listener] = getPolicy("delete_remote_data");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data");
do_check_false(policy.pendingDeleteRemoteData);
let nextSubmissionDate = policy.nextDataSubmissionDate;
@ -532,7 +545,7 @@ add_test(function test_delete_remote_data() {
// Ensure that deletion requests take priority over regular data submission.
add_test(function test_delete_remote_data_priority() {
let [policy, prefs, listener] = getPolicy("delete_remote_data_priority");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data_priority");
let now = new Date();
defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000));
@ -553,7 +566,7 @@ add_test(function test_delete_remote_data_priority() {
});
add_test(function test_delete_remote_data_backoff() {
let [policy, prefs, listener] = getPolicy("delete_remote_data_backoff");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data_backoff");
let now = new Date();
defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000));
@ -586,7 +599,7 @@ add_test(function test_delete_remote_data_backoff() {
// If we request delete while an upload is in progress, delete should be
// scheduled immediately after upload.
add_test(function test_delete_remote_data_in_progress_upload() {
let [policy, prefs, listener] = getPolicy("delete_remote_data_in_progress_upload");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data_in_progress_upload");
let now = new Date();
defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000));
@ -616,7 +629,7 @@ add_test(function test_delete_remote_data_in_progress_upload() {
});
add_test(function test_polling() {
let [policy, prefs, listener] = getPolicy("polling");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("polling");
// Ensure checkStateAndTrigger is called at a regular interval.
let now = new Date();
@ -632,7 +645,7 @@ add_test(function test_polling() {
do_check_true(now2.getTime() - now.getTime() >= 500);
now = now2;
HealthReportPolicy.prototype.checkStateAndTrigger.call(policy);
DataReportingPolicy.prototype.checkStateAndTrigger.call(policy);
if (count >= 2) {
policy.stopPolling();
@ -652,7 +665,7 @@ add_test(function test_polling() {
// This is probably covered by other tests. But, it's best to have explicit
// coverage from a higher-level.
add_test(function test_polling_implicit_acceptance() {
let [policy, prefs, listener] = getPolicy("polling_implicit_acceptance");
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("polling_implicit_acceptance");
// Redefine intervals with shorter, test-friendly values.
Object.defineProperty(policy, "POLL_INTERVAL_MSEC", {
@ -670,7 +683,7 @@ add_test(function test_polling_implicit_acceptance() {
print("checkStateAndTrigger count: " + count);
// Account for some slack.
HealthReportPolicy.prototype.checkStateAndTrigger.call(policy);
DataReportingPolicy.prototype.checkStateAndTrigger.call(policy);
// What should happen on different invocations:
//

View File

@ -0,0 +1,5 @@
[DEFAULT]
head =
tail =
[test_policy.js]

View File

@ -1,14 +1,4 @@
# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
# browser: {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
# suite (comm): {92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}
# metro browser: {99bceaaa-e3c6-48c1-b981-ef9b46b67d60}
component {e354c59b-b252-4040-b6dd-b71864e3e35c} HealthReportService.js
contract @mozilla.org/healthreport/service;1 {e354c59b-b252-4040-b6dd-b71864e3e35c}
category app-startup HealthReportService service,@mozilla.org/healthreport/service;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66}
# Register Firefox Health Report providers.
category healthreport-js-provider AddonsProvider resource://gre/modules/services/healthreport/providers.jsm
category healthreport-js-provider AppInfoProvider resource://gre/modules/services/healthreport/providers.jsm
category healthreport-js-provider CrashesProvider resource://gre/modules/services/healthreport/providers.jsm

View File

@ -11,13 +11,11 @@ include $(DEPTH)/config/autoconf.mk
modules := \
healthreporter.jsm \
policy.jsm \
profile.jsm \
providers.jsm \
$(NULL)
testing_modules := \
mocks.jsm \
utils.jsm \
$(NULL)
@ -32,7 +30,6 @@ TESTING_JS_MODULE_DIR := services/healthreport
EXTRA_COMPONENTS := \
HealthReportComponents.manifest \
HealthReportService.js \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -2,23 +2,24 @@
* 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/. */
pref("healthreport.documentServerURI", "https://data.mozilla.com/");
pref("healthreport.documentServerNamespace", "metrics");
pref("healthreport.logging.consoleEnabled", true);
pref("healthreport.logging.consoleLevel", "Warn");
pref("healthreport.policy.currentDaySubmissionFailureCount", 0);
pref("healthreport.policy.dataSubmissionEnabled", true);
pref("healthreport.policy.dataSubmissionPolicyAccepted", false);
pref("healthreport.policy.dataSubmissionPolicyBypassAcceptance", false);
pref("healthreport.policy.dataSubmissionPolicyNotifiedTime", "0");
pref("healthreport.policy.dataSubmissionPolicyResponseType", "");
pref("healthreport.policy.dataSubmissionPolicyResponseTime", "0");
pref("healthreport.policy.firstRunTime", "0");
pref("healthreport.policy.lastDataSubmissionFailureTime", "0");
pref("healthreport.policy.lastDataSubmissionRequestedTime", "0");
pref("healthreport.policy.lastDataSubmissionSuccessfulTime", "0");
pref("healthreport.policy.nextDataSubmissionTime", "0");
pref("healthreport.service.enabled", true);
pref("healthreport.service.loadDelayMsec", 10000);
pref("healthreport.service.providerCategories", "healthreport-js-provider");
pref("datareporting.healthreport.currentDaySubmissionFailureCount", 0);
pref("datareporting.healthreport.documentServerURI", "https://data.mozilla.com/");
pref("datareporting.healthreport.documentServerNamespace", "metrics");
pref("datareporting.healthreport.infoURL", "http://www.mozilla.org/legal/privacy/firefox.html#health-report");
pref("datareporting.healthreport.logging.consoleEnabled", true);
pref("datareporting.healthreport.logging.consoleLevel", "Warn");
pref("datareporting.healthreport.lastDataSubmissionFailureTime", "0");
pref("datareporting.healthreport.lastDataSubmissionRequestedTime", "0");
pref("datareporting.healthreport.lastDataSubmissionSuccessfulTime", "0");
pref("datareporting.healthreport.nextDataSubmissionTime", "0");
pref("datareporting.healthreport.pendingDeleteRemoteData", false);
// Health Report is enabled by default on all channels.
pref("datareporting.healthreport.uploadEnabled", true);
pref("datareporting.healthreport.service.enabled", true);
pref("datareporting.healthreport.service.loadDelayMsec", 10000);
pref("datareporting.healthreport.service.providerCategories", "healthreport-js-provider");
pref("datareporting.healthreport.about.glossaryUrl", "https://services.mozilla.com/healthreport/glossary.html");
pref("datareporting.healthreport.about.reportUrl", "https://services.mozilla.com/healthreport/placeholder.html");

View File

@ -11,7 +11,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-common/bagheeraclient.js");
Cu.import("resource://services-common/log4moz.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://services-common/preferences.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://gre/modules/commonjs/promise/core.js");
@ -20,7 +19,6 @@ Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/services/healthreport/policy.jsm");
// Oldest year to allow in date preferences. This module was implemented in
@ -81,12 +79,19 @@ const DEFAULT_DATABASE_NAME = "healthreport.sqlite";
* @param branch
* (string) The preferences branch to use for state storage. The value
* must end with a period (.).
*
* @param policy
* (HealthReportPolicy) Policy driving execution of HealthReporter.
*/
function HealthReporter(branch) {
function HealthReporter(branch, policy) {
if (!branch.endsWith(".")) {
throw new Error("Branch must end with a period (.): " + branch);
}
if (!policy) {
throw new Error("Must provide policy to HealthReporter constructor.");
}
this._log = Log4Moz.repository.getLogger("Services.HealthReport.HealthReporter");
this._log.info("Initializing health reporter instance against " + branch);
@ -100,10 +105,9 @@ function HealthReporter(branch) {
throw new Error("No server namespace defined. Did you forget a pref?");
}
this._dbName = this._prefs.get("dbName") || DEFAULT_DATABASE_NAME;
this._policy = policy;
let policyBranch = new Preferences(branch + "policy.");
this._policy = new HealthReportPolicy(policyBranch, this);
this._dbName = this._prefs.get("dbName") || DEFAULT_DATABASE_NAME;
this._storage = null;
this._storageInProgress = false;
@ -210,6 +214,14 @@ HealthReporter.prototype = Object.freeze({
this._prefs.set("lastSubmitID", value || "");
},
/**
* Whether this instance will upload data to a server.
*/
get willUploadData() {
return this._policy.dataSubmissionPolicyAccepted &&
this._policy.healthReportUploadEnabled;
},
/**
* Whether remote data is currently stored.
*
@ -289,7 +301,6 @@ HealthReporter.prototype = Object.freeze({
return;
}
this._policy.startPolling();
this._log.info("HealthReporter started.");
this._initialized = true;
Services.obs.addObserver(this, "idle-daily", false);
@ -327,9 +338,6 @@ HealthReporter.prototype = Object.freeze({
this._initialized = false;
this._shutdownRequested = true;
// Safe to call multiple times.
this._policy.stopPolling();
if (this._collectorInProgress) {
this._log.warn("Collector is in progress of initializing. Waiting to finish.");
return;
@ -552,45 +560,14 @@ HealthReporter.prototype = Object.freeze({
},
/**
* Record the user's rejection of the data submission policy.
* Called to initiate a data upload.
*
* This should be what everything uses to disable data submission.
*
* @param reason
* (string) Why data submission is being disabled.
* The passed argument is a `DataSubmissionRequest` from policy.jsm.
*/
recordPolicyRejection: function (reason) {
this._policy.recordUserRejection(reason);
},
/**
* Record the user's acceptance of the data submission policy.
*
* This should be what everything uses to enable data submission.
*
* @param reason
* (string) Why data submission is being enabled.
*/
recordPolicyAcceptance: function (reason) {
this._policy.recordUserAcceptance(reason);
},
/**
* Whether the data submission policy has been accepted.
*
* If this is true, health data will be submitted unless one of the kill
* switches is active.
*/
get dataSubmissionPolicyAccepted() {
return this._policy.dataSubmissionPolicyAccepted;
},
/**
* Whether this health reporter will upload data to a server.
*/
get willUploadData() {
return this._policy.dataSubmissionPolicyAccepted &&
this._policy.dataUploadEnabled;
requestDataUpload: function (request) {
this.collectMeasurements()
.then(this._uploadData.bind(this, request),
this._onSubmitDataRequestFailure.bind(this));
},
/**
@ -768,7 +745,7 @@ HealthReporter.prototype = Object.freeze({
}.bind(this));
},
_deleteRemoteData: function (request) {
deleteRemoteData: function (request) {
if (!this.lastSubmitID) {
this._log.info("Received request to delete remote data but no data stored.");
request.onNoDataAvailable();
@ -859,28 +836,5 @@ HealthReporter.prototype = Object.freeze({
return new Date();
},
//-----------------------------
// HealthReportPolicy listeners
//-----------------------------
onRequestDataUpload: function (request) {
this.collectMeasurements()
.then(this._uploadData.bind(this, request),
this._onSubmitDataRequestFailure.bind(this));
},
onNotifyDataPolicy: function (request) {
// This isn't very loosely coupled. We may want to have this call
// registered listeners instead.
Observers.notify("healthreport:notify-data-policy:request", request);
},
onRequestRemoteDelete: function (request) {
this._deleteRemoteData(request);
},
//------------------------------------
// End of HealthReportPolicy listeners
//------------------------------------
});

View File

@ -216,8 +216,8 @@ this.createFakeCrash = function (submitted=false, date=new Date()) {
*
* The purpose of this type is to aid testing of startup and shutdown.
*/
this.InspectedHealthReporter = function (branch) {
HealthReporter.call(this, branch);
this.InspectedHealthReporter = function (branch, policy) {
HealthReporter.call(this, branch, policy);
this.onStorageCreated = null;
this.onCollectorInitialized = null;

View File

@ -728,6 +728,13 @@ AddonsProvider.prototype = Object.freeze({
"onUninstalled",
],
// Add-on types for which full details are uploaded in the
// ActiveAddonsMeasurement. All other types are ignored.
FULL_DETAIL_TYPES: [
"plugin",
"extension",
],
name: "org.mozilla.addons",
measurementTypes: [
@ -815,6 +822,13 @@ AddonsProvider.prototype = Object.freeze({
let data = {addons: {}, counts: {}};
for (let addon of addons) {
let type = addon.type;
data.counts[type] = (data.counts[type] || 0) + 1;
if (this.FULL_DETAIL_TYPES.indexOf(addon.type) == -1) {
continue;
}
let optOutPref = "extensions." + addon.id + ".getAddons.cache.enabled";
if (!this._prefs.get(optOutPref, true)) {
this._log.debug("Ignoring add-on that's opted out of AMO updates: " +
@ -837,8 +851,6 @@ AddonsProvider.prototype = Object.freeze({
data.addons[addon.id] = obj;
let type = addon.type;
data.counts[type] = (data.counts[type] || 0) + 1;
}
return data;

View File

@ -9,7 +9,7 @@ Cu.import("resource://services-common/observers.js");
Cu.import("resource://services-common/preferences.js");
Cu.import("resource://gre/modules/commonjs/promise/core.js");
Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm");
Cu.import("resource://gre/modules/services/healthreport/policy.jsm");
Cu.import("resource://gre/modules/services/datareporting/policy.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://testing-common/services-common/bagheeraserver.js");
@ -36,12 +36,29 @@ function defineNow(policy, now) {
function getJustReporter(name, uri=SERVER_URI, inspected=false) {
let branch = "healthreport.testing. " + name + ".";
let prefs = new Preferences(branch);
let prefs = new Preferences(branch + "healthreport.");
prefs.set("documentServerURI", uri);
prefs.set("dbName", name);
let reporter;
let policyPrefs = new Preferences(branch + "policy.");
let policy = new DataReportingPolicy(policyPrefs, prefs, {
onRequestDataUpload: function (request) {
reporter.requestDataUpload(request);
},
onNotifyDataPolicy: function (request) { },
onRequestRemoteDelete: function (request) {
reporter.deleteRemoteData(request);
},
});
let type = inspected ? InspectedHealthReporter : HealthReporter;
return new type(branch);
reporter = new type(branch + "healthreport.", policy);
return reporter;
}
function getReporter(name, uri, inspected) {
@ -251,7 +268,7 @@ add_task(function test_data_submission_transport_failure() {
let deferred = Promise.defer();
let request = new DataSubmissionRequest(deferred, new Date(Date.now + 30000));
reporter.onRequestDataUpload(request);
reporter.requestDataUpload(request);
yield deferred.promise;
do_check_eq(request.state, request.SUBMISSION_FAILURE_SOFT);
@ -268,7 +285,7 @@ add_task(function test_data_submission_success() {
let deferred = Promise.defer();
let request = new DataSubmissionRequest(deferred, new Date());
reporter.onRequestDataUpload(request);
reporter.requestDataUpload(request);
yield deferred.promise;
do_check_eq(request.state, request.SUBMISSION_SUCCESS);
do_check_true(reporter.lastPingDate.getTime() > 0);
@ -336,22 +353,23 @@ add_task(function test_request_remote_data_deletion() {
add_task(function test_policy_accept_reject() {
let [reporter, server] = yield getReporterAndServer("policy_accept_reject");
do_check_false(reporter.dataSubmissionPolicyAccepted);
let policy = reporter._policy;
do_check_false(policy.dataSubmissionPolicyAccepted);
do_check_false(reporter.willUploadData);
reporter.recordPolicyAcceptance();
do_check_true(reporter.dataSubmissionPolicyAccepted);
policy.recordUserAcceptance();
do_check_true(policy.dataSubmissionPolicyAccepted);
do_check_true(reporter.willUploadData);
reporter.recordPolicyRejection();
do_check_false(reporter.dataSubmissionPolicyAccepted);
policy.recordUserRejection();
do_check_false(policy.dataSubmissionPolicyAccepted);
do_check_false(reporter.willUploadData);
reporter._shutdown();
yield shutdownServer(server);
});
add_task(function test_upload_save_payload() {
let [reporter, server] = yield getReporterAndServer("upload_save_payload");

View File

@ -5,24 +5,14 @@
const modules = [
"healthreporter.jsm",
"policy.jsm",
"profile.jsm",
"providers.jsm",
];
const test_modules = [
"mocks.jsm",
];
function run_test() {
for (let m of modules) {
let resource = "resource://gre/modules/services/healthreport/" + m;
Components.utils.import(resource, {});
}
for (let m of test_modules) {
let resource = "resource://testing-common/services/healthreport/" + m;
Components.utils.import(resource, {});
}
}

View File

@ -75,6 +75,19 @@ add_task(function test_collect() {
installDate: now,
updateDate: now,
},
// Is counted but full details are omitted because it is a theme.
{
id: "addon2",
userDisabled: false,
appDisabled: false,
version: "3",
type: "theme",
scope: 1,
foreignInstall: false,
hasBinaryComponents: false,
installDate: now,
updateDate: now,
},
];
monkeypatchAddons(provider, addons);
@ -109,9 +122,10 @@ add_task(function test_collect() {
do_check_true(data.days.hasDay(now));
value = data.days.getDay(now);
do_check_eq(value.size, 2);
do_check_eq(value.size, 3);
do_check_eq(value.get("extension"), 1);
do_check_eq(value.get("plugin"), 1);
do_check_eq(value.get("theme"), 1);
yield provider.shutdown();
yield storage.close();

View File

@ -4,7 +4,6 @@ tail =
[test_load_modules.js]
[test_profile.js]
[test_policy.js]
[test_healthreporter.js]
[test_provider_addons.js]
[test_provider_appinfo.js]

View File

@ -9,6 +9,7 @@ add_makefiles "
services/crypto/Makefile
services/crypto/component/Makefile
services/healthreport/Makefile
services/datareporting/Makefile
services/metrics/Makefile
services/sync/Makefile
services/sync/locales/Makefile
@ -20,6 +21,7 @@ if [ "$ENABLE_TESTS" ]; then
services/common/tests/Makefile
services/crypto/tests/Makefile
services/healthreport/tests/Makefile
services/datareporting/tests/Makefile
services/metrics/tests/Makefile
services/sync/tests/Makefile
"

View File

@ -83,7 +83,7 @@ Collector.prototype = Object.freeze({
return;
}
let [provider, deferred] = this._providerInitQueue.pop();
let [provider, deferred] = this._providerInitQueue.shift();
this._providerInitializing = true;
this._log.info("Initializing provider with storage: " + provider.name);

View File

@ -1251,6 +1251,7 @@ MetricsStorageSqliteBackend.prototype = Object.freeze({
throw new Error("enqueueOperation expects a function. Got: " + typeof(func));
}
this._log.trace("Enqueueing operation.");
let deferred = Promise.defer();
this._queuedOperations.push([func, deferred]);
@ -1268,7 +1269,7 @@ MetricsStorageSqliteBackend.prototype = Object.freeze({
}
this._log.trace("Performing queued operation.");
let [func, deferred] = this._queuedOperations.pop();
let [func, deferred] = this._queuedOperations.shift();
let promise;
try {

View File

@ -195,6 +195,50 @@ add_task(function test_field_registration_repopulation() {
yield backend.close();
});
add_task(function test_enqueue_operation_execution_order() {
let backend = yield Metrics.Storage("enqueue_operation_execution_order");
let executionCount = 0;
let fns = {
op1: function () {
do_check_eq(executionCount, 1);
},
op2: function () {
do_check_eq(executionCount, 2);
},
op3: function () {
do_check_eq(executionCount, 3);
},
};
function enqueuedOperation(fn) {
let deferred = Promise.defer();
CommonUtils.nextTick(function onNextTick() {
executionCount++;
fn();
deferred.resolve();
});
return deferred.promise;
}
let promises = [];
for (let i = 1; i <= 3; i++) {
let fn = fns["op" + i];
promises.push(backend.enqueueOperation(enqueuedOperation.bind(this, fn)));
}
for (let promise of promises) {
yield promise;
}
yield backend.close();
});
add_task(function test_enqueue_operation_many() {
let backend = yield Metrics.Storage("enqueue_operation_many");

View File

@ -6,4 +6,3 @@ tail =
[test_metrics_provider.js]
[test_metrics_collector.js]
[test_metrics_storage.js]
skip-if = true

View File

@ -89,6 +89,7 @@ Tracker.prototype = {
return;
}
Utils.namedTimer(function() {
this._log.debug("Saving changed IDs to " + this.file);
Utils.jsonSave("changes/" + this.file, this, this.changedIDs, cb);
}, 1000, this, "_lazySave");
},
@ -136,7 +137,7 @@ Tracker.prototype = {
// Add/update the entry if we have a newer time
if ((this.changedIDs[id] || -Infinity) < when) {
this._log.trace("Adding changed ID: " + [id, when]);
this._log.trace("Adding changed ID: " + id + ", " + when);
this.changedIDs[id] = when;
this.saveChangedIDs(this.onSavedChangedIDs);
}

View File

@ -405,8 +405,10 @@ HistoryTracker.prototype = {
onVisit: function (uri, vid, time, session, referrer, trans, guid) {
if (this.ignoreAll) {
this._log.trace("ignoreAll: ignoring visit for " + guid);
return;
}
this._log.trace("onVisit: " + uri.spec);
if (this.addChangedID(guid)) {
this.score += SCORE_INCREMENT_SMALL;

View File

@ -30,7 +30,9 @@ tracker.persistChangedIDs = false;
let _counter = 0;
function addVisit() {
let uri = Utils.makeURI("http://getfirefox.com/" + _counter);
let uriString = "http://getfirefox.com/" + _counter++;
let uri = Utils.makeURI(uriString);
_("Adding visit for URI " + uriString);
let place = {
uri: uri,
visits: [ {
@ -38,26 +40,30 @@ function addVisit() {
transitionType: PlacesUtils.history.TRANSITION_LINK
} ]
};
let cb = Async.makeSpinningCallback();
PlacesUtils.asyncHistory.updatePlaces(place, {
handleError: function Add_handleError() {
_("Error adding visit for " + uriString);
cb(new Error("Error adding history entry"));
},
handleResult: function Add_handleResult() {
_("Added visit for " + uriString);
cb();
},
handleCompletion: function Add_handleCompletion() {
// Nothing to do
}
});
// Spin the event loop to embed this async call in a sync API
// Spin the event loop to embed this async call in a sync API.
cb.wait();
_counter++;
return uri;
}
function run_test() {
initTestLogging("Trace");
Log4Moz.repository.getLogger("Sync.Tracker.History").level = Log4Moz.Level.Trace;
run_next_test();
}
@ -91,6 +97,7 @@ add_test(function test_start_tracking() {
_("Tell the tracker to start tracking changes.");
onScoreUpdated(function() {
_("Score updated in test_start_tracking.");
do_check_attribute_count(tracker.changedIDs, 1);
do_check_eq(tracker.score, SCORE_INCREMENT_SMALL);
run_next_test();
@ -101,12 +108,18 @@ add_test(function test_start_tracking() {
});
add_test(function test_start_tracking_twice() {
_("Verifying preconditions from test_start_tracking.");
do_check_attribute_count(tracker.changedIDs, 1);
do_check_eq(tracker.score, SCORE_INCREMENT_SMALL);
_("Notifying twice won't do any harm.");
onScoreUpdated(function() {
_("Score updated in test_start_tracking_twice.");
do_check_attribute_count(tracker.changedIDs, 2);
do_check_eq(tracker.score, 2 * SCORE_INCREMENT_SMALL);
run_next_test();
});
Svc.Obs.notify("weave:engine:start-tracking");
addVisit();
});

View File

@ -3,99 +3,97 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import json
import logging
import optparse
import os
import sys
import time
from threading import RLock
from tps import TPSFirefoxRunner, TPSTestRunner
from tps import TPSTestRunner
def main():
parser = optparse.OptionParser()
parser.add_option("--mobile",
action = "store_true", dest = "mobile",
default = False,
help = "run with mobile settings")
parser.add_option("--testfile",
action = "store", type = "string", dest = "testfile",
default = '../../services/sync/tests/tps/test_sync.js',
help = "path to the test file to run "
"[default: %default]")
parser.add_option("--logfile",
action = "store", type = "string", dest = "logfile",
default = 'tps.log',
help = "path to the log file [default: %default]")
parser.add_option("--resultfile",
action = "store", type = "string", dest = "resultfile",
default = 'tps_result.json',
help = "path to the result file [default: %default]")
parser.add_option("--binary",
action = "store", type = "string", dest = "binary",
default = None,
help = "path to the Firefox binary, specified either as "
"a local file or a url; if omitted, the PATH "
"will be searched;")
parser.add_option("--configfile",
action = "store", type = "string", dest = "configfile",
default = None,
help = "path to the config file to use "
"[default: %default]")
parser.add_option("--pulsefile",
action = "store", type = "string", dest = "pulsefile",
default = None,
help = "path to file containing a pulse message in "
"json format that you want to inject into the monitor")
parser.add_option("--ignore-unused-engines",
default=False,
action="store_true",
dest="ignore_unused_engines",
help="If defined, don't load unused engines in individual tests."
" Has no effect for pulse monitor.")
(options, args) = parser.parse_args()
parser = optparse.OptionParser()
parser.add_option("--mobile",
action = "store_true", dest = "mobile",
default = False,
help = "run with mobile settings")
parser.add_option("--testfile",
action = "store", type = "string", dest = "testfile",
default = '../../services/sync/tests/tps/test_sync.js',
help = "path to the test file to run "
"[default: %default]")
parser.add_option("--logfile",
action = "store", type = "string", dest = "logfile",
default = 'tps.log',
help = "path to the log file [default: %default]")
parser.add_option("--resultfile",
action = "store", type = "string", dest = "resultfile",
default = 'tps_result.json',
help = "path to the result file [default: %default]")
parser.add_option("--binary",
action = "store", type = "string", dest = "binary",
default = None,
help = "path to the Firefox binary, specified either as "
"a local file or a url; if omitted, the PATH "
"will be searched;")
parser.add_option("--configfile",
action = "store", type = "string", dest = "configfile",
default = None,
help = "path to the config file to use "
"[default: %default]")
parser.add_option("--pulsefile",
action = "store", type = "string", dest = "pulsefile",
default = None,
help = "path to file containing a pulse message in "
"json format that you want to inject into the monitor")
parser.add_option("--ignore-unused-engines",
default=False,
action="store_true",
dest="ignore_unused_engines",
help="If defined, don't load unused engines in individual tests."
" Has no effect for pulse monitor.")
(options, args) = parser.parse_args()
configfile = options.configfile
if configfile is None:
if os.environ.get('VIRTUAL_ENV'):
configfile = os.path.join(os.path.dirname(__file__), 'config.json')
if configfile is None or not os.access(configfile, os.F_OK):
raise Exception("Unable to find config.json in a VIRTUAL_ENV; you must "
"specify a config file using the --configfile option")
configfile = options.configfile
if configfile is None:
if os.environ.get('VIRTUAL_ENV'):
configfile = os.path.join(os.path.dirname(__file__), 'config.json')
if configfile is None or not os.access(configfile, os.F_OK):
raise Exception("Unable to find config.json in a VIRTUAL_ENV; you must "
"specify a config file using the --configfile option")
# load the config file
f = open(configfile, 'r')
configcontent = f.read()
f.close()
config = json.loads(configcontent)
# load the config file
f = open(configfile, 'r')
configcontent = f.read()
f.close()
config = json.loads(configcontent)
rlock = RLock()
rlock = RLock()
print 'using result file', options.resultfile
print 'using result file', options.resultfile
extensionDir = config.get("extensiondir")
if not extensionDir or extensionDir == '__EXTENSIONDIR__':
extensionDir = os.path.join(os.getcwd(), "..", "..", "services", "sync", "tps")
else:
if sys.platform == 'win32':
# replace msys-style paths with proper Windows paths
import re
m = re.match('^\/\w\/', extensionDir)
if m:
extensionDir = "%s:/%s" % (m.group(0)[1:2], extensionDir[3:])
extensionDir = extensionDir.replace("/", "\\")
extensionDir = config.get("extensiondir")
if not extensionDir or extensionDir == '__EXTENSIONDIR__':
extensionDir = os.path.join(os.getcwd(), "..", "..", "services", "sync", "tps", "extensions")
else:
if sys.platform == 'win32':
# replace msys-style paths with proper Windows paths
import re
m = re.match('^\/\w\/', extensionDir)
if m:
extensionDir = "%s:/%s" % (m.group(0)[1:2], extensionDir[3:])
extensionDir = extensionDir.replace("/", "\\")
TPS = TPSTestRunner(extensionDir,
testfile=options.testfile,
logfile=options.logfile,
binary=options.binary,
config=config,
rlock=rlock,
mobile=options.mobile,
resultfile=options.resultfile,
ignore_unused_engines=options.ignore_unused_engines)
TPS.run_tests()
TPS = TPSTestRunner(extensionDir,
testfile=options.testfile,
logfile=options.logfile,
binary=options.binary,
config=config,
rlock=rlock,
mobile=options.mobile,
resultfile=options.resultfile,
ignore_unused_engines=options.ignore_unused_engines)
TPS.run_tests()
if __name__ == "__main__":
main()
main()

View File

@ -6,91 +6,89 @@ import copy
import httplib2
import os
import shutil
import sys
import mozinstall
from mozprocess.pid import get_pids
from mozprofile import Profile
from mozrunner import FirefoxRunner
class TPSFirefoxRunner(object):
PROCESS_TIMEOUT = 240
def __init__(self, binary):
if binary is not None and ('http://' in binary or 'ftp://' in binary):
self.url = binary
self.binary = None
else:
self.url = None
self.binary = binary
self.runner = None
self.installdir = None
def __del__(self):
if self.installdir:
shutil.rmtree(self.installdir, True)
def download_url(self, url, dest=None):
h = httplib2.Http()
resp, content = h.request(url, "GET")
if dest == None:
dest = os.path.basename(url)
local = open(dest, 'wb')
local.write(content)
local.close()
return dest
def download_build(self, installdir='downloadedbuild', appname='firefox'):
self.installdir = os.path.abspath(installdir)
buildName = os.path.basename(self.url)
pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)),
buildName)
# delete the build if it already exists
if os.access(pathToBuild, os.F_OK):
os.remove(pathToBuild)
# download the build
print "downloading build"
self.download_url(self.url, pathToBuild)
# install the build
print "installing %s" % pathToBuild
shutil.rmtree(self.installdir, True)
binary = mozinstall.install(src=pathToBuild, dest=self.installdir)
# remove the downloaded archive
os.remove(pathToBuild)
return binary
def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None):
"""Runs the given FirefoxRunner with the given Profile, waits
for completion, then returns the process exit code
"""
if profile is None:
profile = Profile()
self.profile = profile
if self.binary is None and self.url:
self.binary = self.download_build()
if self.runner is None:
self.runner = FirefoxRunner(self.profile, binary=self.binary)
self.runner.profile = self.profile
if env is not None:
self.runner.env.update(env)
if args is not None:
self.runner.cmdargs = copy.copy(args)
self.runner.start()
status = self.runner.process_handler.waitForFinish(timeout=timeout)
return status
PROCESS_TIMEOUT = 240
def __init__(self, binary):
if binary is not None and ('http://' in binary or 'ftp://' in binary):
self.url = binary
self.binary = None
else:
self.url = None
self.binary = binary
self.runner = None
self.installdir = None
def __del__(self):
if self.installdir:
shutil.rmtree(self.installdir, True)
def download_url(self, url, dest=None):
h = httplib2.Http()
resp, content = h.request(url, "GET")
if dest == None:
dest = os.path.basename(url)
local = open(dest, 'wb')
local.write(content)
local.close()
return dest
def download_build(self, installdir='downloadedbuild', appname='firefox'):
self.installdir = os.path.abspath(installdir)
buildName = os.path.basename(self.url)
pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)),
buildName)
# delete the build if it already exists
if os.access(pathToBuild, os.F_OK):
os.remove(pathToBuild)
# download the build
print "downloading build"
self.download_url(self.url, pathToBuild)
# install the build
print "installing %s" % pathToBuild
shutil.rmtree(self.installdir, True)
binary = mozinstall.install(src=pathToBuild, dest=self.installdir)
# remove the downloaded archive
os.remove(pathToBuild)
return binary
def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None):
"""Runs the given FirefoxRunner with the given Profile, waits
for completion, then returns the process exit code
"""
if profile is None:
profile = Profile()
self.profile = profile
if self.binary is None and self.url:
self.binary = self.download_build()
if self.runner is None:
self.runner = FirefoxRunner(self.profile, binary=self.binary)
self.runner.profile = self.profile
if env is not None:
self.runner.env.update(env)
if args is not None:
self.runner.cmdargs = copy.copy(args)
self.runner.start()
status = self.runner.process_handler.waitForFinish(timeout=timeout)
return status

View File

@ -18,10 +18,10 @@ DOCROOT = '.'
class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
allow_reuse_address = True
class MozRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def translate_path(self, path):
# It appears that the default path is '/' and os.path.join makes the '/'
# It appears that the default path is '/' and os.path.join makes the '/'
o = urlparse(path)
sep = '/'
@ -92,9 +92,9 @@ class MozHttpd(object):
for fileName in fileList:
if fileName == webline:
found = True
if (found == False):
print "NOT FOUND: " + webline.strip()
print "NOT FOUND: " + webline.strip()
def stop(self):
if self.httpd:
@ -102,4 +102,3 @@ class MozHttpd(object):
self.httpd.server_close()
__del__ = stop

View File

@ -2,76 +2,74 @@
# 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/.
import os
import re
class TPSTestPhase(object):
lineRe = re.compile(
r"^(.*?)test phase (?P<matchphase>\d+): (?P<matchstatus>.*)$")
lineRe = re.compile(
r"^(.*?)test phase (?P<matchphase>\d+): (?P<matchstatus>.*)$")
def __init__(self, phase, profile, testname, testpath, logfile, env,
firefoxRunner, logfn, ignore_unused_engines=False):
self.phase = phase
self.profile = profile
self.testname = str(testname) # this might be passed in as unicode
self.testpath = testpath
self.logfile = logfile
self.env = env
self.firefoxRunner = firefoxRunner
self.log = logfn
self.ignore_unused_engines = ignore_unused_engines
self._status = None
self.errline = ''
def __init__(self, phase, profile, testname, testpath, logfile, env,
firefoxRunner, logfn, ignore_unused_engines=False):
self.phase = phase
self.profile = profile
self.testname = str(testname) # this might be passed in as unicode
self.testpath = testpath
self.logfile = logfile
self.env = env
self.firefoxRunner = firefoxRunner
self.log = logfn
self.ignore_unused_engines = ignore_unused_engines
self._status = None
self.errline = ''
@property
def phasenum(self):
match = re.match('.*?(\d+)', self.phase)
if match:
return match.group(1)
@property
def phasenum(self):
match = re.match('.*?(\d+)', self.phase)
if match:
return match.group(1)
@property
def status(self):
return self._status if self._status else 'unknown'
@property
def status(self):
return self._status if self._status else 'unknown'
def run(self):
# launch Firefox
args = [ '-tps', self.testpath,
'-tpsphase', self.phasenum,
'-tpslogfile', self.logfile ]
def run(self):
# launch Firefox
args = [ '-tps', self.testpath,
'-tpsphase', self.phasenum,
'-tpslogfile', self.logfile ]
if self.ignore_unused_engines:
args.append('--ignore-unused-engines')
if self.ignore_unused_engines:
args.append('--ignore-unused-engines')
self.log("\nlaunching Firefox for phase %s with args %s\n" %
(self.phase, str(args)))
returncode = self.firefoxRunner.run(env=self.env,
args=args,
profile=self.profile)
self.log("\nlaunching Firefox for phase %s with args %s\n" %
(self.phase, str(args)))
self.firefoxRunner.run(env=self.env,
args=args,
profile=self.profile)
# parse the logfile and look for results from the current test phase
found_test = False
f = open(self.logfile, 'r')
for line in f:
# parse the logfile and look for results from the current test phase
found_test = False
f = open(self.logfile, 'r')
for line in f:
# skip to the part of the log file that deals with the test we're running
if not found_test:
if line.find("Running test %s" % self.testname) > -1:
found_test = True
else:
continue
# skip to the part of the log file that deals with the test we're running
if not found_test:
if line.find("Running test %s" % self.testname) > -1:
found_test = True
else:
continue
# look for the status of the current phase
match = self.lineRe.match(line)
if match:
if match.group("matchphase") == self.phasenum:
self._status = match.group("matchstatus")
break
# look for the status of the current phase
match = self.lineRe.match(line)
if match:
if match.group("matchphase") == self.phasenum:
self._status = match.group("matchstatus")
break
# set the status to FAIL if there is TPS error
if line.find("CROSSWEAVE ERROR: ") > -1 and not self._status:
self._status = "FAIL"
self.errline = line[line.find("CROSSWEAVE ERROR: ") + len("CROSSWEAVE ERROR: "):]
f.close()
# set the status to FAIL if there is TPS error
if line.find("CROSSWEAVE ERROR: ") > -1 and not self._status:
self._status = "FAIL"
self.errline = line[line.find("CROSSWEAVE ERROR: ") + len("CROSSWEAVE ERROR: "):]
f.close()

View File

@ -2,19 +2,14 @@
# 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/.
import httplib
import json
import os
import platform
import random
import re
import socket
import tempfile
import time
import traceback
import urllib
from threading import RLock
from mozprofile import Profile
@ -23,413 +18,413 @@ from tps.phase import TPSTestPhase
from tps.mozhttpd import MozHttpd
class TempFile(object):
"""Class for temporary files that delete themselves when garbage-collected.
"""
"""Class for temporary files that delete themselves when garbage-collected.
"""
def __init__(self, prefix=None):
self.fd, self.filename = self.tmpfile = tempfile.mkstemp(prefix=prefix)
def __init__(self, prefix=None):
self.fd, self.filename = self.tmpfile = tempfile.mkstemp(prefix=prefix)
def write(self, data):
if self.fd:
os.write(self.fd, data)
def write(self, data):
if self.fd:
os.write(self.fd, data)
def close(self):
if self.fd:
os.close(self.fd)
self.fd = None
def close(self):
if self.fd:
os.close(self.fd)
self.fd = None
def cleanup(self):
if self.fd:
self.close()
if os.access(self.filename, os.F_OK):
os.remove(self.filename)
def cleanup(self):
if self.fd:
self.close()
if os.access(self.filename, os.F_OK):
os.remove(self.filename)
__del__ = cleanup
__del__ = cleanup
class TPSTestRunner(object):
default_env = { 'MOZ_CRASHREPORTER_DISABLE': '1',
'GNOME_DISABLE_CRASH_DIALOG': '1',
'XRE_NO_WINDOWS_CRASH_DIALOG': '1',
'MOZ_NO_REMOTE': '1',
'XPCOM_DEBUG_BREAK': 'warn',
}
default_preferences = { 'app.update.enabled' : False,
'extensions.getAddons.get.url': 'http://127.0.0.1:4567/en-US/firefox/api/%API_VERSION%/search/guid:%IDS%',
'extensions.update.enabled' : False,
'extensions.update.notifyUser' : False,
'browser.shell.checkDefaultBrowser' : False,
'browser.tabs.warnOnClose' : False,
'browser.warnOnQuit': False,
'browser.sessionstore.resume_from_crash': False,
'services.sync.addons.ignoreRepositoryChecking': True,
'services.sync.firstSync': 'notReady',
'services.sync.lastversion': '1.0',
'services.sync.log.rootLogger': 'Trace',
'services.sync.log.logger.engine.addons': 'Trace',
'services.sync.log.logger.service.main': 'Trace',
'services.sync.log.logger.engine.bookmarks': 'Trace',
'services.sync.log.appender.console': 'Trace',
'services.sync.log.appender.debugLog.enabled': True,
'toolkit.startup.max_resumed_crashes': -1,
'browser.dom.window.dump.enabled': True,
# Allow installing extensions dropped into the profile folder
'extensions.autoDisableScopes': 10,
# Don't open a dialog to show available add-on updates
'extensions.update.notifyUser' : False,
}
syncVerRe = re.compile(
r"Sync version: (?P<syncversion>.*)\n")
ffVerRe = re.compile(
r"Firefox version: (?P<ffver>.*)\n")
ffDateRe = re.compile(
r"Firefox builddate: (?P<ffdate>.*)\n")
default_env = { 'MOZ_CRASHREPORTER_DISABLE': '1',
'GNOME_DISABLE_CRASH_DIALOG': '1',
'XRE_NO_WINDOWS_CRASH_DIALOG': '1',
'MOZ_NO_REMOTE': '1',
'XPCOM_DEBUG_BREAK': 'warn',
}
default_preferences = { 'app.update.enabled' : False,
'extensions.getAddons.get.url': 'http://127.0.0.1:4567/en-US/firefox/api/%API_VERSION%/search/guid:%IDS%',
'extensions.update.enabled' : False,
'extensions.update.notifyUser' : False,
'browser.shell.checkDefaultBrowser' : False,
'browser.tabs.warnOnClose' : False,
'browser.warnOnQuit': False,
'browser.sessionstore.resume_from_crash': False,
'services.sync.addons.ignoreRepositoryChecking': True,
'services.sync.firstSync': 'notReady',
'services.sync.lastversion': '1.0',
'services.sync.log.rootLogger': 'Trace',
'services.sync.log.logger.engine.addons': 'Trace',
'services.sync.log.logger.service.main': 'Trace',
'services.sync.log.logger.engine.bookmarks': 'Trace',
'services.sync.log.appender.console': 'Trace',
'services.sync.log.appender.debugLog.enabled': True,
'toolkit.startup.max_resumed_crashes': -1,
'browser.dom.window.dump.enabled': True,
# Allow installing extensions dropped into the profile folder
'extensions.autoDisableScopes': 10,
# Don't open a dialog to show available add-on updates
'extensions.update.notifyUser' : False,
}
syncVerRe = re.compile(
r"Sync version: (?P<syncversion>.*)\n")
ffVerRe = re.compile(
r"Firefox version: (?P<ffver>.*)\n")
ffDateRe = re.compile(
r"Firefox builddate: (?P<ffdate>.*)\n")
def __init__(self, extensionDir,
testfile="sync.test",
binary=None, config=None, rlock=None, mobile=False,
logfile="tps.log", resultfile="tps_result.json",
ignore_unused_engines=False):
self.extensions = []
self.testfile = testfile
self.logfile = os.path.abspath(logfile)
self.resultfile = resultfile
self.binary = binary
self.ignore_unused_engines = ignore_unused_engines
self.config = config if config else {}
self.repo = None
self.changeset = None
self.branch = None
self.numfailed = 0
self.numpassed = 0
self.nightly = False
self.rlock = rlock
self.mobile = mobile
self.tpsxpi = None
self.firefoxRunner = None
self.extensionDir = extensionDir
self.productversion = None
self.addonversion = None
self.postdata = {}
self.errorlogs = {}
def __init__(self, extensionDir,
testfile="sync.test",
binary=None, config=None, rlock=None, mobile=False,
logfile="tps.log", resultfile="tps_result.json",
ignore_unused_engines=False):
self.extensions = []
self.testfile = testfile
self.logfile = os.path.abspath(logfile)
self.resultfile = resultfile
self.binary = binary
self.ignore_unused_engines = ignore_unused_engines
self.config = config if config else {}
self.repo = None
self.changeset = None
self.branch = None
self.numfailed = 0
self.numpassed = 0
self.nightly = False
self.rlock = rlock
self.mobile = mobile
self.tpsxpi = None
self.firefoxRunner = None
self.extensionDir = extensionDir
self.productversion = None
self.addonversion = None
self.postdata = {}
self.errorlogs = {}
@property
def mobile(self):
return self._mobile
@property
def mobile(self):
return self._mobile
@mobile.setter
def mobile(self, value):
self._mobile = value
self.synctype = 'desktop' if not self._mobile else 'mobile'
@mobile.setter
def mobile(self, value):
self._mobile = value
self.synctype = 'desktop' if not self._mobile else 'mobile'
def log(self, msg, printToConsole=False):
"""Appends a string to the logfile"""
def log(self, msg, printToConsole=False):
"""Appends a string to the logfile"""
f = open(self.logfile, 'a')
f.write(msg)
f.close()
if printToConsole:
print msg
f = open(self.logfile, 'a')
f.write(msg)
f.close()
if printToConsole:
print msg
def writeToResultFile(self, postdata, body=None,
sendTo=['crossweave@mozilla.com']):
"""Writes results to test file"""
def writeToResultFile(self, postdata, body=None,
sendTo=['crossweave@mozilla.com']):
"""Writes results to test file"""
results = {'results': []}
results = {'results': []}
if os.access(self.resultfile, os.F_OK):
f = open(self.resultfile, 'r')
results = json.loads(f.read())
f.close()
if os.access(self.resultfile, os.F_OK):
f = open(self.resultfile, 'r')
results = json.loads(f.read())
f.close()
f = open(self.resultfile, 'w')
if body is not None:
postdata['body'] = body
if self.numpassed is not None:
postdata['numpassed'] = self.numpassed
if self.numfailed is not None:
postdata['numfailed'] = self.numfailed
if self.firefoxRunner and self.firefoxRunner.url:
postdata['firefoxrunnerurl'] = self.firefoxRunner.url
f = open(self.resultfile, 'w')
if body is not None:
postdata['body'] = body
if self.numpassed is not None:
postdata['numpassed'] = self.numpassed
if self.numfailed is not None:
postdata['numfailed'] = self.numfailed
if self.firefoxRunner and self.firefoxRunner.url:
postdata['firefoxrunnerurl'] = self.firefoxRunner.url
postdata['sendTo'] = sendTo
results['results'].append(postdata)
f.write(json.dumps(results, indent=2))
f.close()
postdata['sendTo'] = sendTo
results['results'].append(postdata)
f.write(json.dumps(results, indent=2))
f.close()
def _zip_add_file(self, zip, file, rootDir):
zip.write(os.path.join(rootDir, file), file)
def _zip_add_file(self, zip, file, rootDir):
zip.write(os.path.join(rootDir, file), file)
def _zip_add_dir(self, zip, dir, rootDir):
try:
zip.write(os.path.join(rootDir, dir), dir)
except:
# on some OS's, adding directory entries doesn't seem to work
pass
for root, dirs, files in os.walk(os.path.join(rootDir, dir)):
for f in files:
zip.write(os.path.join(root, f), os.path.join(dir, f))
def run_single_test(self, testdir, testname):
testpath = os.path.join(testdir, testname)
self.log("Running test %s\n" % testname)
# Create a random account suffix that is used when creating test
# accounts on a staging server.
account_suffix = {"account-suffix": ''.join([str(random.randint(0,9))
for i in range(1,6)])}
self.config['account'].update(account_suffix)
# Read and parse the test file, merge it with the contents of the config
# file, and write the combined output to a temporary file.
f = open(testpath, 'r')
testcontent = f.read()
f.close()
try:
test = json.loads(testcontent)
except:
test = json.loads(testcontent[testcontent.find("{"):testcontent.find("}") + 1])
testcontent += 'var config = %s;\n' % json.dumps(self.config, indent=2)
testcontent += 'var seconds_since_epoch = %d;\n' % int(time.time())
tmpfile = TempFile(prefix='tps_test_')
tmpfile.write(testcontent)
tmpfile.close()
# generate the profiles defined in the test, and a list of test phases
profiles = {}
phaselist = []
for phase in test:
profilename = test[phase]
# create the profile if necessary
if not profilename in profiles:
profiles[profilename] = Profile(preferences = self.preferences,
addons = self.extensions)
# create the test phase
phaselist.append(TPSTestPhase(
phase,
profiles[profilename],
testname,
tmpfile.filename,
self.logfile,
self.env,
self.firefoxRunner,
self.log,
ignore_unused_engines=self.ignore_unused_engines))
# sort the phase list by name
phaselist = sorted(phaselist, key=lambda phase: phase.phase)
# run each phase in sequence, aborting at the first failure
for phase in phaselist:
phase.run()
# if a failure occurred, dump the entire sync log into the test log
if phase.status != "PASS":
for profile in profiles:
self.log("\nDumping sync log for profile %s\n" % profiles[profile].profile)
for root, dirs, files in os.walk(os.path.join(profiles[profile].profile, 'weave', 'logs')):
for f in files:
weavelog = os.path.join(profiles[profile].profile, 'weave', 'logs', f)
if os.access(weavelog, os.F_OK):
with open(weavelog, 'r') as fh:
for line in fh:
possible_time = line[0:13]
if len(possible_time) == 13 and possible_time.isdigit():
time_ms = int(possible_time)
formatted = time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(time_ms / 1000))
self.log('%s.%03d %s' % (
formatted, time_ms % 1000, line[14:] ))
else:
self.log(line)
break;
# grep the log for FF and sync versions
f = open(self.logfile)
logdata = f.read()
match = self.syncVerRe.search(logdata)
sync_version = match.group("syncversion") if match else 'unknown'
match = self.ffVerRe.search(logdata)
firefox_version = match.group("ffver") if match else 'unknown'
match = self.ffDateRe.search(logdata)
firefox_builddate = match.group("ffdate") if match else 'unknown'
f.close()
if phase.status == 'PASS':
logdata = ''
else:
# we only care about the log data for this specific test
logdata = logdata[logdata.find('Running test %s' % (str(testname))):]
result = {
'PASS': lambda x: ('TEST-PASS', ''),
'FAIL': lambda x: ('TEST-UNEXPECTED-FAIL', x.rstrip()),
'unknown': lambda x: ('TEST-UNEXPECTED-FAIL', 'test did not complete')
} [phase.status](phase.errline)
logstr = "\n%s | %s%s\n" % (result[0], testname, (' | %s' % result[1] if result[1] else ''))
try:
repoinfo = self.firefoxRunner.runner.get_repositoryInfo()
except:
repoinfo = {}
apprepo = repoinfo.get('application_repository', '')
appchangeset = repoinfo.get('application_changeset', '')
# save logdata to a temporary file for posting to the db
tmplogfile = None
if logdata:
tmplogfile = TempFile(prefix='tps_log_')
tmplogfile.write(logdata)
tmplogfile.close()
self.errorlogs[testname] = tmplogfile
resultdata = ({ "productversion": { "version": firefox_version,
"buildid": firefox_builddate,
"builddate": firefox_builddate[0:8],
"product": "Firefox",
"repository": apprepo,
"changeset": appchangeset,
},
"addonversion": { "version": sync_version,
"product": "Firefox Sync" },
"name": testname,
"message": result[1],
"state": result[0],
"logdata": logdata
})
self.log(logstr, True)
for phase in phaselist:
print "\t%s: %s" % (phase.phase, phase.status)
if phase.status == 'FAIL':
break
return resultdata
def run_tests(self):
# delete the logfile if it already exists
if os.access(self.logfile, os.F_OK):
os.remove(self.logfile)
# Make a copy of the default env variables and preferences, and update
# them for mobile settings if needed.
self.env = self.default_env.copy()
self.preferences = self.default_preferences.copy()
if self.mobile:
self.preferences.update({'services.sync.client.type' : 'mobile'})
# Acquire a lock to make sure no other threads are running tests
# at the same time.
if self.rlock:
self.rlock.acquire()
try:
# Create the Firefox runner, which will download and install the
# build, as needed.
if not self.firefoxRunner:
self.firefoxRunner = TPSFirefoxRunner(self.binary)
# now, run the test group
self.run_test_group()
except:
traceback.print_exc()
self.numpassed = 0
self.numfailed = 1
try:
self.writeToResultFile(self.postdata,
'<pre>%s</pre>' % traceback.format_exc())
except:
traceback.print_exc()
else:
try:
if self.numfailed > 0 or self.numpassed == 0:
To = self.config['email'].get('notificationlist')
else:
To = self.config['email'].get('passednotificationlist')
self.writeToResultFile(self.postdata,
sendTo=To)
except:
traceback.print_exc()
def _zip_add_dir(self, zip, dir, rootDir):
try:
self.writeToResultFile(self.postdata,
'<pre>%s</pre>' % traceback.format_exc())
zip.write(os.path.join(rootDir, dir), dir)
except:
traceback.print_exc()
# on some OS's, adding directory entries doesn't seem to work
pass
for root, dirs, files in os.walk(os.path.join(rootDir, dir)):
for f in files:
zip.write(os.path.join(root, f), os.path.join(dir, f))
# release our lock
if self.rlock:
self.rlock.release()
def run_single_test(self, testdir, testname):
testpath = os.path.join(testdir, testname)
self.log("Running test %s\n" % testname)
# dump out a summary of test results
print 'Test Summary\n'
for test in self.postdata.get('tests', {}):
print '%s | %s | %s' % (test['state'], test['name'], test['message'])
# Create a random account suffix that is used when creating test
# accounts on a staging server.
account_suffix = {"account-suffix": ''.join([str(random.randint(0,9))
for i in range(1,6)])}
self.config['account'].update(account_suffix)
def run_test_group(self):
self.results = []
self.extensions = []
# Read and parse the test file, merge it with the contents of the config
# file, and write the combined output to a temporary file.
f = open(testpath, 'r')
testcontent = f.read()
f.close()
try:
test = json.loads(testcontent)
except:
test = json.loads(testcontent[testcontent.find("{"):testcontent.find("}") + 1])
# set the OS we're running on
os_string = platform.uname()[2] + " " + platform.uname()[3]
if os_string.find("Darwin") > -1:
os_string = "Mac OS X " + platform.mac_ver()[0]
if platform.uname()[0].find("Linux") > -1:
os_string = "Linux " + platform.uname()[5]
if platform.uname()[0].find("Win") > -1:
os_string = "Windows " + platform.uname()[3]
testcontent += 'var config = %s;\n' % json.dumps(self.config, indent=2)
testcontent += 'var seconds_since_epoch = %d;\n' % int(time.time())
# reset number of passed/failed tests
self.numpassed = 0
self.numfailed = 0
tmpfile = TempFile(prefix='tps_test_')
tmpfile.write(testcontent)
tmpfile.close()
# build our tps.xpi extension
self.extensions.append(os.path.join(self.extensionDir, 'tps'))
self.extensions.append(os.path.join(self.extensionDir, "mozmill"))
# generate the profiles defined in the test, and a list of test phases
profiles = {}
phaselist = []
for phase in test:
profilename = test[phase]
# build the test list
try:
f = open(self.testfile)
jsondata = f.read()
f.close()
testfiles = json.loads(jsondata)
testlist = testfiles['tests']
except ValueError:
testlist = [os.path.basename(self.testfile)]
testdir = os.path.dirname(self.testfile)
# create the profile if necessary
if not profilename in profiles:
profiles[profilename] = Profile(preferences = self.preferences,
addons = self.extensions)
self.mozhttpd = MozHttpd(port=4567, docroot=testdir)
self.mozhttpd.start()
# create the test phase
phaselist.append(TPSTestPhase(
phase,
profiles[profilename],
testname,
tmpfile.filename,
self.logfile,
self.env,
self.firefoxRunner,
self.log,
ignore_unused_engines=self.ignore_unused_engines))
# run each test, and save the results
for test in testlist:
result = self.run_single_test(testdir, test)
# sort the phase list by name
phaselist = sorted(phaselist, key=lambda phase: phase.phase)
if not self.productversion:
self.productversion = result['productversion']
if not self.addonversion:
self.addonversion = result['addonversion']
# run each phase in sequence, aborting at the first failure
for phase in phaselist:
phase.run()
self.results.append({'state': result['state'],
'name': result['name'],
'message': result['message'],
'logdata': result['logdata']})
if result['state'] == 'TEST-PASS':
self.numpassed += 1
else:
self.numfailed += 1
# if a failure occurred, dump the entire sync log into the test log
if phase.status != "PASS":
for profile in profiles:
self.log("\nDumping sync log for profile %s\n" % profiles[profile].profile)
for root, dirs, files in os.walk(os.path.join(profiles[profile].profile, 'weave', 'logs')):
for f in files:
weavelog = os.path.join(profiles[profile].profile, 'weave', 'logs', f)
if os.access(weavelog, os.F_OK):
with open(weavelog, 'r') as fh:
for line in fh:
possible_time = line[0:13]
if len(possible_time) == 13 and possible_time.isdigit():
time_ms = int(possible_time)
formatted = time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(time_ms / 1000))
self.log('%s.%03d %s' % (
formatted, time_ms % 1000, line[14:] ))
else:
self.log(line)
break;
self.mozhttpd.stop()
# grep the log for FF and sync versions
f = open(self.logfile)
logdata = f.read()
match = self.syncVerRe.search(logdata)
sync_version = match.group("syncversion") if match else 'unknown'
match = self.ffVerRe.search(logdata)
firefox_version = match.group("ffver") if match else 'unknown'
match = self.ffDateRe.search(logdata)
firefox_builddate = match.group("ffdate") if match else 'unknown'
f.close()
if phase.status == 'PASS':
logdata = ''
else:
# we only care about the log data for this specific test
logdata = logdata[logdata.find('Running test %s' % (str(testname))):]
# generate the postdata we'll use to post the results to the db
self.postdata = { 'tests': self.results,
'os':os_string,
'testtype': 'crossweave',
'productversion': self.productversion,
'addonversion': self.addonversion,
'synctype': self.synctype,
}
result = {
'PASS': lambda x: ('TEST-PASS', ''),
'FAIL': lambda x: ('TEST-UNEXPECTED-FAIL', x.rstrip()),
'unknown': lambda x: ('TEST-UNEXPECTED-FAIL', 'test did not complete')
} [phase.status](phase.errline)
logstr = "\n%s | %s%s\n" % (result[0], testname, (' | %s' % result[1] if result[1] else ''))
try:
repoinfo = self.firefoxRunner.runner.get_repositoryInfo()
except:
repoinfo = {}
apprepo = repoinfo.get('application_repository', '')
appchangeset = repoinfo.get('application_changeset', '')
# save logdata to a temporary file for posting to the db
tmplogfile = None
if logdata:
tmplogfile = TempFile(prefix='tps_log_')
tmplogfile.write(logdata)
tmplogfile.close()
self.errorlogs[testname] = tmplogfile
resultdata = ({ "productversion": { "version": firefox_version,
"buildid": firefox_builddate,
"builddate": firefox_builddate[0:8],
"product": "Firefox",
"repository": apprepo,
"changeset": appchangeset,
},
"addonversion": { "version": sync_version,
"product": "Firefox Sync" },
"name": testname,
"message": result[1],
"state": result[0],
"logdata": logdata
})
self.log(logstr, True)
for phase in phaselist:
print "\t%s: %s" % (phase.phase, phase.status)
if phase.status == 'FAIL':
break
return resultdata
def run_tests(self):
# delete the logfile if it already exists
if os.access(self.logfile, os.F_OK):
os.remove(self.logfile)
# Make a copy of the default env variables and preferences, and update
# them for mobile settings if needed.
self.env = self.default_env.copy()
self.preferences = self.default_preferences.copy()
if self.mobile:
self.preferences.update({'services.sync.client.type' : 'mobile'})
# Acquire a lock to make sure no other threads are running tests
# at the same time.
if self.rlock:
self.rlock.acquire()
try:
# Create the Firefox runner, which will download and install the
# build, as needed.
if not self.firefoxRunner:
self.firefoxRunner = TPSFirefoxRunner(self.binary)
# now, run the test group
self.run_test_group()
except:
traceback.print_exc()
self.numpassed = 0
self.numfailed = 1
try:
self.writeToResultFile(self.postdata,
'<pre>%s</pre>' % traceback.format_exc())
except:
traceback.print_exc()
else:
try:
if self.numfailed > 0 or self.numpassed == 0:
To = self.config['email'].get('notificationlist')
else:
To = self.config['email'].get('passednotificationlist')
self.writeToResultFile(self.postdata,
sendTo=To)
except:
traceback.print_exc()
try:
self.writeToResultFile(self.postdata,
'<pre>%s</pre>' % traceback.format_exc())
except:
traceback.print_exc()
# release our lock
if self.rlock:
self.rlock.release()
# dump out a summary of test results
print 'Test Summary\n'
for test in self.postdata.get('tests', {}):
print '%s | %s | %s' % (test['state'], test['name'], test['message'])
def run_test_group(self):
self.results = []
self.extensions = []
# set the OS we're running on
os_string = platform.uname()[2] + " " + platform.uname()[3]
if os_string.find("Darwin") > -1:
os_string = "Mac OS X " + platform.mac_ver()[0]
if platform.uname()[0].find("Linux") > -1:
os_string = "Linux " + platform.uname()[5]
if platform.uname()[0].find("Win") > -1:
os_string = "Windows " + platform.uname()[3]
# reset number of passed/failed tests
self.numpassed = 0
self.numfailed = 0
# build our tps.xpi extension
self.extensions.append(os.path.join(self.extensionDir, 'tps'))
self.extensions.append(os.path.join(self.extensionDir, "mozmill"))
# build the test list
try:
f = open(self.testfile)
jsondata = f.read()
f.close()
testfiles = json.loads(jsondata)
testlist = testfiles['tests']
except ValueError:
testlist = [os.path.basename(self.testfile)]
testdir = os.path.dirname(self.testfile)
self.mozhttpd = MozHttpd(port=4567, docroot=testdir)
self.mozhttpd.start()
# run each test, and save the results
for test in testlist:
result = self.run_single_test(testdir, test)
if not self.productversion:
self.productversion = result['productversion']
if not self.addonversion:
self.addonversion = result['addonversion']
self.results.append({'state': result['state'],
'name': result['name'],
'message': result['message'],
'logdata': result['logdata']})
if result['state'] == 'TEST-PASS':
self.numpassed += 1
else:
self.numfailed += 1
self.mozhttpd.stop()
# generate the postdata we'll use to post the results to the db
self.postdata = { 'tests': self.results,
'os':os_string,
'testtype': 'crossweave',
'productversion': self.productversion,
'addonversion': self.addonversion,
'synctype': self.synctype,
}

View File

@ -8,57 +8,57 @@ from testrunner import TPSTestRunner
class TPSTestThread(Thread):
def __init__(self, extensionDir, builddata=None,
testfile=None, logfile=None, rlock=None, config=None):
assert(builddata)
assert(config)
self.extensionDir = extensionDir
self.builddata = builddata
self.testfile = testfile
self.logfile = logfile
self.rlock = rlock
self.config = config
Thread.__init__(self)
def __init__(self, extensionDir, builddata=None,
testfile=None, logfile=None, rlock=None, config=None):
assert(builddata)
assert(config)
self.extensionDir = extensionDir
self.builddata = builddata
self.testfile = testfile
self.logfile = logfile
self.rlock = rlock
self.config = config
Thread.__init__(self)
def run(self):
# run the tests in normal mode ...
TPS = TPSTestRunner(self.extensionDir,
testfile=self.testfile,
logfile=self.logfile,
binary=self.builddata['buildurl'],
config=self.config,
rlock=self.rlock,
mobile=False)
TPS.run_tests()
def run(self):
# run the tests in normal mode ...
TPS = TPSTestRunner(self.extensionDir,
testfile=self.testfile,
logfile=self.logfile,
binary=self.builddata['buildurl'],
config=self.config,
rlock=self.rlock,
mobile=False)
TPS.run_tests()
# Get the binary used by this TPS instance, and use it in subsequent
# ones, so it doesn't have to be re-downloaded each time.
binary = TPS.firefoxRunner.binary
# Get the binary used by this TPS instance, and use it in subsequent
# ones, so it doesn't have to be re-downloaded each time.
binary = TPS.firefoxRunner.binary
# ... and then again in mobile mode
TPS_mobile = TPSTestRunner(self.extensionDir,
testfile=self.testfile,
logfile=self.logfile,
binary=binary,
config=self.config,
rlock=self.rlock,
mobile=True)
TPS_mobile.run_tests()
# ... and then again in mobile mode
TPS_mobile = TPSTestRunner(self.extensionDir,
testfile=self.testfile,
logfile=self.logfile,
binary=binary,
config=self.config,
rlock=self.rlock,
mobile=True)
TPS_mobile.run_tests()
# ... and again via the staging server, if credentials are present
stageaccount = self.config.get('stageaccount')
if stageaccount:
username = stageaccount.get('username')
password = stageaccount.get('password')
passphrase = stageaccount.get('passphrase')
if username and password and passphrase:
stageconfig = self.config.copy()
stageconfig['account'] = stageaccount.copy()
TPS_stage = TPSTestRunner(self.extensionDir,
testfile=self.testfile,
logfile=self.logfile,
binary=binary,
config=stageconfig,
rlock=self.rlock,
mobile=False)#, autolog=self.autolog)
TPS_stage.run_tests()
# ... and again via the staging server, if credentials are present
stageaccount = self.config.get('stageaccount')
if stageaccount:
username = stageaccount.get('username')
password = stageaccount.get('password')
passphrase = stageaccount.get('passphrase')
if username and password and passphrase:
stageconfig = self.config.copy()
stageconfig['account'] = stageaccount.copy()
TPS_stage = TPSTestRunner(self.extensionDir,
testfile=self.testfile,
logfile=self.logfile,
binary=binary,
config=stageconfig,
rlock=self.rlock,
mobile=False)#, autolog=self.autolog)
TPS_stage.run_tests()

View File

@ -88,6 +88,7 @@ skip-if = os == "android"
[include:services/common/tests/unit/xpcshell.ini]
[include:services/crypto/tests/unit/xpcshell.ini]
[include:services/crypto/components/tests/unit/xpcshell.ini]
[include:services/datareporting/tests/xpcshell/xpcshell.ini]
[include:services/healthreport/tests/xpcshell/xpcshell.ini]
[include:services/metrics/tests/xpcshell/xpcshell.ini]
[include:services/sync/tests/unit/xpcshell.ini]