gecko/browser/app/profile/extensions/testpilot@labs.mozilla.com/modules/setup.js

841 lines
31 KiB
JavaScript
Raw Normal View History

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Test Pilot.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Atul Varma <atul@mozilla.com>
* Jono X <jono@mozilla.com>
* Raymond Lee <raymond@appcoast.com>
* Jorge Villalobos <jorge@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
EXPORTED_SYMBOLS = ["TestPilotSetup", "POPUP_SHOW_ON_NEW",
"POPUP_SHOW_ON_FINISH", "POPUP_SHOW_ON_RESULTS",
"ALWAYS_SUBMIT_DATA", "RUN_AT_ALL_PREF"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const EXTENSION_ID = "testpilot@labs.mozilla.com";
const VERSION_PREF ="extensions.testpilot.lastversion";
const FIRST_RUN_PREF ="extensions.testpilot.firstRunUrl";
const RUN_AT_ALL_PREF = "extensions.testpilot.runStudies";
const POPUP_SHOW_ON_NEW = "extensions.testpilot.popup.showOnNewStudy";
const POPUP_SHOW_ON_FINISH = "extensions.testpilot.popup.showOnStudyFinished";
const POPUP_SHOW_ON_RESULTS = "extensions.testpilot.popup.showOnNewResults";
const POPUP_CHECK_INTERVAL = "extensions.testpilot.popup.delayAfterStartup";
const POPUP_REMINDER_INTERVAL = "extensions.testpilot.popup.timeBetweenChecks";
const ALWAYS_SUBMIT_DATA = "extensions.testpilot.alwaysSubmitData";
const LOG_FILE_NAME = "TestPilotErrorLog.log";
let TestPilotSetup = {
didReminderAfterStartup: false,
startupComplete: false,
_shortTimer: null,
_longTimer: null,
_remoteExperimentLoader: null, // TODO make this a lazy initializer too?
taskList: [],
version: "",
// Lazy initializers:
__application: null,
get _application() {
if (this.__application == null) {
this.__application = Cc["@mozilla.org/fuel/application;1"]
.getService(Ci.fuelIApplication);
}
return this.__application;
},
get _prefs() {
return this._application.prefs;
},
__loader: null,
get _loader() {
if (this.__loader == null) {
let Cuddlefish = {};
Components.utils.import("resource://testpilot/modules/lib/cuddlefish.js",
Cuddlefish);
let repo = this._logRepo;
this.__loader = new Cuddlefish.Loader(
{rootPaths: ["resource://testpilot/modules/",
"resource://testpilot/modules/lib/"],
console: repo.getLogger("TestPilot.Loader")
});
}
return this.__loader;
},
__feedbackManager: null,
get _feedbackManager() {
if (this.__feedbackManager == null) {
let FeedbackModule = {};
Cu.import("resource://testpilot/modules/feedback.js", FeedbackModule);
this.__feedbackManager = FeedbackModule.FeedbackManager;
}
return this.__feedbackManager;
},
__dataStoreModule: null,
get _dataStoreModule() {
if (this.__dataStoreModule == null) {
this.__dataStoreModule = {};
Cu.import("resource://testpilot/modules/experiment_data_store.js",
this._dataStoreModule);
}
return this.__dataStoreModule;
},
__extensionUpdater: null,
get _extensionUpdater() {
if (this.__extensionUpdater == null) {
let ExUpdate = {};
Cu.import("resource://testpilot/modules/extension-update.js",
ExUpdate);
this.__extensionUpdater = ExUpdate.TestPilotExtensionUpdate;
}
return this.__extensionUpdater;
},
__logRepo: null,
get _logRepo() {
// Note: This hits the disk so it's an expensive operation; don't call it
// on startup.
if (this.__logRepo == null) {
let Log4MozModule = {};
Cu.import("resource://testpilot/modules/log4moz.js", Log4MozModule);
let props = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
let logFile = props.get("ProfD", Components.interfaces.nsIFile);
logFile.append(LOG_FILE_NAME);
let formatter = new Log4MozModule.Log4Moz.BasicFormatter;
let root = Log4MozModule.Log4Moz.repository.rootLogger;
root.level = Log4MozModule.Log4Moz.Level["All"];
let appender = new Log4MozModule.Log4Moz.RotatingFileAppender(logFile, formatter);
root.addAppender(appender);
this.__logRepo = Log4MozModule.Log4Moz.repository;
}
return this.__logRepo;
},
__logger: null,
get _logger() {
if (this.__logger == null) {
this.__logger = this._logRepo.getLogger("TestPilot.Setup");
}
return this.__logger;
},
__taskModule: null,
get _taskModule() {
if (this.__taskModule == null) {
this.__taskModule = {};
Cu.import("resource://testpilot/modules/tasks.js", this.__taskModule);
}
return this.__taskModule;
},
__stringBundle: null,
get _stringBundle() {
if (this.__stringBundle == null) {
this.__stringBundle =
Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService).
createBundle("chrome://testpilot/locale/main.properties");
}
return this.__stringBundle;
},
__obs: null,
get _obs() {
if (this.__obs == null) {
this.__obs = this._loader.require("observer-service");
}
return this.__obs;
},
_isFfx4BetaVersion: function TPS__isFfx4BetaVersion() {
let result = Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator)
.compare("3.7a1pre", this._application.version);
if (result < 0) {
return true;
} else {
return false;
}
},
_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._isFfx4BetaVersion()) {
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.
let logger = this._logger;
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;
}
// Set up observation for task state changes
var self = this;
this._obs.add("testpilot:task:changed", this.onTaskStatusChanged, self);
this._obs.add(
"testpilot:task:dataAutoSubmitted", this._onTaskDataAutoSubmitted, self);
// Set up observation for application shutdown.
this._obs.add("quit-application", this.globalShutdown, self);
// Set up observation for enter/exit private browsing:
this._obs.add("private-browsing", this.onPrivateBrowsingMode, self);
// Set up timers to remind user x minutes after startup
// and once per day thereafter. Use nsITimer so it doesn't belong to
// any one window.
logger.trace("Setting interval for showing reminders...");
this._shortTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._shortTimer.initWithCallback(
{ notify: function(timer) { self._doHousekeeping();} },
this._prefs.getValue(POPUP_CHECK_INTERVAL, 180000),
Ci.nsITimer.TYPE_REPEATING_SLACK
);
this._longTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._longTimer.initWithCallback(
{ notify: function(timer) {
self.reloadRemoteExperiments(function() {
self._notifyUserOfTasks();
});
}}, this._prefs.getValue(POPUP_REMINDER_INTERVAL, 86400000),
Ci.nsITimer.TYPE_REPEATING_SLACK);
this.getVersion(function() {
// Show first run page (in front window) if newly installed or upgraded.
let currVersion = self._prefs.getValue(VERSION_PREF, "firstrun");
if (currVersion != self.version) {
if(!self._isFfx4BetaVersion()) {
self._prefs.setValue(VERSION_PREF, self.version);
let browser = self._getFrontBrowserWindow().getBrowser();
let url = self._prefs.getValue(FIRST_RUN_PREF, "");
let tab = browser.addTab(url);
browser.selectedTab = tab;
}
// Don't show first run page in ffx4 beta version.
}
// Install tasks. (This requires knowing the version, so it is
// inside the callback from getVersion.)
self.checkForTasks(function() {
/* Callback to complete startup after we finish
* checking for tasks. */
self.startupComplete = true;
logger.trace("I'm in the callback from checkForTasks.");
// Send startup message to each task:
for (let i = 0; i < self.taskList.length; i++) {
self.taskList[i].onAppStartup();
}
self._obs.notify("testpilot:startup:complete", "", null);
/* onWindowLoad gets called once for each window,
* but only after we fire this notification. */
logger.trace("Testpilot startup complete.");
});
});
} catch(e) {
logger.error("Error in testPilot startup: " + e);
}
},
globalShutdown: function TPS_globalShutdown() {
let logger = this._logger;
logger.trace("Global shutdown. Unregistering everything.");
let self = this;
for (let i = 0; i < self.taskList.length; i++) {
self.taskList[i].onAppShutdown();
self.taskList[i].onExperimentShutdown();
}
this.taskList = [];
this._loader.unload();
this._obs.remove("testpilot:task:changed", this.onTaskStatusChanged, self);
this._obs.remove(
"testpilot:task:dataAutoSubmitted", this._onTaskDataAutoSubmitted, self);
this._obs.remove("quit-application", this.globalShutdown, self);
this._obs.remove("private-browsing", this.onPrivateBrowsingMode, self);
this._loader.unload();
this._shortTimer.cancel();
this._longTimer.cancel();
logger.trace("Done unregistering everything.");
},
_getFrontBrowserWindow: function TPS__getFrontWindow() {
let wm = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator);
// TODO Is "most recent" the same as "front"?
return wm.getMostRecentWindow("navigator:browser");
},
onPrivateBrowsingMode: function TPS_onPrivateBrowsingMode(topic, data) {
for (let i = 0; i < this.taskList.length; i++) {
if (data == "enter") {
this.taskList[i].onEnterPrivateBrowsing();
} else if (data == "exit") {
this.taskList[i].onExitPrivateBrowsing();
}
}
},
onWindowUnload: function TPS__onWindowRegistered(window) {
this._logger.trace("Called TestPilotSetup.onWindow unload!");
for (let i = 0; i < this.taskList.length; i++) {
this.taskList[i].onWindowClosed(window);
}
},
onWindowLoad: function TPS_onWindowLoad(window) {
this._logger.trace("Called TestPilotSetup.onWindowLoad!");
// Run this stuff once per window...
let self = this;
// Register listener for URL loads, that will notify all tasks about
// new page:
let appcontent = window.document.getElementById("appcontent");
if (appcontent) {
appcontent.addEventListener("DOMContentLoaded", function(event) {
let newUrl = event.originalTarget.URL;
self._feedbackManager.fillInFeedbackPage(newUrl, window);
for (i = 0; i < self.taskList.length; i++) {
self.taskList[i].onUrlLoad(newUrl, event);
}
}, true);
}
// Let each task know about the new window.
for (let i = 0; i < this.taskList.length; i++) {
this.taskList[i].onNewWindow(window);
}
},
addTask: function TPS_addTask(testPilotTask) {
// TODO raise some kind of exception if a task with the same ID already
// exists. No excuse to ever be running two copies of the same task.
this.taskList.push(testPilotTask);
},
_showNotification: function TPS__showNotification(task, fragile, text, title,
iconClass, showSubmit,
showAlwaysSubmitCheckbox,
linkText, linkUrl,
isExtensionUpdate,
onCloseCallback) {
/* TODO: Refactor the arguments of this function, it's getting really
* unweildly.... maybe pass in an object, or even make a notification an
* object that you create and then call .show() on. */
// If there are multiple windows, show notifications in the frontmost
// window.
let window = this._getFrontBrowserWindow();
let doc = window.document;
let popup = doc.getElementById("pilot-notification-popup");
let anchor;
if (this._isFfx4BetaVersion()) {
/* 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. */
anchor = doc.getElementById("feedback-menu-button");
popup.setAttribute("class", "tail-up");
} else {
anchor = doc.getElementById("pilot-notifications-button");
popup.setAttribute("class", "tail-down");
}
let textLabel = doc.getElementById("pilot-notification-text");
let titleLabel = doc.getElementById("pilot-notification-title");
let icon = doc.getElementById("pilot-notification-icon");
let submitBtn = doc.getElementById("pilot-notification-submit");
let closeBtn = doc.getElementById("pilot-notification-close");
let link = doc.getElementById("pilot-notification-link");
let alwaysSubmitCheckbox =
doc.getElementById("pilot-notification-always-submit-checkbox");
let self = this;
// Set all appropriate attributes on popup:
if (isExtensionUpdate) {
popup.setAttribute("tpisextensionupdate", "true");
}
popup.setAttribute("noautohide", !fragile);
titleLabel.setAttribute("value", title);
while (textLabel.lastChild) {
textLabel.removeChild(textLabel.lastChild);
}
textLabel.appendChild(doc.createTextNode(text));
if (iconClass) {
// css will set the image url based on the class.
icon.setAttribute("class", iconClass);
}
alwaysSubmitCheckbox.setAttribute("hidden", !showAlwaysSubmitCheckbox);
if (showSubmit) {
if (isExtensionUpdate) {
submitBtn.setAttribute("label",
this._stringBundle.GetStringFromName(
"testpilot.notification.update"));
submitBtn.onclick = function() {
this._extensionUpdater.check(EXTENSION_ID);
self._hideNotification(window, onCloseCallback);
};
} else {
submitBtn.setAttribute("label",
this._stringBundle.GetStringFromName("testpilot.submit"));
// Functionality for submit button:
submitBtn.onclick = function() {
self._hideNotification(window, onCloseCallback);
if (showAlwaysSubmitCheckbox && alwaysSubmitCheckbox.checked) {
self._prefs.setValue(ALWAYS_SUBMIT_DATA, true);
}
task.upload( function(success) {
if (success) {
self._showNotification(
task, true,
self._stringBundle.GetStringFromName(
"testpilot.notification.thankYouForUploadingData.message"),
self._stringBundle.GetStringFromName(
"testpilot.notification.thankYouForUploadingData"),
"study-submitted", false, false,
self._stringBundle.GetStringFromName("testpilot.moreInfo"),
task.defaultUrl);
} else {
// TODO any point in showing an error message here?
}
});
};
}
}
submitBtn.setAttribute("hidden", !showSubmit);
// 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) {
task.loadPage();
} else {
self._openChromeless(linkUrl);
}
self._hideNotification(window, onCloseCallback);
}
};
link.setAttribute("hidden", false);
} else {
link.setAttribute("hidden", true);
}
closeBtn.onclick = function() {
self._hideNotification(window, onCloseCallback);
};
// Show the popup:
popup.hidden = false;
popup.setAttribute("open", "true");
popup.openPopup( anchor, "after_end");
},
_openChromeless: function TPS__openChromeless(url) {
let window = this._getFrontBrowserWindow();
window.TestPilotWindowUtils.openChromeless(url);
},
_hideNotification: function TPS__hideNotification(window, onCloseCallback) {
/* Note - we take window as an argument instead of just using the frontmost
* window because the window order might have changed since the notification
* appeared and we want to be sure we close the notification in the same
* window as we opened it in! */
let popup = window.document.getElementById("pilot-notification-popup");
popup.hidden = true;
popup.setAttribute("open", "false");
popup.removeAttribute("tpisextensionupdate");
popup.hidePopup();
if (onCloseCallback) {
onCloseCallback();
}
},
_isShowingUpdateNotification : function() {
let window = this._getFrontBrowserWindow();
let popup = window.document.getElementById("pilot-notification-popup");
return popup.hasAttribute("tpisextensionupdate");
},
_notifyUserOfTasks: function TPS__notifyUser() {
// Check whether there are tasks needing attention, and if any are
// found, show the popup door-hanger thingy.
let i, task;
let TaskConstants = this._taskModule.TaskConstants;
// if showing extension update notification, don't do anything.
if (this._isShowingUpdateNotification()) {
return;
}
// 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++) {
task = this.taskList[i];
if (task.status == TaskConstants.STATUS_FINISHED) {
if (!this._prefs.getValue(ALWAYS_SUBMIT_DATA, false)) {
this._showNotification(
task, false,
this._stringBundle.formatStringFromName(
"testpilot.notification.readyToSubmit.message", [task.title],
1),
this._stringBundle.GetStringFromName(
"testpilot.notification.readyToSubmit"),
"study-finished", true, true,
this._stringBundle.GetStringFromName("testpilot.moreInfo"),
task.defaultUrl);
// We return after showing something, because it only makes
// sense to show one notification at a time!
return;
}
}
}
}
// 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++) {
task = this.taskList[i];
if (task.status == TaskConstants.STATUS_PENDING ||
task.status == TaskConstants.STATUS_NEW) {
if (task.taskType == TaskConstants.TYPE_EXPERIMENT) {
this._showNotification(
task, false,
this._stringBundle.formatStringFromName(
"testpilot.notification.newTestPilotStudy.pre.message",
[task.title], 1),
this._stringBundle.GetStringFromName(
"testpilot.notification.newTestPilotStudy"),
"new-study", false, false,
this._stringBundle.GetStringFromName("testpilot.moreInfo"),
task.defaultUrl, false, function() {
/* on close callback (Bug 575767) -- when the "new study
* starting" popup is dismissed, then the study can start. */
task.changeStatus(TaskConstants.STATUS_IN_PROGRESS, true);
TestPilotSetup.reloadRemoteExperiments();
});
return;
} else if (task.taskType == TaskConstants.TYPE_SURVEY) {
this._showNotification(
task, false,
this._stringBundle.formatStringFromName(
"testpilot.notification.newTestPilotSurvey.message",
[task.title], 1),
this._stringBundle.GetStringFromName(
"testpilot.notification.newTestPilotSurvey"),
"new-study", false, false,
this._stringBundle.GetStringFromName("testpilot.moreInfo"),
task.defaultUrl);
task.changeStatus(TaskConstants.STATUS_IN_PROGRESS, true);
return;
}
}
}
}
// And finally, new experiment results:
if (this._prefs.getValue(POPUP_SHOW_ON_RESULTS, false)) {
for (i = 0; i < this.taskList.length; i++) {
task = this.taskList[i];
if (task.taskType == TaskConstants.TYPE_RESULTS &&
task.status == TaskConstants.STATUS_NEW) {
this._showNotification(
task, true,
this._stringBundle.formatStringFromName(
"testpilot.notification.newTestPilotResults.message",
[task.title], 1),
this._stringBundle.GetStringFromName(
"testpilot.notification.newTestPilotResults"),
"new-results", false, false,
this._stringBundle.GetStringFromName("testpilot.moreInfo"),
task.defaultUrl);
// Having shown the notification, advance the status of the
// results, so that this notification won't be shown again
task.changeStatus(TaskConstants.STATUS_ARCHIVED, true);
return;
}
}
}
},
_doHousekeeping: function TPS__doHousekeeping() {
// check date on all tasks:
for (let i = 0; i < this.taskList.length; i++) {
let task = this.taskList[i];
task.checkDate();
}
// Do a full reminder -- but at most once per browser session
if (!this.didReminderAfterStartup) {
this._logger.trace("Doing reminder after startup...");
this.didReminderAfterStartup = true;
this._notifyUserOfTasks();
}
},
onTaskStatusChanged: function TPS_onTaskStatusChanged() {
this._notifyUserOfTasks();
},
_onTaskDataAutoSubmitted: function(subject, data) {
this._showNotification(
subject, true,
this._stringBundle.formatStringFromName(
"testpilot.notification.autoUploadedData.message",
[subject.title], 1),
this._stringBundle.GetStringFromName(
"testpilot.notification.autoUploadedData"),
"study-submitted", false, false,
this._stringBundle.GetStringFromName("testpilot.moreInfo"),
subject.defaultUrl);
},
getVersion: function TPS_getVersion(callback) {
// Application.extensions undefined in Firefox 4; will use the new
// asynchrounous API, store string in this.version, and call the
// callback when done.
if (this._application.extensions) {
this.version = this._application.extensions.get(EXTENSION_ID).version;
callback();
} else {
let self = this;
self._application.getExtensions(function(extensions) {
self.version = extensions.get(EXTENSION_ID).version;
callback();
});
}
},
_isNewerThanMe: function TPS__isNewerThanMe(versionString) {
let result = Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator)
.compare(this.version, versionString);
if (result < 0) {
return true; // versionString is newer than my version
} else {
return false; // versionString is the same as or older than my version
}
},
_isNewerThanFirefox: function TPS__isNewerThanFirefox(versionString) {
let result = Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator)
.compare(this._application.version, versionString);
if (result < 0) {
return true; // versionString is newer than Firefox
} else {
return false; // versionString is the same as or older than Firefox
}
},
_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.
// If the experiment doesn't specify minimum versions, attempt to run it.
let logger = this._logger;
try {
let minTpVer, minFxVer, expName;
if (experiment.experimentInfo) {
minTpVer = experiment.experimentInfo.minTPVersion;
minFxVer = experiment.experimentInfo.minFXVersion;
expName = experiment.experimentInfo.testName;
} else if (experiment.surveyInfo) {
minTpVer = experiment.surveyInfo.minTPVersion;
minFxVer = experiment.surveyInfo.minFXVersion;
expName = experiment.surveyInfo.surveyName;
}
// Minimum test pilot version:
if (minTpVer && this._isNewerThanMe(minTpVer)) {
logger.warn("Not loading " + expName);
logger.warn("Because it requires Test Pilot version " + minTpVer);
// Let user know there is a newer version of Test Pilot available:
if (!this._isShowingUpdateNotification()) {
this._showNotification(
null, false,
this._stringBundle.GetStringFromName(
"testpilot.notification.extensionUpdate.message"),
this._stringBundle.GetStringFromName(
"testpilot.notification.extensionUpdate"),
"update-extension", true, false, "", "", true);
}
return false;
}
// Minimum firefox version:
if (minFxVer && this._isNewerThanFirefox(minFxVer)) {
logger.warn("Not loading " + expName);
logger.warn("Because it requires Firefox version " + minFxVer);
return false;
}
} catch (e) {
logger.warn("Error in requirements check " + e);
}
return true;
},
checkForTasks: function TPS_checkForTasks(callback) {
let logger = this._logger;
if (! this._remoteExperimentLoader ) {
logger.trace("Now requiring remote experiment loader:");
let remoteLoaderModule = this._loader.require("remote-experiment-loader");
logger.trace("Now instantiating remoteExperimentLoader:");
let rel = new remoteLoaderModule.RemoteExperimentLoader(this._logRepo);
this._remoteExperimentLoader = rel;
}
let self = this;
this._remoteExperimentLoader.checkForUpdates(
function(success) {
logger.info("Getting updated experiments... Success? " + success);
// Actually, we do exactly the same thing whether we succeeded in
// downloading new contents or not...
let experiments = self._remoteExperimentLoader.getExperiments();
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);
}
} 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 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();
}
}
);
},
reloadRemoteExperiments: function TPS_reloadRemoteExperiments(callback) {
for (let i = 0; i < this.taskList.length; i++) {
this.taskList[i].onExperimentShutdown();
}
this.taskList = [];
this._loader.unload();
this.checkForTasks(callback);
},
getTaskById: function TPS_getTaskById(id) {
for (let i = 0; i < this.taskList.length; i++) {
let task = this.taskList[i];
if (task.id == id) {
return task;
}
}
return null;
},
getAllTasks: function TPS_getAllTasks() {
return this.taskList;
}
};