Bug 719455 - Update TestPilot from version 1.1.2 to version 1.2.2. r=dao

This commit is contained in:
Gregg Lind 2012-10-07 12:13:02 -04:00
Родитель 3aa11667d0
Коммит e8a96022c7
30 изменённых файлов: 1311 добавлений и 307 удалений

Просмотреть файл

@ -1,5 +1,6 @@
resource testpilot ./
content testpilot content/
locale testpilot en-US locale/en-US/
skin testpilot skin skin/all/
skin testpilot-os skin skin/linux/ os=Linux
skin testpilot-os skin skin/linux/ os=SunOS
@ -10,6 +11,7 @@ overlay chrome://browser/content/macBrowserOverlay.xul chrome://testpilot/conten
overlay chrome://browser/content/browser.xul chrome://testpilot/content/browser.xul
style chrome://global/content/customizeToolbar.xul chrome://testpilot/content/browser.css
# For the menubar on Mac
overlay chrome://testpilot/content/all-studies-window.xul chrome://browser/content/macBrowserOverlay.xul application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} os=Darwin

Просмотреть файл

@ -23,9 +23,8 @@
-moz-padding-start: 5px;
}
#pilot-notifications-button {
margin-right: 10px;
#tp-notification-popup-icon {
list-style-image: url("chrome://testpilot/skin/testpilot_16x16.png");
}
/* Popup Bounding Box */
@ -36,25 +35,15 @@
margin-top: -6px;
margin-right: -3px;
width: 480px;
/* FIXES: #725850 based on #717262
/* Needed whilst we support Gecko < 13 */
border-image: url(chrome://testpilot-os/skin/notification-tail-up.png) 26 56 22 18 / 26px 56px 22px 18px round stretch;
/* Supported in Gecko >= 13 */
border-image: url(chrome://testpilot-os/skin/notification-tail-up.png) 26 50 22 18 fill stretch;
border-width: 26px 56px 22px 18px;
border-style: solid;
}
.tail-up {
border-width: 26px 56px 22px 18px;
border-style: solid;
-moz-border-image: url(chrome://testpilot-os/skin/notification-tail-up.png) 26 56 22 18 fill round stretch;
}
/* tail-down uses the old styling; it doesn't look as good as the new styling,
but the new styling doesn't work on 3.6.
TODO: If someone is using 3.7.* or 4.* but is NOT on the beta channel and
installed Test Pilot from AMO, they should get the new styling, similar
to .tail-up! */
.tail-down {
border-width: 26px 50px 22px 18px;
border-style: solid;
-moz-border-image: url(chrome://testpilot/skin/notification-tail-down.png) 26 50 22 18 fill repeat;
color: white;
}
.pilot-notification-popup-container {
-moz-appearance: none;
@ -67,9 +56,14 @@
margin-bottom: 12px;
}
#pilot-notification-text,
#pilot-notification-text {
margin-bottom: 5px;
}
#pilot-notification-link {
margin-bottom: 5px;
text-decoration: underline;
cursor: pointer;
}
#pilot-notification-close {
@ -240,11 +234,6 @@ image.results-thumbnail {
margin: 10px;
}
.notification-link {
text-decoration: underline;
cursor: pointer;
}
prefpane .groupbox-body {
-moz-appearance: none;
padding: 8px 4px 4px 4px;

Просмотреть файл

@ -4,6 +4,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://testpilot/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://testpilot-os/skin/feedback.css" type="text/css"?>
<!DOCTYPE overlay [
<!ENTITY % testpilotDTD SYSTEM "chrome://testpilot/locale/main.dtd">

Просмотреть файл

@ -1,13 +1,28 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<!-- 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 PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>Test Pilot Debug Page</title>
<script type="text/javascript" src="jquery.min.js"></script>
<script src="experiment-page.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
var numstartupattempts=0;
function startup() {
numstartupattempts += 1;
dump('numattempts: ' + numstartupattempts + "\n");
var dropsuccess = populateFileDropdown();
if (! dropsuccess) {
$('h1#status').fadeIn('slow','linear');
window.setTimeout(function() {startup()}, 2000); // try again!
} else {
showSelectedTaskStatus()
showIndexFileDropdown();
$('h1#status').fadeOut("slow", "linear");
};
};
function getEid() {
var selector = document.getElementById("task-selector");
var i = selector.selectedIndex;
@ -30,7 +45,7 @@
if (errors.length > 0) {
str = "<ul>";
for each (let errStr in errors) {
str += "<li>" + errStr + "</li>";
str += "<li>" + errStr + "</li>";
}
str += "</ul>";
} else {
@ -62,7 +77,7 @@
[jarStore, loader.fs])});
dump("Debug page Requiring toolbar study.\n");
var toolbarStudy = clientLoader.require("toolbar-study");
}
function remindMe() {
@ -74,6 +89,7 @@
function getCodeStorage() {
Components.utils.import("resource://testpilot/modules/setup.js");
var loader = TestPilotSetup._remoteExperimentLoader;
if (! loader) {return undefined;}
return loader._jarStore;
}
@ -136,6 +152,8 @@
function populateFileDropdown() {
var codeStore = getCodeStorage();
if (! codeStore ) { return false }
var files = codeStore.listAllFiles();
var selector = document.getElementById("file-selector");
var opt, i;
@ -155,6 +173,7 @@
opt.setAttribute("value", tasks[i].id);
selector.appendChild(opt);
}
return true;
}
function showSelectedTaskStatus() {
@ -216,6 +235,9 @@
}
}
$(function(){startup()});
</script>
<style type="text/css">
@ -224,7 +246,8 @@
</head>
<body onload="populateFileDropdown();showSelectedTaskStatus();showIndexFileDropdown();">
<body>
<h1 id='status'><blink>loading experiments</blink></h1> <!-- take that, web standards! -->
<fieldset>
<p><select id="task-selector" onchange="showSelectedTaskStatus();"></select> Current Status = <span id="show-status-span"></span>.
@ -252,7 +275,7 @@ or set it to
<button onclick="reloadAllExperiments();">Reload All Experiments</button>
<button onclick="remindMe();">Notify Me</button>
<button onclick="testJarStore();">Test Jar Store</button>
Index file:
Index file:
<select id="index-file-selector" onchange="setSelectedIndexFile();">
<option value="index.json">index.json</option>
<option value="index-dev.json">index-dev.json</option>

Просмотреть файл

@ -158,7 +158,7 @@ var stringBundle;
}
// write, create, truncate
foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0);
foStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
converter.init(foStream, "UTF-8", 0, 0);
converter.writeString(csvString);
converter.close();

Просмотреть файл

@ -0,0 +1,428 @@
/* 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/. */
const PAGE_TYPE_STATUS = 0;
const PAGE_TYPE_QUIT = 1;
var stringBundle;
function showRawData(experimentId) {
window.openDialog(
"chrome://testpilot/content/raw-data-dialog.xul",
"TestPilotRawDataDialog", "chrome,centerscreen,resizable,scrollbars",
experimentId);
}
function getUrlParam(name) {
// from http://www.netlobo.com/url_query_string_javascript.html
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.href);
if( results == null )
return "";
else
return results[1];
}
function uploadData() {
Components.utils.import("resource://testpilot/modules/setup.js");
let eid = getUrlParam("eid");
let task = TestPilotSetup.getTaskById(eid);
// If always-submit-checkbox is checked, set the pref
if (task._recursAutomatically) {
let checkBox = document.getElementById("always-submit-checkbox");
if (checkBox && checkBox.checked) {
task.setRecurPref(TaskConstants.ALWAYS_SUBMIT);
}
}
// Study web content must provide an element with id 'upload-status'.
// Fill it first with a message about data being uploaded; if there's
// an error, replace it with the error message.
let uploadStatus = document.getElementById("upload-status");
uploadStatus.innerHTML =
stringBundle.GetStringFromName("testpilot.statusPage.uploadingData");
task.upload( function(success) {
if (success) {
window.location =
"chrome://testpilot/content/status.html?eid=" + eid;
} else {
// Replace 'now uploading' message
let errorParagraph = document.createElement("p");
errorParagraph.innerHTML = stringBundle.GetStringFromName("testpilot.statusPage.uploadErrorMsg");
let willRetryParagraph = document.createElement("p");
willRetryParagraph.innerHTML = stringBundle.GetStringFromName("testpilot.statusPage.willRetry");
uploadStatus.innerHTML = "";
uploadStatus.appendChild(errorParagraph);
uploadStatus.appendChild(willRetryParagraph);
}
});
}
function deleteData() {
Components.utils.import("resource://testpilot/modules/setup.js");
Components.utils.import("resource://testpilot/modules/tasks.js");
let eid = getUrlParam("eid");
let task = TestPilotSetup.getTaskById(eid);
task.dataStore.wipeAllData();
// reload the URL after wiping all data.
window.location = "chrome://testpilot/content/status.html?eid=" + eid;
}
function saveCanvas(canvas) {
const nsIFilePicker = Components.interfaces.nsIFilePicker;
let fp = Components.classes["@mozilla.org/filepicker;1"].
createInstance(nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) {
if (aResult == nsIFilePicker.returnOK ||
aResult == nsIFilePicker.returnReplace) {
const nsIWebBrowserPersist =
Components.interfaces.nsIWebBrowserPersist;
let file = fp.file;
// create a data url from the canvas and then create URIs of the
// source and targets
let io = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
let source = io.newURI(canvas.toDataURL("image/png"), "UTF8", null);
let target = io.newFileURI(file);
// prepare to save the canvas data
let persist = Components.classes[
"@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].
createInstance(nsIWebBrowserPersist);
persist.persistFlags = nsIWebBrowserPersist.
PERSIST_FLAGS_REPLACE_EXISTING_FILES;
persist.persistFlags |= nsIWebBrowserPersist.
PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
// displays a download dialog (remove these 3 lines for silent download)
let xfer = Components.classes["@mozilla.org/transfer;1"].
createInstance(Components.interfaces.nsITransfer);
xfer.init(source, target, "", null, null, null, persist, false);
persist.progressListener = xfer;
// save the canvas data to the file
persist.saveURI(source, null, null, null, null, file, null);
}
};
fp.init(window, null, nsIFilePicker.modeSave);
fp.appendFilters(nsIFilePicker.filterImages | nsIFilePicker.filterAll);
fp.defaultString = "canvas.png";
fp.open(fpCallback);
}
function exportData() {
const nsIFilePicker = Components.interfaces.nsIFilePicker;
let eid = getUrlParam("eid");
let task = TestPilotSetup.getTaskById(eid);
let fp = Components.classes["@mozilla.org/filepicker;1"].
createInstance(nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) {
if (aResult == nsIFilePicker.returnOK ||
aResult == nsIFilePicker.returnReplace) {
const nsIWebBrowserPersist =
Components.interfaces.nsIWebBrowserPersist;
let foStream =
Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
let converter =
Components.classes["@mozilla.org/intl/converter-output-stream;1"].
createInstance(Components.interfaces.nsIConverterOutputStream);
let file = fp.file;
let dataStore = task.dataStore;
let columnNames = dataStore.getHumanReadableColumnNames();
let propertyNames = dataStore.getPropertyNames();
let csvString = "";
// titles
for (let i = 0; i < columnNames.length; i++) {
csvString += "\"" + columnNames[i] + "\",";
}
if (csvString.length > 0) {
csvString = csvString.substring(0, (csvString.length - 1));
csvString += "\n";
}
dataStore.getAllDataAsJSON(true, function(rawData) {
// data
for (let i = 0; i < rawData.length; i++) {
for (let j = 0; j < columnNames.length; j++) {
csvString += "\"" + rawData[i][propertyNames[j]] + "\",";
}
csvString = csvString.substring(0, (csvString.length - 1));
csvString += "\n";
}
// write, create, truncate
foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0);
converter.init(foStream, "UTF-8", 0, 0);
converter.writeString(csvString);
converter.close();
});
}
};
fp.init(window, null, nsIFilePicker.modeSave);
fp.appendFilters(nsIFilePicker.filterImages | nsIFilePicker.filterAll);
fp.defaultString = task.title + ".csv";
fp.open(fpCallback);
}
function openLink(url) {
// open the link in the chromeless window
let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
let recentWindow = wm.getMostRecentWindow("navigator:browser");
if (recentWindow) {
recentWindow.TestPilotWindowUtils.openInTab(url);
} else {
window.open(url);
}
}
function getTestEndingDate(experimentId) {
Components.utils.import("resource://testpilot/modules/setup.js");
var task = TestPilotSetup.getTaskById(experimentId);
var endDate = new Date(task.endDate);
var diff = (endDate - Date.now());
var span = document.getElementById("test-end-time");
if (!span) {
return;
}
if (diff < 0) {
span.innerHTML =
stringBundle.GetStringFromName("testpilot.statusPage.endedAlready");
return;
}
var hours = diff / (60 * 60 * 1000);
if (hours < 24) {
span.innerHTML =
stringBundle.formatStringFromName(
"testpilot.statusPage.todayAt", [endDate.toLocaleTimeString()], 1);
} else {
span.innerHTML =
stringBundle.formatStringFromName(
"testpilot.statusPage.endOn", [endDate.toLocaleString()], 1);
}
}
function showMetaData() {
Components.utils.import("resource://testpilot/modules/metadata.js");
Components.utils.import("resource://gre/modules/PluralForm.jsm");
MetadataCollector.getMetadata(function(md) {
var mdLocale = document.getElementById("md-locale");
if (mdLocale)
mdLocale.innerHTML = md.location;
var mdVersion = document.getElementById("md-version");
if (mdVersion)
mdVersion.innerHTML = md.version;
var mdOs = document.getElementById("md-os");
if (mdOs)
mdOs.innerHTML = md.operatingSystem;
var mdNumExt = document.getElementById("md-num-ext");
if (mdNumExt) {
// This computes the correctly localized singular or plural string
// of the number of extensions, e.g. "1 extension", "2 extensions", etc.
let str = stringBundle.GetStringFromName("testpilot.statusPage.numExtensions");
var numExt = md.extensions.length;
mdNumExt.innerHTML = PluralForm.get(numExt, str).replace("#1", numExt);
}
});
}
function onQuitPageLoad() {
Components.utils.import("resource://testpilot/modules/setup.js");
setStrings(PAGE_TYPE_QUIT);
let eid = getUrlParam("eid");
let task = TestPilotSetup.getTaskById(eid);
let header = document.getElementById("about-quit-title");
header.innerHTML =
stringBundle.formatStringFromName(
"testpilot.quitPage.aboutToQuit", [task.title], 1);
if (task._recursAutomatically) {
document.getElementById("recur-options").setAttribute("style", "");
document.getElementById("recur-checkbox-container").
setAttribute("style", "");
}
}
function quitExperiment() {
Components.utils.import("resource://testpilot/modules/setup.js");
Components.utils.import("resource://testpilot/modules/tasks.js");
let eid = getUrlParam("eid");
let reason = document.getElementById("reason-for-quit").value;
let task = TestPilotSetup.getTaskById(eid);
task.optOut(reason, function(success) {
// load the you-are-canceleed page.
window.location = "chrome://testpilot/content/status.html?eid=" + eid;
});
// If opt-out-forever checkbox is checked, opt out forever!
if (task._recursAutomatically) {
let checkBox = document.getElementById("opt-out-forever");
if (checkBox.checked) {
task.setRecurPref(TaskConstants.NEVER_SUBMIT);
}
// quit test so rescheduling
task._reschedule();
}
}
function updateRecurSettings() {
Components.utils.import("resource://testpilot/modules/setup.js");
let eid = getUrlParam("eid");
let experiment = TestPilotSetup.getTaskById(eid);
let recurSelector = document.getElementById("recur-selector");
let newValue = recurSelector.options[recurSelector.selectedIndex].value;
experiment.setRecurPref(parseInt(newValue));
}
function showRecurControls(experiment) {
Components.utils.import("resource://testpilot/modules/tasks.js");
let recurPrefSpan = document.getElementById("recur-pref");
if (!recurPrefSpan) {
return;
}
let days = experiment._recurrenceInterval;
recurPrefSpan.innerHTML =
stringBundle.formatStringFromName(
"testpilot.statusPage.recursEveryNumberOfDays", [days], 1);
let controls = document.getElementById("recur-controls");
let selector = document.createElement("select");
controls.appendChild(selector);
selector.setAttribute("onchange", "updateRecurSettings();");
selector.setAttribute("id", "recur-selector");
let option = document.createElement("option");
option.setAttribute("value", TaskConstants.ASK_EACH_TIME);
if (experiment.recurPref == TaskConstants.ASK_EACH_TIME) {
option.setAttribute("selected", "true");
}
option.innerHTML =
stringBundle.GetStringFromName(
"testpilot.statusPage.askMeBeforeSubmitData");
selector.appendChild(option);
option = document.createElement("option");
option.setAttribute("value", TaskConstants.ALWAYS_SUBMIT);
if (experiment.recurPref == TaskConstants.ALWAYS_SUBMIT) {
option.setAttribute("selected", "true");
}
option.innerHTML =
stringBundle.GetStringFromName(
"testpilot.statusPage.alwaysSubmitData");
selector.appendChild(option);
option = document.createElement("option");
option.setAttribute("value", TaskConstants.NEVER_SUBMIT);
if (experiment.recurPref == TaskConstants.NEVER_SUBMIT) {
option.setAttribute("selected", "true");
}
option.innerHTML =
stringBundle.GetStringFromName(
"testpilot.statusPage.neverSubmitData");
selector.appendChild(option);
}
function loadExperimentPage() {
Components.utils.import("resource://testpilot/modules/setup.js");
Components.utils.import("resource://testpilot/modules/tasks.js");
var contentDiv = document.getElementById("experiment-specific-text");
var dataPrivacyDiv = document.getElementById("data-privacy-text");
// Get experimentID from the GET args of page
var eid = getUrlParam("eid");
var experiment = TestPilotSetup.getTaskById(eid);
if (!experiment) {
// Possible that experiments aren't done loading yet. Try again in
// a few seconds.
contentDiv.innerHTML =
stringBundle.GetStringFromName("testpilot.statusPage.loading");
window.setTimeout(function() { loadExperimentPage(); }, 2000);
return;
}
// Let the experiment fill in its web content (asynchronous)
experiment.getWebContent(function(webContent) {
contentDiv.innerHTML = webContent;
// Metadata and start/end date should be filled in for every experiment:
showMetaData();
getTestEndingDate(eid);
if (experiment._recursAutomatically &&
experiment.status != TaskConstants.STATUS_FINISHED) {
showRecurControls(experiment);
}
// Do whatever the experiment's web content wants done on load
// (Usually drawing a graph) - must be done after innerHTML is set.
experiment.webContent.onPageLoad(experiment, document, jQuery);
});
experiment.getDataPrivacyContent(function(dataPrivacyContent) {
if (dataPrivacyContent && dataPrivacyContent.length > 0) {
dataPrivacyDiv.innerHTML = dataPrivacyContent;
dataPrivacyDiv.removeAttribute("hidden");
}
});
}
function onStatusPageLoad() {
setStrings(PAGE_TYPE_STATUS);
/* An experiment ID (eid) must be provided in the url params. Show status
* for that experiment.*/
loadExperimentPage();
}
function setStrings(pageType) {
stringBundle =
Components.classes["@mozilla.org/intl/stringbundle;1"].
getService(Components.interfaces.nsIStringBundleService).
createBundle("chrome://testpilot/locale/main.properties");
let map;
let mapLength;
if (pageType == PAGE_TYPE_STATUS) {
map = [
{ id: "page-title", stringKey: "testpilot.fullBrandName" },
{ id: "comments-and-discussions-link",
stringKey: "testpilot.page.commentsAndDiscussions" },
{ id: "propose-test-link",
stringKey: "testpilot.page.proposeATest" },
{ id: "testpilot-twitter-link",
stringKey: "testpilot.page.testpilotOnTwitter" }
];
} else if (pageType == PAGE_TYPE_QUIT) {
map = [
{ id: "page-title", stringKey: "testpilot.fullBrandName" },
{ id: "comments-and-discussions-link",
stringKey: "testpilot.page.commentsAndDiscussions" },
{ id: "propose-test-link",
stringKey: "testpilot.page.proposeATest" },
{ id: "testpilot-twitter-link",
stringKey: "testpilot.page.testpilotOnTwitter" },
{ id: "optional-message",
stringKey: "testpilot.quitPage.optionalMessage" },
{ id: "reason-text",
stringKey: "testpilot.quitPage.reason" },
{ id: "recur-options",
stringKey: "testpilot.quitPage.recurringStudy" },
{ id: "quit-forever-text",
stringKey: "testpilot.quitPage.quitForever" },
{ id: "quit-study-link",
stringKey: "testpilot.quitPage.quitStudy" }
];
}
mapLength = map.length;
for (let i = 0; i < mapLength; i++) {
let entry = map[i];
document.getElementById(entry.id).innerHTML =
stringBundle.GetStringFromName(entry.stringKey);
}
}

Просмотреть файл

@ -0,0 +1,21 @@
--- content/experiment-page.js
+++ content/experiment-page.js
@@ -156,17 +156,17 @@
for (let j = 0; j < columnNames.length; j++) {
csvString += "\"" + rawData[i][propertyNames[j]] + "\",";
}
csvString = csvString.substring(0, (csvString.length - 1));
csvString += "\n";
}
// write, create, truncate
- foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0);
+ foStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
converter.init(foStream, "UTF-8", 0, 0);
converter.writeString(csvString);
converter.close();
});
}
}
function openLink(url) {

Просмотреть файл

@ -3,9 +3,6 @@
- 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/. -->
<?xml-stylesheet href="chrome://testpilot/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://testpilot-os/skin/feedback.css" type="text/css"?>
<!DOCTYPE overlay [
<!ENTITY % testpilotDTD SYSTEM "chrome://testpilot/locale/main.dtd">
%testpilotDTD;

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,67 @@
<?xml version="1.0"?>
<!DOCTYPE bindings [
<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
%notificationDTD;
]>
<bindings id="popupBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="testpilot-notification"
extends="chrome://global/content/bindings/notification.xml#popup-notification">
<!-- Remember to keep this up to date with the changes to base binding
at http://hg.mozilla.org/mozilla-central/file/tip/toolkit/content/widgets/notification.xml-->
<content align="start">
<xul:image class="popup-notification-icon" anonid="notification-icon"
xbl:inherits="popupid"/>
<xul:vbox flex="1">
<xul:label anonid="notification-title" class="testpilot-notification-title"/>
<xul:description class="popup-notification-description"
xbl:inherits="xbl:text=label"/>
<xul:spacer flex="1"/>
<xul:hbox class="popup-notification-button-container"
pack="end" align="center">
<xul:button anonid="button"
class="popup-notification-menubutton"
type="menu-button"
xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
<xul:menupopup anonid="menupopup"
xbl:inherits="oncommand=menucommand">
<children/>
<xul:menuitem class="menuitem-iconic popup-notification-closeitem"
label="&closeNotificationItem.label;"
xbl:inherits="oncommand=closeitemcommand"/>
</xul:menupopup>
</xul:button>
</xul:hbox>
</xul:vbox>
<xul:vbox pack="start">
<xul:toolbarbutton anonid="closebutton"
class="messageCloseButton popup-notification-closebutton tabbable"
xbl:inherits="oncommand=closebuttoncommand"
tooltiptext="&closeNotification.tooltip;"/>
</xul:vbox>
</content>
<implementation>
<constructor><![CDATA[
this.title.value = this.notification.options.title;
this.icon.classList.add(this.notification.options.iconClass);
this.closebutton.addEventListener("command",
this.notification.options.closeButtonFunc,
false);
]]>
</constructor>
<field name="title" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "notification-title");
</field>
<field name="icon" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "notification-icon");
</field>
</implementation>
</binding>
</bindings>

Просмотреть файл

@ -3,51 +3,40 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
padding-bottom: 0px;
padding-bottom: 0px;
padding-top: 20px;
padding-left: 0px;
padding-right: 0px;
}
body {
body {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Lucida, Arial, Helvetica, sans-serif;
font-family: 'DroidSans';
background: #fff url('chrome://testpilot/skin/bg.jpg') repeat-x top center;
padding: 0px;
padding: 0px;
color:#787878;
font-size:12px;
line-height: 18px;
margin: 0 auto;
}
h1 {font-family: 'DroidSans'; color: #3a3a3a; font-size: 28px; font-weight: normal; letter-spacing: -1px}
h2 {font-family: 'DroidSans'; color: #54717b; font-size: 24px; line-height: 22px;font-weight: normal; letter-spacing: -1px}
h1 {font-family: 'sans-serif'; color: #3a3a3a; font-size: 28px; font-weight: normal; letter-spacing: -1px}
h2 {font-family: 'sans-serif'; color: #54717b; font-size: 24px; line-height: 22px;font-weight: normal; letter-spacing: -1px}
p, ul {font-size: 12px;}
a:link, a:hover, a:active, a:focus, a:visited {color: #9f423b; text-decoration: none;}
a:hover {color: #9f423b; text-decoration: none;}
.bold {font-family: 'DroidSans-Bold'; color:#3a3a3a;}
.bold {color:#3a3a3a;}
.address {margin-left: 20px;}
.inactive {color:#ccc;}
li {list-style-type: circle;}
.spacer {height: 75px;}
@font-face{
font-family: 'DroidSans';
src: url('chrome://testpilot/skin/fonts/DroidSans.ttf') format('truetype');
}
@font-face{
font-family: 'DroidSans-Bold';
src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
}
#container {
margin: 0px auto;
width: 950px;
@ -61,14 +50,14 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
#contentWelcome {
margin-top: 120px;
padding: 8px 24px 24px 24px;
font-family: 'DroidSans';
font-family: 'sans-serif';
text-align: left;
}
#content {
margin-top: 60px;
padding: 8px 24px 24px 24px;
font-family: 'DroidSans';
font-family: 'sans-serif';
text-align: left;
}
@ -78,16 +67,6 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
margin-top: 240px;
}
.downloadButton {
margin-bottom: -10px;
margin-top: -2px;
margin-right: 12px;
}
.downloadH1 {font-family: 'DroidSans-bold'; color: #3a3a3a; font-size: 24px; font-weight: normal; letter-spacing: -1px; margin-right: 8px;}
.downloadH2 {font-family: 'DroidSans'; color: #3a3a3a; font-size: 22px; font-weight: normal; letter-spacing: -1px; line-height: 28px; margin-left: 46px;}
#intro {
float: left;
width: 500px;
@ -102,60 +81,60 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
}
.button {
font-family: 'DroidSans';
font-family: 'sans-serif';
font-size: 16px;
padding: 8px 12px;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 1px 1px,
inset rgba(255, 255, 255, 1) 0 3px 1px,
inset rgba(255, 255, 255, 0.3) 0 16px 0px,
inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
rgba(255, 255, 255, 1) 0 1px,
rgba(133, 153, 166, 0.3) 0px 1px 12px;
background-color: #e7eaec;
//display: inline;
/*display: inline;*/
}
.home_button {
font-family: 'DroidSans';
font-family: 'sans-serif';
font-size: 16px;
padding: 8px 12px;
width: 240px;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 1px 1px,
inset rgba(255, 255, 255, 1) 0 3px 1px,
inset rgba(255, 255, 255, 0.3) 0 16px 0px,
inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
rgba(255, 255, 255, 1) 0 1px,
rgba(133, 153, 166, 0.3) 0px 1px 12px;
background-color: #e7eaec;
//display: inline;
/*display: inline;*/
}
.callout {
font-family: 'DroidSans';
font-family: 'sans-serif';
font-size: 16px;
padding: 8px 24px;
margin: 24px auto;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/callout.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 12px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 12px;
//display: inline;
/*display: inline;*/
}
.home_callout {
font-family: 'DroidSans';
font-family: 'sans-serif';
font-size: 16px;
vertical-align: middle;
width: 240px;
@ -164,11 +143,11 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/callout.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 12px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 12px;
//display: inline;
/*display: inline;*/
}
.homeIcon {
@ -189,7 +168,7 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
margin-right: 6px;
}
/* ------- MENU -------
/* ------- MENU -------
#menu {
margin: 20px auto;
@ -204,7 +183,7 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
border-bottom: 3px solid #adb6ba;
-moz-border-bottom-colors:#adb6ba #e7eaec #e7eaec;
background-color: #fff;
}
}
*/
@ -222,7 +201,7 @@ src: url('chrome://testpilot/skin/fonts/DroidSans-Bold.ttf') format('truetype');
font-size: 14px;
text-shadow: 1px 1px 1px rgba(173, 182, 186, 1);
background-color: rgba(173, 182, 186, 0.3);
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 -10px 12px;
padding: 9px 8px 8px 8px;
}

Просмотреть файл

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://testpilot/content/browser.css" type="text/css"?>
<!DOCTYPE overlay [
<!ENTITY % testpilotDTD SYSTEM "chrome://testpilot/locale/main.dtd">
%testpilotDTD;
]>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<statusbar id="status-bar">
<statusbarpanel id="pilot-notifications-button"
class="statusbarpanel-iconic"
insertbefore="security-button"
onmousedown="
event.preventDefault();
TestPilotMenuUtils.onMenuButtonMouseDown();"
image="chrome://testpilot/skin/testpilot_16x16.png"/>
<panel id="pilot-notification-popup" hidden="true" noautofocus="true"
level="parent" position="after_start">
<vbox class="pilot-notification-popup-container">
<hbox class="pilot-notification-toprow">
<image id="pilot-notification-icon" />
<vbox pack="center">
<label id="pilot-notification-title" class="pilot-title" />
</vbox>
<spacer flex="1" />
<vbox pack="start">
<image id="pilot-notification-close"
tooltiptext="&testpilot.notification.close.tooltip;" />
</vbox>
</hbox>
<description id="pilot-notification-text" />
<hbox align="right"><label id="pilot-notification-link" /></hbox>
<hbox>
<checkbox id="pilot-notification-always-submit-checkbox"
label="&testpilot.settings.alwaysSubmitData.shortLabel;" />
<spacer flex="1" />
</hbox>
<hbox align="right">
<button id="pilot-notification-submit" />
</hbox>
</vbox>
</panel>
</statusbar>
</overlay>

Просмотреть файл

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://testpilot/content/browser.css" type="text/css"?>
<!DOCTYPE overlay [
<!ENTITY % testpilotDTD SYSTEM "chrome://testpilot/locale/main.dtd">
%testpilotDTD;
]>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<!-- anchor for the fx4 style notifications -->
<toolbar id="nav-bar">
<box id="tp-notification-popup-box" hidden="true" align="center">
<image id="tp-notification-popup-icon" class="notification-anchor-icon" role="button"/>
</box>
<panel id="testpilot-notification-popup" type="arrow" position="after_start"
hidden="true" orient="vertical" role="alert">
</panel>
</toolbar>
</overlay>

Просмотреть файл

@ -54,15 +54,8 @@
</menupopup>
</menu>
<statusbar id="status-bar">
<statusbarpanel id="pilot-notifications-button"
class="statusbarpanel-iconic"
insertbefore="security-button"
onmousedown="
event.preventDefault();
TestPilotMenuUtils.onMenuButtonMouseDown();"
image="chrome://testpilot/skin/testpilot_16x16.png"/>
<toolbar id="nav-bar">
<image id="tp-notification-popup-icon" role="button" hidden="true"/>
<panel id="pilot-notification-popup" hidden="true" noautofocus="true"
level="parent" position="after_start">
<vbox class="pilot-notification-popup-container">
@ -78,7 +71,7 @@
</vbox>
</hbox>
<description id="pilot-notification-text" />
<hbox align="right"><label id="pilot-notification-link" /></hbox>
<hbox align="right"><label id="pilot-notification-link"/></hbox>
<hbox>
<checkbox id="pilot-notification-always-submit-checkbox"
label="&testpilot.settings.alwaysSubmitData.label;" />
@ -89,5 +82,5 @@
</hbox>
</vbox>
</panel>
</statusbar>
</toolbar>
</overlay>

Просмотреть файл

@ -49,11 +49,11 @@ var TestPilotWelcomePage = {
{ id: "testpilot-addon-text",
stringKey: "testpilot.welcomePage.testpilotAddon" },
{ id: "icon-explanation-text",
stringKey: "testpilot.welcomePage.iconExplanation" },
stringKey: "testpilot.welcomePage.iconExplanation2" },
{ id: "icon-explanation-more-text",
stringKey: "testpilot.welcomePage.moreIconExplanation" },
stringKey: "testpilot.welcomePage.moreIconExplanation2" },
{ id: "notification-info-text",
stringKey: "testpilot.welcomePage.notificationInfo" },
stringKey: "testpilot.welcomePage.notificationInfo2" },
{ id: "privacy-policy-link",
stringKey: "testpilot.welcomePage.privacyPolicy" },
{ id: "legal-notices-link",

Просмотреть файл

@ -10,11 +10,11 @@
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>testpilot@labs.mozilla.com</em:id>
<em:version>1.1.2</em:version>
<em:version>1.2.2</em:version>
<em:type>2</em:type>
<!-- Target Application this extension can install into,
with minimum and maximum supported versions. -->
<!-- Target Application this extension can install into,
with minimum and maximum supported versions. -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
@ -22,7 +22,7 @@
<em:maxVersion>@FIREFOX_VERSION@</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>Feedback</em:name>
<em:description>Help make Firefox better by giving feedback.</em:description>

Просмотреть файл

@ -0,0 +1,33 @@
<!ENTITY testpilot.brand.label "Test Pilot">
<!ENTITY testpilot.settings.label "Settings">
<!ENTITY testpilot.settings.dataSubmission.label "Data Submission">
<!ENTITY testpilot.settings.notifications.label "Notifications">
<!ENTITY testpilot.settings.notifyWhen.label "Notify me when…">
<!ENTITY testpilot.settings.readyToSubmit.label "A study is ready to submit">
<!ENTITY testpilot.settings.newStudy.label "There's a new study">
<!ENTITY testpilot.settings.hasNewResults.label "A study has new results">
<!ENTITY testpilot.settings.alwaysSubmitData.label "Automatically submit my data (don't ask me)">
<!ENTITY testpilot.allYourStudies.label "All Your User Studies…">
<!ENTITY testpilot.about.label "About Test Pilot">
<!-- all studies window -->
<!ENTITY testpilot.studiesWindow.title "Your Test Pilot Studies">
<!ENTITY testpilot.studiesWindow.currentStudies.label "Current Studies">
<!ENTITY testpilot.studiesWindow.finishedStudies.label "Finished Studies">
<!ENTITY testpilot.studiesWindow.studyFindings.label "Study Findings">
<!ENTITY testpilot.studiesWindow.settings.label "Settings">
<!ENTITY testpilot.studiesWindow.stillLoadingMessage "Loading, please wait…">
<!-- raw data dialog -->
<!ENTITY testpilot.rawDataWindow.title "Test Pilot: Raw Data">
<!-- notification -->
<!ENTITY testpilot.notification.close.tooltip "Close">
<!-- Firefox 4 beta version UI -->
<!ENTITY testpilot.enable.label "Turn On User Studies">
<!ENTITY testpilot.feedbackbutton.label "Feedback">
<!ENTITY testpilot.happy.label "Firefox Made Me Happy Because…">
<!ENTITY testpilot.sad.label "Firefox Made Me Sad Because…">
<!ENTITY testpilot.broken.label "Report this website as broken…">
<!ENTITY testpilot.idea.label "Give us a suggestion…">

Просмотреть файл

@ -0,0 +1,98 @@
# description for add-on manager
extensions.testpilot@labs.mozilla.com.description = Help make Firefox better by running user studies.
# common
testpilot.fullBrandName = Mozilla Labs Test Pilot
testpilot.moreInfo = More Info
testpilot.submit = Submit
testpilot.takeSurvey = Take the Survey
# Feedback button menu
testpilot.turnOn = Turn On User Studies
testpilot.turnOff = Turn Off User Studies
# studies window
testpilot.studiesWindow.noStudies = We are working on a new study now; it will knock on your door soon! Stay Tuned!
testpilot.studiesWindow.uploading = Uploading…
testpilot.studiesWindow.unableToReachServer = Unable to reach Mozilla; please try again later.
testpilot.studiesWindow.thanksForContributing = Thanks for contributing!
testpilot.studiesWindow.finishedOn = Finished on %S
testpilot.studiesWindow.canceledStudy = (You canceled this study)
testpilot.studiesWindow.missedStudy = (You missed this study)
testpilot.studiesWindow.willStart = Will start on %S
testpilot.studiesWindow.gatheringData = Currently gathering data.
testpilot.studiesWindow.willFinish = Will finish on %S
testpilot.studiesWindow.proposeStudy = Propose your own study
# for pages
testpilot.page.commentsAndDiscussions = Comments & Discussions »
testpilot.page.proposeATest = Propose a Test »
testpilot.page.testpilotOnTwitter = @MozTestPilot on Twitter »
# status page
testpilot.statusPage.uploadingData = Now uploading data…
testpilot.statusPage.uploadErrorMsg = Oops! There was an error connecting to the Mozilla servers. Maybe your network connection is down?
testpilot.statusPage.willRetry = Test Pilot will retry automatically, so it's OK to close this page now.
testpilot.statusPage.endedAlready = (It has already ended and you should not be seeing this page)
testpilot.statusPage.todayAt = today, at %S
testpilot.statusPage.endOn = on %S
# LOCALIZATION NOTE (numExtensions): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 = number of extensions
# example: "2 extensions"
testpilot.statusPage.numExtensions = #1 extension;#1 extensions
testpilot.statusPage.recursEveryNumberOfDays = This test recurs every %S days. Each time it completes:
testpilot.statusPage.askMeBeforeSubmitData = Ask me whether I want to submit my data.
testpilot.statusPage.alwaysSubmitData = Always submit my data, and don't ask me about it.
testpilot.statusPage.neverSubmitData = Never submit my data, and don't ask me about it.
testpilot.statusPage.loading = Loading, please wait a moment…
# quit page
testpilot.quitPage.aboutToQuit = You are about to quit the "%S" study.
testpilot.quitPage.optionalMessage = (Optional) If you have a minute, please let us know why you have chosen to quit the study.
testpilot.quitPage.reason = Reason:
testpilot.quitPage.recurringStudy = This is a recurring study. Normally we will let you know the next time we run the study. If you never want to hear about this study again, check the box below:
testpilot.quitPage.quitForever = Quit this recurring study.
testpilot.quitPage.quitStudy = Quit the Study »
# welcome page
testpilot.welcomePage.thankYou = Thank You for Joining the Test Pilot Team!
testpilot.welcomePage.gettingStarted = Getting Started
testpilot.welcomePage.pleaseTake = Please take the
testpilot.welcomePage.backgroundSurvey = Pilot Background Survey
testpilot.welcomePage.clickToOpenStudiesWindow = Click here to see the studies that are currently running.
testpilot.welcomePage.testpilotAddon = Test Pilot Add-on
testpilot.welcomePage.iconExplanation2 = « look for this icon in the Tools menu to find the Test Pilot sub-menu.
testpilot.welcomePage.moreIconExplanation2 = Use the sub-menu to control settings and see what studies are running.
testpilot.welcomePage.notificationInfo2 = Test Pilot will pop up a notification when a study needs your attention.
testpilot.welcomePage.privacyPolicy = Privacy Policy
testpilot.welcomePage.legalNotices = Legal Notices
# survey page
testpilot.surveyPage.saveAnswers = Save Answers
testpilot.surveyPage.submitAnswers = Submit Answers
testpilot.surveyPage.changeAnswers = Change Answers
testpilot.surveyPage.loading = Loading, please wait a moment…
testpilot.surveyPage.thankYouForFinishingSurvey = Thank you for finishing this survey. Your answers will be uploaded along with the next set of experimental data.
testpilot.surveyPage.reviewOrChangeYourAnswers = If you would like to review or change your answers, you can do so at any time using the button below.
# modules/task.js
testpilot.finishedTask.finishedStudy = Excellent! You finished the "%S" Study!
testpilot.finishedTask.allRelatedDataDeleted = All data related to this study has been removed from your computer.
# modules/setup.js
testpilot.notification.update = Update…
testpilot.notification.thankYouForUploadingData = Thanks!
testpilot.notification.thankYouForUploadingData.message = Thank you for uploading your data.
testpilot.notification.readyToSubmit = Ready to Submit
testpilot.notification.readyToSubmit.message = The Test Pilot "%S" study is finished gathering data and is ready to submit.
testpilot.notification.newTestPilotStudy = New Test Pilot Study
testpilot.notification.newTestPilotStudy.pre.message = The Test Pilot "%S" study is about to begin.
testpilot.notification.newTestPilotSurvey = New Test Pilot Survey
testpilot.notification.newTestPilotSurvey.message = %S
testpilot.notification.newTestPilotResults = New Test Pilot Results
testpilot.notification.newTestPilotResults.message = New results are now available for the Test Pilot "%S" study.
testpilot.notification.autoUploadedData = Thank you!
testpilot.notification.autoUploadedData.message = The Test Pilot "%S" study is completed and your data has been submitted!
testpilot.notification.extensionUpdate = Extension Update
testpilot.notification.extensionUpdate.message = One of your studies requires a newer version of Test Pilot. You can get the latest version using the Add-ons window.

Просмотреть файл

@ -15,14 +15,34 @@ EXPORTED_SYMBOLS = ["TestPilotUIBuilder"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const UPDATE_CHANNEL_PREF = "app.update.channel";
const POPUP_SHOW_ON_NEW = "extensions.testpilot.popup.showOnNewStudy";
const POPUP_CHECK_INTERVAL = "extensions.testpilot.popup.delayAfterStartup";
var TestPilotUIBuilder = {
__prefs: null,
get _prefs() {
this.__prefs = Cc["@mozilla.org/preferences-service;1"]
delete this._prefs;
return this._prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
return this.__prefs;
},
get _prefDefaultBranch() {
delete this._prefDefaultBranch;
return this._prefDefaultBranch = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService).getDefaultBranch("");
},
get _comparator() {
delete this._comparator;
return this._comparator = Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator);
},
get _appVersion() {
delete this._appVersion;
return this._appVersion = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo).version;
},
buildTestPilotInterface: function(window) {
@ -34,6 +54,12 @@ var TestPilotUIBuilder = {
feedbackButton = palette.getElementsByAttribute("id", "feedback-menu-button").item(0);
}
feedbackButton.parentNode.removeChild(feedbackButton);
/* Default prefs for test pilot version - default to NOT notifying user about new
* studies starting. Note we're setting default values, not current values -- we
* want these to be overridden by any user set values!!*/
this._prefDefaultBranch.setBoolPref(POPUP_SHOW_ON_NEW, false);
this._prefDefaultBranch.setIntPref(POPUP_CHECK_INTERVAL, 180000);
},
buildFeedbackInterface: function(window) {
@ -62,23 +88,24 @@ var TestPilotUIBuilder = {
} catch (e) {
}
}
/* Pref defaults for Feedback version: default to notifying user about new
* studies starting. Note we're setting default values, not current values -- we
* want these to be overridden by any user set values!!*/
this._prefDefaultBranch.setBoolPref(POPUP_SHOW_ON_NEW, true);
this._prefDefaultBranch.setIntPref(POPUP_CHECK_INTERVAL, 600000);
},
isBetaChannel: function() {
channelUsesFeedback: function() {
// Beta and aurora channels use feedback interface; nightly and release channels don't.
let channel = this._prefs.getCharPref(UPDATE_CHANNEL_PREF);
let channel = this._prefDefaultBranch.getCharPref(UPDATE_CHANNEL_PREF);
return (channel == "beta") || (channel == "betatest") || (channel == "aurora");
},
appVersionIsFinal: function() {
// Return true iff app version >= 4.0 AND there is no "beta" or "rc" in version string.
let appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
let version = appInfo.version;
let versionChecker = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
.getService(Components.interfaces.nsIVersionComparator);
if (versionChecker.compare(version, "4.0") >= 0) {
if (version.indexOf("b") == -1 && version.indexOf("rc") == -1) {
if (this._comparator.compare(this._appVersion, "4.0") >= 0) {
if (this._appVersion.indexOf("b") == -1 && this._appVersion.indexOf("rc") == -1) {
return true;
}
}
@ -93,23 +120,14 @@ var TestPilotUIBuilder = {
return;
}
/* Overlay Feedback XUL if we're in the beta update channel, Test Pilot XUL otherwise.
* Once the overlay is complete, call buildFeedbackInterface() or buildTestPilotInterface(). */
let self = this;
if (this.isBetaChannel()) {
window.document.loadOverlay("chrome://testpilot/content/feedback-browser.xul",
{observe: function(subject, topic, data) {
if (topic == "xul-overlay-merged") {
self.buildFeedbackInterface(window);
}
}});
/* Overlay Feedback XUL if we're in the beta update channel, Test Pilot XUL otherwise, and
* call buildFeedbackInterface() or buildTestPilotInterface(). */
if (this.channelUsesFeedback()) {
window.document.loadOverlay("chrome://testpilot/content/feedback-browser.xul", null);
this.buildFeedbackInterface(window);
} else {
window.document.loadOverlay("chrome://testpilot/content/tp-browser.xul",
{observe: function(subject, topic, data) {
if (topic == "xul-overlay-merged") {
self.buildTestPilotInterface(window);
}
}});
window.document.loadOverlay("chrome://testpilot/content/tp-browser.xul", null);
this.buildTestPilotInterface(window);
}
}
};

Просмотреть файл

@ -71,7 +71,7 @@ JarStore.prototype = {
let istream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
// open for reading
istream.init(jarFile, 0x01, 0444, 0);
istream.init(jarFile, 0x01, parseInt("0444", 8), 0);
let ch = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
// Use SHA256, it's more secure than MD5:
@ -112,7 +112,7 @@ JarStore.prototype = {
jarFile.create( Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
let stream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
stream.init(jarFile, 0x04 | 0x08 | 0x20, 0600, 0); // readwrite, create, truncate
stream.init(jarFile, 0x04 | 0x08 | 0x20, parseInt("0600", 8), 0); // readwrite, create, truncate
stream.write(rawData, rawData.length);
if (stream instanceof Ci.nsISafeOutputStream) {
stream.finish();

Просмотреть файл

@ -0,0 +1,73 @@
/* 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/. */
(function(global) {
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
var exports = {};
var dirsvc = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
function MozFile(path) {
var file = Cc['@mozilla.org/file/local;1']
.createInstance(Ci.nsILocalFile);
file.initWithPath(path);
return {
get directoryEntries() {
try {
return file.directoryEntries;
} catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
throw new Error("path does not exist: " + file.path);
}
},
__proto__: file
};
}
exports.join = function join(base) {
if (arguments.length < 2)
throw new Error("need at least 2 args");
base = MozFile(base);
for (var i = 1; i < arguments.length; i++)
base.append(arguments[i]);
return base.path;
};
exports.dirname = function dirname(path) {
return MozFile(path).parent.path;
};
exports.list = function list(path) {
var entries = MozFile(path).directoryEntries;
var entryNames = [];
while(entries.hasMoreElements()) {
var entry = entries.getNext();
entry.QueryInterface(Ci.nsIFile);
entryNames.push(entry.leafName);
}
return entryNames;
};
if (global.window) {
// We're being loaded in a chrome window, or a web page with
// UniversalXPConnect privileges.
global.File = exports;
} else if (global.exports) {
// We're being loaded in a SecurableModule.
for (name in exports) {
global.exports[name] = exports[name];
}
} else {
// We're being loaded in a JS module.
global.EXPORTED_SYMBOLS = [];
for (name in exports) {
global.EXPORTED_SYMBOLS.push(name);
global[name] = exports[name];
}
}
})(this);

Просмотреть файл

@ -88,7 +88,7 @@
exports.SandboxFactory.prototype = {
createSandbox: function createSandbox(options) {
var principal = resolvePrincipal(options.principal,
var principal = resolvePrincipal('b' in options ? options.b : undefined,
this._defaultPrincipal);
return {

Просмотреть файл

@ -0,0 +1,216 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* The TestPilotSetup object will choose one of these implementations to instantiate.
* The interface for all NotificationManager implementations is:
showNotification: function(window, features, choices) {},
hideNotification: function(window) {}
*/
EXPORTED_SYMBOLS = ["CustomNotificationManager", "PopupNotificationManager"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
/* CustomNotificationManager: the one where notifications
* come up from the Test Pilot icon in the addon bar. For Firefox 3.6. */
function CustomNotificationManager(anchorId, tailIsUp) {
this._anchorId = anchorId;
this._tailIsUp = tailIsUp;
}
CustomNotificationManager.prototype = {
showNotification: function TP_OldNotfn_showNotification(window, features, choices) {
let doc = window.document;
let popup = doc.getElementById("pilot-notification-popup");
let textLabel = doc.getElementById("pilot-notification-text");
let titleLabel = doc.getElementById("pilot-notification-title");
let icon = doc.getElementById("pilot-notification-icon");
let button = doc.getElementById("pilot-notification-submit");
let closeBtn = doc.getElementById("pilot-notification-close");
let link = doc.getElementById("pilot-notification-link");
let checkbox = doc.getElementById("pilot-notification-always-submit-checkbox");
let self = this;
let buttonChoice = null;
let linkChoice = null;
let checkBoxChoice = null;
if (this._tailIsUp) {
popup.setAttribute("class", "tail-up");
} else {
popup.setAttribute("class", "tail-down");
}
popup.setAttribute("noautohide", !(features.fragile));
if (features.title) {
titleLabel.setAttribute("value", features.title);
}
while (textLabel.lastChild) {
textLabel.removeChild(textLabel.lastChild);
}
if (features.text) {
textLabel.appendChild(doc.createTextNode(features.text));
}
if (features.iconClass) {
// css will set the image url based on the class.
icon.setAttribute("class", features.iconClass);
}
/* Go through the specified choices and figure out which one to turn into a link, which one
* (if any) to turn into a button, and which one (if any) to turn into a check box. */
for (let i = 0; i < choices.length; i++) {
switch(choices[i].customUiType) {
case "button":
buttonChoice = choices[i];
break;
case "link":
linkChoice = choices[i];
break;
case "checkbox":
checkBoxChoice = choices[i];
break;
}
}
// Create check box if specified:
if (checkBoxChoice) {
checkbox.removeAttribute("hidden");
checkbox.setAttribute("label", checkBoxChoice.label);
} else {
checkbox.setAttribute("hidden", true);
}
// Create button if specified:
if (buttonChoice) {
button.setAttribute("label", buttonChoice.label);
button.onclick = function(event) {
if (event.button == 0) {
if (checkbox.checked && checkBoxChoice) {
checkBoxChoice.callback();
}
buttonChoice.callback();
self.hideNotification(window);
if (features.closeCallback) {
features.closeCallback();
}
}
};
button.removeAttribute("hidden");
} else {
button.setAttribute("hidden", true);
}
// Create the link if specified:
if (linkChoice) {
link.setAttribute("value", linkChoice.label);
link.setAttribute("class", "notification-link");
link.onclick = function(event) {
if (event.button == 0) {
linkChoice.callback();
self.hideNotification(window);
if (features.closeCallback) {
features.closeCallback();
}
}
};
link.removeAttribute("hidden");
} else {
link.setAttribute("hidden", true);
}
closeBtn.onclick = function() {
self.hideNotification(window);
if (features.closeCallback) {
features.closeCallback();
}
};
// Show the popup:
popup.hidden = false;
popup.setAttribute("open", "true");
let anchorElement = window.document.getElementById(this._anchorId);
popup.openPopup(anchorElement, "after_end");
},
hideNotification: function TP_OldNotfn_hideNotification(window) {
let popup = window.document.getElementById("pilot-notification-popup");
popup.removeAttribute("open");
popup.hidePopup();
}
};
// For Fx 4.0 + , uses the built-in doorhanger notification system (but with my own anchor icon)
function PopupNotificationManager() {
/* In the future, we may want to anchor these to the Feedback button if present,
* but for now that option is unimplemented. */
this._popupModule = {};
Components.utils.import("resource://gre/modules/PopupNotifications.jsm", this._popupModule);
this._pn = null;
}
PopupNotificationManager.prototype = {
showNotification: function TP_NewNotfn_showNotification(window, features, choices) {
let self = this;
let tabbrowser = window.getBrowser();
let panel = window.document.getElementById("testpilot-notification-popup");
let iconBox = window.document.getElementById("tp-notification-popup-box");
let defaultChoice = null;
let additionalChoices = [];
// hide any existing notification so we don't get a weird stack
this.hideNotification();
// Create notifications object for window
this._pn = new this._popupModule.PopupNotifications(tabbrowser, panel, iconBox);
/* Add hideNotification() calls to the callbacks of each choice -- the client code shouldn't
* have to worry about hiding the notification in its callbacks.*/
for (let i = 0; i < choices.length; i++) {
let choice = choices[i];
let choiceWithHide = {
label: choice.label,
accessKey: choice.accessKey,
callback: function() {
self.hideNotification();
choice.callback();
}};
// Take the first one to be the default choice:
if (i == 0) {
defaultChoice = choiceWithHide;
} else {
additionalChoices.push(choiceWithHide);
}
}
this._notifRef = this._pn.show(tabbrowser.selectedBrowser,
"testpilot",
features.text,
"tp-notification-popup-icon", // All TP notifications use this icon
defaultChoice,
additionalChoices,
{persistWhileVisible: true,
removeOnDismissal: features.fragile,
title: features.title,
iconClass: features.iconClass,
closeButtonFunc: function() {
self.hideNotification();
},
eventCallback: function(stateChange){
/* Note - closeCallback() will be called AFTER the button handler,
* and will be called no matter whether the notification is closed via
* close button or a menu button item.
*/
if (stateChange == "removed" && features.closeCallback) {
features.closeCallback();
}
self._notifRef = null;
}});
// See http://mxr.mozilla.org/mozilla-central/source/toolkit/content/PopupNotifications.jsm
},
hideNotification: function TP_NewNotfn_hideNotification() {
if (this._notifRef && this._pn) {
this._pn.remove(this._notifRef);
this._notifRef = null;
}
}
};

Просмотреть файл

@ -3,10 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const BASE_URL_PREF = "extensions.testpilot.indexBaseURL";
const SSL_DOWNLOAD_REQUIRED_PREF = "extensions.testpilot.ssldownloadrequired";
var Cuddlefish = require("cuddlefish");
var resolveUrl = require("url").resolve;
var SecurableModule = require("securable-module");
let JarStore = require("jar-code-store").JarStore;
let prefs = require("preferences-service");
/* Security info should look like this:
* Security Info:
@ -100,11 +103,16 @@ function downloadFile(url, cb, lastModified) {
console.info("Using binary mode to download jar file.");
req.overrideMimeType('text/plain; charset=x-user-defined');
}
req.addEventListener("readystatechange", function(aEvt) {
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200) {
// check security channel:
if (verifyChannelSecurity(req.channel)) {
// check security channel, unless the user is ignoring that.
let ssldownloadrequired= prefs.get(SSL_DOWNLOAD_REQUIRED_PREF,true);
if (!ssldownloadrequired) {
dump("not requiring ssl download for experiements. use at your own risk!\n");
dump("change this with: " + SSL_DOWNLOAD_REQUIRED_PREF + "\n");
}
if (!ssldownloadrequired | verifyChannelSecurity(req.channel)) {
cb(req.responseText);
} else {
cb(null);
@ -122,7 +130,7 @@ function downloadFile(url, cb, lastModified) {
cb(null);
}
}
}, false);
};
req.send();
}
@ -377,7 +385,7 @@ exports.RemoteExperimentLoader.prototype = {
let foStream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
foStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0666", 8), 0);
// write, create, truncate
let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
createInstance(Ci.nsIConverterOutputStream);

Просмотреть файл

@ -24,6 +24,8 @@ const UPDATE_CHANNEL_PREF = "app.update.channel";
const LOG_FILE_NAME = "TestPilotErrorLog.log";
const RANDOM_DEPLOY_PREFIX = "extensions.testpilot.deploymentRandomizer";
Cu.import("resource://testpilot/modules/interface.js");
let TestPilotSetup = {
didReminderAfterStartup: false,
startupComplete: false,
@ -151,30 +153,6 @@ let TestPilotSetup = {
return this.__obs;
},
_isBetaChannel: function TPS__isBetaChannel() {
// Beta and aurora channels use feedback interface; nightly and release channels don't.
let channel = this._prefs.getValue(UPDATE_CHANNEL_PREF, "");
return (channel == "beta") || (channel == "betatest") || (channel == "aurora");
},
_setPrefDefaultsForVersion: function TPS__setPrefDefaultsForVersion() {
/* A couple of preferences need different default values depending on
* whether we're in the Firefox 4 beta version or the standalone TP version
*/
let ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService);
let prefBranch = ps.getDefaultBranch("");
/* note we're setting default values, not current values -- these
* get overridden by any user set values. */
if (this._isBetaChannel()) {
prefBranch.setBoolPref(POPUP_SHOW_ON_NEW, true);
prefBranch.setIntPref(POPUP_CHECK_INTERVAL, 600000);
} else {
prefBranch.setBoolPref(POPUP_SHOW_ON_NEW, false);
prefBranch.setIntPref(POPUP_CHECK_INTERVAL, 180000);
}
},
globalStartup: function TPS__doGlobalSetup() {
// Only ever run this stuff ONCE, on the first window restore.
// Should get called by the Test Pilot component.
@ -182,7 +160,6 @@ let TestPilotSetup = {
logger.trace("TestPilotSetup.globalStartup was called.");
try {
this._setPrefDefaultsForVersion();
if (!this._prefs.getValue(RUN_AT_ALL_PREF, true)) {
logger.trace("Test Pilot globally disabled: Not starting up.");
return;
@ -222,7 +199,7 @@ let TestPilotSetup = {
/* Show first run page (in front window) only the first time after install;
* Don't show first run page in Feedback UI version. */
if ((self._prefs.getValue(VERSION_PREF, "") == "") &&
(!self._interfaceBuilder.channelUsesFeedback())) {
(!TestPilotUIBuilder.channelUsesFeedback())) {
self._prefs.setValue(VERSION_PREF, self.version);
let browser = self._getFrontBrowserWindow().getBrowser();
let url = self._prefs.getValue(FIRST_RUN_PREF, "");
@ -343,16 +320,21 @@ let TestPilotSetup = {
let doc = window.document;
let popup = doc.getElementById("pilot-notification-popup");
let anchor;
if (this._isBetaChannel()) {
/* If we're in the Ffx4Beta version, popups come down from feedback
* button, but if we're in the standalone extension version, they
* come up from status bar icon. */
let anchor, xOffset;
if (TestPilotUIBuilder.channelUsesFeedback()) {
/* If we're in the Ffx4Beta version, popups hang down from the feedback
* button. In the standalone extension version, they hang down from
* a temporary Test Pilot icon that appears in the toolbar. */
anchor = doc.getElementById("feedback-menu-button");
popup.setAttribute("class", "tail-up");
xOffset = 0;
} else {
anchor = doc.getElementById("pilot-notifications-button");
popup.setAttribute("class", "tail-down");
anchor = doc.getElementById("tp-notification-popup-icon");
anchor.hidden = false;
/* By default, right edge of notification will line up with right edge of anchor.
* That looks fine for feedback button, but the test pilot icon is narrower and
* this alignment causes the pointy part of the arrow to miss the icon. Fix this
* by shifting the notification to the right 24 pixels. */
xOffset = 24;
}
let textLabel = doc.getElementById("pilot-notification-text");
let titleLabel = doc.getElementById("pilot-notification-title");
@ -421,7 +403,6 @@ let TestPilotSetup = {
// Create the link if specified:
if (linkText && (linkUrl || task)) {
link.setAttribute("value", linkText);
link.setAttribute("class", "notification-link");
link.onclick = function(event) {
if (event.button == 0) {
if (task) {
@ -444,7 +425,7 @@ let TestPilotSetup = {
// Show the popup:
popup.hidden = false;
popup.setAttribute("open", "true");
popup.openPopup( anchor, "after_end");
popup.openPopup( anchor, "after_end", xOffset, 0);
},
_openChromeless: function TPS__openChromeless(url) {
@ -462,6 +443,12 @@ let TestPilotSetup = {
popup.setAttribute("open", "false");
popup.removeAttribute("tpisextensionupdate");
popup.hidePopup();
if (!TestPilotUIBuilder.channelUsesFeedback()) {
// If we're using the temporary test pilot icon as an anchor, hide it now
let icon = window.document.getElementById("tp-notification-popup-icon");
icon.hidden = true;
}
if (onCloseCallback) {
onCloseCallback();
}
@ -487,7 +474,7 @@ let TestPilotSetup = {
// Highest priority is if there is a finished test (needs a decision)
if (this._prefs.getValue(POPUP_SHOW_ON_FINISH, false)) {
for (i = 0; i < this.taskList.length; i++) {
for (let i = 0; i < this.taskList.length; i++) {
task = this.taskList[i];
if (task.status == TaskConstants.STATUS_FINISHED) {
if (!this._prefs.getValue(ALWAYS_SUBMIT_DATA, false)) {
@ -512,7 +499,7 @@ let TestPilotSetup = {
// If there's no finished test, next highest priority is new tests that
// are starting...
if (this._prefs.getValue(POPUP_SHOW_ON_NEW, false)) {
for (i = 0; i < this.taskList.length; i++) {
for (let i = 0; i < this.taskList.length; i++) {
task = this.taskList[i];
if (task.status == TaskConstants.STATUS_PENDING ||
task.status == TaskConstants.STATUS_NEW) {
@ -538,11 +525,12 @@ let TestPilotSetup = {
task, false,
this._stringBundle.formatStringFromName(
"testpilot.notification.newTestPilotSurvey.message",
[task.title], 1),
// in task.js summary falls back to title if undefined or empty, but we double make sure :)
[task.summary || task.title],1),
this._stringBundle.GetStringFromName(
"testpilot.notification.newTestPilotSurvey"),
"new-study", false, false,
this._stringBundle.GetStringFromName("testpilot.moreInfo"),
this._stringBundle.GetStringFromName("testpilot.takeSurvey"),
task.defaultUrl);
task.changeStatus(TaskConstants.STATUS_IN_PROGRESS, true);
return;
@ -553,7 +541,7 @@ let TestPilotSetup = {
// And finally, new experiment results:
if (this._prefs.getValue(POPUP_SHOW_ON_RESULTS, false)) {
for (i = 0; i < this.taskList.length; i++) {
for (let i = 0; i < this.taskList.length; i++) {
task = this.taskList[i];
if (task.taskType == TaskConstants.TYPE_RESULTS &&
task.status == TaskConstants.STATUS_NEW) {
@ -645,15 +633,17 @@ let TestPilotSetup = {
}
},
_experimentRequirementsAreMet: function TPS__requirementsMet(experiment) {
/* Returns true if we we meet the requirements to run this experiment
* (e.g. meet the minimum Test Pilot version and Firefox version)
* false if not.
* Default is always to run the study - return true UNLESS the study
* specifies a requirement that we don't meet. */
_checkExperimentRequirements: function TPS__requirementsMet(experiment, callback) {
/* Async.
* Calls callback with a true if we we meet the requirements to run this
* experiment (e.g. meet the minimum Test Pilot version and Firefox version)
* calls callback with a false if not.
* All of the requirements that a study can specify - firefox version, test pilot
* version, filter function etc - default to true if not provided. callback(true)
* UNLESS the study specifies a requirement that we don't meet. */
let logger = this._logger;
try {
let minTpVer, minFxVer, expName, runOrNotFunc, randomDeployment;
let minTpVer, minFxVer, expName, filterFunc, randomDeployment;
/* Could be an experiment, which specifies experimentInfo, or survey,
* which specifies surveyInfo. */
let info = experiment.experimentInfo ?
@ -662,12 +652,13 @@ let TestPilotSetup = {
if (!info) {
// If neither one is supplied, study lacks metadata required to run
logger.warn("Study lacks minimum metadata to run.");
return false;
callback(false);
return;
}
minTpVer = info.minTPVersion;
minFxVer = info.minFXVersion;
expName = info.testName;
runOrNotFunc = info.runOrNotFunc;
filterFunc = info.filter;
randomDeployment = info.randomDeployment;
// Minimum test pilot version:
@ -685,44 +676,54 @@ let TestPilotSetup = {
"testpilot.notification.extensionUpdate"),
"update-extension", true, false, "", "", true);
}
return false;
callback(false);
return;
}
// Minimum firefox version:
if (minFxVer && this._isNewerThanFirefox(minFxVer)) {
logger.warn("Not loading " + expName);
logger.warn("Because it requires Firefox version " + minFxVer);
return false;
callback(false);
return;
}
// Random deployment (used to give study to random subsample of users)
if (randomDeployment) {
/* Roll a hundred-sided die. Remember what we roll for later reference. A study
/* Roll a 100*uniform_random. Remember what we roll for later reference or reuse. A study
* using random subsample deployment will provide a range (say, 0 ~ 30) which means
* only users who roll within that range will run the study. */
* only users who roll not outside that range (inclusive) will run the study.
* ie., [0,1.5] -> 1.5% prob
*/
let prefName = RANDOM_DEPLOY_PREFIX + "." + randomDeployment.rolloutCode;
let myRoll = this._prefs.getValue(prefName, null);
if (myRoll == null) {
myRoll = Math.floor(Math.random()*100);
this._prefs.setValue(prefName, myRoll);
myRoll = Math.random()*100;
this._prefs.setValue(prefName, JSON.stringify(myRoll));
} else {
myRoll = Number(myRoll); // cast it
}
if (myRoll < randomDeployment.minRoll) {
return false;
callback(false);
return;
}
if (myRoll > randomDeployment.maxRoll) {
return false;
callback(false);
return;
}
}
/* The all-purpose, arbitrary code "Should this study run?" function - if
* provided, use its return value. */
if (runOrNotFunc) {
return runOrNotFunc();
* provided, use it. (filterFunc must be asynchronous too!)*/
if (filterFunc) {
filterFunc(callback);
return;
}
} catch (e) {
logger.warn("Error in requirements check " + e);
callback(false); // if something went wrong, err on the side of not running the study
}
return true;
callback(true);
},
checkForTasks: function TPS_checkForTasks(callback) {
@ -743,65 +744,71 @@ let TestPilotSetup = {
// downloading new contents or not...
let experiments = self._remoteExperimentLoader.getExperiments();
let numExperimentsProcessed = 0;
let numExperiments = Object.keys(experiments).length;
for (let filename in experiments) {
if (!self._experimentRequirementsAreMet(experiments[filename])) {
continue;
}
try {
// The try-catch ensures that if something goes wrong in loading one
// experiment, the other experiments after that one still get loaded.
logger.trace("Attempting to load experiment " + filename);
let task;
// Could be a survey: check if surveyInfo is exported:
if (experiments[filename].surveyInfo != undefined) {
let sInfo = experiments[filename].surveyInfo;
// If it supplies questions, it's a built-in survey.
// If not, it's a web-based survey.
if (!sInfo.surveyQuestions) {
task = new self._taskModule.TestPilotWebSurvey(sInfo);
} else {
task = new self._taskModule.TestPilotBuiltinSurvey(sInfo);
self._checkExperimentRequirements(experiments[filename], function(requirementsMet) {
if (requirementsMet) {
try {
/* The try-catch ensures that if something goes wrong in loading one
* experiment, the other experiments after that one still get loaded. */
logger.trace("Attempting to load experiment " + filename);
let task;
// Could be a survey: check if surveyInfo is exported:
if (experiments[filename].surveyInfo != undefined) {
let sInfo = experiments[filename].surveyInfo;
// If it supplies questions, it's a built-in survey.
// If not, it's a web-based survey.
if (!sInfo.surveyQuestions) {
task = new self._taskModule.TestPilotWebSurvey(sInfo);
} else {
task = new self._taskModule.TestPilotBuiltinSurvey(sInfo);
}
} else {
// This one must be an experiment.
let expInfo = experiments[filename].experimentInfo;
let dsInfo = experiments[filename].dataStoreInfo;
let dataStore = new self._dataStoreModule.ExperimentDataStore(
dsInfo.fileName, dsInfo.tableName, dsInfo.columns );
let webContent = experiments[filename].webContent;
task = new self._taskModule.TestPilotExperiment(expInfo,
dataStore,
experiments[filename].handlers,
webContent);
}
self.addTask(task);
logger.info("Loaded task " + filename);
} catch (e) {
logger.warn("Failed to load task " + filename + ": " + e);
}
} else {
// This one must be an experiment.
let expInfo = experiments[filename].experimentInfo;
let dsInfo = experiments[filename].dataStoreInfo;
let dataStore = new self._dataStoreModule.ExperimentDataStore(
dsInfo.fileName, dsInfo.tableName, dsInfo.columns );
let webContent = experiments[filename].webContent;
task = new self._taskModule.TestPilotExperiment(expInfo,
dataStore,
experiments[filename].handlers,
webContent);
}
self.addTask(task);
logger.info("Loaded task " + filename);
} catch (e) {
logger.warn("Failed to load task " + filename + ": " + e);
}
} // end if requirements met
// whether loading succeeded or failed, we're done processing that one; increment the count:
numExperimentsProcessed ++;
if (numExperimentsProcessed == numExperiments) {
// all done with experiments -- do results and legacy studies:
let results = self._remoteExperimentLoader.getStudyResults();
for (let r in results) {
let studyResult = new self._taskModule.TestPilotStudyResults(results[r]);
self.addTask(studyResult);
}
/* Legacy studies = stuff we no longer have the code for, but
* if the user participated in it we want to keep that metadata. */
let legacyStudies = self._remoteExperimentLoader.getLegacyStudies();
for (let l in legacyStudies) {
let legacyStudy = new self._taskModule.TestPilotLegacyStudy(legacyStudies[l]);
self.addTask(legacyStudy);
}
// Finally, call the callback if there is one
if (callback) {
callback();
}
} // end of if all experiments are processed
}); // end of call to check experiment requirements
} // end for filename in experiments
// Handling new results is much simpler:
let results = self._remoteExperimentLoader.getStudyResults();
for (let r in results) {
let studyResult = new self._taskModule.TestPilotStudyResults(results[r]);
self.addTask(studyResult);
}
/* Legacy studies = stuff we no longer have the code for, but
* if the user participated in it we want to keep that metadata. */
let legacyStudies = self._remoteExperimentLoader.getLegacyStudies();
for (let l in legacyStudies) {
let legacyStudy = new self._taskModule.TestPilotLegacyStudy(legacyStudies[l]);
self.addTask(legacyStudy);
}
if (callback) {
callback();
}
}
);
); // end of call to checkForUpdates
},
reloadRemoteExperiments: function TPS_reloadRemoteExperiments(callback) {

Просмотреть файл

@ -813,7 +813,7 @@ TestPilotExperiment.prototype = {
req.setRequestHeader("Content-type", "application/json");
req.setRequestHeader("Content-length", dataString.length);
req.setRequestHeader("Connection", "close");
req.addEventListener("readystatechange", function(aEvt) {
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200 || req.status == 201 || req.status == 202) {
let location = req.getResponseHeader("Location");
@ -854,7 +854,7 @@ TestPilotExperiment.prototype = {
callback(false);
}
}
}, false);
};
req.send(dataString);
});
},
@ -886,7 +886,7 @@ TestPilotExperiment.prototype = {
req.setRequestHeader("Content-type", "application/json");
req.setRequestHeader("Content-length", dataString.length);
req.setRequestHeader("Connection", "close");
req.addEventListener("readystatechange", function(aEvt) {
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200 || req.status == 201 || req.status == 202) {
logger.info("Quit reason posted successfully " + req.responseText);
@ -900,7 +900,7 @@ TestPilotExperiment.prototype = {
}
}
}
}, false);
};
logger.trace("Sending quit reason.");
req.send(dataString);
} else {
@ -1036,7 +1036,7 @@ TestPilotBuiltinSurvey.prototype = {
req.setRequestHeader("Content-type", "application/json");
req.setRequestHeader("Content-length", params.length);
req.setRequestHeader("Connection", "close");
req.addEventListener("readystatechange", function(aEvt) {
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
if (req.status == 200 || req.status == 201 ||
req.status == 202) {
@ -1066,7 +1066,7 @@ TestPilotBuiltinSurvey.prototype = {
callback(false);
}
}
}, false);
};
req.send(params);
});
}

Просмотреть файл

@ -3,43 +3,43 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
padding-bottom: 40px;
padding-bottom: 40px;
padding-top: 20px;
padding-left: 0px;
padding-right: 0px;
}
body {
body {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Lucida, Arial, Helvetica, sans-serif;
background: #fff url('chrome://testpilot/skin/images/bg-status.jpg') repeat-x top center; padding: 0px;
background: #fff url('chrome://testpilot/skin/images/bg-status.jpg') repeat-x top center; padding: 0px;
color:#787878;
font-size:12px;
line-height: 18px;
margin: 0 auto;
}
h1 {color: #3a3a3a; font-size: 28px; font-weight: normal; letter-spacing: -1px}
h2 {color: #54717b; font-size: 24px; line-height: 24px;font-weight: normal; letter-spacing: -1px}
h3 {color: #54717b; font-size: 18px; line-height: 18px;font-weight: normal; letter-spacing: -1px}
p, ul {font-size: 12px;}
a:link, a:hover, a:active, a:focus, a:visited {color: #9f423b; text-decoration: none;}
a:hover {color: #9f423b; text-decoration: none;}
.bold {color:#3a3a3a;}
.address {margin-left: 20px;}
.inactive {color:#ccc;}
li {list-style-type: circle;}
.spacer {height: 40px;}
.center {text-align: center;}
.data {
background-color: #fff;
margin-bottom: 6px;
border: 1px solid rgba(133, 153, 166, 0.2);
@ -48,11 +48,9 @@ body {
padding: 6px;
box-shadow:
rgba(133, 153, 166, 0.4) 0px 1px 17px;
-webkit-box-shadow:
rgba(133, 153, 166, 0.4) 0px 1px 24px;
}
.dataBox {
font-size: 16px;
padding: 6px 20px 20px 20px;
@ -89,16 +87,6 @@ body {
margin-top: 240px;
}
.downloadButton {
margin-bottom: -10px;
margin-top: -2px;
margin-right: 12px;
}
.downloadH1 {color: #3a3a3a; font-size: 24px; font-weight: normal; letter-spacing: -1px; margin-right: 8px;}
.downloadH2 {color: #3a3a3a; font-size: 22px; font-weight: normal; letter-spacing: -1px; line-height: 28px; margin-left: 46px;}
#intro {
float: left;
width: 500px;
@ -117,12 +105,12 @@ body {
padding: 8px 12px;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 1px 1px,
inset rgba(255, 255, 255, 1) 0 3px 1px,
inset rgba(255, 255, 255, 0.3) 0 16px 0px,
inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
rgba(255, 255, 255, 1) 0 1px,
rgba(133, 153, 166, 0.3) 0px 1px 8.5px;
background-color: #e7eaec;
@ -135,12 +123,12 @@ body {
width: 240px;
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 1px 1px,
inset rgba(255, 255, 255, 1) 0 3px 1px,
inset rgba(255, 255, 255, 0.3) 0 16px 0px,
inset rgba(0, 0, 0, 0.2) 0 -1px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
inset rgba(0, 0, 0, 0.1) 0 -2px 1px,
rgba(255, 255, 255, 1) 0 1px,
rgba(133, 153, 166, 0.3) 0px 1px 8.5px;
background-color: #e7eaec;
@ -154,7 +142,7 @@ body {
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 8.5px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 8.5px;
@ -175,7 +163,7 @@ body {
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 8.5px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 8.5px;
@ -191,7 +179,7 @@ body {
color: rgba(0, 0, 0, 0.8);
border-radius: 0.5em;
background: rgba(220, 240, 247, 0.8) url('chrome://testpilot/skin/images/callout_continue.png') no-repeat top center;
box-shadow:
box-shadow:
inset rgba(185, 221, 234, 0.2) 0 -10px 8.5px,
inset rgba(185, 221, 234, 1) 0 0px 1px,
inset rgba(255, 255, 255, 0.2) 0 10px 8.5px;
@ -227,7 +215,7 @@ body {
margin-right: 6px;
}
/* ------- MENU -------
/* ------- MENU -------
#menu {
margin: 20px auto;
@ -242,7 +230,7 @@ body {
border-bottom: 3px solid #adb6ba;
-moz-border-bottom-colors:#adb6ba #e7eaec #e7eaec;
background-color: #fff;
}
}
*/
@ -260,7 +248,7 @@ body {
font-size: 14px;
text-shadow: 1px 1px 1px rgba(173, 182, 186, 1);
background-color: rgba(173, 182, 186, 0.3);
box-shadow:
box-shadow:
inset rgba(0, 0, 0, 0.2) 0 -10px 8.5px;
padding: 9px 8px 8px 8px;
}

Просмотреть файл

@ -4,6 +4,7 @@
#pilot-notification-popup {
-moz-appearance: none;
border-image: none;
background-color: Menu;
background-image: -moz-linear-gradient(hsla(0,0%,100%,.2), transparent);
box-shadow: inset 0 0 7px hsla(0,0%,100%,.2),
@ -14,11 +15,6 @@
width: 480px;
}
.tail-up,
.tail-down {
-moz-border-image: none;
}
.pilot-notification-popup-container {
-moz-appearance: none;
margin: 0;

Просмотреть файл

@ -6,7 +6,7 @@
#pilot-notification-submit {
-moz-appearance: none;
background: #666
background: #666
-moz-linear-gradient(rgba(110,110,110,.9), rgba(70,70,70,.9) 49%,
rgba(60,60,60,.9) 51%, rgba(50,50,50,.9));
background-clip: padding-box;
@ -33,6 +33,6 @@
color: #fff;
}
.notification-link {
#pilot-notification-link {
color: #fff;
}

Просмотреть файл

@ -407,7 +407,7 @@ function clearAllPrefsForStudy(studyId) {
dump("Looking for prefs to delete...\n");
let prefService = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService)
.QueryInterface(Ci.nsIPrefBranch);
.QueryInterface(Ci.nsIPrefBranch2);
let prefStem = "extensions.testpilot";
let prefNames = prefService.getChildList(prefStem);
for each (let prefName in prefNames) {
@ -783,4 +783,4 @@ function testSameGUIDs() {
}
// TODO test for continuity of GUID with recurring study (longitudinal) - i don't think this
// has actually been working so far because a new GUID is generted every time the study starts up...
// has actually been working so far because a new GUID is generted every time the study starts up...