зеркало из https://github.com/mozilla/gecko-dev.git
Bug 719455 - Update TestPilot from version 1.1.2 to version 1.2.2. r=dao
This commit is contained in:
Родитель
3aa11667d0
Коммит
e8a96022c7
|
@ -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;
|
||||
|
|
2
browser/app/profile/extensions/testpilot@labs.mozilla.com/content/jquery.min.js
поставляемый
Normal file
2
browser/app/profile/extensions/testpilot@labs.mozilla.com/content/jquery.min.js
поставляемый
Normal file
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -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...
|
Загрузка…
Ссылка в новой задаче