diff options
Diffstat (limited to 'browser')
87 files changed, 335 insertions, 7662 deletions
diff --git a/browser/LICENSE b/browser/LICENSE index 99d9d6bcd..f1b2067a7 100644 --- a/browser/LICENSE +++ b/browser/LICENSE @@ -2,6 +2,10 @@ Please see the file ../toolkit/content/license.html for the copyright licensing conditions attached to this codebase, including copies of the licenses concerned. -You are not granted rights or licenses to the trademarks of the -Mozilla Foundation or any party, including without limitation the -Firefox name or logo. +You are not granted rights or licenses to the trademarks of Moonchild +Productions or any other party, including without limitation the +Basilisk name or logo. + +The Serpent logo in branding/unofficial is derived from "Sea Serpent" +by Lorc, licensed under the Creative Commons license CC-BY 3.0 + diff --git a/browser/app/blocklist.xml b/browser/app/blocklist.xml index 9fb217ed8..239fe43a4 100644 --- a/browser/app/blocklist.xml +++ b/browser/app/blocklist.xml @@ -1,5 +1,5 @@ <?xml version='1.0' encoding='UTF-8'?> -<blocklist lastupdate="1521022292000" xmlns="http://www.mozilla.org/2006/addons-blocklist"> +<blocklist lastupdate="1521130300000" xmlns="http://www.mozilla.org/2006/addons-blocklist"> <emItems> <emItem blockID="i988" id="{b12785f5-d8d0-4530-a3ea-5c4263b85bef}"> <prefs/> @@ -2087,7 +2087,7 @@ <prefs/> <versionRange minVersion="0" maxVersion="15.0.5" severity="1"/> </emItem> - <emItem blockID="2447476f-043b-4d0b-9d3c-8e859c97d950" id="{44e4b2cf-77ba-4f76-aca7-f3fcbc2dda2f} "> + <emItem blockID="2447476f-043b-4d0b-9d3c-8e859c97d950" id="{44e4b2cf-77ba-4f76-aca7-f3fcbc2dda2f}"> <prefs/> <versionRange minVersion="0" maxVersion="*" severity="3"/> </emItem> diff --git a/browser/app/nsBrowserApp.cpp b/browser/app/nsBrowserApp.cpp index ac2e85ea3..184b1fc2e 100644 --- a/browser/app/nsBrowserApp.cpp +++ b/browser/app/nsBrowserApp.cpp @@ -24,11 +24,6 @@ #include "nsStringGlue.h" #ifdef XP_WIN -#ifdef MOZ_ASAN -// ASAN requires basilisk.exe to be built with -MD, and it's OK if we don't -// support Windows XP SP2 in ASAN builds. -#define XRE_DONT_SUPPORT_XPSP2 -#endif #define XRE_WANT_ENVIRON #define strcasecmp _stricmp #ifdef MOZ_SANDBOX diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index d6de538d7..0ef9d4ab5 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -229,11 +229,6 @@ pref("browser.slowStartup.notificationDisabled", false); pref("browser.slowStartup.timeThreshold", 40000); pref("browser.slowStartup.maxSamples", 5); -// This url, if changed, MUST continue to point to an https url. Pulling arbitrary content to inject into -// this page over http opens us up to a man-in-the-middle attack that we'd rather not face. If you are a downstream -// repackager of this code using an alternate snippet url, please keep your users safe -pref("browser.aboutHomeSnippets.updateUrl", "https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/"); - pref("browser.enable_automatic_image_resizing", true); pref("browser.casting.enabled", false); pref("browser.chrome.site_icons", true); @@ -1397,13 +1392,6 @@ pref("browser.translation.engine", "bing"); // Determines if Telemetry pings can be archived locally. pref("toolkit.telemetry.archive.enabled", true); -// Telemetry experiments settings. -pref("experiments.enabled", true); -pref("experiments.manifest.fetchIntervalSeconds", 86400); -pref("experiments.manifest.uri", "https://telemetry-experiment.cdn.mozilla.net/manifest/v1/firefox/%VERSION%/%CHANNEL%"); -// Whether experiments are supported by the current application profile. -pref("experiments.supported", true); - // Enable GMP support in the addon manager. pref("media.gmp-provider.enabled", true); diff --git a/browser/base/content/abouthome/aboutHome.css b/browser/base/content/abouthome/aboutHome.css index c0b02e257..bc3f9882c 100644 --- a/browser/base/content/abouthome/aboutHome.css +++ b/browser/base/content/abouthome/aboutHome.css @@ -49,8 +49,7 @@ a { background-repeat: no-repeat; } -#searchIconAndTextContainer, -#snippets { +#searchIconAndTextContainer { width: 470px; } @@ -168,48 +167,6 @@ a { transition-duration: 0ms; } -#defaultSnippet1, -#defaultSnippet2, -#rightsSnippet { - display: block; - min-height: 38px; - background: 0 center no-repeat; - padding: 6px 0; - padding-inline-start: 49px; -} - -#rightsSnippet[hidden] { - display: none; -} - -#defaultSnippet1:dir(rtl), -#defaultSnippet2:dir(rtl), -#rightsSnippet:dir(rtl) { - background-position: right 0 center; -} - -#defaultSnippet1 { - background-image: url("chrome://browser/content/abouthome/snippet1.png"); -} - -#defaultSnippet2 { - background-image: url("chrome://browser/content/abouthome/snippet2.png"); -} - -#snippets { - display: inline-block; - text-align: start; - margin: 12px 0; - color: #3c3c3c; - font-size: 75%; - /* 12px is the computed font size, 15px the computed line height of the snippets - with Segoe UI on a default Windows 7 setup. The 15/12 multiplier approximately - converts em from units of font-size to units of line-height. The goal is to - preset the height of a three-line snippet to avoid visual moving/flickering as - the snippets load. */ - min-height: calc(15/12 * 3em); -} - #launcher { display: -moz-box; -moz-box-align: center; @@ -385,20 +342,6 @@ body[narrow] #restorePreviousSession::before { background-image: url("chrome://branding/content/about-logo@2x.png"); } - #defaultSnippet1, - #defaultSnippet2, - #rightsSnippet { - background-size: 40px; - } - - #defaultSnippet1 { - background-image: url("chrome://browser/content/abouthome/snippet1@2x.png"); - } - - #defaultSnippet2 { - background-image: url("chrome://browser/content/abouthome/snippet2@2x.png"); - } - .launchButton::before, #aboutMozilla::before { transform: scale(.5); diff --git a/browser/base/content/abouthome/aboutHome.js b/browser/base/content/abouthome/aboutHome.js index 50f3e01cd..0cbcc835a 100644 --- a/browser/base/content/abouthome/aboutHome.js +++ b/browser/base/content/abouthome/aboutHome.js @@ -6,23 +6,10 @@ /* import-globals-from ../contentSearchUI.js */ -// The process of adding a new default snippet involves: -// * add a new entity to aboutHome.dtd -// * add a <span/> for it in aboutHome.xhtml -// * add an entry here in the proper ordering (based on spans) -// The <a/> part of the snippet will be linked to the corresponding url. -const DEFAULT_SNIPPETS_URLS = [ - "https://www.mozilla.org/firefox/features/?utm_source=snippet&utm_medium=snippet&utm_campaign=default+feature+snippet" -, "https://addons.mozilla.org/firefox/?utm_source=snippet&utm_medium=snippet&utm_campaign=addons" -]; - -const SNIPPETS_UPDATE_INTERVAL_MS = 14400000; // 4 hours. - // IndexedDB storage constants. const DATABASE_NAME = "abouthome"; const DATABASE_VERSION = 1; const DATABASE_STORAGE = "persistent"; -const SNIPPETS_OBJECTSTORE_NAME = "snippets"; var searchText; // This global tracks if the page has been set up before, to prevent double inits @@ -33,13 +20,6 @@ var gObserver = new MutationObserver(function (mutations) { if (mutation.attributeName == "session") { fitToWidth(); } - if (mutation.attributeName == "snippetsVersion") { - if (!gInitialized) { - ensureSnippetsMapThen(loadSnippets); - gInitialized = true; - } - return; - } } }); @@ -90,126 +70,6 @@ window.addEventListener("keypress", ev => { searchText.value += ev.key; }); -// This object has the same interface as Map and is used to store and retrieve -// the snippets data. It is lazily initialized by ensureSnippetsMapThen(), so -// be sure its callback returned before trying to use it. -var gSnippetsMap; -var gSnippetsMapCallbacks = []; - -/** - * Ensure the snippets map is properly initialized. - * - * @param aCallback - * Invoked once the map has been initialized, gets the map as argument. - * @note Snippets should never directly manage the underlying storage, since - * it may change inadvertently. - */ -function ensureSnippetsMapThen(aCallback) -{ - if (gSnippetsMap) { - aCallback(gSnippetsMap); - return; - } - - // Handle multiple requests during the async initialization. - gSnippetsMapCallbacks.push(aCallback); - if (gSnippetsMapCallbacks.length > 1) { - // We are already updating, the callbacks will be invoked when done. - return; - } - - let invokeCallbacks = function () { - if (!gSnippetsMap) { - gSnippetsMap = Object.freeze(new Map()); - } - - for (let callback of gSnippetsMapCallbacks) { - callback(gSnippetsMap); - } - gSnippetsMapCallbacks.length = 0; - } - - let openRequest = indexedDB.open(DATABASE_NAME, {version: DATABASE_VERSION, - storage: DATABASE_STORAGE}); - - openRequest.onerror = function (event) { - // Try to delete the old database so that we can start this process over - // next time. - indexedDB.deleteDatabase(DATABASE_NAME); - invokeCallbacks(); - }; - - openRequest.onupgradeneeded = function (event) { - let db = event.target.result; - if (!db.objectStoreNames.contains(SNIPPETS_OBJECTSTORE_NAME)) { - db.createObjectStore(SNIPPETS_OBJECTSTORE_NAME); - } - } - - openRequest.onsuccess = function (event) { - let db = event.target.result; - - db.onerror = function (event) { - invokeCallbacks(); - } - - db.onversionchange = function (event) { - event.target.close(); - invokeCallbacks(); - } - - let cache = new Map(); - let cursorRequest; - try { - cursorRequest = db.transaction(SNIPPETS_OBJECTSTORE_NAME) - .objectStore(SNIPPETS_OBJECTSTORE_NAME).openCursor(); - } catch (ex) { - console.error(ex); - invokeCallbacks(); - return; - } - - cursorRequest.onerror = function (event) { - invokeCallbacks(); - } - - cursorRequest.onsuccess = function(event) { - let cursor = event.target.result; - - // Populate the cache from the persistent storage. - if (cursor) { - cache.set(cursor.key, cursor.value); - cursor.continue(); - return; - } - - // The cache has been filled up, create the snippets map. - gSnippetsMap = Object.freeze({ - get: (aKey) => cache.get(aKey), - set: function (aKey, aValue) { - db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite") - .objectStore(SNIPPETS_OBJECTSTORE_NAME).put(aValue, aKey); - return cache.set(aKey, aValue); - }, - has: (aKey) => cache.has(aKey), - delete: function (aKey) { - db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite") - .objectStore(SNIPPETS_OBJECTSTORE_NAME).delete(aKey); - return cache.delete(aKey); - }, - clear: function () { - db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite") - .objectStore(SNIPPETS_OBJECTSTORE_NAME).clear(); - return cache.clear(); - }, - get size() { return cache.size; }, - }); - - setTimeout(invokeCallbacks, 0); - } - } -} - function onSearchSubmit(aEvent) { gContentSearchController.search(aEvent); @@ -246,146 +106,6 @@ function setupSearch() */ function loadCompleted() { - var event = new CustomEvent("AboutHomeLoadSnippetsCompleted", {bubbles:true}); - document.dispatchEvent(event); -} - -/** - * Update the local snippets from the remote storage, then show them through - * showSnippets. - */ -function loadSnippets() -{ - if (!gSnippetsMap) - throw new Error("Snippets map has not properly been initialized"); - - // Allow tests to modify the snippets map before using it. - var event = new CustomEvent("AboutHomeLoadSnippets", {bubbles:true}); - document.dispatchEvent(event); - - // Check cached snippets version. - let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0; - let currentVersion = document.documentElement.getAttribute("snippetsVersion"); - if (cachedVersion < currentVersion) { - // The cached snippets are old and unsupported, restart from scratch. - gSnippetsMap.clear(); - } - - // Check last snippets update. - let lastUpdate = gSnippetsMap.get("snippets-last-update"); - let updateURL = document.documentElement.getAttribute("snippetsURL"); - let shouldUpdate = !lastUpdate || - Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS; - if (updateURL && shouldUpdate) { - // Try to update from network. - let xhr = new XMLHttpRequest(); - xhr.timeout = 5000; - // Even if fetching should fail we don't want to spam the server, thus - // set the last update time regardless its results. Will retry tomorrow. - gSnippetsMap.set("snippets-last-update", Date.now()); - xhr.onloadend = function (event) { - if (xhr.status == 200) { - gSnippetsMap.set("snippets", xhr.responseText); - gSnippetsMap.set("snippets-cached-version", currentVersion); - } - showSnippets(); - loadCompleted(); - }; - try { - xhr.open("GET", updateURL, true); - xhr.send(null); - } catch (ex) { - showSnippets(); - loadCompleted(); - return; - } - } else { - showSnippets(); - loadCompleted(); - } -} - -/** - * Shows locally cached remote snippets, or default ones when not available. - * - * @note: snippets should never invoke showSnippets(), or they may cause - * a "too much recursion" exception. - */ -var _snippetsShown = false; -function showSnippets() -{ - let snippetsElt = document.getElementById("snippets"); - - // Show about:rights notification, if needed. - let showRights = document.documentElement.getAttribute("showKnowYourRights"); - if (showRights) { - let rightsElt = document.getElementById("rightsSnippet"); - let anchor = rightsElt.getElementsByTagName("a")[0]; - anchor.href = "about:rights"; - snippetsElt.appendChild(rightsElt); - rightsElt.removeAttribute("hidden"); - return; - } - - if (!gSnippetsMap) - throw new Error("Snippets map has not properly been initialized"); - if (_snippetsShown) { - // There's something wrong with the remote snippets, just in case fall back - // to the default snippets. - showDefaultSnippets(); - throw new Error("showSnippets should never be invoked multiple times"); - } - _snippetsShown = true; - - let snippets = gSnippetsMap.get("snippets"); - // If there are remotely fetched snippets, try to to show them. - if (snippets) { - // Injecting snippets can throw if they're invalid XML. - try { - snippetsElt.innerHTML = snippets; - // Scripts injected by innerHTML are inactive, so we have to relocate them - // through DOM manipulation to activate their contents. - Array.forEach(snippetsElt.getElementsByTagName("script"), function(elt) { - let relocatedScript = document.createElement("script"); - relocatedScript.type = "text/javascript;version=1.8"; - relocatedScript.text = elt.text; - elt.parentNode.replaceChild(relocatedScript, elt); - }); - return; - } catch (ex) { - // Bad content, continue to show default snippets. - } - } - - showDefaultSnippets(); -} - -/** - * Clear snippets element contents and show default snippets. - */ -function showDefaultSnippets() -{ - // Clear eventual contents... - let snippetsElt = document.getElementById("snippets"); - snippetsElt.innerHTML = ""; - - // ...then show default snippets. - let defaultSnippetsElt = document.getElementById("defaultSnippets"); - let entries = defaultSnippetsElt.querySelectorAll("span"); - // Choose a random snippet. Assume there is always at least one. - let randIndex = Math.floor(Math.random() * entries.length); - let entry = entries[randIndex]; - // Inject url in the eventual link. - if (DEFAULT_SNIPPETS_URLS[randIndex]) { - let links = entry.getElementsByTagName("a"); - // Default snippets can have only one link, otherwise something is messed - // up in the translation. - if (links.length == 1) { - links[0].href = DEFAULT_SNIPPETS_URLS[randIndex]; - } - } - // Move the default snippet to the snippets element. - snippetsElt.appendChild(entry); } function fitToWidth() { diff --git a/browser/base/content/abouthome/aboutHome.xhtml b/browser/base/content/abouthome/aboutHome.xhtml index c288e732e..22bf2e7e8 100644 --- a/browser/base/content/abouthome/aboutHome.xhtml +++ b/browser/base/content/abouthome/aboutHome.xhtml @@ -46,15 +46,6 @@ <input id="searchSubmit" type="button" onclick="onSearchSubmit(event)" title="&contentSearchSubmit.tooltip;"/> </div> - - <div id="snippetContainer"> - <div id="defaultSnippets" hidden="true"> - <span id="defaultSnippet1">&abouthome.defaultSnippet1.v1;</span> - <span id="defaultSnippet2">&abouthome.defaultSnippet2.v1;</span> - </div> - <span id="rightsSnippet" hidden="true">&abouthome.rightsSnippet;</span> - <div id="snippets"/> - </div> </div> <div class="spacer"/> @@ -73,7 +64,5 @@ <button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button> </div> - <a id="aboutMozilla" href="https://www.mozilla.org/about/?utm_source=about-home&utm_medium=Referral" - aria-label="&abouthome.aboutMozilla.label;"/> </body> </html> diff --git a/browser/base/content/browser-context.inc b/browser/base/content/browser-context.inc index 51b14d152..3061cccdd 100644 --- a/browser/base/content/browser-context.inc +++ b/browser/base/content/browser-context.inc @@ -456,12 +456,14 @@ oncommand="gContextMenu.openPasswordManager();"/> </menupopup> </menu> +#ifdef MOZ_DEVTOOLS <menuseparator id="inspect-separator" hidden="true"/> <menuitem id="context-inspect" hidden="true" label="&inspectContextMenu.label;" accesskey="&inspectContextMenu.accesskey;" oncommand="gContextMenu.inspectNode();"/> +#endif <menuseparator id="context-media-eme-separator" hidden="true"/> <menuitem id="context-media-eme-learnmore" class="menuitem-iconic" diff --git a/browser/base/content/browser-media.js b/browser/base/content/browser-media.js index f721be74d..bd5c5b227 100644 --- a/browser/base/content/browser-media.js +++ b/browser/base/content/browser-media.js @@ -45,11 +45,23 @@ var gEMEHandler = { } return true; }, - getLearnMoreLink: function(msgId) { - let text = gNavigatorBundle.getString("emeNotifications." + msgId + ".learnMoreLabel"); + getEMEDisabledFragment: function(msgId) { + let mainMessage = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.message"); + let [prefix, suffix] = mainMessage.split(/%(?:\$\d)?S/).map(s => document.createTextNode(s)); + let text = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.learnMoreLabel"); let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL"); - return "<label class='text-link' href='" + baseURL + "drm-content'>" + - text + "</label>"; + let link = document.createElement("label"); + link.className = "text-link"; + link.setAttribute("href", baseURL + "drm-content"); + link.textContent = text; + + let fragment = document.createDocumentFragment(); + [prefix, link, suffix].forEach(n => fragment.appendChild(n)); + return fragment; + }, + getMessageWithBrandName: function(notificationId) { + let msgId = "emeNotifications." + notificationId + ".message"; + return gNavigatorBundle.getFormattedString(msgId, [this._brandShortName]); }, receiveMessage: function({target: browser, data: data}) { let parsedData; @@ -67,7 +79,8 @@ var gEMEHandler = { let notificationId; let buttonCallback; - let params = []; + // Notification message can be either a string or a DOM fragment. + let notificationMessage; switch (status) { case "available": case "cdm-created": @@ -82,17 +95,17 @@ var gEMEHandler = { case "cdm-disabled": notificationId = "drmContentDisabled"; buttonCallback = gEMEHandler.ensureEMEEnabled.bind(gEMEHandler, browser, keySystem) - params = [this.getLearnMoreLink(notificationId)]; + notificationMessage = this.getEMEDisabledFragment(); break; case "cdm-insufficient-version": notificationId = "drmContentCDMInsufficientVersion"; - params = [this._brandShortName]; + notificationMessage = this.getMessageWithBrandName(notificationId); break; case "cdm-not-installed": notificationId = "drmContentCDMInstalling"; - params = [this._brandShortName]; + notificationMessage = this.getMessageWithBrandName(notificationId); break; case "cdm-not-supported": @@ -104,44 +117,29 @@ var gEMEHandler = { return; } - this.showNotificationBar(browser, notificationId, keySystem, params, buttonCallback); - }, - showNotificationBar: function(browser, notificationId, keySystem, labelParams, callback) { + // Now actually create the notification + let box = gBrowser.getNotificationBox(browser); if (box.getNotificationWithValue(notificationId)) { return; } - let msgPrefix = "emeNotifications." + notificationId + "."; - let msgId = msgPrefix + "message"; - - let message = labelParams.length ? - gNavigatorBundle.getFormattedString(msgId, labelParams) : - gNavigatorBundle.getString(msgId); - let buttons = []; - if (callback) { + if (buttonCallback) { + let msgPrefix = "emeNotifications." + notificationId + "."; let btnLabelId = msgPrefix + "button.label"; let btnAccessKeyId = msgPrefix + "button.accesskey"; buttons.push({ label: gNavigatorBundle.getString(btnLabelId), accessKey: gNavigatorBundle.getString(btnAccessKeyId), - callback: callback + callback: buttonCallback }); } let iconURL = "chrome://browser/skin/drm-icon.svg#chains-black"; - // Do a little dance to get rich content into the notification: - let fragment = document.createDocumentFragment(); - let descriptionContainer = document.createElement("description"); - descriptionContainer.innerHTML = message; - while (descriptionContainer.childNodes.length) { - fragment.appendChild(descriptionContainer.childNodes[0]); - } - - box.appendNotification(fragment, notificationId, iconURL, box.PRIORITY_WARNING_MEDIUM, - buttons); + box.appendNotification(notificationMessage, notificationId, iconURL, + box.PRIORITY_WARNING_MEDIUM, buttons); }, showPopupNotificationForSuccess: function(browser, keySystem) { // We're playing EME content! Remove any "we can't play because..." messages. diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js index a447880be..83c737977 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -299,24 +299,23 @@ var StarUI = { parent.setAttribute("open", "true"); } } - let panel = this.panel; - let target = panel; - if (target.parentNode) { - // By targeting the panel's parent and using a capturing listener, we - // can have our listener called before others waiting for the panel to - // be shown (which probably expect the panel to be fully initialized) - target = target.parentNode; - } - target.addEventListener("popupshown", function shownListener(event) { - if (event.target == panel) { - target.removeEventListener("popupshown", shownListener, true); - - gEditItemOverlay.initPanel({ node: aNode - , hiddenRows: ["description", "location", - "loadInSidebar", "keyword"] - , focusedElement: "preferred"}); + let onPanelReady = fn => { + let target = this.panel; + if (target.parentNode) { + // By targeting the panel's parent and using a capturing listener, we + // can have our listener called before others waiting for the panel to + // be shown (which probably expect the panel to be fully initialized) + target = target.parentNode; } - }, true); + target.addEventListener("popupshown", function(event) { + fn(); + }, {"capture": true, "once": true}); + }; + gEditItemOverlay.initPanel({ node: aNode + , onPanelReady + , hiddenRows: ["description", "location", + "loadInSidebar", "keyword"] + , focusedElement: "preferred"}); this.panel.openPopup(aAnchorElement, aPosition); }), diff --git a/browser/base/content/browser-plugins.js b/browser/base/content/browser-plugins.js index ad070df12..c1bc65860 100644 --- a/browser/base/content/browser-plugins.js +++ b/browser/base/content/browser-plugins.js @@ -63,9 +63,7 @@ var gPluginHandler = { msg.data.pluginID); break; case "PluginContent:SubmitReport": - if (AppConstants.MOZ_CRASHREPORTER) { - this.submitReport(msg.data.runID, msg.data.keyVals, msg.data.submitURLOptIn); - } + // Nothing to do here break; case "PluginContent:LinkClickCallback": switch (msg.data.name) { @@ -98,11 +96,8 @@ var gPluginHandler = { }, submitReport: function submitReport(runID, keyVals, submitURLOptIn) { - if (!AppConstants.MOZ_CRASHREPORTER) { - return; - } - Services.prefs.setBoolPref("dom.ipc.plugins.reportCrashURL", submitURLOptIn); - PluginCrashReporter.submitCrashReport(runID, keyVals); + /*** STUB ***/ + return; }, // Callback for user clicking a "reload page" link @@ -461,18 +456,7 @@ var gPluginHandler = { // If we don't have a minidumpID, we can't (or didn't) submit anything. // This can happen if the plugin is killed from the task manager. - let state; - if (!AppConstants.MOZ_CRASHREPORTER || !gCrashReporter.enabled) { - // This state tells the user that crash reporting is disabled, so we - // cannot send a report. - state = "noSubmit"; - } else if (!pluginDumpID) { - // This state tells the user that there is no crash report available. - state = "noReport"; - } else { - // This state asks the user to submit a crash report. - state = "please"; - } + let state = "noSubmit"; let mm = window.getGroupMessageManager("browsers"); mm.broadcastAsyncMessage("BrowserPlugins:NPAPIPluginProcessCrashed", @@ -513,22 +497,6 @@ var gPluginHandler = { callback: function() { browser.reload(); }, }]; - if (AppConstants.MOZ_CRASHREPORTER && - PluginCrashReporter.hasCrashReport(pluginID)) { - let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label"); - let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey"); - let submitButton = { - label: submitLabel, - accessKey: submitKey, - popup: null, - callback: () => { - PluginCrashReporter.submitCrashReport(pluginID); - }, - }; - - buttons.push(submitButton); - } - notification = notificationBox.appendNotification(messageString, "plugin-crashed", iconURL, priority, buttons); diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index d41e94ae6..696a2871a 100755 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -53,19 +53,16 @@ Cu.import("resource://gre/modules/NotificationDB.jsm"); ["UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"], ["Weave", "resource://services-sync/main.js"], ["fxAccounts", "resource://gre/modules/FxAccounts.jsm"], +#ifdef MOZ_DEVTOOLS + // Note: Do not delete! It is used for: base/content/nsContextMenu.js ["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"], - ["gDevToolsBrowser", "resource://devtools/client/framework/gDevTools.jsm"], +#endif ["webrtcUI", "resource:///modules/webrtcUI.jsm", ] ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource)); XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing", "resource://gre/modules/SafeBrowsing.jsm"); -if (AppConstants.MOZ_CRASHREPORTER) { - XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter", - "resource:///modules/ContentCrashHandlers.jsm"); -} - // lazy service getters [ ["Favicons", "@mozilla.org/browser/favicon-service;1", "mozIAsyncFavicons"], @@ -74,13 +71,6 @@ if (AppConstants.MOZ_CRASHREPORTER) { ["gDNSService", "@mozilla.org/network/dns-service;1", "nsIDNSService"], ].forEach(([name, cc, ci]) => XPCOMUtils.defineLazyServiceGetter(this, name, cc, ci)); -if (AppConstants.MOZ_CRASHREPORTER) { - XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter", - "@mozilla.org/xre/app-info;1", - "nsICrashReporter"); -} - - XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() { let tmp = {}; Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", tmp); @@ -214,7 +204,8 @@ var gInitialPages = [ "about:home", "about:privatebrowsing", "about:welcomeback", - "about:sessionrestore" + "about:sessionrestore", + "about:logopage" ]; function* browserWindows() { @@ -4582,23 +4573,6 @@ var XULBrowserWindow = { setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0); else this.asyncUpdateUI(); - - if (AppConstants.MOZ_CRASHREPORTER && aLocationURI) { - let uri = aLocationURI.clone(); - try { - // If the current URI contains a username/password, remove it. - uri.userPass = ""; - } catch (ex) { /* Ignore failures on about: URIs. */ } - - try { - gCrashReporter.annotateCrashReport("URL", uri.spec); - } catch (ex) { - // Don't make noise when the crash reporter is built but not enabled. - if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) { - throw ex; - } - } - } }, asyncUpdateUI: function () { @@ -7893,15 +7867,6 @@ var TabContextMenu = { } }; -Object.defineProperty(this, "HUDService", { - get: function HUDService_getter() { - let devtools = Cu.import("resource://devtools/shared/Loader.jsm", {}).devtools; - return devtools.require("devtools/client/webconsole/hudservice").HUDService; - }, - configurable: true, - enumerable: true -}); - // Prompt user to restart the browser in safe mode function safeModeRestart() { if (Services.appinfo.inSafeMode) { @@ -7959,30 +7924,6 @@ function duplicateTabIn(aTab, where, delta) { } } -var Scratchpad = { - openScratchpad: function SP_openScratchpad() { - return this.ScratchpadManager.openScratchpad(); - } -}; - -XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() { - let tmp = {}; - Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm", tmp); - return tmp.ScratchpadManager; -}); - -var ResponsiveUI = { - toggle: function RUI_toggle() { - this.ResponsiveUIManager.toggle(window, gBrowser.selectedTab); - } -}; - -XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() { - let tmp = {}; - Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", tmp); - return tmp.ResponsiveUIManager; -}); - var MousePosTracker = { _listeners: new Set(), _x: 0, diff --git a/browser/base/content/tab-content.js b/browser/base/content/tab-content.js index 05f8e00ab..7e803796a 100644 --- a/browser/base/content/tab-content.js +++ b/browser/base/content/tab-content.js @@ -147,13 +147,10 @@ var AboutHomeListener = { if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isContentWindowPrivate(content)) doc.getElementById("launcher").setAttribute("session", "true"); - // Inject search engine and snippets URL. + // Inject search engine URL. let docElt = doc.documentElement; - // Set snippetsVersion last, which triggers to show the snippets when it's set. - docElt.setAttribute("snippetsURL", aData.snippetsURL); if (aData.showKnowYourRights) docElt.setAttribute("showKnowYourRights", "true"); - docElt.setAttribute("snippetsVersion", aData.snippetsVersion); }, onPageLoad: function() { diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js index 833369f4d..0b703b6f8 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -35,7 +35,7 @@ var gBidiUI = false; * Determines whether the given url is considered a special URL for new tabs. */ function isBlankPageURL(aURL) { - return aURL == "about:blank" || aURL == BROWSER_NEW_TAB_URL; + return aURL == "about:blank" || aURL == "about:newtab" || aURL == "about:logopage"; } function getBrowserURL() diff --git a/browser/base/jar.mn b/browser/base/jar.mn index a65c77338..9cbfe7c15 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -67,7 +67,7 @@ browser.jar: content/browser/aboutTabCrashed.js (content/aboutTabCrashed.js) content/browser/aboutTabCrashed.xhtml (content/aboutTabCrashed.xhtml) * content/browser/browser.css (content/browser.css) - content/browser/browser.js (content/browser.js) +* content/browser/browser.js (content/browser.js) * content/browser/browser.xul (content/browser.xul) content/browser/browser-addons.js (content/browser-addons.js) content/browser/browser-captivePortal.js (content/browser-captivePortal.js) diff --git a/browser/branding/shared/preferences.inc b/browser/branding/shared/preferences.inc index 08f6c950b..90fd3da06 100644 --- a/browser/branding/shared/preferences.inc +++ b/browser/branding/shared/preferences.inc @@ -31,3 +31,5 @@ pref("browser.safebrowsing.downloads.remote.enabled", false); // Disable the UI controls for it as well for Basilisk-official. pref("browser.safebrowsing.UI.enabled", false); +// +pref("general.useragent.appVersionIsBuildID", true); diff --git a/browser/branding/unofficial/VisualElements_150.png b/browser/branding/unofficial/VisualElements_150.png Binary files differindex 461961e8d..eb74e4adc 100644 --- a/browser/branding/unofficial/VisualElements_150.png +++ b/browser/branding/unofficial/VisualElements_150.png diff --git a/browser/branding/unofficial/VisualElements_70.png b/browser/branding/unofficial/VisualElements_70.png Binary files differindex aad81f40d..571532a9b 100644 --- a/browser/branding/unofficial/VisualElements_70.png +++ b/browser/branding/unofficial/VisualElements_70.png diff --git a/browser/branding/unofficial/branding.nsi b/browser/branding/unofficial/branding.nsi index 34214453f..77f08a4cb 100644 --- a/browser/branding/unofficial/branding.nsi +++ b/browser/branding/unofficial/branding.nsi @@ -8,19 +8,19 @@ # BrandFullNameInternal is used for some registry and file system values # instead of BrandFullName and typically should not be modified. -!define BrandFullNameInternal "Mozilla Developer Preview" -!define CompanyName "mozilla.org" -!define URLInfoAbout "https://www.mozilla.org" -!define HelpLink "https://support.mozilla.org" +!define BrandFullNameInternal "Serpent" +!define CompanyName "Moonchild Productions" +!define URLInfoAbout "http://www.basilisk-browser.org" +!define HelpLink "https://forum.palemoon.org" -!define URLStubDownload "http://download.mozilla.org/?os=win&lang=${AB_CD}&product=firefox-latest" -!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=release&installer_lang=${AB_CD}" -!define URLSystemRequirements "https://www.mozilla.org/firefox/system-requirements/" +!define URLStubDownload "" +!define URLManualDownload "" +!define URLSystemRequirements "" !define Channel "unofficial" # The installer's certificate name and issuer expected by the stub installer -!define CertNameDownload "Mozilla Corporation" -!define CertIssuerDownload "DigiCert SHA2 Assured ID Code Signing CA" +!define CertNameDownload "" +!define CertIssuerDownload "" # Dialog units are used so the UI displays correctly with the system's DPI # settings. diff --git a/browser/branding/unofficial/configure.sh b/browser/branding/unofficial/configure.sh index edd3bd3e8..ea4e37e45 100644 --- a/browser/branding/unofficial/configure.sh +++ b/browser/branding/unofficial/configure.sh @@ -2,4 +2,4 @@ # 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/. -MOZ_APP_DISPLAYNAME=Nightly +MOZ_APP_DISPLAYNAME=Serpent diff --git a/browser/branding/unofficial/content/about-background.png b/browser/branding/unofficial/content/about-background.png Binary files differindex 70eb8dafd..e36211b08 100644 --- a/browser/branding/unofficial/content/about-background.png +++ b/browser/branding/unofficial/content/about-background.png diff --git a/browser/branding/unofficial/content/about-logo.png b/browser/branding/unofficial/content/about-logo.png Binary files differindex 4c7214ba3..c5a838178 100644 --- a/browser/branding/unofficial/content/about-logo.png +++ b/browser/branding/unofficial/content/about-logo.png diff --git a/browser/branding/unofficial/content/about-logo@2x.png b/browser/branding/unofficial/content/about-logo@2x.png Binary files differindex 3526eda54..48c31564c 100644 --- a/browser/branding/unofficial/content/about-logo@2x.png +++ b/browser/branding/unofficial/content/about-logo@2x.png diff --git a/browser/branding/unofficial/content/about-wordmark.svg b/browser/branding/unofficial/content/about-wordmark.svg index 60b278d03..ce7a5c07b 100644 --- a/browser/branding/unofficial/content/about-wordmark.svg +++ b/browser/branding/unofficial/content/about-wordmark.svg @@ -1,22 +1,87 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- 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/. --> -<svg xmlns="http://www.w3.org/2000/svg" width="132px" height="48px" viewBox="0 0 132 48"> - <path fill="#fff" d="M60.6,14.3l-2.4-2.4C57,12.7,56,13,54.7,13c-3,0-3.8-1.4-7.6-1.4c-5.4,0-9.2,3.4-9.2,8.4 - c0,3.3,2.2,6.1,5.6,7.2c-3.4,1-4.5,2.2-4.5,4.3c0,2.2,1.8,3.6,4.7,3.6h3.8c2.5,0,3.9,0.2,4.9,0.9c0.9,0.6,1.4,1.6,1.4,3 - c0,3.1-2.2,4.4-6,4.4c-2,0-3.8-0.5-5.1-1.2c-0.9-0.6-1.5-1.6-1.5-2.9c0-0.8,0.3-1.7,0.7-2.2l-4.1,0.4c-0.3,1-0.5,1.7-0.5,2.6 - c0,3.5,3,6.4,10.8,6.4c6.1,0,9.9-2.5,9.9-7.9c0-2.1-0.8-3.9-2.7-5.3c-1.5-1.1-3.1-1.4-6-1.4h-4c-1.3,0-2-0.5-2-1.2 - c0-0.8,1.1-1.7,4.5-2.9c1.8,0,3.4-0.3,4.7-1.1c2.3-1.4,3.7-4.1,3.7-6.8c0-1.6-0.5-3-1.5-4.3c0.4,0.2,1.1,0.3,1.7,0.3 - C57.9,15.8,59,15.4,60.6,14.3z M47.1,24.8c-3.1,0-4.8-1.7-4.8-4.8c0-3.5,1.6-5.1,4.7-5.1c3.3,0,4.6,1.5,4.6,4.9 - C51.6,23.1,50.1,24.8,47.1,24.8z M30.7,1.3c-1.7,0-3,1.4-3,3.1s1.4,3,3,3c1.7,0,3.1-1.3,3.1-3C33.7,2.7,32.4,1.3,30.7,1.3z - M107.7,34.5c-1.1,0-1.4-0.6-1.4-2.5V6.5c0-3.8-0.6-5.9-0.6-5.9l-3.9,0.8c0,0,0.6,1.9,0.6,5.1v26.4c0,1.8,0.4,2.8,1.2,3.5 - c0.7,0.7,1.7,1,2.9,1c1,0,1.5-0.1,2.5-0.5l-0.8-2.5C108.2,34.4,107.8,34.5,107.7,34.5z M74.7,11.6c-3.2,0-6.1,1.8-8.3,3.9 - c0,0,0.2-1.8,0.2-3.4V6.3c0-3.8-0.7-5.9-0.7-5.9l-3.9,0.7c0,0,0.7,1.9,0.7,5.1V37h3.9V19.3c2.1-2.7,4.9-4.2,7.2-4.2 - c1.3,0,2.3,0.4,2.9,1c0.7,0.7,0.9,1.8,0.9,3.7V37h3.8V19.1c0-1.8-0.1-2.6-0.4-3.6C80.4,13.2,77.7,11.6,74.7,11.6z M127.4,12.1 - l-4.9,16.4c-0.6,2-1.6,5.2-1.6,5.2s-0.7-3.9-1.5-6.2l-5.1-16.2l-3.9,1.3l5.4,15.6c0.8,2.5,2.2,7.4,2.5,9l1.6-0.3 - c-1.3,5.1-2.5,6.7-5.7,7.6l1.2,2.7c4.4-1,6.4-4.3,8-9.3l8.6-25.8H127.4z M96.9,15l1.2-2.9h-6.2c0-3.3,0.5-7.2,0.5-7.2l-4.1,0.9 - c0,0-0.4,3.9-0.4,6.3h-3.2V15h3.2v17.1c0,2.5,0.7,4.1,2.4,5c0.9,0.4,1.9,0.7,3.3,0.7c1.8,0,3.1-0.4,4.4-1l-0.6-2.5 - c-0.7,0.3-1.3,0.5-2.4,0.5c-2.4,0-3.2-0.9-3.2-3.7V15H96.9z M28.6,37h4.1V11.5l-4.1,0.6V37z M18.9,21.3c0,5,0.4,10.5,0.4,10.5 - s-1.4-3.8-3.2-7.2L4.8,2.7H0V37h4.2L4,17.1c0-4.5-0.4-9.3-0.4-9.3s1.7,4.1,3.9,8.2l11,21h4.3V2.7h-4L18.9,21.3z"/> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="132px" + height="48px" + viewBox="0 0 132 48" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="about-wordmark.svg"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1215" + inkscape:window-height="778" + id="namedview6" + showgrid="false" + inkscape:zoom="2.4090909" + inkscape:cx="91.154186" + inkscape:cy="24" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <g + transform="scale(0.83939803,1.1913299)" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.56122971px;line-height:125%;font-family:'Levenim MT';-inkscape-font-specification:'Levenim MT';letter-spacing:0px;word-spacing:0px;fill:#a8e6db;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="text4138"> + <path + d="m 18.521413,24.115665 q 0,3.470569 -2.618513,5.881264 -2.535386,2.348349 -6.0475185,2.348349 -5.6942271,0 -9.22714164,-6.463156 L 3.2259712,24.323483 q 2.7432042,5.04999 6.3384644,5.04999 3.4082234,0 4.9460804,-2.639295 0.706583,-1.205347 0.706583,-2.556167 0,-1.537857 -1.039093,-3.013369 Q 12.827186,19.23193 9.1487985,16.488726 5.3872836,13.703958 4.1819363,12.082974 2.560952,9.9008792 2.560952,7.4070572 q 0,-4.2394975 3.6576056,-6.2345551 1.7248936,-0.93518331 3.761515,-0.93518331 4.3641884,0 8.0217944,5.00842591 L 15.508045,7.1368931 Q 14.115661,5.3080903 13.138914,4.5391618 11.684184,3.3961601 9.917727,3.3961601 q -1.7664573,0 -2.9510228,1.0598743 -1.246911,1.0806563 -1.246911,2.8263317 0,2.0574032 1.9327121,3.9693339 0.56111,0.540328 3.9485517,3.054932 3.117277,2.306785 4.655134,4.15637 2.265222,2.763986 2.265222,5.652663 z" + style="" + id="path4143" /> + <path + d="m 46.743165,20.499623 -20.324649,0 q 0.08313,3.803079 2.369131,6.338464 2.410694,2.660077 6.151427,2.660077 3.616042,0 6.130646,-2.202876 1.143002,-0.997529 2.47304,-3.221187 l 2.452259,1.288475 q -2.286004,4.468098 -6.338465,6.026737 -1.974275,0.768928 -4.468097,0.768928 -5.091554,0 -8.437432,-3.36666 -3.325096,-3.366659 -3.325096,-8.458213 0,-4.301843 2.639295,-7.668503 3.345878,-4.2810608 8.956978,-4.2810608 5.777354,0 9.227141,4.3849708 2.452259,3.117277 2.493822,7.730848 z M 43.54276,17.985019 q -1.080656,-4.301843 -4.634352,-5.964391 -1.828803,-0.852056 -3.844643,-0.852056 -3.325096,0 -5.715008,2.140531 -1.745676,1.558639 -2.639295,4.675916 l 16.833298,0 z" + style="" + id="path4145" /> + <path + d="m 62.516592,9.1527326 -1.517075,2.4522584 q -0.914401,-0.394855 -1.537857,-0.394855 -3.491351,0 -4.862953,4.904517 -0.540328,1.932712 -0.540328,7.813975 l 0,7.647721 -2.971805,0 0,-22.610653 2.971805,0 0,3.304314 q 2.639295,-3.8862058 5.881264,-3.8862058 1.205347,0 2.576949,0.7689284 z" + style="" + id="path4147" /> + <path + d="m 88.577029,20.208677 q 0,4.94608 -3.325096,8.416649 -3.366659,3.532915 -8.271176,3.532915 -5.382499,0 -9.060887,-4.468098 l 0,12.157383 -2.888677,0 0,-30.88183 2.888677,0 0,4.15637 q 3.449787,-4.7382618 8.977759,-4.7382618 4.883735,0 8.271177,3.4705688 3.408223,3.470569 3.408223,8.354304 z m -2.930241,0.103909 q 0,-2.410694 -1.205347,-4.572007 -1.205347,-2.161312 -3.283532,-3.345878 -2.057403,-1.205347 -4.48888,-1.205347 -3.865424,0 -6.421592,2.660077 -2.535385,2.660077 -2.535385,6.546283 0,5.403281 4.468097,7.897103 2.140531,1.205347 4.530444,1.205347 2.410694,0 4.488879,-1.246911 2.01584,-1.226129 3.221187,-3.387441 1.226129,-2.161313 1.226129,-4.551226 z" + style="" + id="path4149" /> + <path + d="m 116.23767,20.499623 -20.324647,0 q 0.08313,3.803079 2.36913,6.338464 2.410697,2.660077 6.151427,2.660077 3.61604,0 6.13065,-2.202876 1.143,-0.997529 2.47304,-3.221187 l 2.45226,1.288475 q -2.28601,4.468098 -6.33847,6.026737 -1.97427,0.768928 -4.4681,0.768928 -5.09155,0 -8.437428,-3.36666 -3.325096,-3.366659 -3.325096,-8.458213 0,-4.301843 2.639295,-7.668503 3.345878,-4.2810608 8.956979,-4.2810608 5.77735,0 9.22714,4.3849708 2.45226,3.117277 2.49382,7.730848 z m -3.2004,-2.514604 q -1.08066,-4.301843 -4.63436,-5.964391 -1.8288,-0.852056 -3.84464,-0.852056 -3.32509,0 -5.715007,2.140531 -1.745675,1.558639 -2.639295,4.675916 l 16.833302,0 z" + style="" + id="path4151" /> + <path + d="m 141.4045,31.576349 -2.88868,0 0,-10.78578 q 0,-3.823861 -0.33251,-5.216245 -1.08066,-4.509661 -5.69423,-4.509661 -2.63929,0 -4.73826,1.745675 -2.07819,1.724894 -2.7432,4.322625 -0.41564,1.641766 -0.41564,6.151428 l 0,8.291958 -2.90946,0 0,-22.610653 2.90946,0 0,4.052461 q 3.47057,-4.6343528 8.47899,-4.6343528 2.47304,0 4.44732,1.2676928 1.99506,1.246911 2.93024,3.470569 0.95597,2.202876 0.95597,6.816447 l 0,11.637836 z" + style="" + id="path4153" /> + <path + d="m 157.69747,11.459518 -4.61357,0 0,20.116831 -2.93024,0 0,-20.116831 -3.96934,0 0,-2.493822 3.96934,0 0,-8.3958676 2.93024,0 0,8.3958676 4.61357,0 0,2.493822 z" + style="" + id="path4155" /> + </g> </svg> diff --git a/browser/branding/unofficial/content/about.png b/browser/branding/unofficial/content/about.png Binary files differindex 231449344..e323c8df5 100644 --- a/browser/branding/unofficial/content/about.png +++ b/browser/branding/unofficial/content/about.png diff --git a/browser/branding/unofficial/content/icon48.png b/browser/branding/unofficial/content/icon48.png Binary files differindex 5fc7861e5..16e022a64 100644 --- a/browser/branding/unofficial/content/icon48.png +++ b/browser/branding/unofficial/content/icon48.png diff --git a/browser/branding/unofficial/content/icon64.png b/browser/branding/unofficial/content/icon64.png Binary files differindex 83f7016bc..9860917e3 100644 --- a/browser/branding/unofficial/content/icon64.png +++ b/browser/branding/unofficial/content/icon64.png diff --git a/browser/branding/unofficial/default16.png b/browser/branding/unofficial/default16.png Binary files differindex d285a90b4..67ef39df8 100644 --- a/browser/branding/unofficial/default16.png +++ b/browser/branding/unofficial/default16.png diff --git a/browser/branding/unofficial/default32.png b/browser/branding/unofficial/default32.png Binary files differindex 95adf2497..2f709e6b2 100644 --- a/browser/branding/unofficial/default32.png +++ b/browser/branding/unofficial/default32.png diff --git a/browser/branding/unofficial/default48.png b/browser/branding/unofficial/default48.png Binary files differindex d38185f54..02a1e14c1 100644 --- a/browser/branding/unofficial/default48.png +++ b/browser/branding/unofficial/default48.png diff --git a/browser/branding/unofficial/firefox.icns b/browser/branding/unofficial/firefox.icns Binary files differindex 0c6941acf..2c613634b 100644 --- a/browser/branding/unofficial/firefox.icns +++ b/browser/branding/unofficial/firefox.icns diff --git a/browser/branding/unofficial/firefox.ico b/browser/branding/unofficial/firefox.ico Binary files differindex 5217a6c0b..0c7acb61b 100644 --- a/browser/branding/unofficial/firefox.ico +++ b/browser/branding/unofficial/firefox.ico diff --git a/browser/branding/unofficial/locales/en-US/brand.dtd b/browser/branding/unofficial/locales/en-US/brand.dtd index cf4596ae0..17c243606 100644 --- a/browser/branding/unofficial/locales/en-US/brand.dtd +++ b/browser/branding/unofficial/locales/en-US/brand.dtd @@ -2,8 +2,9 @@ - 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/. --> -<!ENTITY brandShorterName "Nightly"> -<!ENTITY brandShortName "Nightly"> -<!ENTITY brandFullName "Nightly"> -<!ENTITY vendorShortName "Mozilla"> +<!ENTITY brandShorterName "Serpent"> +<!ENTITY brandShortName "Serpent"> +<!ENTITY brandFullName "Serpent"> +<!ENTITY vendorShortName "Moonchild"> +<!ENTITY vendorFullName "Moonchild Productions"> <!ENTITY trademarkInfo.part1 " "> diff --git a/browser/branding/unofficial/locales/en-US/brand.properties b/browser/branding/unofficial/locales/en-US/brand.properties index 8cd2c2ec9..80349f0e3 100644 --- a/browser/branding/unofficial/locales/en-US/brand.properties +++ b/browser/branding/unofficial/locales/en-US/brand.properties @@ -2,9 +2,10 @@ # 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/. -brandShorterName=Nightly -brandShortName=Nightly -brandFullName=Nightly -vendorShortName=Mozilla +brandShorterName=Serpent +brandShortName=Serpent +brandFullName=Serpent +vendorShortName=Moonchild +vendorFullName=Moonchild Productions syncBrandShortName=Sync diff --git a/browser/branding/unofficial/mozicon128.png b/browser/branding/unofficial/mozicon128.png Binary files differindex 471cf4645..739b61084 100644 --- a/browser/branding/unofficial/mozicon128.png +++ b/browser/branding/unofficial/mozicon128.png diff --git a/browser/branding/unofficial/basilisk.VisualElementsManifest.xml b/browser/branding/unofficial/serpent.VisualElementsManifest.xml index 7654e0ab7..5046ee7da 100644 --- a/browser/branding/unofficial/basilisk.VisualElementsManifest.xml +++ b/browser/branding/unofficial/serpent.VisualElementsManifest.xml @@ -4,5 +4,5 @@ Square150x150Logo='browser\VisualElements\VisualElements_150.png' Square70x70Logo='browser\VisualElements\VisualElements_70.png' ForegroundText='light' - BackgroundColor='#14171a'/> + BackgroundColor='#304D7E'/> </Application> diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index a09932d95..717ae9c48 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -35,75 +35,117 @@ struct RedirEntry { URI_SAFE_FOR_UNTRUSTED_CONTENT. */ static RedirEntry kRedirMap[] = { - { "blocked", "chrome://browser/content/blockedSite.xhtml", + { + "basilisk", "chrome://global/content/memoriam.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "blocked", "chrome://browser/content/blockedSite.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, - { "certerror", "chrome://browser/content/aboutNetError.xhtml", + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "certerror", "chrome://browser/content/aboutNetError.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, - { "socialerror", "chrome://browser/content/aboutSocialError.xhtml", + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "socialerror", "chrome://browser/content/aboutSocialError.xhtml", nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, - { "providerdirectory", "chrome://browser/content/aboutProviderDirectory.xhtml", + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "providerdirectory", "chrome://browser/content/aboutProviderDirectory.xhtml", nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, - { "tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml", + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, - { "feeds", "chrome://browser/content/feeds/subscribe.xhtml", + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "feeds", "chrome://browser/content/feeds/subscribe.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, - { "privatebrowsing", "chrome://browser/content/aboutPrivateBrowsing.xhtml", + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "privatebrowsing", "chrome://browser/content/aboutPrivateBrowsing.xhtml", nsIAboutModule::URI_MUST_LOAD_IN_CHILD | - nsIAboutModule::ALLOW_SCRIPT }, - { "rights", - "chrome://global/content/aboutRights.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "rights", "chrome://global/content/aboutRights.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::MAKE_LINKABLE | - nsIAboutModule::ALLOW_SCRIPT }, - { "robots", "chrome://browser/content/aboutRobots.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "robots", "chrome://browser/content/aboutRobots.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::ALLOW_SCRIPT }, - { "searchreset", "chrome://browser/content/search/searchReset.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "searchreset", "chrome://browser/content/search/searchReset.xhtml", nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, - { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml", - nsIAboutModule::ALLOW_SCRIPT }, - { "welcomeback", "chrome://browser/content/aboutWelcomeBack.xhtml", - nsIAboutModule::ALLOW_SCRIPT }, - { "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul", - nsIAboutModule::ALLOW_SCRIPT }, - // Linkable because of indexeddb use (bug 1228118) + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "welcomeback", "chrome://browser/content/aboutWelcomeBack.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul", + nsIAboutModule::ALLOW_SCRIPT + }, { "home", "chrome://browser/content/abouthome/aboutHome.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT | + // Linkable because of indexeddb use (bug 1228118) nsIAboutModule::MAKE_LINKABLE | nsIAboutModule::ENABLE_INDEXED_DB }, - // the newtab's actual URL will be determined when the channel is created - { "newtab", "about:blank", - nsIAboutModule::ALLOW_SCRIPT }, - { "preferences", "chrome://browser/content/preferences/in-content/preferences.xul", - nsIAboutModule::ALLOW_SCRIPT }, - { "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul", - nsIAboutModule::ALLOW_SCRIPT }, + { + // the newtab's actual URL will be determined when the channel is created + "newtab", "about:blank", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "preferences", "chrome://browser/content/preferences/in-content/preferences.xul", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul", + nsIAboutModule::ALLOW_SCRIPT + }, #ifdef MOZ_SERVICES_HEALTHREPORT - { "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml", - nsIAboutModule::ALLOW_SCRIPT }, + { + "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, #endif - { "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml", - nsIAboutModule::ALLOW_SCRIPT }, - { "reader", "chrome://global/content/reader/aboutReader.html", + { + "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "reader", "chrome://global/content/reader/aboutReader.html", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, }; static const int kRedirTotal = ArrayLength(kRedirMap); diff --git a/browser/components/build/nsModule.cpp b/browser/components/build/nsModule.cpp index f85d8812c..1fad0ce68 100644 --- a/browser/components/build/nsModule.cpp +++ b/browser/components/build/nsModule.cpp @@ -85,6 +85,7 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = { { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID }, #endif { NS_FEEDSNIFFER_CONTRACTID, &kNS_FEEDSNIFFER_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "basilisk", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "blocked", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "socialerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, diff --git a/browser/components/nsBrowserContentHandler.js b/browser/components/nsBrowserContentHandler.js index b366c3f81..74144fc1b 100644 --- a/browser/components/nsBrowserContentHandler.js +++ b/browser/components/nsBrowserContentHandler.js @@ -558,7 +558,7 @@ nsBrowserContentHandler.prototype = { if (overridePage && startPage && !willRestoreSession && !skipStartPage) return overridePage + "|" + startPage; - return overridePage || startPage || "about:blank"; + return overridePage || startPage || "about:blank" || "about:logopage"; }, get startPage() { diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index f97c173a0..448bb910d 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -70,15 +70,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s ["webrtcUI", "resource:///modules/webrtcUI.jsm"], ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource)); -if (AppConstants.MOZ_CRASHREPORTER) { - XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter", - "resource:///modules/ContentCrashHandlers.jsm"); - XPCOMUtils.defineLazyModuleGetter(this, "UnsubmittedCrashHandler", - "resource:///modules/ContentCrashHandlers.jsm"); - XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit", - "resource://gre/modules/CrashSubmit.jsm"); -} - XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() { return Services.strings.createBundle('chrome://branding/locale/brand.properties'); }); @@ -710,10 +701,6 @@ BrowserGlue.prototype = { } TabCrashHandler.init(); - if (AppConstants.MOZ_CRASHREPORTER) { - PluginCrashReporter.init(); - UnsubmittedCrashHandler.init(); - } Services.obs.notifyObservers(null, "browser-ui-startup-complete", ""); }, diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js index e26cfb138..d59f5c764 100644 --- a/browser/components/places/content/editBookmarkOverlay.js +++ b/browser/components/places/content/editBookmarkOverlay.js @@ -57,12 +57,14 @@ var gEditItemOverlay = { } } let focusedElement = aInitInfo.focusedElement; + let onPanelReady = aInitInfo.onPanelReady; return this._paneInfo = { itemId, itemGuid, isItem, isURI, uri, title, isBookmark, isFolderShortcut, isParentReadOnly, bulkTagging, uris, - visibleRows, postData, isTag, focusedElement }; + visibleRows, postData, isTag, focusedElement, + onPanelReady }; }, get initialized() { @@ -214,7 +216,8 @@ var gEditItemOverlay = { let { itemId, isItem, isURI, isBookmark, bulkTagging, uris, - visibleRows, focusedElement } = this._setPaneInfo(aInfo); + visibleRows, focusedElement, + onPanelReady } = this._setPaneInfo(aInfo); let showOrCollapse = (rowId, isAppropriateForInput, nameInHiddenRows = null) => { @@ -286,22 +289,34 @@ var gEditItemOverlay = { this._observersAdded = true; } - // The focusedElement possible values are: - // * preferred: focus the field that the user touched first the last - // time the pane was shown (either namePicker or tagsField) - // * first: focus the first non collapsed textbox - // Note: since all controls are collapsed by default, we don't get the - // default XUL dialog behavior, that selects the first control, so we set - // the focus explicitly. - let elt; - if (focusedElement === "preferred") { - elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField")); - } else if (focusedElement === "first") { - elt = document.querySelector("textbox:not([collapsed=true])"); - } - if (elt) { - elt.focus(); - elt.select(); + let focusElement = () => { + // The focusedElement possible values are: + // * preferred: focus the field that the user touched first the last + // time the pane was shown (either namePicker or tagsField) + // * first: focus the first non collapsed textbox + // Note: since all controls are collapsed by default, we don't get the + // default XUL dialog behavior, that selects the first control, so we set + // the focus explicitly. + // Note: If focusedElement === "preferred", this file expects gPrefService + // to be defined in the global scope. + let elt; + if (focusedElement === "preferred") { + /* eslint-disable no-undef */ + elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField")); + /* eslint-enable no-undef */ + } else if (focusedElement === "first") { + elt = document.querySelector("textbox:not([collapsed=true])"); + } + if (elt) { + elt.focus(); + elt.select(); + } + }; + + if (onPanelReady) { + onPanelReady(focusElement); + } else { + focusElement(); } }, @@ -335,10 +350,23 @@ var gEditItemOverlay = { if (aElement.value != aValue) { aElement.value = aValue; - // Clear the undo stack - let editor = aElement.editor; - if (editor) - editor.transactionManager.clear(); + // Clear the editor's undo stack + let transactionManager; + try { + transactionManager = aElement.editor.transactionManager; + } catch (e) { + // When retrieving the transaction manager, editor may be null resulting + // in a TypeError. Additionally, the transaction manager may not + // exist yet, which causes access to it to throw NS_ERROR_FAILURE. + // In either event, the transaction manager doesn't exist it, so we + // don't need to worry about clearing it. + if (!(e instanceof TypeError) && e.result != Cr.NS_ERROR_FAILURE) { + throw e; + } + } + if (transactionManager) { + transactionManager.clear(); + } } }, diff --git a/browser/components/preferences/in-content/advanced.js b/browser/components/preferences/in-content/advanced.js index 448a21dae..5f9458eee 100644 --- a/browser/components/preferences/in-content/advanced.js +++ b/browser/components/preferences/in-content/advanced.js @@ -40,9 +40,6 @@ var gAdvancedPane = { this.updateReadPrefs(); } this.updateOfflineApps(); - if (AppConstants.MOZ_CRASHREPORTER) { - this.initSubmitCrashes(); - } this.initTelemetry(); if (AppConstants.MOZ_TELEMETRY_REPORTING) { this.initSubmitHealthReport(); diff --git a/browser/components/preferences/in-content/advanced.xul b/browser/components/preferences/in-content/advanced.xul index facaaeaa9..4973f8e09 100644 --- a/browser/components/preferences/in-content/advanced.xul +++ b/browser/components/preferences/in-content/advanced.xul @@ -54,13 +54,6 @@ type="bool"/> #endif - <!-- Data Choices tab --> -#ifdef MOZ_CRASHREPORTER - <preference id="browser.crashReports.unsubmittedCheck.autoSubmit2" - name="browser.crashReports.unsubmittedCheck.autoSubmit2" - type="bool"/> -#endif - <!-- Network tab --> <preference id="browser.cache.disk.capacity" name="browser.cache.disk.capacity" @@ -233,22 +226,6 @@ </vbox> </groupbox> #endif -#ifdef MOZ_CRASHREPORTER - <groupbox> - <caption> - <checkbox id="automaticallySubmitCrashesBox" - preference="browser.crashReports.unsubmittedCheck.autoSubmit2" - label="&alwaysSubmitCrashReports.label;" - accesskey="&alwaysSubmitCrashReports.accesskey;"/> - </caption> - <hbox class="indent"> - <label flex="1">&crashReporterDesc2.label;</label> - <spacer flex="10"/> - <label id="crashReporterLearnMore" - class="text-link">&crashReporterLearnMore.label;</label> - </hbox> - </groupbox> -#endif </tabpanel> #endif diff --git a/browser/confvars.sh b/browser/confvars.sh index 8cdd6e1f3..03b4cea97 100755 --- a/browser/confvars.sh +++ b/browser/confvars.sh @@ -56,6 +56,9 @@ MOZ_JSDOWNLOADS=1 MOZ_WEBRTC=1 MOZ_WEBEXTENSIONS=1 MOZ_DEVTOOLS=1 +MOZ_SERVICES_COMMON=1 +MOZ_SERVICES_SYNC=1 +MOZ_SERVICES_HEALTHREPORT=1 # Disable checking that add-ons are signed by the trusted root MOZ_ADDON_SIGNING=0 diff --git a/browser/experiments/.eslintrc.js b/browser/experiments/.eslintrc.js deleted file mode 100644 index 1f6b11d67..000000000 --- a/browser/experiments/.eslintrc.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -module.exports = { - "rules": { - "no-unused-vars": ["error", { - "vars": "all", - "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$", - "args": "none" - }] - } -}; diff --git a/browser/experiments/Experiments.jsm b/browser/experiments/Experiments.jsm deleted file mode 100644 index e9a63f19f..000000000 --- a/browser/experiments/Experiments.jsm +++ /dev/null @@ -1,2354 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = [ - "Experiments", -]; - -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://gre/modules/Preferences.jsm"); -Cu.import("resource://gre/modules/AsyncShutdown.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", - "resource://gre/modules/UpdateUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", - "resource://gre/modules/AddonManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", - "resource://gre/modules/AddonManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment", - "resource://gre/modules/TelemetryEnvironment.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog", - "resource://gre/modules/TelemetryLog.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "TelemetryUtils", - "resource://gre/modules/TelemetryUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", - "resource://services-common/utils.js"); - -XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter", - "@mozilla.org/xre/app-info;1", - "nsICrashReporter"); - -const FILE_CACHE = "experiments.json"; -const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed"; -const MANIFEST_VERSION = 1; -const CACHE_VERSION = 1; - -const KEEP_HISTORY_N_DAYS = 180; - -const PREF_BRANCH = "experiments."; -const PREF_ENABLED = "enabled"; // experiments.enabled -const PREF_ACTIVE_EXPERIMENT = "activeExperiment"; // whether we have an active experiment -const PREF_LOGGING = "logging"; -const PREF_LOGGING_LEVEL = PREF_LOGGING + ".level"; // experiments.logging.level -const PREF_LOGGING_DUMP = PREF_LOGGING + ".dump"; // experiments.logging.dump -const PREF_MANIFEST_URI = "manifest.uri"; // experiments.logging.manifest.uri -const PREF_FORCE_SAMPLE = "force-sample-value"; // experiments.force-sample-value - -const PREF_BRANCH_TELEMETRY = "toolkit.telemetry."; -const PREF_TELEMETRY_ENABLED = "enabled"; - -const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties"; -const STRING_TYPE_NAME = "type.%ID%.name"; - -const CACHE_WRITE_RETRY_DELAY_SEC = 60 * 3; -const MANIFEST_FETCH_TIMEOUT_MSEC = 60 * 3 * 1000; // 3 minutes - -const TELEMETRY_LOG = { - // log(key, [kind, experimentId, details]) - ACTIVATION_KEY: "EXPERIMENT_ACTIVATION", - ACTIVATION: { - // Successfully activated. - ACTIVATED: "ACTIVATED", - // Failed to install the add-on. - INSTALL_FAILURE: "INSTALL_FAILURE", - // Experiment does not meet activation requirements. Details will - // be provided. - REJECTED: "REJECTED", - }, - - // log(key, [kind, experimentId, optionalDetails...]) - TERMINATION_KEY: "EXPERIMENT_TERMINATION", - TERMINATION: { - // The Experiments service was disabled. - SERVICE_DISABLED: "SERVICE_DISABLED", - // Add-on uninstalled. - ADDON_UNINSTALLED: "ADDON_UNINSTALLED", - // The experiment disabled itself. - FROM_API: "FROM_API", - // The experiment expired (e.g. by exceeding the end date). - EXPIRED: "EXPIRED", - // Disabled after re-evaluating conditions. If this is specified, - // details will be provided. - RECHECK: "RECHECK", - }, -}; -XPCOMUtils.defineConstant(this, "TELEMETRY_LOG", TELEMETRY_LOG); - -const gPrefs = new Preferences(PREF_BRANCH); -const gPrefsTelemetry = new Preferences(PREF_BRANCH_TELEMETRY); -var gExperimentsEnabled = false; -var gAddonProvider = null; -var gExperiments = null; -var gLogAppenderDump = null; -var gPolicyCounter = 0; -var gExperimentsCounter = 0; -var gExperimentEntryCounter = 0; -var gPreviousProviderCounter = 0; - -// Tracks active AddonInstall we know about so we can deny external -// installs. -var gActiveInstallURLs = new Set(); - -// Tracks add-on IDs that are being uninstalled by us. This allows us -// to differentiate between expected uninstalled and user-driven uninstalls. -var gActiveUninstallAddonIDs = new Set(); - -var gLogger; -var gLogDumping = false; - -function configureLogging() { - if (!gLogger) { - gLogger = Log.repository.getLogger("Browser.Experiments"); - gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); - } - gLogger.level = gPrefs.get(PREF_LOGGING_LEVEL, Log.Level.Warn); - - let logDumping = gPrefs.get(PREF_LOGGING_DUMP, false); - if (logDumping != gLogDumping) { - if (logDumping) { - gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter()); - gLogger.addAppender(gLogAppenderDump); - } else { - gLogger.removeAppender(gLogAppenderDump); - gLogAppenderDump = null; - } - gLogDumping = logDumping; - } -} - -// Loads a JSON file using OS.file. file is a string representing the path -// of the file to be read, options contains additional options to pass to -// OS.File.read. -// Returns a Promise resolved with the json payload or rejected with -// OS.File.Error or JSON.parse() errors. -function loadJSONAsync(file, options) { - return Task.spawn(function*() { - let rawData = yield OS.File.read(file, options); - // Read json file into a string - let data; - try { - // Obtain a converter to read from a UTF-8 encoded input stream. - let converter = new TextDecoder(); - data = JSON.parse(converter.decode(rawData)); - } catch (ex) { - gLogger.error("Experiments: Could not parse JSON: " + file + " " + ex); - throw ex; - } - return data; - }); -} - -// Returns a promise that is resolved with the AddonInstall for that URL. -function addonInstallForURL(url, hash) { - let deferred = Promise.defer(); - AddonManager.getInstallForURL(url, install => deferred.resolve(install), - "application/x-xpinstall", hash); - return deferred.promise; -} - -// Returns a promise that is resolved with an Array<Addon> of the installed -// experiment addons. -function installedExperimentAddons() { - let deferred = Promise.defer(); - AddonManager.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons.filter(a => !a.appDisabled)); - }); - return deferred.promise; -} - -// Takes an Array<Addon> and returns a promise that is resolved when the -// addons are uninstalled. -function uninstallAddons(addons) { - let ids = new Set(addons.map(addon => addon.id)); - let deferred = Promise.defer(); - - let listener = {}; - listener.onUninstalled = addon => { - if (!ids.has(addon.id)) { - return; - } - - ids.delete(addon.id); - if (ids.size == 0) { - AddonManager.removeAddonListener(listener); - deferred.resolve(); - } - }; - - AddonManager.addAddonListener(listener); - - for (let addon of addons) { - // Disabling the add-on before uninstalling is necessary to cause tests to - // pass. This might be indicative of a bug in XPIProvider. - // TODO follow up in bug 992396. - addon.userDisabled = true; - addon.uninstall(); - } - - return deferred.promise; -} - -/** - * The experiments module. - */ - -var Experiments = { - /** - * Provides access to the global `Experiments.Experiments` instance. - */ - instance: function () { - if (!gExperiments) { - gExperiments = new Experiments.Experiments(); - } - - return gExperiments; - }, -}; - -/* - * The policy object allows us to inject fake enviroment data from the - * outside by monkey-patching. - */ - -Experiments.Policy = function () { - this._log = Log.repository.getLoggerWithMessagePrefix( - "Browser.Experiments.Policy", - "Policy #" + gPolicyCounter++ + "::"); - - // Set to true to ignore hash verification on downloaded XPIs. This should - // not be used outside of testing. - this.ignoreHashes = false; -}; - -Experiments.Policy.prototype = { - now: function () { - return new Date(); - }, - - random: function () { - let pref = gPrefs.get(PREF_FORCE_SAMPLE); - if (pref !== undefined) { - let val = Number.parseFloat(pref); - this._log.debug("random sample forced: " + val); - if (isNaN(val) || val < 0) { - return 0; - } - if (val > 1) { - return 1; - } - return val; - } - return Math.random(); - }, - - futureDate: function (offset) { - return new Date(this.now().getTime() + offset); - }, - - oneshotTimer: function (callback, timeout, thisObj, name) { - return CommonUtils.namedTimer(callback, timeout, thisObj, name); - }, - - updatechannel: function () { - return UpdateUtils.UpdateChannel; - }, - - locale: function () { - let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry); - return chrome.getSelectedLocale("global"); - }, - - /** - * For testing a race condition, one of the tests delays the callback of - * writing the cache by replacing this policy function. - */ - delayCacheWrite: function(promise) { - return promise; - }, -}; - -function AlreadyShutdownError(message="already shut down") { - Error.call(this, message); - let error = new Error(); - this.name = "AlreadyShutdownError"; - this.message = message; - this.stack = error.stack; -} -AlreadyShutdownError.prototype = Object.create(Error.prototype); -AlreadyShutdownError.prototype.constructor = AlreadyShutdownError; - -function CacheWriteError(message="Error writing cache file") { - Error.call(this, message); - let error = new Error(); - this.name = "CacheWriteError"; - this.message = message; - this.stack = error.stack; -} -CacheWriteError.prototype = Object.create(Error.prototype); -CacheWriteError.prototype.constructor = CacheWriteError; - -/** - * Manages the experiments and provides an interface to control them. - */ - -Experiments.Experiments = function (policy=new Experiments.Policy()) { - let log = Log.repository.getLoggerWithMessagePrefix( - "Browser.Experiments.Experiments", - "Experiments #" + gExperimentsCounter++ + "::"); - - // At the time of this writing, Experiments.jsm has severe - // crashes. For forensics purposes, keep the last few log - // messages in memory and upload them in case of crash. - this._forensicsLogs = []; - this._forensicsLogs.length = 30; - this._log = Object.create(log); - this._log.log = (level, string, params) => { - this._addToForensicsLog("Experiments", string); - log.log(level, string, params); - }; - - this._log.trace("constructor"); - - // Capture the latest error, for forensics purposes. - this._latestError = null; - - - this._policy = policy; - - // This is a Map of (string -> ExperimentEntry), keyed with the experiment id. - // It holds both the current experiments and history. - // Map() preserves insertion order, which means we preserve the manifest order. - // This is null until we've successfully completed loading the cache from - // disk the first time. - this._experiments = null; - this._refresh = false; - this._terminateReason = null; // or TELEMETRY_LOG.TERMINATION.... - this._dirty = false; - - // Loading the cache happens once asynchronously on startup - this._loadTask = null; - - // The _main task handles all other actions: - // * refreshing the manifest off the network (if _refresh) - // * disabling/enabling experiments - // * saving the cache (if _dirty) - this._mainTask = null; - - // Timer for re-evaluating experiment status. - this._timer = null; - - this._shutdown = false; - this._networkRequest = null; - - // We need to tell when we first evaluated the experiments to fire an - // experiments-changed notification when we only loaded completed experiments. - this._firstEvaluate = true; - - this.init(); -}; - -Experiments.Experiments.prototype = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]), - - /** - * `true` if the experiments manager is currently setup (has been fully initialized - * and not uninitialized yet). - */ - get isReady() { - return !this._shutdown; - }, - - init: function () { - this._shutdown = false; - configureLogging(); - - gExperimentsEnabled = gPrefs.get(PREF_ENABLED, false) && TelemetryUtils.isTelemetryEnabled; - this._log.trace("enabled=" + gExperimentsEnabled + ", " + this.enabled); - - gPrefs.observe(PREF_LOGGING, configureLogging); - gPrefs.observe(PREF_MANIFEST_URI, this.updateManifest, this); - gPrefs.observe(PREF_ENABLED, this._toggleExperimentsEnabled, this); - - gPrefsTelemetry.observe(PREF_TELEMETRY_ENABLED, this._telemetryStatusChanged, this); - - AddonManager.shutdown.addBlocker("Experiments.jsm shutdown", - this.uninit.bind(this), - this._getState.bind(this) - ); - - this._registerWithAddonManager(); - - this._loadTask = this._loadFromCache(); - - return this._loadTask.then( - () => { - this._log.trace("_loadTask finished ok"); - this._loadTask = null; - return this._run(); - }, - (e) => { - this._log.error("_loadFromCache caught error: " + e); - this._latestError = e; - throw e; - } - ); - }, - - /** - * Uninitialize this instance. - * - * This function is susceptible to race conditions. If it is called multiple - * times before the previous uninit() has completed or if it is called while - * an init() operation is being performed, the object may get in bad state - * and/or deadlock could occur. - * - * @return Promise<> - * The promise is fulfilled when all pending tasks are finished. - */ - uninit: Task.async(function* () { - this._log.trace("uninit: started"); - yield this._loadTask; - this._log.trace("uninit: finished with _loadTask"); - - if (!this._shutdown) { - this._log.trace("uninit: no previous shutdown"); - this._unregisterWithAddonManager(); - - gPrefs.ignore(PREF_LOGGING, configureLogging); - gPrefs.ignore(PREF_MANIFEST_URI, this.updateManifest, this); - gPrefs.ignore(PREF_ENABLED, this._toggleExperimentsEnabled, this); - - gPrefsTelemetry.ignore(PREF_TELEMETRY_ENABLED, this._telemetryStatusChanged, this); - - if (this._timer) { - this._timer.clear(); - } - } - - this._shutdown = true; - if (this._mainTask) { - if (this._networkRequest) { - try { - this._log.trace("Aborting pending network request: " + this._networkRequest); - this._networkRequest.abort(); - } catch (e) { - // pass - } - } - try { - this._log.trace("uninit: waiting on _mainTask"); - yield this._mainTask; - } catch (e) { - // We error out of tasks after shutdown via this exception. - this._log.trace(`uninit: caught error - ${e}`); - if (!(e instanceof AlreadyShutdownError)) { - this._latestError = e; - throw e; - } - } - } - - this._log.info("Completed uninitialization."); - }), - - // Return state information, for debugging purposes. - _getState: function() { - let activeExperiment = this._getActiveExperiment(); - let state = { - isShutdown: this._shutdown, - isEnabled: gExperimentsEnabled, - isRefresh: this._refresh, - isDirty: this._dirty, - isFirstEvaluate: this._firstEvaluate, - hasLoadTask: !!this._loadTask, - hasMainTask: !!this._mainTask, - hasTimer: !!this._hasTimer, - hasAddonProvider: !!gAddonProvider, - latestLogs: this._forensicsLogs, - experiments: this._experiments ? [...this._experiments.keys()] : null, - terminateReason: this._terminateReason, - activeExperiment: activeExperiment ? activeExperiment.id : null, - }; - if (this._latestError) { - if (typeof this._latestError == "object") { - state.latestError = { - message: this._latestError.message, - stack: this._latestError.stack - }; - } else { - state.latestError = "" + this._latestError; - } - } - return state; - }, - - _addToForensicsLog: function (what, string) { - this._forensicsLogs.shift(); - let timeInSec = Math.floor(Services.telemetry.msSinceProcessStart() / 1000); - this._forensicsLogs.push(`${timeInSec}: ${what} - ${string}`); - }, - - _registerWithAddonManager: function (previousExperimentsProvider) { - this._log.trace("Registering instance with Addon Manager."); - - AddonManager.addAddonListener(this); - AddonManager.addInstallListener(this); - - if (!gAddonProvider) { - // The properties of this AddonType should be kept in sync with the - // experiment AddonType registered in XPIProvider. - this._log.trace("Registering previous experiment add-on provider."); - gAddonProvider = previousExperimentsProvider || new Experiments.PreviousExperimentProvider(this); - AddonManagerPrivate.registerProvider(gAddonProvider, [ - new AddonManagerPrivate.AddonType("experiment", - URI_EXTENSION_STRINGS, - STRING_TYPE_NAME, - AddonManager.VIEW_TYPE_LIST, - 11000, - AddonManager.TYPE_UI_HIDE_EMPTY), - ]); - } - - }, - - _unregisterWithAddonManager: function () { - this._log.trace("Unregistering instance with Addon Manager."); - - this._log.trace("Removing install listener from add-on manager."); - AddonManager.removeInstallListener(this); - this._log.trace("Removing addon listener from add-on manager."); - AddonManager.removeAddonListener(this); - this._log.trace("Finished unregistering with addon manager."); - - if (gAddonProvider) { - this._log.trace("Unregistering previous experiment add-on provider."); - AddonManagerPrivate.unregisterProvider(gAddonProvider); - gAddonProvider = null; - } - }, - - /* - * Change the PreviousExperimentsProvider that this instance uses. - * For testing only. - */ - _setPreviousExperimentsProvider: function (provider) { - this._unregisterWithAddonManager(); - this._registerWithAddonManager(provider); - }, - - /** - * Throws an exception if we've already shut down. - */ - _checkForShutdown: function() { - if (this._shutdown) { - throw new AlreadyShutdownError("uninit() already called"); - } - }, - - /** - * Whether the experiments feature is enabled. - */ - get enabled() { - return gExperimentsEnabled; - }, - - /** - * Toggle whether the experiments feature is enabled or not. - */ - set enabled(enabled) { - this._log.trace("set enabled(" + enabled + ")"); - gPrefs.set(PREF_ENABLED, enabled); - }, - - _toggleExperimentsEnabled: Task.async(function* (enabled) { - this._log.trace("_toggleExperimentsEnabled(" + enabled + ")"); - let wasEnabled = gExperimentsEnabled; - gExperimentsEnabled = enabled && TelemetryUtils.isTelemetryEnabled; - - if (wasEnabled == gExperimentsEnabled) { - return; - } - - if (gExperimentsEnabled) { - yield this.updateManifest(); - } else { - yield this.disableExperiment(TELEMETRY_LOG.TERMINATION.SERVICE_DISABLED); - if (this._timer) { - this._timer.clear(); - } - } - }), - - _telemetryStatusChanged: function () { - this._toggleExperimentsEnabled(gExperimentsEnabled); - }, - - /** - * Returns a promise that is resolved with an array of `ExperimentInfo` objects, - * which provide info on the currently and recently active experiments. - * The array is in chronological order. - * - * The experiment info is of the form: - * { - * id: <string>, - * name: <string>, - * description: <string>, - * active: <boolean>, - * endDate: <integer>, // epoch ms - * detailURL: <string>, - * ... // possibly extended later - * } - * - * @return Promise<Array<ExperimentInfo>> Array of experiment info objects. - */ - getExperiments: function () { - return Task.spawn(function*() { - yield this._loadTask; - let list = []; - - for (let [id, experiment] of this._experiments) { - if (!experiment.startDate) { - // We only collect experiments that are or were active. - continue; - } - - list.push({ - id: id, - name: experiment._name, - description: experiment._description, - active: experiment.enabled, - endDate: experiment.endDate.getTime(), - detailURL: experiment._homepageURL, - branch: experiment.branch, - }); - } - - // Sort chronologically, descending. - list.sort((a, b) => b.endDate - a.endDate); - return list; - }.bind(this)); - }, - - /** - * Returns the ExperimentInfo for the active experiment, or null - * if there is none. - */ - getActiveExperiment: function () { - let experiment = this._getActiveExperiment(); - if (!experiment) { - return null; - } - - let info = { - id: experiment.id, - name: experiment._name, - description: experiment._description, - active: experiment.enabled, - endDate: experiment.endDate.getTime(), - detailURL: experiment._homepageURL, - }; - - return info; - }, - - /** - * Experiment "branch" support. If an experiment has multiple branches, it - * can record the branch with the experiment system and it will - * automatically be included in data reporting (FHR/telemetry payloads). - */ - - /** - * Set the experiment branch for the specified experiment ID. - * @returns Promise<> - */ - setExperimentBranch: Task.async(function*(id, branchstr) { - yield this._loadTask; - let e = this._experiments.get(id); - if (!e) { - throw new Error("Experiment not found"); - } - e.branch = String(branchstr); - this._log.trace("setExperimentBranch(" + id + ", " + e.branch + ") _dirty=" + this._dirty); - this._dirty = true; - Services.obs.notifyObservers(null, EXPERIMENTS_CHANGED_TOPIC, null); - yield this._run(); - }), - /** - * Get the branch of the specified experiment. If the experiment is unknown, - * throws an error. - * - * @param id The ID of the experiment. Pass null for the currently running - * experiment. - * @returns Promise<string|null> - * @throws Error if the specified experiment ID is unknown, or if there is no - * current experiment. - */ - getExperimentBranch: Task.async(function*(id=null) { - yield this._loadTask; - let e; - if (id) { - e = this._experiments.get(id); - if (!e) { - throw new Error("Experiment not found"); - } - } else { - e = this._getActiveExperiment(); - if (e === null) { - throw new Error("No active experiment"); - } - } - return e.branch; - }), - - /** - * Determine whether another date has the same UTC day as now(). - */ - _dateIsTodayUTC: function (d) { - let now = this._policy.now(); - - return stripDateToMidnight(now).getTime() == stripDateToMidnight(d).getTime(); - }, - - /** - * Obtain the entry of the most recent active experiment that was active - * today. - * - * If no experiment was active today, this resolves to nothing. - * - * Assumption: Only a single experiment can be active at a time. - * - * @return Promise<object> - */ - lastActiveToday: function () { - return Task.spawn(function* getMostRecentActiveExperimentTask() { - let experiments = yield this.getExperiments(); - - // Assumption: Ordered chronologically, descending, with active always - // first. - for (let experiment of experiments) { - if (experiment.active) { - return experiment; - } - - if (experiment.endDate && this._dateIsTodayUTC(experiment.endDate)) { - return experiment; - } - } - return null; - }.bind(this)); - }, - - _run: function() { - this._log.trace("_run"); - this._checkForShutdown(); - if (!this._mainTask) { - this._mainTask = Task.spawn(function*() { - try { - yield this._main(); - } catch (e) { - // In the CacheWriteError case we want to reschedule - if (!(e instanceof CacheWriteError)) { - this._log.error("_main caught error: " + e); - return; - } - } finally { - this._mainTask = null; - } - this._log.trace("_main finished, scheduling next run"); - try { - yield this._scheduleNextRun(); - } catch (ex) { - // We error out of tasks after shutdown via this exception. - if (!(ex instanceof AlreadyShutdownError)) { - throw ex; - } - } - }.bind(this)); - } - return this._mainTask; - }, - - _main: function*() { - do { - this._log.trace("_main iteration"); - yield this._loadTask; - if (!gExperimentsEnabled) { - this._refresh = false; - } - - if (this._refresh) { - yield this._loadManifest(); - } - yield this._evaluateExperiments(); - if (this._dirty) { - yield this._saveToCache(); - } - // If somebody called .updateManifest() or disableExperiment() - // while we were running, go again right now. - } - while (this._refresh || this._terminateReason || this._dirty); - }, - - _loadManifest: function*() { - this._log.trace("_loadManifest"); - let uri = Services.urlFormatter.formatURLPref(PREF_BRANCH + PREF_MANIFEST_URI); - - this._checkForShutdown(); - - this._refresh = false; - try { - let responseText = yield this._httpGetRequest(uri); - this._log.trace("_loadManifest() - responseText=\"" + responseText + "\""); - - if (this._shutdown) { - return; - } - - let data = JSON.parse(responseText); - this._updateExperiments(data); - } catch (e) { - this._log.error("_loadManifest - failure to fetch/parse manifest (continuing anyway): " + e); - } - }, - - /** - * Fetch an updated list of experiments and trigger experiment updates. - * Do only use when experiments are enabled. - * - * @return Promise<> - * The promise is resolved when the manifest and experiment list is updated. - */ - updateManifest: function () { - this._log.trace("updateManifest()"); - - if (!gExperimentsEnabled) { - return Promise.reject(new Error("experiments are disabled")); - } - - if (this._shutdown) { - return Promise.reject(Error("uninit() alrady called")); - } - - this._refresh = true; - return this._run(); - }, - - notify: function (timer) { - this._log.trace("notify()"); - this._checkForShutdown(); - return this._run(); - }, - - // START OF ADD-ON LISTENERS - - onUninstalled: function (addon) { - this._log.trace("onUninstalled() - addon id: " + addon.id); - if (gActiveUninstallAddonIDs.has(addon.id)) { - this._log.trace("matches pending uninstall"); - return; - } - let activeExperiment = this._getActiveExperiment(); - if (!activeExperiment || activeExperiment._addonId != addon.id) { - return; - } - - this.disableExperiment(TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED); - }, - - /** - * @returns {Boolean} returns false when we cancel the install. - */ - onInstallStarted: function (install) { - if (install.addon.type != "experiment") { - return true; - } - - this._log.trace("onInstallStarted() - " + install.addon.id); - if (install.addon.appDisabled) { - // This is a PreviousExperiment - return true; - } - - // We want to be in control of all experiment add-ons: reject installs - // for add-ons that we don't know about. - - // We have a race condition of sorts to worry about here. We have 2 - // onInstallStarted listeners. This one (the global one) and the one - // created as part of ExperimentEntry._installAddon. Because of the order - // they are registered in, this one likely executes first. Unfortunately, - // this means that the add-on ID is not yet set on the ExperimentEntry. - // So, we can't just look at this._trackedAddonIds because the new experiment - // will have its add-on ID set to null. We work around this by storing a - // identifying field - the source URL of the install - in a module-level - // variable (so multiple Experiments instances doesn't cancel each other - // out). - - if (this._trackedAddonIds.has(install.addon.id)) { - this._log.info("onInstallStarted allowing install because add-on ID " + - "tracked by us."); - return true; - } - - if (gActiveInstallURLs.has(install.sourceURI.spec)) { - this._log.info("onInstallStarted allowing install because install " + - "tracked by us."); - return true; - } - - this._log.warn("onInstallStarted cancelling install of unknown " + - "experiment add-on: " + install.addon.id); - return false; - }, - - // END OF ADD-ON LISTENERS. - - _getExperimentByAddonId: function (addonId) { - for (let [, entry] of this._experiments) { - if (entry._addonId === addonId) { - return entry; - } - } - - return null; - }, - - /* - * Helper function to make HTTP GET requests. Returns a promise that is resolved with - * the responseText when the request is complete. - */ - _httpGetRequest: function (url) { - this._log.trace("httpGetRequest(" + url + ")"); - let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); - - this._networkRequest = xhr; - let deferred = Promise.defer(); - - let log = this._log; - let errorhandler = (evt) => { - log.error("httpGetRequest::onError() - Error making request to " + url + ": " + evt.type); - deferred.reject(new Error("Experiments - XHR error for " + url + " - " + evt.type)); - this._networkRequest = null; - }; - xhr.onerror = errorhandler; - xhr.ontimeout = errorhandler; - xhr.onabort = errorhandler; - - xhr.onload = (event) => { - if (xhr.status !== 200 && xhr.state !== 0) { - log.error("httpGetRequest::onLoad() - Request to " + url + " returned status " + xhr.status); - deferred.reject(new Error("Experiments - XHR status for " + url + " is " + xhr.status)); - this._networkRequest = null; - return; - } - - deferred.resolve(xhr.responseText); - this._networkRequest = null; - }; - - try { - xhr.open("GET", url); - - if (xhr.channel instanceof Ci.nsISupportsPriority) { - xhr.channel.priority = Ci.nsISupportsPriority.PRIORITY_LOWEST; - } - - xhr.timeout = MANIFEST_FETCH_TIMEOUT_MSEC; - xhr.send(null); - } catch (e) { - this._log.error("httpGetRequest() - Error opening request to " + url + ": " + e); - return Promise.reject(new Error("Experiments - Error opening XHR for " + url)); - } - return deferred.promise; - }, - - /* - * Path of the cache file we use in the profile. - */ - get _cacheFilePath() { - return OS.Path.join(OS.Constants.Path.profileDir, FILE_CACHE); - }, - - /* - * Part of the main task to save the cache to disk, called from _main. - */ - _saveToCache: function* () { - this._log.trace("_saveToCache"); - let path = this._cacheFilePath; - this._dirty = false; - try { - let textData = JSON.stringify({ - version: CACHE_VERSION, - data: [...this._experiments.values()].map(e => e.toJSON()), - }); - - let encoder = new TextEncoder(); - let data = encoder.encode(textData); - let options = { tmpPath: path + ".tmp", compression: "lz4" }; - yield this._policy.delayCacheWrite(OS.File.writeAtomic(path, data, options)); - } catch (e) { - // We failed to write the cache, it's still dirty. - this._dirty = true; - this._log.error("_saveToCache failed and caught error: " + e); - throw new CacheWriteError(); - } - - this._log.debug("_saveToCache saved to " + path); - }, - - /* - * Task function, load the cached experiments manifest file from disk. - */ - _loadFromCache: Task.async(function* () { - this._log.trace("_loadFromCache"); - let path = this._cacheFilePath; - try { - let result = yield loadJSONAsync(path, { compression: "lz4" }); - this._populateFromCache(result); - } catch (e) { - if (e instanceof OS.File.Error && e.becauseNoSuchFile) { - // No cached manifest yet. - this._experiments = new Map(); - } else { - throw e; - } - } - }), - - _populateFromCache: function (data) { - this._log.trace("populateFromCache() - data: " + JSON.stringify(data)); - - // If the user has a newer cache version than we can understand, we fail - // hard; no experiments should be active in this older client. - if (CACHE_VERSION !== data.version) { - throw new Error("Experiments::_populateFromCache() - invalid cache version"); - } - - let experiments = new Map(); - for (let item of data.data) { - let entry = new Experiments.ExperimentEntry(this._policy); - if (!entry.initFromCacheData(item)) { - continue; - } - - // Discard old experiments if they ended more than 180 days ago. - if (entry.shouldDiscard()) { - // We discarded an experiment, the cache needs to be updated. - this._dirty = true; - continue; - } - - experiments.set(entry.id, entry); - } - - this._experiments = experiments; - }, - - /* - * Update the experiment entries from the experiments - * array in the manifest - */ - _updateExperiments: function (manifestObject) { - this._log.trace("_updateExperiments() - experiments: " + JSON.stringify(manifestObject)); - - if (manifestObject.version !== MANIFEST_VERSION) { - this._log.warning("updateExperiments() - unsupported version " + manifestObject.version); - } - - let experiments = new Map(); // The new experiments map - - // Collect new and updated experiments. - for (let data of manifestObject.experiments) { - let entry = this._experiments.get(data.id); - - if (entry) { - if (!entry.updateFromManifestData(data)) { - this._log.error("updateExperiments() - Invalid manifest data for " + data.id); - continue; - } - } else { - entry = new Experiments.ExperimentEntry(this._policy); - if (!entry.initFromManifestData(data)) { - continue; - } - } - - if (entry.shouldDiscard()) { - continue; - } - - experiments.set(entry.id, entry); - } - - // Make sure we keep experiments that are or were running. - // We remove them after KEEP_HISTORY_N_DAYS. - for (let [id, entry] of this._experiments) { - if (experiments.has(id)) { - continue; - } - - if (!entry.startDate || entry.shouldDiscard()) { - this._log.trace("updateExperiments() - discarding entry for " + id); - continue; - } - - experiments.set(id, entry); - } - - this._experiments = experiments; - this._dirty = true; - }, - - getActiveExperimentID: function() { - if (!this._experiments) { - return null; - } - let e = this._getActiveExperiment(); - if (!e) { - return null; - } - return e.id; - }, - - getActiveExperimentBranch: function() { - if (!this._experiments) { - return null; - } - let e = this._getActiveExperiment(); - if (!e) { - return null; - } - return e.branch; - }, - - _getActiveExperiment: function () { - let enabled = [...this._experiments.values()].filter(experiment => experiment._enabled); - - if (enabled.length == 1) { - return enabled[0]; - } - - if (enabled.length > 1) { - this._log.error("getActiveExperimentId() - should not have more than 1 active experiment"); - throw new Error("have more than 1 active experiment"); - } - - return null; - }, - - /** - * Disables all active experiments. - * - * @return Promise<> Promise that will get resolved once the task is done or failed. - */ - disableExperiment: function (reason) { - if (!reason) { - throw new Error("Must specify a termination reason."); - } - - this._log.trace("disableExperiment()"); - this._terminateReason = reason; - return this._run(); - }, - - /** - * The Set of add-on IDs that we know about from manifests. - */ - get _trackedAddonIds() { - if (!this._experiments) { - return new Set(); - } - - return new Set([...this._experiments.values()].map(e => e._addonId)); - }, - - /* - * Task function to check applicability of experiments, disable the active - * experiment if needed and activate the first applicable candidate. - */ - _evaluateExperiments: function*() { - this._log.trace("_evaluateExperiments"); - - this._checkForShutdown(); - - // The first thing we do is reconcile our state against what's in the - // Addon Manager. It's possible that the Addon Manager knows of experiment - // add-ons that we don't. This could happen if an experiment gets installed - // when we're not listening or if there is a bug in our synchronization - // code. - // - // We have a few options of what to do with unknown experiment add-ons - // coming from the Addon Manager. Ideally, we'd convert these to - // ExperimentEntry instances and stuff them inside this._experiments. - // However, since ExperimentEntry contain lots of metadata from the - // manifest and trying to make up data could be error prone, it's safer - // to not try. Furthermore, if an experiment really did come from us, we - // should have some record of it. In the end, we decide to discard all - // knowledge for these unknown experiment add-ons. - let installedExperiments = yield installedExperimentAddons(); - let expectedAddonIds = this._trackedAddonIds; - let unknownAddons = installedExperiments.filter(a => !expectedAddonIds.has(a.id)); - if (unknownAddons.length) { - this._log.warn("_evaluateExperiments() - unknown add-ons in AddonManager: " + - unknownAddons.map(a => a.id).join(", ")); - - yield uninstallAddons(unknownAddons); - } - - let activeExperiment = this._getActiveExperiment(); - let activeChanged = false; - - if (!activeExperiment) { - // Avoid this pref staying out of sync if there were e.g. crashes. - gPrefs.set(PREF_ACTIVE_EXPERIMENT, false); - } - - // Ensure the active experiment is in the proper state. This may install, - // uninstall, upgrade, or enable the experiment add-on. What exactly is - // abstracted away from us by design. - if (activeExperiment) { - let changes; - let shouldStopResult = yield activeExperiment.shouldStop(); - if (shouldStopResult.shouldStop) { - let expireReasons = ["endTime", "maxActiveSeconds"]; - let kind, reason; - - if (expireReasons.indexOf(shouldStopResult.reason[0]) != -1) { - kind = TELEMETRY_LOG.TERMINATION.EXPIRED; - reason = null; - } else { - kind = TELEMETRY_LOG.TERMINATION.RECHECK; - reason = shouldStopResult.reason; - } - changes = yield activeExperiment.stop(kind, reason); - } - else if (this._terminateReason) { - changes = yield activeExperiment.stop(this._terminateReason); - } - else { - changes = yield activeExperiment.reconcileAddonState(); - } - - if (changes) { - this._dirty = true; - activeChanged = true; - } - - if (!activeExperiment._enabled) { - activeExperiment = null; - activeChanged = true; - } - } - - this._terminateReason = null; - - if (!activeExperiment && gExperimentsEnabled) { - for (let [id, experiment] of this._experiments) { - let applicable; - let reason = null; - try { - applicable = yield experiment.isApplicable(); - } - catch (e) { - applicable = false; - reason = e; - } - - if (!applicable && reason && reason[0] != "was-active") { - // Report this from here to avoid over-reporting. - let data = [TELEMETRY_LOG.ACTIVATION.REJECTED, id]; - data = data.concat(reason); - const key = TELEMETRY_LOG.ACTIVATION_KEY; - TelemetryLog.log(key, data); - this._log.trace("evaluateExperiments() - added " + key + " to TelemetryLog: " + JSON.stringify(data)); - } - - if (!applicable) { - continue; - } - - this._log.debug("evaluateExperiments() - activating experiment " + id); - try { - yield experiment.start(); - activeChanged = true; - activeExperiment = experiment; - this._dirty = true; - break; - } catch (e) { - // On failure, clean up the best we can and try the next experiment. - this._log.error("evaluateExperiments() - Unable to start experiment: " + e.message); - experiment._enabled = false; - yield experiment.reconcileAddonState(); - } - } - } - - gPrefs.set(PREF_ACTIVE_EXPERIMENT, activeExperiment != null); - - if (activeChanged || this._firstEvaluate) { - Services.obs.notifyObservers(null, EXPERIMENTS_CHANGED_TOPIC, null); - this._firstEvaluate = false; - } - - if ("@mozilla.org/toolkit/crash-reporter;1" in Cc && activeExperiment) { - try { - gCrashReporter.annotateCrashReport("ActiveExperiment", activeExperiment.id); - gCrashReporter.annotateCrashReport("ActiveExperimentBranch", activeExperiment.branch); - } catch (e) { - // It's ok if crash reporting is disabled. - } - } - }, - - /* - * Schedule the soonest re-check of experiment applicability that is needed. - */ - _scheduleNextRun: function () { - this._checkForShutdown(); - - if (this._timer) { - this._timer.clear(); - } - - if (!gExperimentsEnabled || this._experiments.length == 0) { - return; - } - - let time = null; - let now = this._policy.now().getTime(); - if (this._dirty) { - // If we failed to write the cache, we should try again periodically - time = now + 1000 * CACHE_WRITE_RETRY_DELAY_SEC; - } - - for (let [, experiment] of this._experiments) { - let scheduleTime = experiment.getScheduleTime(); - if (scheduleTime > now) { - if (time !== null) { - time = Math.min(time, scheduleTime); - } else { - time = scheduleTime; - } - } - } - - if (time === null) { - // No schedule time found. - return; - } - - this._log.trace("scheduleExperimentEvaluation() - scheduling for "+time+", now: "+now); - this._policy.oneshotTimer(this.notify, time - now, this, "_timer"); - }, -}; - - -/* - * Represents a single experiment. - */ - -Experiments.ExperimentEntry = function (policy) { - this._policy = policy || new Experiments.Policy(); - let log = Log.repository.getLoggerWithMessagePrefix( - "Browser.Experiments.Experiments", - "ExperimentEntry #" + gExperimentEntryCounter++ + "::"); - this._log = Object.create(log); - this._log.log = (level, string, params) => { - if (gExperiments) { - gExperiments._addToForensicsLog("ExperimentEntry", string); - } - log.log(level, string, params); - }; - - // Is the experiment supposed to be running. - this._enabled = false; - // When this experiment was started, if ever. - this._startDate = null; - // When this experiment was ended, if ever. - this._endDate = null; - // The condition data from the manifest. - this._manifestData = null; - // For an active experiment, signifies whether we need to update the xpi. - this._needsUpdate = false; - // A random sample value for comparison against the manifest conditions. - this._randomValue = null; - // When this entry was last changed for respecting history retention duration. - this._lastChangedDate = null; - // Has this experiment failed to activate before? - this._failedStart = false; - // The experiment branch - this._branch = null; - - // We grab these from the addon after download. - this._name = null; - this._description = null; - this._homepageURL = null; - this._addonId = null; -}; - -Experiments.ExperimentEntry.prototype = { - MANIFEST_REQUIRED_FIELDS: new Set([ - "id", - "xpiURL", - "xpiHash", - "startTime", - "endTime", - "maxActiveSeconds", - "appName", - "channel", - ]), - - MANIFEST_OPTIONAL_FIELDS: new Set([ - "maxStartTime", - "minVersion", - "maxVersion", - "version", - "minBuildID", - "maxBuildID", - "buildIDs", - "os", - "locale", - "sample", - "disabled", - "frozen", - "jsfilter", - ]), - - SERIALIZE_KEYS: new Set([ - "_enabled", - "_manifestData", - "_needsUpdate", - "_randomValue", - "_failedStart", - "_name", - "_description", - "_homepageURL", - "_addonId", - "_startDate", - "_endDate", - "_branch", - ]), - - DATE_KEYS: new Set([ - "_startDate", - "_endDate", - ]), - - UPGRADE_KEYS: new Map([ - ["_branch", null], - ]), - - ADDON_CHANGE_NONE: 0, - ADDON_CHANGE_INSTALL: 1, - ADDON_CHANGE_UNINSTALL: 2, - ADDON_CHANGE_ENABLE: 4, - - /* - * Initialize entry from the manifest. - * @param data The experiment data from the manifest. - * @return boolean Whether initialization succeeded. - */ - initFromManifestData: function (data) { - if (!this._isManifestDataValid(data)) { - return false; - } - - this._manifestData = data; - - this._randomValue = this._policy.random(); - this._lastChangedDate = this._policy.now(); - - return true; - }, - - get enabled() { - return this._enabled; - }, - - get id() { - return this._manifestData.id; - }, - - get branch() { - return this._branch; - }, - - set branch(v) { - this._branch = v; - }, - - get startDate() { - return this._startDate; - }, - - get endDate() { - if (!this._startDate) { - return null; - } - - let endTime = 0; - - if (!this._enabled) { - return this._endDate; - } - - let maxActiveMs = 1000 * this._manifestData.maxActiveSeconds; - endTime = Math.min(1000 * this._manifestData.endTime, - this._startDate.getTime() + maxActiveMs); - - return new Date(endTime); - }, - - get needsUpdate() { - return this._needsUpdate; - }, - - /* - * Initialize entry from the cache. - * @param data The entry data from the cache. - * @return boolean Whether initialization succeeded. - */ - initFromCacheData: function (data) { - for (let [key, dval] of this.UPGRADE_KEYS) { - if (!(key in data)) { - data[key] = dval; - } - } - - for (let key of this.SERIALIZE_KEYS) { - if (!(key in data) && !this.DATE_KEYS.has(key)) { - this._log.error("initFromCacheData() - missing required key " + key); - return false; - } - } - - if (!this._isManifestDataValid(data._manifestData)) { - return false; - } - - // Dates are restored separately from epoch ms, everything else is just - // copied in. - - this.SERIALIZE_KEYS.forEach(key => { - if (!this.DATE_KEYS.has(key)) { - this[key] = data[key]; - } - }); - - this.DATE_KEYS.forEach(key => { - if (key in data) { - let date = new Date(); - date.setTime(data[key]); - this[key] = date; - } - }); - - // In order for the experiment's data expiration mechanism to work, use the experiment's - // |_endData| as the |_lastChangedDate| (if available). - this._lastChangedDate = this._endDate ? this._endDate : this._policy.now(); - - return true; - }, - - /* - * Returns a JSON representation of this object. - */ - toJSON: function () { - let obj = {}; - - // Dates are serialized separately as epoch ms. - - this.SERIALIZE_KEYS.forEach(key => { - if (!this.DATE_KEYS.has(key)) { - obj[key] = this[key]; - } - }); - - this.DATE_KEYS.forEach(key => { - if (this[key]) { - obj[key] = this[key].getTime(); - } - }); - - return obj; - }, - - /* - * Update from the experiment data from the manifest. - * @param data The experiment data from the manifest. - * @return boolean Whether updating succeeded. - */ - updateFromManifestData: function (data) { - let old = this._manifestData; - - if (!this._isManifestDataValid(data)) { - return false; - } - - if (this._enabled) { - if (old.xpiHash !== data.xpiHash) { - // A changed hash means we need to update active experiments. - this._needsUpdate = true; - } - } else if (this._failedStart && - (old.xpiHash !== data.xpiHash) || - (old.xpiURL !== data.xpiURL)) { - // Retry installation of previously invalid experiments - // if hash or url changed. - this._failedStart = false; - } - - this._manifestData = data; - this._lastChangedDate = this._policy.now(); - - return true; - }, - - /* - * Is this experiment applicable? - * @return Promise<> Resolved if the experiment is applicable. - * If it is not applicable it is rejected with - * a Promise<string> which contains the reason. - */ - isApplicable: function () { - let versionCmp = Cc["@mozilla.org/xpcom/version-comparator;1"] - .getService(Ci.nsIVersionComparator); - let app = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); - let runtime = Cc["@mozilla.org/xre/app-info;1"] - .getService(Ci.nsIXULRuntime); - - let locale = this._policy.locale(); - let channel = this._policy.updatechannel(); - let data = this._manifestData; - - let now = this._policy.now() / 1000; // The manifest times are in seconds. - let maxActive = data.maxActiveSeconds || 0; - let startSec = (this.startDate || 0) / 1000; - - this._log.trace("isApplicable() - now=" + now - + ", randomValue=" + this._randomValue); - - // Not applicable if it already ran. - - if (!this.enabled && this._endDate) { - return Promise.reject(["was-active"]); - } - - // Define and run the condition checks. - - let simpleChecks = [ - { name: "failedStart", - condition: () => !this._failedStart }, - { name: "disabled", - condition: () => !data.disabled }, - { name: "frozen", - condition: () => !data.frozen || this._enabled }, - { name: "startTime", - condition: () => now >= data.startTime }, - { name: "endTime", - condition: () => now < data.endTime }, - { name: "maxStartTime", - condition: () => this._startDate || !data.maxStartTime || now <= data.maxStartTime }, - { name: "maxActiveSeconds", - condition: () => !this._startDate || now <= (startSec + maxActive) }, - { name: "appName", - condition: () => !data.appName || data.appName.indexOf(app.name) != -1 }, - { name: "minBuildID", - condition: () => !data.minBuildID || app.platformBuildID >= data.minBuildID }, - { name: "maxBuildID", - condition: () => !data.maxBuildID || app.platformBuildID <= data.maxBuildID }, - { name: "buildIDs", - condition: () => !data.buildIDs || data.buildIDs.indexOf(app.platformBuildID) != -1 }, - { name: "os", - condition: () => !data.os || data.os.indexOf(runtime.OS) != -1 }, - { name: "channel", - condition: () => !data.channel || data.channel.indexOf(channel) != -1 }, - { name: "locale", - condition: () => !data.locale || data.locale.indexOf(locale) != -1 }, - { name: "sample", - condition: () => data.sample === undefined || this._randomValue <= data.sample }, - { name: "version", - condition: () => !data.version || data.version.indexOf(app.version) != -1 }, - { name: "minVersion", - condition: () => !data.minVersion || versionCmp.compare(app.version, data.minVersion) >= 0 }, - { name: "maxVersion", - condition: () => !data.maxVersion || versionCmp.compare(app.version, data.maxVersion) <= 0 }, - ]; - - for (let check of simpleChecks) { - let result = check.condition(); - if (!result) { - this._log.debug("isApplicable() - id=" - + data.id + " - test '" + check.name + "' failed"); - return Promise.reject([check.name]); - } - } - - if (data.jsfilter) { - return this._runFilterFunction(data.jsfilter); - } - - return Promise.resolve(true); - }, - - /* - * Run the jsfilter function from the manifest in a sandbox and return the - * result (forced to boolean). - */ - _runFilterFunction: Task.async(function* (jsfilter) { - this._log.trace("runFilterFunction() - filter: " + jsfilter); - - let ssm = Services.scriptSecurityManager; - const nullPrincipal = ssm.createNullPrincipal({}); - let options = { - sandboxName: "telemetry experiments jsfilter sandbox", - wantComponents: false, - }; - - let sandbox = Cu.Sandbox(nullPrincipal, options); - try { - Cu.evalInSandbox(jsfilter, sandbox); - } catch (e) { - this._log.error("runFilterFunction() - failed to eval jsfilter: " + e.message); - throw ["jsfilter-evalfailed"]; - } - - let currentEnvironment = yield TelemetryEnvironment.onInitialized(); - - Object.defineProperty(sandbox, "_e", - { get: () => Cu.cloneInto(currentEnvironment, sandbox) }); - - let result = false; - try { - result = !!Cu.evalInSandbox("filter({get telemetryEnvironment() { return _e; } })", sandbox); - } - catch (e) { - this._log.debug("runFilterFunction() - filter function failed: " - + e.message + ", " + e.stack); - throw ["jsfilter-threw", e.message]; - } - finally { - Cu.nukeSandbox(sandbox); - } - - if (!result) { - throw ["jsfilter-false"]; - } - - return true; - }), - - /* - * Start running the experiment. - * - * @return Promise<> Resolved when the operation is complete. - */ - start: Task.async(function* () { - this._log.trace("start() for " + this.id); - - this._enabled = true; - return yield this.reconcileAddonState(); - }), - - // Async install of the addon for this experiment, part of the start task above. - _installAddon: Task.async(function* () { - let deferred = Promise.defer(); - - let hash = this._policy.ignoreHashes ? null : this._manifestData.xpiHash; - - let install = yield addonInstallForURL(this._manifestData.xpiURL, hash); - gActiveInstallURLs.add(install.sourceURI.spec); - - let failureHandler = (install, handler) => { - let message = "AddonInstall " + handler + " for " + this.id + ", state=" + - (install.state || "?") + ", error=" + install.error; - this._log.error("_installAddon() - " + message); - this._failedStart = true; - gActiveInstallURLs.delete(install.sourceURI.spec); - - TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY, - [TELEMETRY_LOG.ACTIVATION.INSTALL_FAILURE, this.id]); - - deferred.reject(new Error(message)); - }; - - let listener = { - _expectedID: null, - - onDownloadEnded: install => { - this._log.trace("_installAddon() - onDownloadEnded for " + this.id); - - if (install.existingAddon) { - this._log.warn("_installAddon() - onDownloadEnded, addon already installed"); - } - - if (install.addon.type !== "experiment") { - this._log.error("_installAddon() - onDownloadEnded, wrong addon type"); - install.cancel(); - } - }, - - onInstallStarted: install => { - this._log.trace("_installAddon() - onInstallStarted for " + this.id); - - if (install.existingAddon) { - this._log.warn("_installAddon() - onInstallStarted, addon already installed"); - } - - if (install.addon.type !== "experiment") { - this._log.error("_installAddon() - onInstallStarted, wrong addon type"); - return false; - } - return undefined; - }, - - onInstallEnded: install => { - this._log.trace("_installAddon() - install ended for " + this.id); - gActiveInstallURLs.delete(install.sourceURI.spec); - - this._lastChangedDate = this._policy.now(); - this._startDate = this._policy.now(); - this._enabled = true; - - TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY, - [TELEMETRY_LOG.ACTIVATION.ACTIVATED, this.id]); - - let addon = install.addon; - this._name = addon.name; - this._addonId = addon.id; - this._description = addon.description || ""; - this._homepageURL = addon.homepageURL || ""; - - // Experiment add-ons default to userDisabled=true. Enable if needed. - if (addon.userDisabled) { - this._log.trace("Add-on is disabled. Enabling."); - listener._expectedID = addon.id; - AddonManager.addAddonListener(listener); - addon.userDisabled = false; - } else { - this._log.trace("Add-on is enabled. start() completed."); - deferred.resolve(); - } - }, - - onEnabled: addon => { - this._log.info("onEnabled() for " + addon.id); - - if (addon.id != listener._expectedID) { - return; - } - - AddonManager.removeAddonListener(listener); - deferred.resolve(); - }, - }; - - ["onDownloadCancelled", "onDownloadFailed", "onInstallCancelled", "onInstallFailed"] - .forEach(what => { - listener[what] = install => failureHandler(install, what) - }); - - install.addListener(listener); - install.install(); - - return yield deferred.promise; - }), - - /** - * Stop running the experiment if it is active. - * - * @param terminationKind (optional) - * The termination kind, e.g. ADDON_UNINSTALLED or EXPIRED. - * @param terminationReason (optional) - * The termination reason details for termination kind RECHECK. - * @return Promise<> Resolved when the operation is complete. - */ - stop: Task.async(function* (terminationKind, terminationReason) { - this._log.trace("stop() - id=" + this.id + ", terminationKind=" + terminationKind); - if (!this._enabled) { - throw new Error("Must not call stop() on an inactive experiment."); - } - - this._enabled = false; - let now = this._policy.now(); - this._lastChangedDate = now; - this._endDate = now; - - let changes = yield this.reconcileAddonState(); - this._logTermination(terminationKind, terminationReason); - - if (terminationKind == TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED) { - changes |= this.ADDON_CHANGE_UNINSTALL; - } - - return changes; - }), - - /** - * Reconcile the state of the add-on against what it's supposed to be. - * - * If we are active, ensure the add-on is enabled and up to date. - * - * If we are inactive, ensure the add-on is not installed. - */ - reconcileAddonState: Task.async(function* () { - this._log.trace("reconcileAddonState()"); - - if (!this._enabled) { - if (!this._addonId) { - this._log.trace("reconcileAddonState() - Experiment is not enabled and " + - "has no add-on. Doing nothing."); - return this.ADDON_CHANGE_NONE; - } - - let addon = yield this._getAddon(); - if (!addon) { - this._log.trace("reconcileAddonState() - Inactive experiment has no " + - "add-on. Doing nothing."); - return this.ADDON_CHANGE_NONE; - } - - this._log.info("reconcileAddonState() - Uninstalling add-on for inactive " + - "experiment: " + addon.id); - gActiveUninstallAddonIDs.add(addon.id); - yield uninstallAddons([addon]); - gActiveUninstallAddonIDs.delete(addon.id); - return this.ADDON_CHANGE_UNINSTALL; - } - - // If we get here, we're supposed to be active. - - let changes = 0; - - // That requires an add-on. - let currentAddon = yield this._getAddon(); - - // If we have an add-on but it isn't up to date, uninstall it - // (to prepare for reinstall). - if (currentAddon && this._needsUpdate) { - this._log.info("reconcileAddonState() - Uninstalling add-on because update " + - "needed: " + currentAddon.id); - gActiveUninstallAddonIDs.add(currentAddon.id); - yield uninstallAddons([currentAddon]); - gActiveUninstallAddonIDs.delete(currentAddon.id); - changes |= this.ADDON_CHANGE_UNINSTALL; - } - - if (!currentAddon || this._needsUpdate) { - this._log.info("reconcileAddonState() - Installing add-on."); - yield this._installAddon(); - changes |= this.ADDON_CHANGE_INSTALL; - } - - let addon = yield this._getAddon(); - if (!addon) { - throw new Error("Could not obtain add-on for experiment that should be " + - "enabled."); - } - - // If we have the add-on and it is enabled, we are done. - if (!addon.userDisabled) { - return changes; - } - - // Check permissions to see if we can enable the addon. - if (!(addon.permissions & AddonManager.PERM_CAN_ENABLE)) { - throw new Error("Don't have permission to enable addon " + addon.id + ", perm=" + addon.permission); - } - - // Experiment addons should not require a restart. - if (addon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE) { - throw new Error("Experiment addon requires a restart: " + addon.id); - } - - let deferred = Promise.defer(); - - // Else we need to enable it. - let listener = { - onEnabled: enabledAddon => { - if (enabledAddon.id != addon.id) { - return; - } - - AddonManager.removeAddonListener(listener); - deferred.resolve(); - }, - }; - - for (let handler of ["onDisabled", "onOperationCancelled", "onUninstalled"]) { - listener[handler] = (evtAddon) => { - if (evtAddon.id != addon.id) { - return; - } - - AddonManager.removeAddonListener(listener); - deferred.reject("Failed to enable addon " + addon.id + " due to: " + handler); - }; - } - - this._log.info("reconcileAddonState() - Activating add-on: " + addon.id); - AddonManager.addAddonListener(listener); - addon.userDisabled = false; - yield deferred.promise; - changes |= this.ADDON_CHANGE_ENABLE; - - this._log.info("reconcileAddonState() - Add-on has been enabled: " + addon.id); - return changes; - }), - - /** - * Obtain the underlying Addon from the Addon Manager. - * - * @return Promise<Addon|null> - */ - _getAddon: function () { - if (!this._addonId) { - return Promise.resolve(null); - } - - let deferred = Promise.defer(); - - AddonManager.getAddonByID(this._addonId, (addon) => { - if (addon && addon.appDisabled) { - // Don't return PreviousExperiments. - addon = null; - } - - deferred.resolve(addon); - }); - - return deferred.promise; - }, - - _logTermination: function (terminationKind, terminationReason) { - if (terminationKind === undefined) { - return; - } - - if (!(terminationKind in TELEMETRY_LOG.TERMINATION)) { - this._log.warn("stop() - unknown terminationKind " + terminationKind); - return; - } - - let data = [terminationKind, this.id]; - if (terminationReason) { - data = data.concat(terminationReason); - } - - TelemetryLog.log(TELEMETRY_LOG.TERMINATION_KEY, data); - }, - - /** - * Determine whether an active experiment should be stopped. - */ - shouldStop: function () { - if (!this._enabled) { - throw new Error("shouldStop must not be called on disabled experiments."); - } - - let deferred = Promise.defer(); - this.isApplicable().then( - () => deferred.resolve({shouldStop: false}), - reason => deferred.resolve({shouldStop: true, reason: reason}) - ); - - return deferred.promise; - }, - - /* - * Should this be discarded from the cache due to age? - */ - shouldDiscard: function () { - let limit = this._policy.now(); - limit.setDate(limit.getDate() - KEEP_HISTORY_N_DAYS); - return (this._lastChangedDate < limit); - }, - - /* - * Get next date (in epoch-ms) to schedule a re-evaluation for this. - * Returns 0 if it doesn't need one. - */ - getScheduleTime: function () { - if (this._enabled) { - let startTime = this._startDate.getTime(); - let maxActiveTime = startTime + 1000 * this._manifestData.maxActiveSeconds; - return Math.min(1000 * this._manifestData.endTime, maxActiveTime); - } - - if (this._endDate) { - return this._endDate.getTime(); - } - - return 1000 * this._manifestData.startTime; - }, - - /* - * Perform sanity checks on the experiment data. - */ - _isManifestDataValid: function (data) { - this._log.trace("isManifestDataValid() - data: " + JSON.stringify(data)); - - for (let key of this.MANIFEST_REQUIRED_FIELDS) { - if (!(key in data)) { - this._log.error("isManifestDataValid() - missing required key: " + key); - return false; - } - } - - for (let key in data) { - if (!this.MANIFEST_OPTIONAL_FIELDS.has(key) && - !this.MANIFEST_REQUIRED_FIELDS.has(key)) { - this._log.error("isManifestDataValid() - unknown key: " + key); - return false; - } - } - - return true; - }, -}; - -/** - * Strip a Date down to its UTC midnight. - * - * This will return a cloned Date object. The original is unchanged. - */ -var stripDateToMidnight = function (d) { - let m = new Date(d); - m.setUTCHours(0, 0, 0, 0); - - return m; -}; - -/** - * An Add-ons Manager provider that knows about old experiments. - * - * This provider exposes read-only add-ons corresponding to previously-active - * experiments. The existence of this provider (and the add-ons it knows about) - * facilitates the display of old experiments in the Add-ons Manager UI with - * very little custom code in that component. - */ -this.Experiments.PreviousExperimentProvider = function (experiments) { - this._experiments = experiments; - this._experimentList = []; - this._log = Log.repository.getLoggerWithMessagePrefix( - "Browser.Experiments.Experiments", - "PreviousExperimentProvider #" + gPreviousProviderCounter++ + "::"); -} - -this.Experiments.PreviousExperimentProvider.prototype = Object.freeze({ - name: "PreviousExperimentProvider", - - startup: function () { - this._log.trace("startup()"); - Services.obs.addObserver(this, EXPERIMENTS_CHANGED_TOPIC, false); - }, - - shutdown: function () { - this._log.trace("shutdown()"); - try { - Services.obs.removeObserver(this, EXPERIMENTS_CHANGED_TOPIC); - } catch (e) { - // Prevent crash in mochitest-browser3 on Mulet - } - }, - - observe: function (subject, topic, data) { - switch (topic) { - case EXPERIMENTS_CHANGED_TOPIC: - this._updateExperimentList(); - break; - } - }, - - getAddonByID: function (id, cb) { - for (let experiment of this._experimentList) { - if (experiment.id == id) { - cb(new PreviousExperimentAddon(experiment)); - return; - } - } - - cb(null); - }, - - getAddonsByTypes: function (types, cb) { - if (types && types.length > 0 && types.indexOf("experiment") == -1) { - cb([]); - return; - } - - cb(this._experimentList.map(e => new PreviousExperimentAddon(e))); - }, - - _updateExperimentList: function () { - return this._experiments.getExperiments().then((experiments) => { - let list = experiments.filter(e => !e.active); - - let newMap = new Map(list.map(e => [e.id, e])); - let oldMap = new Map(this._experimentList.map(e => [e.id, e])); - - let added = [...newMap.keys()].filter(id => !oldMap.has(id)); - let removed = [...oldMap.keys()].filter(id => !newMap.has(id)); - - for (let id of added) { - this._log.trace("updateExperimentList() - adding " + id); - let wrapper = new PreviousExperimentAddon(newMap.get(id)); - AddonManagerPrivate.callInstallListeners("onExternalInstall", null, wrapper, null, false); - AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false); - } - - for (let id of removed) { - this._log.trace("updateExperimentList() - removing " + id); - let wrapper = new PreviousExperimentAddon(oldMap.get(id)); - AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false); - } - - this._experimentList = list; - - for (let id of added) { - let wrapper = new PreviousExperimentAddon(newMap.get(id)); - AddonManagerPrivate.callAddonListeners("onInstalled", wrapper); - } - - for (let id of removed) { - let wrapper = new PreviousExperimentAddon(oldMap.get(id)); - AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); - } - - return this._experimentList; - }); - }, -}); - -/** - * An add-on that represents a previously-installed experiment. - */ -function PreviousExperimentAddon(experiment) { - this._id = experiment.id; - this._name = experiment.name; - this._endDate = experiment.endDate; - this._description = experiment.description; -} - -PreviousExperimentAddon.prototype = Object.freeze({ - // BEGIN REQUIRED ADDON PROPERTIES - - get appDisabled() { - return true; - }, - - get blocklistState() { - Ci.nsIBlocklistService.STATE_NOT_BLOCKED - }, - - get creator() { - return new AddonManagerPrivate.AddonAuthor(""); - }, - - get foreignInstall() { - return false; - }, - - get id() { - return this._id; - }, - - get isActive() { - return false; - }, - - get isCompatible() { - return true; - }, - - get isPlatformCompatible() { - return true; - }, - - get name() { - return this._name; - }, - - get pendingOperations() { - return AddonManager.PENDING_NONE; - }, - - get permissions() { - return 0; - }, - - get providesUpdatesSecurely() { - return true; - }, - - get scope() { - return AddonManager.SCOPE_PROFILE; - }, - - get type() { - return "experiment"; - }, - - get userDisabled() { - return true; - }, - - get version() { - return null; - }, - - // END REQUIRED PROPERTIES - - // BEGIN OPTIONAL PROPERTIES - - get description() { - return this._description; - }, - - get updateDate() { - return new Date(this._endDate); - }, - - // END OPTIONAL PROPERTIES - - // BEGIN REQUIRED METHODS - - isCompatibleWith: function (appVersion, platformVersion) { - return true; - }, - - findUpdates: function (listener, reason, appVersion, platformVersion) { - AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, - appVersion, platformVersion); - }, - - // END REQUIRED METHODS - - /** - * The end-date of the experiment, required for the Addon Manager UI. - */ - - get endDate() { - return this._endDate; - }, - -}); diff --git a/browser/experiments/Experiments.manifest b/browser/experiments/Experiments.manifest deleted file mode 100644 index 4a6a05a60..000000000 --- a/browser/experiments/Experiments.manifest +++ /dev/null @@ -1,6 +0,0 @@ -component {f7800463-3b97-47f9-9341-b7617e6d8d49} ExperimentsService.js -contract @mozilla.org/browser/experiments-service;1 {f7800463-3b97-47f9-9341-b7617e6d8d49} -category update-timer ExperimentsService @mozilla.org/browser/experiments-service;1,getService,experiments-update-timer,experiments.manifest.fetchIntervalSeconds,86400 -category profile-after-change ExperimentsService @mozilla.org/browser/experiments-service;1 - - diff --git a/browser/experiments/ExperimentsService.js b/browser/experiments/ExperimentsService.js deleted file mode 100644 index 53e811251..000000000 --- a/browser/experiments/ExperimentsService.js +++ /dev/null @@ -1,118 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const {interfaces: Ci, utils: Cu} = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Preferences.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Experiments", - "resource:///modules/experiments/Experiments.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", - "resource://services-common/utils.js"); - -const PREF_EXPERIMENTS_ENABLED = "experiments.enabled"; -const PREF_ACTIVE_EXPERIMENT = "experiments.activeExperiment"; // whether we have an active experiment -const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled"; -const PREF_TELEMETRY_UNIFIED = "toolkit.telemetry.unified"; -const DELAY_INIT_MS = 30 * 1000; - -// Whether the FHR/Telemetry unification features are enabled. -// Changing this pref requires a restart. -const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_TELEMETRY_UNIFIED, false); - -XPCOMUtils.defineLazyGetter( - this, "gPrefs", () => { - return new Preferences(); - }); - -XPCOMUtils.defineLazyGetter( - this, "gExperimentsEnabled", () => { - // We can enable experiments if either unified Telemetry or FHR is on, and the user - // has opted into Telemetry. - return gPrefs.get(PREF_EXPERIMENTS_ENABLED, false) && - IS_UNIFIED_TELEMETRY && gPrefs.get(PREF_TELEMETRY_ENABLED, false); - }); - -XPCOMUtils.defineLazyGetter( - this, "gActiveExperiment", () => { - return gPrefs.get(PREF_ACTIVE_EXPERIMENT); - }); - -function ExperimentsService() { - this._initialized = false; - this._delayedInitTimer = null; -} - -ExperimentsService.prototype = { - classID: Components.ID("{f7800463-3b97-47f9-9341-b7617e6d8d49}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]), - - notify: function (timer) { - if (!gExperimentsEnabled) { - return; - } - if (OS.Constants.Path.profileDir === undefined) { - throw Error("Update timer fired before profile was initialized?"); - } - let instance = Experiments.instance(); - if (instance.isReady) { - instance.updateManifest(); - } - }, - - _delayedInit: function () { - if (!this._initialized) { - this._initialized = true; - Experiments.instance(); // for side effects - } - }, - - observe: function (subject, topic, data) { - switch (topic) { - case "profile-after-change": - if (gExperimentsEnabled) { - Services.obs.addObserver(this, "quit-application", false); - Services.obs.addObserver(this, "sessionstore-state-finalized", false); - Services.obs.addObserver(this, "EM-loaded", false); - - if (gActiveExperiment) { - this._initialized = true; - Experiments.instance(); // for side effects - } - } - break; - case "sessionstore-state-finalized": - if (!this._initialized) { - CommonUtils.namedTimer(this._delayedInit, DELAY_INIT_MS, this, "_delayedInitTimer"); - } - break; - case "EM-loaded": - if (!this._initialized) { - Experiments.instance(); // for side effects - this._initialized = true; - - if (this._delayedInitTimer) { - this._delayedInitTimer.clear(); - } - } - break; - case "quit-application": - Services.obs.removeObserver(this, "quit-application"); - Services.obs.removeObserver(this, "sessionstore-state-finalized"); - Services.obs.removeObserver(this, "EM-loaded"); - if (this._delayedInitTimer) { - this._delayedInitTimer.clear(); - } - break; - } - }, -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ExperimentsService]); diff --git a/browser/experiments/Makefile.in b/browser/experiments/Makefile.in deleted file mode 100644 index 5558582a6..000000000 --- a/browser/experiments/Makefile.in +++ /dev/null @@ -1,16 +0,0 @@ -# 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/. - -include $(topsrcdir)/config/rules.mk - -# This is so hacky. Waiting on bug 988938. -addondir = $(srcdir)/test/addons -testdir = $(topobjdir)/_tests/xpcshell/browser/experiments/test/xpcshell - -misc:: $(call mkdir_deps,$(testdir)) - $(EXIT_ON_ERROR) \ - for dir in $(addondir)/*; do \ - base=`basename $$dir`; \ - (cd $$dir && zip -qr $(testdir)/$$base.xpi *); \ - done diff --git a/browser/experiments/docs/index.rst b/browser/experiments/docs/index.rst deleted file mode 100644 index 11e5d4faa..000000000 --- a/browser/experiments/docs/index.rst +++ /dev/null @@ -1,13 +0,0 @@ -===================== -Telemetry Experiments -===================== - -Telemetry Experiments is a feature of Firefox that allows the installation -of add-ons called experiments to a subset of the Firefox population for -the purposes of experimenting with changes and collecting data on specific -aspects of application usage. - -.. toctree:: - :maxdepth: 1 - - manifest diff --git a/browser/experiments/docs/manifest.rst b/browser/experiments/docs/manifest.rst deleted file mode 100644 index d4fad5243..000000000 --- a/browser/experiments/docs/manifest.rst +++ /dev/null @@ -1,429 +0,0 @@ -.. _experiments_manifests: - -===================== -Experiments Manifests -===================== - -*Experiments Manifests* are documents that describe the set of active -experiments a client may run. - -*Experiments Manifests* are fetched periodically by clients. When -fetched, clients look at the experiments within the manifest and -determine which experiments are applicable. If an experiment is -applicable, the client may download and start the experiment. - -Manifest Format -=============== - -Manifests are JSON documents where the main element is an object. - -The *schema* of the object is versioned and defined by the presence -of a top-level ``version`` property, whose integer value is the -schema version used by that manifest. Each version is documented -in the sections below. - -Version 1 ---------- - -Version 1 is the original manifest format. - -The following properties may exist in the root object: - -experiments - An array of objects describing candidate experiments. The format of - these objects is documented below. - - An array is used to create an explicit priority of experiments. - Experiments listed at the beginning of the array take priority over - experiments that follow. - -Experiments Objects -^^^^^^^^^^^^^^^^^^^ - -Each object in the ``experiments`` array may contain the following -properties: - -id - (required) String identifier of this experiment. The identifier should - be treated as opaque by clients. It is used to uniquely identify an - experiment for all of time. - -xpiURL - (required) String URL of the XPI that implements this experiment. - - If the experiment is activated, the client will download and install this - XPI. - -xpiHash - (required) String hash of the XPI that implements this experiment. - - The value is composed of a hash identifier followed by a colon - followed by the hash value. e.g. - `sha1:f677428b9172e22e9911039aef03f3736e7f78a7`. `sha1` and `sha256` - are the two supported hashing mechanisms. The hash value is the hex - encoding of the binary hash. - - When the client downloads the XPI for the experiment, it should compare - the hash of that XPI against this value. If the hashes don't match, - the client should not install the XPI. - - Clients may also use this hash as a means of determining when an - experiment's XPI has changed and should be refreshed. - -startTime - Integer seconds since UNIX epoch that this experiment should - start. Clients should not start an experiment if *now()* is less than - this value. - -maxStartTime - (optional) Integer seconds since UNIX epoch after which this experiment - should no longer start. - - Some experiments may wish to impose hard deadlines after which no new - clients should activate the experiment. This property may be used to - facilitate that. - -endTime - Integer seconds since UNIX epoch after which this experiment - should no longer run. Clients should cease an experiment when the current - time is beyond this value. - -maxActiveSeconds - Integer seconds defining the max wall time this experiment should be - active for. - - The client should deactivate the experiment this many seconds after - initial activation. - - This value only involves wall time, not browser activity or session time. - -appName - Array of application names this experiment should run on. - - An application name comes from ``nsIXULAppInfo.name``. It is a value - like ``Firefox``, ``Fennec``, or `B2G`. - - The client should compare its application name against the members of - this array. If a match is found, the experiment is applicable. - -minVersion - (optional) String version number of the minimum application version this - experiment should run on. - - A version number is something like ``27.0.0`` or ``28``. - - The client should compare its version number to this value. If the client's - version is greater or equal to this version (using a version-aware comparison - function), the experiment is applicable. - - If this is not specified, there is no lower bound to versions this - experiment should run on. - -maxVersion - (optional) String version number of the maximum application version this - experiment should run on. - - This is similar to ``minVersion`` except it sets the upper bound for - application versions. - - If the client's version is less than or equal to this version, the - experiment is applicable. - - If this is not specified, there is no upper bound to versions this - experiment should run on. - -version - (optional) Array of application versions this experiment should run on. - - This is similar to ``minVersion`` and ``maxVersion`` except only a - whitelisted set of specific versions are allowed. - - The client should compare its version to members of this array. If a match - is found, the experiment is applicable. - -minBuildID - (optional) String minimum Build ID this experiment should run on. - - Build IDs are values like ``201402261424``. - - The client should perform a string comparison of its Build ID against this - value. If its value is greater than or equal to this value, the experiment - is applicable. - -maxBuildID - (optional) String maximum Build ID this experiment should run on. - - This is similar to ``minBuildID`` except it sets the upper bound - for Build IDs. - - The client should perform a string comparison of its Build ID against - this value. If its value is less than or equal to this value, the - experiment is applicable. - -buildIDs - (optional) Array of Build IDs this experiment should run on. - - This is similar to ``minBuildID`` and ``maxBuildID`` except only a - whitelisted set of Build IDs are considered. - - The client should compare its Build ID to members of this array. If a - match is found, the experiment is applicable. - -os - (optional) Array of operating system identifiers this experiment should - run on. - - Values for this array come from ``nsIXULRuntime.OS``. - - The client will compare its operating system identifier to members - of this array. If a match is found, the experiment is applicable to the - client. - -channel - (optional) Array of release channel identifiers this experiment should run - on. - - The client will compare its channel to members of this array. If a match - is found, the experiment is applicable. - - If this property is not defined, the client should assume the experiment - is to run on all channels. - -locale - (optional) Array of locale identifiers this experiment should run on. - - A locale identifier is a string like ``en-US`` or ``zh-CN`` and is - obtained by looking at - ``nsIXULChromeRegistry.getSelectedLocale("global")``. - - The client should compare its locale identifier to members of this array. - If a match is found, the experiment is applicable. - - If this property is not defined, the client should assume the experiment - is to run on all locales. - -sample - (optional) Decimal number indicating the sampling rate for this experiment. - - This will contain a value between ``0.0`` and ``1.0``. The client should - generate a random decimal between ``0.0`` and ``1.0``. If the randomly - generated number is less than or equal to the value of this field, the - experiment is applicable. - -disabled - (optional) Boolean value indicating whether an experiment is disabled. - - Normally, experiments are deactivated after a certain time has passed or - after the experiment itself determines it no longer needs to run (perhaps - it collected sufficient data already). - - This property serves as a backup mechanism to remotely disable an - experiment before it was scheduled to be disabled. It can be used to - kill experiments that are found to be doing wrong or bad things or that - aren't useful. - - If this property is not defined or is false, the client should assume - the experiment is active and a candidate for activation. - -frozen - (optional) Boolean value indicating this experiment is frozen and no - longer accepting new enrollments. - - If a client sees a true value in this field, it should not attempt to - activate an experiment. - -jsfilter - (optional) JavaScript code that will be evaluated to determine experiment - applicability. - - This property contains the string representation of JavaScript code that - will be evaluated in a sandboxed environment using JavaScript's - ``eval()``. - - The string is expected to contain the definition of a JavaScript function - ``filter(context)``. This function receives as its argument an object - holding application state. See the section below for the definition of - this object. - - The purpose of this property is to allow experiments to define complex - rules and logic for evaluating experiment applicability in a manner - that is privacy conscious and doesn't require the transmission of - excessive data. - - The return value of this filter indicates whether the experiment is - applicable. Functions should return true if the experiment is - applicable. - - If an experiment is not applicable, they should throw an Error whose - message contains the reason the experiment is not applicable. This - message may be logged and sent to remote servers, so it should not - contain private or otherwise sensitive data that wouldn't normally - be submitted. - - If a falsey (or undefined) value is returned, the client should - assume the experiment is not applicable. - - If this property is not defined, the client does not consider a custom - JavaScript filter function when determining whether an experiment is - applicable. - -JavaScript Filter Context Objects -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The object passed to a ``jsfilter`` ``filter()`` function contains the -following properties: - -healthReportSubmissionEnabled - This property contains a boolean indicating whether Firefox Health - Report has its data submission flag enabled (whether Firefox Health - Report is sending data to remote servers). - -healthReportPayload - This property contains the current Firefox Health Report payload. - - The payload format is documented at :ref:`healthreport_dataformat`. - -telemetryPayload - This property contains the current Telemetry payload. - -The evaluation sandbox for the JavaScript filters may be destroyed -immediately after ``filter()`` returns. This function should not assume -async code will finish. - -Experiment Applicability and Client Behavior -============================================ - -The point of an experiment manifest is to define which experiments are -available and where and how to run them. This section explains those -rules in more detail. - -Many of the properties in *Experiment Objects* are related to determining -whether an experiment should run on a given client. This evaluation is -performed client side. - -1. Multiple conditions in an experiment ---------------------------------------- - -If multiple conditions are defined for an experiment, the client should -combine each condition with a logical *AND*: all conditions must be -satisfied for an experiment to run. If one condition fails, the experiment -is not applicable. - -2. Active experiment disappears from manifest ---------------------------------------------- - -If a specific experiment disappears from the manifest, the client should -continue conducting an already-active experiment. Furthermore, the -client should remember what the expiration events were for an experiment -and honor them. - -The rationale here is that we want to prevent an accidental deletion -or temporary failure on the server to inadvertantly deactivate -supposed-to-be-active experiments. We also don't want premature deletion -of an experiment from the manifest to result in indefinite activation -periods. - -3. Inactive experiment disappears from manifest ------------------------------------------------ - -If an inactive but scheduled-to-be-active experiment disappears from the -manifest, the client should not activate the experiment. - -If that experiment reappears in the manifest, the client should not -treat that experiment any differently than any other new experiment. Put -another way, the fact an inactive experiment disappears and then -reappears should not be significant. - -The rationale here is that server operators should have complete -control of an inactive experiment up to it's go-live date. - -4. Re-evaluating applicability on manifest refresh --------------------------------------------------- - -When an experiment manifest is refreshed or updated, the client should -re-evaluate the applicability of each experiment therein. - -The rationale here is that the server may change the parameters of an -experiment and want clients to pick those up. - -5. Activating a previously non-applicable experiment ----------------------------------------------------- - -If the conditions of an experiment change or the state of the client -changes to allow an experiment to transition from previously -non-applicable to applicable, the experiment should be activated. - -For example, if a client is running version 28 and the experiment -initially requires version 29 or above, the client will not mark the -experiment as applicable. But if the client upgrades to version 29 or if -the manifest is updated to require 28 or above, the experiment will -become applicable. - -6. Deactivating a previously active experiment ----------------------------------------------- - -If the conditions of an experiment change or the state of the client -changes and an active experiment is no longer applicable, that -experiment should be deactivated. - -7. Calculation of sampling-based applicability ----------------------------------------------- - -For calculating sampling-based applicability, the client will associate -a random value between ``0.0`` and ``1.0`` for each observed experiment -ID. This random value will be generated the first time sampling -applicability is evaluated. This random value will be persisted and used -in future applicability evaluations for this experiment. - -By saving and re-using the value, the client is able to reliably and -consistently evaluate applicability, even if the sampling threshold -in the manifest changes. - -Clients should retain the randomly-generated sampling value for -experiments that no longer appear in a manifest for a period of at least -30 days. The rationale is that if an experiment disappears and reappears -from a manifest, the client will not have multiple opportunities to -generate a random value that satisfies the sampling criteria. - -8. Incompatible version numbers -------------------------------- - -If a client receives a manifest with a version number that it doesn't -recognize, it should ignore the manifest. - -9. Usage of old manifests -------------------------- - -If a client experiences an error fetching a manifest (server not -available) or if the manifest is corrupt, not readable, or compatible, -the client may use a previously-fetched (cached) manifest. - -10. Updating XPIs ------------------ - -If the URL or hash of an active experiment's XPI changes, the client -should fetch the new XPI, uninstall the old XPI, and install the new -XPI. - -Examples -======== - -Here is an example manifest:: - - { - "version": 1, - "experiments": [ - { - "id": "da9d7f4f-f3f9-4f81-bacd-6f0626ffa360", - "xpiURL": "https://experiments.mozilla.org/foo.xpi", - "xpiHash": "sha1:cb1eb32b89d86d78b7326f416cf404548c5e0099", - "startTime": 1393000000, - "endTime": 1394000000, - "appName": ["Firefox", "Fennec"], - "minVersion": "28", - "maxVersion": "30", - "os": ["windows", "linux", "osx"], - "jsfilter": "function filter(context) { return context.healthReportEnabled; }" - } - ] - } diff --git a/browser/experiments/moz.build b/browser/experiments/moz.build deleted file mode 100644 index a11e4b725..000000000 --- a/browser/experiments/moz.build +++ /dev/null @@ -1,18 +0,0 @@ -# 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/. - -HAS_MISC_RULE = True - -EXTRA_COMPONENTS += [ - 'Experiments.manifest', - 'ExperimentsService.js', -] - -EXTRA_JS_MODULES.experiments += [ - 'Experiments.jsm', -] - -XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini'] - -SPHINX_TREES['experiments'] = 'docs' diff --git a/browser/experiments/test/addons/experiment-1/install.rdf b/browser/experiments/test/addons/experiment-1/install.rdf deleted file mode 100644 index f9d70054a..000000000 --- a/browser/experiments/test/addons/experiment-1/install.rdf +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0"?> - -<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:em="http://www.mozilla.org/2004/em-rdf#"> - - <Description about="urn:mozilla:install-manifest"> - <em:id>test-experiment-1@tests.mozilla.org</em:id> - <em:version>1</em:version> - <em:type>128</em:type> - - <!-- Front End MetaData --> - <em:name>Test experiment 1</em:name> - <em:description>Yet another experiment that experiments experimentally.</em:description> - - </Description> -</RDF> diff --git a/browser/experiments/test/addons/experiment-1a/install.rdf b/browser/experiments/test/addons/experiment-1a/install.rdf deleted file mode 100644 index 7806b11b1..000000000 --- a/browser/experiments/test/addons/experiment-1a/install.rdf +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0"?> - -<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:em="http://www.mozilla.org/2004/em-rdf#"> - - <Description about="urn:mozilla:install-manifest"> - <em:id>test-experiment-1@tests.mozilla.org</em:id> - <em:version>1.1</em:version> - <em:type>128</em:type> - - <!-- Front End MetaData --> - <em:name>Test experiment 1.1</em:name> - <em:description>And yet another experiment that experiments experimentally.</em:description> - - </Description> -</RDF> diff --git a/browser/experiments/test/addons/experiment-2/install.rdf b/browser/experiments/test/addons/experiment-2/install.rdf deleted file mode 100644 index 69122c0ef..000000000 --- a/browser/experiments/test/addons/experiment-2/install.rdf +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0"?> - -<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:em="http://www.mozilla.org/2004/em-rdf#"> - - <Description about="urn:mozilla:install-manifest"> - <em:id>test-experiment-2@tests.mozilla.org</em:id> - <em:version>1</em:version> - <em:type>128</em:type> - - <!-- Front End MetaData --> - <em:name>Test experiment 2</em:name> - <em:description>And yet another experiment that experiments experimentally.</em:description> - - </Description> -</RDF> diff --git a/browser/experiments/test/addons/experiment-racybranch/bootstrap.js b/browser/experiments/test/addons/experiment-racybranch/bootstrap.js deleted file mode 100644 index e8278f50f..000000000 --- a/browser/experiments/test/addons/experiment-racybranch/bootstrap.js +++ /dev/null @@ -1,35 +0,0 @@ -/* exported startup, shutdown, install, uninstall */ - -var {classes: Cc, interfaces: Ci, utils: Cu} = Components; - -Cu.import("resource:///modules/experiments/Experiments.jsm"); - -var gStarted = false; - -function startup(data, reasonCode) { - if (gStarted) { - return; - } - gStarted = true; - - // delay realstartup to trigger the race condition - Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager) - .mainThread.dispatch(realstartup, 0); -} - -function realstartup() { - let experiments = Experiments.instance(); - let experiment = experiments._getActiveExperiment(); - if (experiment.branch) { - Cu.reportError("Found pre-existing branch: " + experiment.branch); - return; - } - - let branch = "racy-set"; - experiments.setExperimentBranch(experiment.id, branch) - .then(null, Cu.reportError); -} - -function shutdown() { } -function install() { } -function uninstall() { } diff --git a/browser/experiments/test/addons/experiment-racybranch/install.rdf b/browser/experiments/test/addons/experiment-racybranch/install.rdf deleted file mode 100644 index cebaede56..000000000 --- a/browser/experiments/test/addons/experiment-racybranch/install.rdf +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
- <Description about="urn:mozilla:install-manifest">
- <em:id>test-experiment-racybranch@tests.mozilla.org</em:id>
- <em:version>1</em:version>
- <em:type>128</em:type>
-
- <!-- Front End MetaData -->
- <em:name>Test experiment racybranch</em:name>
- <em:description>An experiment that sets the experiment branch in a potentially racy way.</em:description>
-
- </Description>
-</RDF>
diff --git a/browser/experiments/test/xpcshell/.eslintrc.js b/browser/experiments/test/xpcshell/.eslintrc.js deleted file mode 100644 index 1f540a05b..000000000 --- a/browser/experiments/test/xpcshell/.eslintrc.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict"; - -module.exports = { - "extends": [ - "../../../../testing/xpcshell/xpcshell.eslintrc.js" - ], - - "rules": { - "no-unused-vars": ["error", { - "vars": "all", - "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$", - "args": "none" - }] - } -}; diff --git a/browser/experiments/test/xpcshell/experiments_1.manifest b/browser/experiments/test/xpcshell/experiments_1.manifest deleted file mode 100644 index 0401ea328..000000000 --- a/browser/experiments/test/xpcshell/experiments_1.manifest +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": 1, - "experiments": [ - { - "id": "test-experiment-1@tests.mozilla.org", - "xpiURL": "https://experiments.mozilla.org/foo.xpi", - "xpiHash": "sha1:cb1eb32b89d86d78b7326f416cf404548c5e0099", - "startTime": 1393000000, - "endTime": 1394000000, - "appName": ["Firefox", "Fennec"], - "minVersion": "28", - "maxVersion": "30", - "maxActiveSeconds": 60, - "os": ["windows", "linux", "osx"], - "channel": ["daily", "weekly", "nightly"], - "jsfilter": "function filter(context) { return true; }" - } - ] -} diff --git a/browser/experiments/test/xpcshell/head.js b/browser/experiments/test/xpcshell/head.js deleted file mode 100644 index ae356ea2d..000000000 --- a/browser/experiments/test/xpcshell/head.js +++ /dev/null @@ -1,199 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -/* exported PREF_EXPERIMENTS_ENABLED, PREF_LOGGING_LEVEL, PREF_LOGGING_DUMP - PREF_MANIFEST_URI, PREF_FETCHINTERVAL, EXPERIMENT1_ID, - EXPERIMENT1_NAME, EXPERIMENT1_XPI_SHA1, EXPERIMENT1A_NAME, - EXPERIMENT1A_XPI_SHA1, EXPERIMENT2_ID, EXPERIMENT2_XPI_SHA1, - EXPERIMENT3_ID, EXPERIMENT4_ID, FAKE_EXPERIMENTS_1, - FAKE_EXPERIMENTS_2, gAppInfo, removeCacheFile, defineNow, - futureDate, dateToSeconds, loadAddonManager, promiseRestartManager, - startAddonManagerOnly, getExperimentAddons, replaceExperiments */ - -var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://testing-common/AddonManagerTesting.jsm"); -Cu.import("resource://testing-common/AddonTestUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", - "resource://gre/modules/AddonManager.jsm"); - -const PREF_EXPERIMENTS_ENABLED = "experiments.enabled"; -const PREF_LOGGING_LEVEL = "experiments.logging.level"; -const PREF_LOGGING_DUMP = "experiments.logging.dump"; -const PREF_MANIFEST_URI = "experiments.manifest.uri"; -const PREF_FETCHINTERVAL = "experiments.manifest.fetchIntervalSeconds"; -const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled"; - -function getExperimentPath(base) { - let p = do_get_cwd(); - p.append(base); - return p.path; -} - -function sha1File(path) { - let f = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - f.initWithPath(path); - let hasher = Cc["@mozilla.org/security/hash;1"] - .createInstance(Ci.nsICryptoHash); - hasher.init(hasher.SHA1); - - let is = Cc["@mozilla.org/network/file-input-stream;1"] - .createInstance(Ci.nsIFileInputStream); - is.init(f, -1, 0, 0); - hasher.updateFromStream(is, Math.pow(2, 32) - 1); - is.close(); - let bytes = hasher.finish(false); - - let rv = ""; - for (let i = 0; i < bytes.length; i++) { - rv += ("0" + bytes.charCodeAt(i).toString(16)).substr(-2); - } - return rv; -} - -const EXPERIMENT1_ID = "test-experiment-1@tests.mozilla.org"; -const EXPERIMENT1_XPI_NAME = "experiment-1.xpi"; -const EXPERIMENT1_NAME = "Test experiment 1"; -const EXPERIMENT1_PATH = getExperimentPath(EXPERIMENT1_XPI_NAME); -const EXPERIMENT1_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1_PATH); - - -const EXPERIMENT1A_XPI_NAME = "experiment-1a.xpi"; -const EXPERIMENT1A_NAME = "Test experiment 1.1"; -const EXPERIMENT1A_PATH = getExperimentPath(EXPERIMENT1A_XPI_NAME); -const EXPERIMENT1A_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1A_PATH); - -const EXPERIMENT2_ID = "test-experiment-2@tests.mozilla.org" -const EXPERIMENT2_XPI_NAME = "experiment-2.xpi"; -const EXPERIMENT2_PATH = getExperimentPath(EXPERIMENT2_XPI_NAME); -const EXPERIMENT2_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT2_PATH); - -const EXPERIMENT3_ID = "test-experiment-3@tests.mozilla.org"; -const EXPERIMENT4_ID = "test-experiment-4@tests.mozilla.org"; - -const FAKE_EXPERIMENTS_1 = [ - { - id: "id1", - name: "experiment1", - description: "experiment 1", - active: true, - detailUrl: "https://dummy/experiment1", - branch: "foo", - }, -]; - -const FAKE_EXPERIMENTS_2 = [ - { - id: "id2", - name: "experiment2", - description: "experiment 2", - active: false, - endDate: new Date(2014, 2, 11, 2, 4, 35, 42).getTime(), - detailUrl: "https://dummy/experiment2", - branch: null, - }, - { - id: "id1", - name: "experiment1", - description: "experiment 1", - active: false, - endDate: new Date(2014, 2, 10, 0, 0, 0, 0).getTime(), - detailURL: "https://dummy/experiment1", - branch: null, - }, -]; - -var gAppInfo = null; - -function removeCacheFile() { - let path = OS.Path.join(OS.Constants.Path.profileDir, "experiments.json"); - return OS.File.remove(path); -} - -function patchPolicy(policy, data) { - for (let key of Object.keys(data)) { - Object.defineProperty(policy, key, { - value: data[key], - writable: true, - }); - } -} - -function defineNow(policy, time) { - patchPolicy(policy, { now: () => new Date(time) }); -} - -function futureDate(date, offset) { - return new Date(date.getTime() + offset); -} - -function dateToSeconds(date) { - return date.getTime() / 1000; -} - -var gGlobalScope = this; -function loadAddonManager() { - AddonTestUtils.init(gGlobalScope); - AddonTestUtils.overrideCertDB(); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - return AddonTestUtils.promiseStartupManager(); -} - -const { - promiseRestartManager, -} = AddonTestUtils; - -// Starts the addon manager without creating app info. We can't directly use -// |loadAddonManager| defined above in test_conditions.js as it would make the test fail. -function startAddonManagerOnly() { - let addonManager = Cc["@mozilla.org/addons/integration;1"] - .getService(Ci.nsIObserver) - .QueryInterface(Ci.nsITimerCallback); - addonManager.observe(null, "addons-startup", null); -} - -function getExperimentAddons(previous=false) { - let deferred = Promise.defer(); - - AddonManager.getAddonsByTypes(["experiment"], (addons) => { - if (previous) { - deferred.resolve(addons); - } else { - deferred.resolve(addons.filter(a => !a.appDisabled)); - } - }); - - return deferred.promise; -} - -function createAppInfo(ID="xpcshell@tests.mozilla.org", name="XPCShell", - version="1.0", platformVersion="1.0") { - AddonTestUtils.createAppInfo(ID, name, version, platformVersion); - gAppInfo = AddonTestUtils.appInfo; -} - -/** - * Replace the experiments on an Experiments with a new list. - * - * This monkeypatches getExperiments(). It doesn't monkeypatch the internal - * experiments list. So its utility is not as great as it could be. - */ -function replaceExperiments(experiment, list) { - Object.defineProperty(experiment, "getExperiments", { - writable: true, - value: () => { - return Promise.resolve(list); - }, - }); -} - -// Experiments require Telemetry to be enabled, and that's not true for debug -// builds. Let's just enable it here instead of going through each test. -Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true); diff --git a/browser/experiments/test/xpcshell/test_activate.js b/browser/experiments/test/xpcshell/test_activate.js deleted file mode 100644 index 60deafbfb..000000000 --- a/browser/experiments/test/xpcshell/test_activate.js +++ /dev/null @@ -1,151 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://testing-common/httpd.js"); -Cu.import("resource:///modules/experiments/Experiments.jsm"); - -const SEC_IN_ONE_DAY = 24 * 60 * 60; -const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; - -var gHttpServer = null; -var gHttpRoot = null; -var gPolicy = null; - -function ManifestEntry(data) { - this.id = data.id || EXPERIMENT1_ID; - this.xpiURL = data.xpiURL || gHttpRoot + EXPERIMENT1_XPI_NAME; - this.xpiHash = data.xpiHash || EXPERIMENT1_XPI_SHA1; - this.appName = data.appName || ["XPCShell"]; - this.channel = data.appName || ["nightly"]; - this.startTime = data.startTime || new Date(2010, 0, 1, 12).getTime() / 1000; - this.endTime = data.endTime || new Date(9001, 0, 1, 12).getTime() / 1000; - this.maxActiveSeconds = data.maxActiveSeconds || 5 * SEC_IN_ONE_DAY; -} - -function run_test() { - run_next_test(); -} - -add_task(function* test_setup() { - loadAddonManager(); - gPolicy = new Experiments.Policy(); - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let port = gHttpServer.identity.primaryPort; - gHttpRoot = "http://localhost:" + port + "/"; - gHttpServer.registerDirectory("/", do_get_cwd()); - do_register_cleanup(() => gHttpServer.stop(() => {})); - - patchPolicy(gPolicy, { - updatechannel: () => "nightly", - }); - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); - Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); -}); - -function isApplicable(experiment) { - let deferred = Promise.defer(); - experiment.isApplicable().then( - result => deferred.resolve({ applicable: true, reason: null }), - reason => deferred.resolve({ applicable: false, reason: reason }) - ); - - return deferred.promise; -} - -add_task(function* test_startStop() { - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 30 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 60 * MS_IN_ONE_DAY); - let manifestData = new ManifestEntry({ - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - }); - let experiment = new Experiments.ExperimentEntry(gPolicy); - experiment.initFromManifestData(manifestData); - - // We need to associate it with the singleton so the onInstallStarted - // Addon Manager listener will know about it. - Experiments.instance()._experiments = new Map(); - Experiments.instance()._experiments.set(experiment.id, experiment); - - let result; - - defineNow(gPolicy, baseDate); - result = yield isApplicable(experiment); - Assert.equal(result.applicable, false, "Experiment should not be applicable."); - Assert.equal(experiment.enabled, false, "Experiment should not be enabled."); - - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "No experiment add-ons are installed."); - - defineNow(gPolicy, futureDate(startDate, 5 * MS_IN_ONE_DAY)); - result = yield isApplicable(experiment); - Assert.equal(result.applicable, true, "Experiment should now be applicable."); - Assert.equal(experiment.enabled, false, "Experiment should not be enabled."); - - let changes = yield experiment.start(); - Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed."); - addons = yield getExperimentAddons(); - Assert.equal(experiment.enabled, true, "Experiment should now be enabled."); - Assert.equal(addons.length, 1, "1 experiment add-on is installed."); - Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect."); - Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled."); - Assert.ok(addons[0].isActive, "The add-on is active."); - - changes = yield experiment.stop(); - Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on was uninstalled."); - addons = yield getExperimentAddons(); - Assert.equal(experiment.enabled, false, "Experiment should not be enabled."); - Assert.equal(addons.length, 0, "Experiment should be uninstalled from the Addon Manager."); - - changes = yield experiment.start(); - Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed."); - addons = yield getExperimentAddons(); - Assert.equal(experiment.enabled, true, "Experiment should now be enabled."); - Assert.equal(addons.length, 1, "1 experiment add-on is installed."); - Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect."); - Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled."); - Assert.ok(addons[0].isActive, "The add-on is active."); - - result = yield experiment.shouldStop(); - Assert.equal(result.shouldStop, false, "shouldStop should be false."); - Assert.equal(experiment.enabled, true, "Experiment should be enabled."); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "Experiment still in add-ons manager."); - Assert.ok(addons[0].isActive, "The add-on is still active."); - - defineNow(gPolicy, futureDate(endDate, MS_IN_ONE_DAY)); - result = yield experiment.shouldStop(); - Assert.equal(result.shouldStop, true, "shouldStop should now be true."); - changes = yield experiment.stop(); - Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on should be uninstalled."); - Assert.equal(experiment.enabled, false, "Experiment should be disabled."); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Experiment add-on is uninstalled."); - - // Ensure hash validation works. - // We set an incorrect hash and expect the install to fail. - experiment._manifestData.xpiHash = "sha1:41014dcc66b4dcedcd973491a1530a32f0517d8a"; - let errored = false; - try { - yield experiment.start(); - } catch (ex) { - errored = true; - } - Assert.ok(experiment._failedStart, "Experiment failed to start."); - Assert.ok(errored, "start() threw an exception."); - - // Make sure "ignore hashes" mode works. - gPolicy.ignoreHashes = true; - changes = yield experiment.start(); - Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL); - yield experiment.stop(); - gPolicy.ignoreHashes = false; -}); diff --git a/browser/experiments/test/xpcshell/test_api.js b/browser/experiments/test/xpcshell/test_api.js deleted file mode 100644 index 9f0112570..000000000 --- a/browser/experiments/test/xpcshell/test_api.js +++ /dev/null @@ -1,1647 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://testing-common/httpd.js"); -Cu.import("resource://testing-common/AddonManagerTesting.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Experiments", - "resource:///modules/experiments/Experiments.jsm"); - -const MANIFEST_HANDLER = "manifests/handler"; - -const SEC_IN_ONE_DAY = 24 * 60 * 60; -const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; - -var gHttpServer = null; -var gHttpRoot = null; -var gDataRoot = null; -var gPolicy = null; -var gManifestObject = null; -var gManifestHandlerURI = null; -var gTimerScheduleOffset = -1; - -function uninstallExperimentAddons() { - return Task.spawn(function* () { - let addons = yield getExperimentAddons(); - for (let a of addons) { - yield AddonManagerTesting.uninstallAddonByID(a.id); - } - }); -} - -function testCleanup(experimentsInstance) { - return Task.spawn(function* () { - yield promiseRestartManager(); - yield uninstallExperimentAddons(); - yield removeCacheFile(); - }); -} - -function run_test() { - run_next_test(); -} - -add_task(function* test_setup() { - loadAddonManager(); - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let port = gHttpServer.identity.primaryPort; - gHttpRoot = "http://localhost:" + port + "/"; - gDataRoot = gHttpRoot + "data/"; - gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER; - gHttpServer.registerDirectory("/data/", do_get_cwd()); - gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify(gManifestObject)); - response.processAsync(); - response.finish(); - }); - do_register_cleanup(() => gHttpServer.stop(() => {})); - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); - Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); - Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI); - Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0); - - gPolicy = new Experiments.Policy(); - patchPolicy(gPolicy, { - updatechannel: () => "nightly", - oneshotTimer: (callback, timeout, thisObj, name) => gTimerScheduleOffset = timeout, - }); -}); - -add_task(function* test_contract() { - Cc["@mozilla.org/browser/experiments-service;1"].getService(); -}); - -// Test basic starting and stopping of experiments. - -add_task(function* test_getExperiments() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY); - let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY); - let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT2_ID, - xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, - xpiHash: EXPERIMENT2_XPI_SHA1, - startTime: dateToSeconds(startDate2), - endTime: dateToSeconds(endDate2), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate1), - endTime: dateToSeconds(endDate1), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - // Data to compare the result of Experiments.getExperiments() against. - - let experimentListData = [ - { - id: EXPERIMENT2_ID, - name: "Test experiment 2", - description: "And yet another experiment that experiments experimentally.", - }, - { - id: EXPERIMENT1_ID, - name: EXPERIMENT1_NAME, - description: "Yet another experiment that experiments experimentally.", - }, - ]; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - // Use updateManifest() to provide for coverage of that path. - - let now = baseDate; - gTimerScheduleOffset = -1; - defineNow(gPolicy, now); - - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - Assert.equal(experiments.getActiveExperimentID(), null, - "getActiveExperimentID should return null"); - - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed."); - - try { - yield experiments.getExperimentBranch(); - Assert.ok(false, "getExperimentBranch should fail with no experiment"); - } - catch (e) { - Assert.ok(true, "getExperimentBranch correctly threw"); - } - - // Trigger update, clock set for experiment 1 to start. - - now = futureDate(startDate1, 5 * MS_IN_ONE_DAY); - gTimerScheduleOffset = -1; - defineNow(gPolicy, now); - - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID, - "getActiveExperimentID should return the active experiment1"); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "An experiment add-on was installed."); - - experimentListData[1].active = true; - experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY; - for (let k of Object.keys(experimentListData[1])) { - Assert.equal(experimentListData[1][k], list[0][k], - "Property " + k + " should match reference data."); - } - - let b = yield experiments.getExperimentBranch(); - Assert.strictEqual(b, null, "getExperimentBranch should return null by default"); - - b = yield experiments.getExperimentBranch(EXPERIMENT1_ID); - Assert.strictEqual(b, null, "getExperimentsBranch should return null (with id)"); - - yield experiments.setExperimentBranch(EXPERIMENT1_ID, "foo"); - b = yield experiments.getExperimentBranch(); - Assert.strictEqual(b, "foo", "getExperimentsBranch should return the set value"); - - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - Assert.equal(gTimerScheduleOffset, 10 * MS_IN_ONE_DAY, - "Experiment re-evaluation should have been scheduled correctly."); - - // Trigger update, clock set for experiment 1 to stop. - - now = futureDate(endDate1, 1000); - gTimerScheduleOffset = -1; - defineNow(gPolicy, now); - - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - Assert.equal(experiments.getActiveExperimentID(), null, - "getActiveExperimentID should return null again"); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry."); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled."); - - experimentListData[1].active = false; - experimentListData[1].endDate = now.getTime(); - for (let k of Object.keys(experimentListData[1])) { - Assert.equal(experimentListData[1][k], list[0][k], - "Property " + k + " should match reference data."); - } - - Assert.equal(gTimerScheduleOffset, startDate2 - now, - "Experiment re-evaluation should have been scheduled correctly."); - - // Trigger update, clock set for experiment 2 to start. - // Use notify() to provide for coverage of that path. - - now = startDate2; - gTimerScheduleOffset = -1; - defineNow(gPolicy, now); - - yield experiments.notify(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT2_ID, - "getActiveExperimentID should return the active experiment2"); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 2, "Experiment list should have 2 entries now."); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "An experiment add-on is installed."); - - experimentListData[0].active = true; - experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY; - for (let i=0; i<experimentListData.length; ++i) { - let entry = experimentListData[i]; - for (let k of Object.keys(entry)) { - Assert.equal(entry[k], list[i][k], - "Entry " + i + " - Property '" + k + "' should match reference data."); - } - } - - Assert.equal(gTimerScheduleOffset, 10 * MS_IN_ONE_DAY, - "Experiment re-evaluation should have been scheduled correctly."); - - // Trigger update, clock set for experiment 2 to stop. - - now = futureDate(startDate2, 10 * MS_IN_ONE_DAY + 1000); - gTimerScheduleOffset = -1; - defineNow(gPolicy, now); - yield experiments.notify(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - Assert.equal(experiments.getActiveExperimentID(), null, - "getActiveExperimentID should return null again2"); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 2, "Experiment list should have 2 entries now."); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "No experiments add-ons are installed."); - - experimentListData[0].active = false; - experimentListData[0].endDate = now.getTime(); - for (let i=0; i<experimentListData.length; ++i) { - let entry = experimentListData[i]; - for (let k of Object.keys(entry)) { - Assert.equal(entry[k], list[i][k], - "Entry " + i + " - Property '" + k + "' should match reference data."); - } - } - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -add_task(function* test_getActiveExperimentID() { - // Check that getActiveExperimentID returns the correct result even - // after .uninit() - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY); - let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate1), - endTime: dateToSeconds(endDate1), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let now = futureDate(startDate1, 5 * MS_IN_ONE_DAY); - gTimerScheduleOffset = -1; - defineNow(gPolicy, now); - - let experiments = new Experiments.Experiments(gPolicy); - yield experiments.updateManifest(); - - Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID, - "getActiveExperimentID should return the active experiment1"); - - yield promiseRestartManager(); - Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID, - "getActiveExperimentID should return the active experiment1 after uninit()"); - - yield testCleanup(experiments); -}); - -// Test that we handle the experiments addon already being -// installed properly. -// We should just pave over them. - -add_task(function* test_addonAlreadyInstalled() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Trigger update, clock set for the experiment to start. - - now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should be active."); - - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "1 add-on is installed."); - - // Install conflicting addon. - - yield AddonManagerTesting.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "1 add-on is installed."); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should still have 1 entry."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should be active."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -add_task(function* test_lastActiveToday() { - let experiments = new Experiments.Experiments(gPolicy); - - replaceExperiments(experiments, FAKE_EXPERIMENTS_1); - - let e = yield experiments.getExperiments(); - Assert.equal(e.length, 1, "Monkeypatch successful."); - Assert.equal(e[0].id, "id1", "ID looks sane"); - Assert.ok(e[0].active, "Experiment is active."); - - let lastActive = yield experiments.lastActiveToday(); - Assert.equal(e[0], lastActive, "Last active object is expected."); - - replaceExperiments(experiments, FAKE_EXPERIMENTS_2); - e = yield experiments.getExperiments(); - Assert.equal(e.length, 2, "Monkeypatch successful."); - - defineNow(gPolicy, e[0].endDate); - - lastActive = yield experiments.lastActiveToday(); - Assert.ok(lastActive, "Have a last active experiment"); - Assert.equal(lastActive, e[0], "Last active object is expected."); - - yield testCleanup(experiments); -}); - -// Test explicitly disabling experiments. - -add_task(function* test_disableExperiment() { - // Dates this test is based on. - - let startDate = new Date(2004, 10, 9, 12); - let endDate = futureDate(startDate, 100 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - // Data to compare the result of Experiments.getExperiments() against. - - let experimentInfo = { - id: EXPERIMENT1_ID, - name: EXPERIMENT1_NAME, - description: "Yet another experiment that experiments experimentally.", - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set for the experiment to start. - - let now = futureDate(startDate, 5 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - - experimentInfo.active = true; - experimentInfo.endDate = now.getTime() + 10 * MS_IN_ONE_DAY; - for (let k of Object.keys(experimentInfo)) { - Assert.equal(experimentInfo[k], list[0][k], - "Property " + k + " should match reference data."); - } - - // Test disabling the experiment. - - now = futureDate(now, 1 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.disableExperiment("foo"); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry."); - - experimentInfo.active = false; - experimentInfo.endDate = now.getTime(); - for (let k of Object.keys(experimentInfo)) { - Assert.equal(experimentInfo[k], list[0][k], - "Property " + k + " should match reference data."); - } - - // Test that updating the list doesn't re-enable it. - - now = futureDate(now, 1 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry."); - - for (let k of Object.keys(experimentInfo)) { - Assert.equal(experimentInfo[k], list[0][k], - "Property " + k + " should match reference data."); - } - - yield testCleanup(experiments); -}); - -add_task(function* test_disableExperimentsFeature() { - // Dates this test is based on. - - let startDate = new Date(2004, 10, 9, 12); - let endDate = futureDate(startDate, 100 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - // Data to compare the result of Experiments.getExperiments() against. - - let experimentInfo = { - id: EXPERIMENT1_ID, - name: EXPERIMENT1_NAME, - description: "Yet another experiment that experiments experimentally.", - }; - - let experiments = new Experiments.Experiments(gPolicy); - Assert.equal(experiments.enabled, true, "Experiments feature should be enabled."); - - // Trigger update, clock set for the experiment to start. - - let now = futureDate(startDate, 5 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - - experimentInfo.active = true; - experimentInfo.endDate = now.getTime() + 10 * MS_IN_ONE_DAY; - for (let k of Object.keys(experimentInfo)) { - Assert.equal(experimentInfo[k], list[0][k], - "Property " + k + " should match reference data."); - } - - // Test disabling experiments. - - experiments._toggleExperimentsEnabled(false); - yield experiments.notify(); - Assert.equal(experiments.enabled, false, "Experiments feature should be disabled now."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry."); - - experimentInfo.active = false; - experimentInfo.endDate = now.getTime(); - for (let k of Object.keys(experimentInfo)) { - Assert.equal(experimentInfo[k], list[0][k], - "Property " + k + " should match reference data."); - } - - // Test that updating the list doesn't re-enable it. - - now = futureDate(now, 1 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - try { - yield experiments.updateManifest(); - } catch (e) { - // Exception expected, the feature is disabled. - } - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry."); - - for (let k of Object.keys(experimentInfo)) { - Assert.equal(experimentInfo[k], list[0][k], - "Property " + k + " should match reference data."); - } - - yield testCleanup(experiments); -}); - -// Test that after a failed experiment install: -// * the next applicable experiment gets installed -// * changing the experiments data later triggers re-evaluation - -add_task(function* test_installFailure() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - { - id: EXPERIMENT2_ID, - xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, - xpiHash: EXPERIMENT2_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - // Data to compare the result of Experiments.getExperiments() against. - - let experimentListData = [ - { - id: EXPERIMENT1_ID, - name: EXPERIMENT1_NAME, - description: "Yet another experiment that experiments experimentally.", - }, - { - id: EXPERIMENT2_ID, - name: "Test experiment 2", - description: "And yet another experiment that experiments experimentally.", - }, - ]; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Trigger update, clock set for experiment 1 & 2 to start, - // invalid hash for experiment 1. - // Order in the manifest matters, so we should start experiment 1, - // fail to install it & start experiment 2 instead. - - now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].xpiHash = "sha1:0000000000000000000000000000000000000000"; - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT2_ID, "Experiment 2 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 2 should be active."); - - // Trigger update, clock set for experiment 2 to stop. - - now = futureDate(now, 20 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - experimentListData[0].active = false; - experimentListData[0].endDate = now; - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT2_ID, "Experiment 2 should be the sole entry."); - Assert.equal(list[0].active, false, "Experiment should not be active."); - - // Trigger update with a fixed entry for experiment 1, - // which should get re-evaluated & started now. - - now = futureDate(now, 20 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].xpiHash = EXPERIMENT1_XPI_SHA1; - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - experimentListData[0].active = true; - experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY; - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 2, "Experiment list should have 2 entries now."); - - for (let i=0; i<experimentListData.length; ++i) { - let entry = experimentListData[i]; - for (let k of Object.keys(entry)) { - Assert.equal(entry[k], list[i][k], - "Entry " + i + " - Property '" + k + "' should match reference data."); - } - } - - yield testCleanup(experiments); -}); - -// Test that after an experiment was disabled by user action, -// the experiment is not activated again if manifest data changes. - -add_task(function* test_userDisabledAndUpdated() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Trigger update, clock set for experiment 1 to start. - - now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should be active."); - let todayActive = yield experiments.lastActiveToday(); - Assert.ok(todayActive, "Last active for today reports a value."); - Assert.equal(todayActive.id, list[0].id, "The entry is what we expect."); - - // Explicitly disable an experiment. - - now = futureDate(now, 20 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.disableExperiment("foo"); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, false, "Experiment should not be active anymore."); - todayActive = yield experiments.lastActiveToday(); - Assert.ok(todayActive, "Last active for today still returns a value."); - Assert.equal(todayActive.id, list[0].id, "The ID is still the same."); - - // Trigger an update with a faked change for experiment 1. - - now = futureDate(now, 20 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - experiments._experiments.get(EXPERIMENT1_ID)._manifestData.xpiHash = - "sha1:0000000000000000000000000000000000000000"; - yield experiments.updateManifest(); - Assert.equal(observerFireCount, expectedObserverFireCount, - "Experiments observer should not have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, false, "Experiment should still be inactive."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -// Test that changing the hash for an active experiments triggers an -// update for it. - -add_task(function* test_updateActiveExperiment() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - let todayActive = yield experiments.lastActiveToday(); - Assert.equal(todayActive, null, "No experiment active today."); - - // Trigger update, clock set for the experiment to start. - - now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should be active."); - Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match."); - todayActive = yield experiments.lastActiveToday(); - Assert.ok(todayActive, "todayActive() returns a value."); - Assert.equal(todayActive.id, list[0].id, "It returns the active experiment."); - - // Trigger an update for the active experiment by changing it's hash (and xpi) - // in the manifest. - - now = futureDate(now, 1 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].xpiHash = EXPERIMENT1A_XPI_SHA1; - gManifestObject.experiments[0].xpiURL = gDataRoot + EXPERIMENT1A_XPI_NAME; - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should still be active."); - Assert.equal(list[0].name, EXPERIMENT1A_NAME, "Experiments name should have been updated."); - todayActive = yield experiments.lastActiveToday(); - Assert.equal(todayActive.id, list[0].id, "last active today is still sane."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -// Tests that setting the disable flag for an active experiment -// stops it. - -add_task(function* test_disableActiveExperiment() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Trigger update, clock set for the experiment to start. - - now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should be active."); - - // Trigger an update with the experiment being disabled. - - now = futureDate(now, 1 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].disabled = true; - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, false, "Experiment 1 should be disabled."); - - // Check that the experiment stays disabled. - - now = futureDate(now, 1 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - delete gManifestObject.experiments[0].disabled; - yield experiments.updateManifest(); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, false, "Experiment 1 should still be disabled."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -// Test that: -// * setting the frozen flag for a not-yet-started experiment keeps -// it from starting -// * after a removing the frozen flag, the experiment can still start - -add_task(function* test_freezePendingExperiment() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Trigger update, clock set for the experiment to start but frozen. - - now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].frozen = true; - yield experiments.updateManifest(); - Assert.equal(observerFireCount, expectedObserverFireCount, - "Experiments observer should have not been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should have no entries yet."); - - // Trigger an update with the experiment not being frozen anymore. - - now = futureDate(now, 1 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - delete gManifestObject.experiments[0].frozen; - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should be active now."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -// Test that setting the frozen flag for an active experiment doesn't -// stop it. - -add_task(function* test_freezeActiveExperiment() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Trigger update, clock set for the experiment to start. - - now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should be active."); - Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match."); - - // Trigger an update with the experiment being disabled. - - now = futureDate(now, 1 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].frozen = true; - yield experiments.updateManifest(); - Assert.equal(observerFireCount, expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should still be active."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -// Test that removing an active experiment from the manifest doesn't -// stop it. - -add_task(function* test_removeActiveExperiment() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - let startDate2 = futureDate(baseDate, 20000 * MS_IN_ONE_DAY); - let endDate2 = futureDate(baseDate, 30000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - { - id: EXPERIMENT2_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT2_XPI_SHA1, - startTime: dateToSeconds(startDate2), - endTime: dateToSeconds(endDate2), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Trigger update, clock set for the experiment to start. - - now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should be active."); - Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match."); - - // Trigger an update with experiment 1 missing from the manifest - - now = futureDate(now, 1 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].frozen = true; - yield experiments.updateManifest(); - Assert.equal(observerFireCount, expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should still be active."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -// Test that we correctly handle experiment start & install failures. - -add_task(function* test_invalidUrl() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME + ".invalid", - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: 0, - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set for the experiment to start. - - let now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gTimerScheduleOffset = null; - - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - Assert.equal(gTimerScheduleOffset, null, "No new timer should have been scheduled."); - - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -// Test that we handle it properly when active experiment addons are being -// uninstalled. - -add_task(function* test_unexpectedUninstall() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Trigger update, clock set for the experiment to start. - - now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, true, "Experiment 1 should be active."); - - // Uninstall the addon through the addon manager instead of stopping it through - // the experiments API. - - yield AddonManagerTesting.uninstallAddonByID(EXPERIMENT1_ID); - yield experiments._mainTask; - - yield experiments.notify(); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.equal(list[0].active, false, "Experiment 1 should not be active anymore."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield testCleanup(experiments); -}); - -// If the Addon Manager knows of an experiment that we don't, it should get -// uninstalled. -add_task(function* testUnknownExperimentsUninstalled() { - let experiments = new Experiments.Experiments(gPolicy); - - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are present."); - - // Simulate us not listening. - experiments._unregisterWithAddonManager(); - yield AddonManagerTesting.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1); - experiments._registerWithAddonManager(); - - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "Experiment 1 installed via AddonManager"); - - // Simulate no known experiments. - gManifestObject = { - "version": 1, - experiments: [], - }; - - yield experiments.updateManifest(); - let fromManifest = yield experiments.getExperiments(); - Assert.equal(fromManifest.length, 0, "No experiments known in manifest."); - - // And the unknown add-on should be gone. - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Experiment 1 was uninstalled."); - - yield testCleanup(experiments); -}); - -// If someone else installs an experiment add-on, we detect and stop that. -add_task(function* testForeignExperimentInstall() { - let experiments = new Experiments.Experiments(gPolicy); - - gManifestObject = { - "version": 1, - experiments: [], - }; - - yield experiments.init(); - - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Precondition: No experiment add-ons present."); - - let failed = false; - try { - yield AddonManagerTesting.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1); - } catch (ex) { - failed = true; - } - Assert.ok(failed, "Add-on install should not have completed successfully"); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Add-on install should have been cancelled."); - - yield testCleanup(experiments); -}); - -// Experiment add-ons will be disabled after Addon Manager restarts. Ensure -// we enable them automatically. -add_task(function* testEnabledAfterRestart() { - let experiments = new Experiments.Experiments(gPolicy); - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: gPolicy.now().getTime() / 1000 - 60, - endTime: gPolicy.now().getTime() / 1000 + 60, - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed."); - - yield experiments.updateManifest(); - let fromManifest = yield experiments.getExperiments(); - Assert.equal(fromManifest.length, 1, "A single experiment is known."); - - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "A single experiment add-on is installed."); - Assert.ok(addons[0].isActive, "That experiment is active."); - - dump("Restarting Addon Manager\n"); - yield promiseRestartManager(); - experiments = new Experiments.Experiments(gPolicy); - - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "The experiment is still there after restart."); - Assert.ok(addons[0].userDisabled, "But it is disabled."); - Assert.equal(addons[0].isActive, false, "And not active."); - - yield experiments.updateManifest(); - Assert.ok(addons[0].isActive, "It activates when the manifest is evaluated."); - - yield testCleanup(experiments); -}); - -// If experiment add-ons were ever started, maxStartTime shouldn't be evaluated -// anymore. Ensure that if maxStartTime is passed but experiment has started -// already, maxStartTime does not cause deactivation. - -add_task(function* testMaxStartTimeEvaluation() { - - // Dates the following tests are based on. - - let startDate = new Date(2014, 5, 1, 12); - let now = futureDate(startDate, 10 * MS_IN_ONE_DAY); - let maxStartDate = futureDate(startDate, 100 * MS_IN_ONE_DAY); - let endDate = futureDate(startDate, 1000 * MS_IN_ONE_DAY); - - defineNow(gPolicy, now); - - // The manifest data we test with. - // We set a value for maxStartTime. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate), - endTime: dateToSeconds(endDate), - maxActiveSeconds: 1000 * SEC_IN_ONE_DAY, - maxStartTime: dateToSeconds(maxStartDate), - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed."); - - yield experiments.updateManifest(); - let fromManifest = yield experiments.getExperiments(); - Assert.equal(fromManifest.length, 1, "A single experiment is known."); - - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "A single experiment add-on is installed."); - Assert.ok(addons[0].isActive, "That experiment is active."); - - dump("Setting current time to maxStartTime + 100 days and reloading manifest\n"); - now = futureDate(maxStartDate, 100 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - yield experiments.updateManifest(); - - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "The experiment is still there."); - Assert.ok(addons[0].isActive, "It is still active."); - - yield testCleanup(experiments); -}); - -// Test coverage for an add-on uninstall disabling the experiment and that it stays -// disabled over restarts. -add_task(function* test_foreignUninstallAndRestart() { - let experiments = new Experiments.Experiments(gPolicy); - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: gPolicy.now().getTime() / 1000 - 60, - endTime: gPolicy.now().getTime() / 1000 + 60, - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed."); - - yield experiments.updateManifest(); - let experimentList = yield experiments.getExperiments(); - Assert.equal(experimentList.length, 1, "A single experiment is known."); - - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "A single experiment add-on is installed."); - Assert.ok(addons[0].isActive, "That experiment is active."); - - yield AddonManagerTesting.uninstallAddonByID(EXPERIMENT1_ID); - yield experiments._mainTask; - - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Experiment add-on should have been removed."); - - experimentList = yield experiments.getExperiments(); - Assert.equal(experimentList.length, 1, "A single experiment is known."); - Assert.equal(experimentList[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.ok(!experimentList[0].active, "Experiment 1 should not be active anymore."); - - // Fake restart behaviour. - yield promiseRestartManager(); - experiments = new Experiments.Experiments(gPolicy); - yield experiments.updateManifest(); - - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "No experiment add-ons installed."); - - experimentList = yield experiments.getExperiments(); - Assert.equal(experimentList.length, 1, "A single experiment is known."); - Assert.equal(experimentList[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry."); - Assert.ok(!experimentList[0].active, "Experiment 1 should not be active."); - - yield testCleanup(experiments); -}); diff --git a/browser/experiments/test/xpcshell/test_cache.js b/browser/experiments/test/xpcshell/test_cache.js deleted file mode 100644 index 4f2bce881..000000000 --- a/browser/experiments/test/xpcshell/test_cache.js +++ /dev/null @@ -1,399 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://testing-common/httpd.js"); -XPCOMUtils.defineLazyModuleGetter(this, "Experiments", - "resource:///modules/experiments/Experiments.jsm"); - -const MANIFEST_HANDLER = "manifests/handler"; - -const SEC_IN_ONE_DAY = 24 * 60 * 60; -const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; - -var gHttpServer = null; -var gHttpRoot = null; -var gDataRoot = null; -var gPolicy = null; -var gManifestObject = null; -var gManifestHandlerURI = null; - -function run_test() { - run_next_test(); -} - -add_task(function* test_setup() { - loadAddonManager(); - yield removeCacheFile(); - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let port = gHttpServer.identity.primaryPort; - gHttpRoot = "http://localhost:" + port + "/"; - gDataRoot = gHttpRoot + "data/"; - gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER; - gHttpServer.registerDirectory("/data/", do_get_cwd()); - gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify(gManifestObject)); - response.processAsync(); - response.finish(); - }); - do_register_cleanup(() => gHttpServer.stop(() => {})); - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); - Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); - Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI); - Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0); - - gPolicy = new Experiments.Policy(); - patchPolicy(gPolicy, { - updatechannel: () => "nightly", - oneshotTimer: (callback, timeout, thisObj, name) => {}, - }); -}); - -function checkExperimentListsEqual(list, list2) { - Assert.equal(list.length, list2.length, "Lists should have the same length.") - - for (let i=0; i<list.length; ++i) { - for (let k of Object.keys(list[i])) { - Assert.equal(list[i][k], list2[i][k], - "Field '" + k + "' should match for list entry " + i + "."); - } - } -} - -function checkExperimentSerializations(experimentEntryIterator) { - for (let experiment of experimentEntryIterator) { - let experiment2 = new Experiments.ExperimentEntry(gPolicy); - let jsonStr = JSON.stringify(experiment.toJSON()); - Assert.ok(experiment2.initFromCacheData(JSON.parse(jsonStr)), - "Should have initialized successfully from JSON serialization."); - Assert.equal(JSON.stringify(experiment), JSON.stringify(experiment2), - "Object stringifications should match."); - } -} - -function validateCache(cachedExperiments, experimentIds) { - let cachedExperimentIds = new Set(cachedExperiments); - Assert.equal(cachedExperimentIds.size, experimentIds.length, - "The number of cached experiments does not match with the provided list"); - for (let id of experimentIds) { - Assert.ok(cachedExperimentIds.has(id), "The cache must contain the experiment with id " + id); - } -} - -// Set up an experiments instance and check if it is properly restored from cache. - -add_task(function* test_cache() { - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - { - id: EXPERIMENT2_ID, - xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, - xpiHash: EXPERIMENT2_XPI_SHA1, - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - { - id: EXPERIMENT3_ID, - xpiURL: "https://inval.id/foo.xpi", - xpiHash: "sha1:0000000000000000000000000000000000000000", - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - // Setup dates for the experiments. - - let baseDate = new Date(2014, 5, 1, 12); - let startDates = []; - let endDates = []; - - for (let i=0; i<gManifestObject.experiments.length; ++i) { - let experiment = gManifestObject.experiments[i]; - startDates.push(futureDate(baseDate, (50 + (150 * i)) * MS_IN_ONE_DAY)); - endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY)); - experiment.startTime = dateToSeconds(startDates[i]); - experiment.endTime = dateToSeconds(endDates[i]); - } - - // Data to compare the result of Experiments.getExperiments() against. - - let experimentListData = [ - { - id: EXPERIMENT2_ID, - name: "Test experiment 2", - description: "And yet another experiment that experiments experimentally.", - }, - { - id: EXPERIMENT1_ID, - name: EXPERIMENT1_NAME, - description: "Yet another experiment that experiments experimentally.", - }, - ]; - - // Trigger update & re-init, clock set to before any activation. - - let now = baseDate; - defineNow(gPolicy, now); - - let experiments = new Experiments.Experiments(gPolicy); - yield experiments.updateManifest(); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - checkExperimentSerializations(experiments._experiments.values()); - - yield promiseRestartManager(); - experiments = new Experiments.Experiments(gPolicy); - - yield experiments._run(); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - checkExperimentSerializations(experiments._experiments.values()); - - // Re-init, clock set for experiment 1 to start. - - now = futureDate(startDates[0], 5 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - - yield promiseRestartManager(); - experiments = new Experiments.Experiments(gPolicy); - yield experiments._run(); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - - experimentListData[1].active = true; - experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY; - checkExperimentListsEqual(experimentListData.slice(1), list); - checkExperimentSerializations(experiments._experiments.values()); - - let branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID); - Assert.strictEqual(branch, null); - - yield experiments.setExperimentBranch(EXPERIMENT1_ID, "testbranch"); - branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID); - Assert.strictEqual(branch, "testbranch"); - - // Re-init, clock set for experiment 1 to stop. - - now = futureDate(now, 20 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - - yield promiseRestartManager(); - experiments = new Experiments.Experiments(gPolicy); - yield experiments._run(); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry."); - - experimentListData[1].active = false; - experimentListData[1].endDate = now.getTime(); - checkExperimentListsEqual(experimentListData.slice(1), list); - checkExperimentSerializations(experiments._experiments.values()); - - branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID); - Assert.strictEqual(branch, "testbranch"); - - // Re-init, clock set for experiment 2 to start. - - now = futureDate(startDates[1], 20 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - - yield promiseRestartManager(); - experiments = new Experiments.Experiments(gPolicy); - yield experiments._run(); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 2, "Experiment list should have 2 entries."); - - experimentListData[0].active = true; - experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY; - checkExperimentListsEqual(experimentListData, list); - checkExperimentSerializations(experiments._experiments.values()); - - // Re-init, clock set for experiment 2 to stop. - - now = futureDate(now, 20 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - - yield promiseRestartManager(); - experiments = new Experiments.Experiments(gPolicy); - yield experiments._run(); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 2, "Experiment list should have 2 entries."); - - experimentListData[0].active = false; - experimentListData[0].endDate = now.getTime(); - checkExperimentListsEqual(experimentListData, list); - checkExperimentSerializations(experiments._experiments.values()); - - // Cleanup. - - yield experiments._toggleExperimentsEnabled(false); - yield promiseRestartManager(); - yield removeCacheFile(); -}); - -add_task(function* test_expiration() { - // The manifest data we test with. - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - { - id: EXPERIMENT2_ID, - xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, - xpiHash: EXPERIMENT2_XPI_SHA1, - maxActiveSeconds: 50 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - // The 3rd experiment will never run, so it's ok to use experiment's 2 data. - { - id: EXPERIMENT3_ID, - xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, - xpiHash: EXPERIMENT2_XPI_SHA1, - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - } - ], - }; - - // Data to compare the result of Experiments.getExperiments() against. - let experimentListData = [ - { - id: EXPERIMENT2_ID, - name: "Test experiment 2", - description: "And yet another experiment that experiments experimentally.", - }, - { - id: EXPERIMENT1_ID, - name: EXPERIMENT1_NAME, - description: "Yet another experiment that experiments experimentally.", - }, - ]; - - // Setup dates for the experiments. - let baseDate = new Date(2014, 5, 1, 12); - let startDates = []; - let endDates = []; - - for (let i=0; i<gManifestObject.experiments.length; ++i) { - let experiment = gManifestObject.experiments[i]; - // Spread out experiments in time so that one experiment can end and expire while - // the next is still running. - startDates.push(futureDate(baseDate, (50 + (200 * i)) * MS_IN_ONE_DAY)); - endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY)); - experiment.startTime = dateToSeconds(startDates[i]); - experiment.endTime = dateToSeconds(endDates[i]); - } - - let now = null; - let experiments = null; - - let setDateAndRestartExperiments = new Task.async(function* (newDate) { - now = newDate; - defineNow(gPolicy, now); - - yield promiseRestartManager(); - experiments = new Experiments.Experiments(gPolicy); - yield experiments._run(); - }); - - // Trigger update & re-init, clock set to before any activation. - now = baseDate; - defineNow(gPolicy, now); - - experiments = new Experiments.Experiments(gPolicy); - yield experiments.updateManifest(); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - // Re-init, clock set for experiment 1 to start... - yield setDateAndRestartExperiments(startDates[0]); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "The first experiment should have started."); - - // ... init again, and set the clock so that the first experiment ends. - yield setDateAndRestartExperiments(endDates[0]); - - // The experiment just ended, it should still be in the cache, but marked - // as finished. - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry."); - - experimentListData[1].active = false; - experimentListData[1].endDate = now.getTime(); - checkExperimentListsEqual(experimentListData.slice(1), list); - validateCache([...experiments._experiments.keys()], [EXPERIMENT1_ID, EXPERIMENT2_ID, EXPERIMENT3_ID]); - - // Start the second experiment. - yield setDateAndRestartExperiments(startDates[1]); - - // The experiments cache should contain the finished experiment and the - // one that's still running. - list = yield experiments.getExperiments(); - Assert.equal(list.length, 2, "Experiment list should have 2 entries."); - - experimentListData[0].active = true; - experimentListData[0].endDate = now.getTime() + 50 * MS_IN_ONE_DAY; - checkExperimentListsEqual(experimentListData, list); - - // Move the clock in the future, just 31 days after the start date of the second experiment, - // so that the cache for the first experiment expires and the second experiment is still running. - yield setDateAndRestartExperiments(futureDate(startDates[1], 31 * MS_IN_ONE_DAY)); - validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]); - - // Make sure that the expired experiment is not reported anymore. - let history = yield experiments.getExperiments(); - Assert.equal(history.length, 1, "Experiments older than 180 days must be removed from the cache."); - - // Test that we don't write expired experiments in the cache. - yield setDateAndRestartExperiments(now); - validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]); - - // The first experiment should be expired and not in the cache, it ended more than - // 180 days ago. We should see the one still running in the cache. - history = yield experiments.getExperiments(); - Assert.equal(history.length, 1, "Expired experiments must not be saved to cache."); - checkExperimentListsEqual(experimentListData.slice(0, 1), history); - - // Test that experiments that are cached locally but never ran are removed from cache - // when they are removed from the manifest (this is cached data, not really history). - gManifestObject["experiments"] = gManifestObject["experiments"].slice(1, 1); - yield experiments.updateManifest(); - validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID]); - - // Cleanup. - yield experiments._toggleExperimentsEnabled(false); - yield promiseRestartManager(); - yield removeCacheFile(); -}); diff --git a/browser/experiments/test/xpcshell/test_cacherace.js b/browser/experiments/test/xpcshell/test_cacherace.js deleted file mode 100644 index ff77cfdc4..000000000 --- a/browser/experiments/test/xpcshell/test_cacherace.js +++ /dev/null @@ -1,102 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://testing-common/httpd.js"); -Cu.import("resource://gre/modules/Timer.jsm"); - -const MANIFEST_HANDLER = "manifests/handler"; - -const SEC_IN_ONE_DAY = 24 * 60 * 60; -const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; - -var gHttpServer = null; -var gHttpRoot = null; -var gDataRoot = null; -var gPolicy = null; -var gManifestObject = null; -var gManifestHandlerURI = null; - -function run_test() { - run_next_test(); -} - -add_task(function* test_setup() { - loadAddonManager(); - yield removeCacheFile(); - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let port = gHttpServer.identity.primaryPort; - gHttpRoot = "http://localhost:" + port + "/"; - gDataRoot = gHttpRoot + "data/"; - gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER; - gHttpServer.registerDirectory("/data/", do_get_cwd()); - gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify(gManifestObject)); - response.processAsync(); - response.finish(); - }); - do_register_cleanup(() => gHttpServer.stop(() => {})); - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); - Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); - Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI); - Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0); - - let ExperimentsScope = Cu.import("resource:///modules/experiments/Experiments.jsm"); - let Experiments = ExperimentsScope.Experiments; - - gPolicy = new Experiments.Policy(); - patchPolicy(gPolicy, { - updatechannel: () => "nightly", - delayCacheWrite: (promise) => { - return new Promise((resolve, reject) => { - promise.then( - (result) => { setTimeout(() => resolve(result), 500); }, - (err) => { reject(err); } - ); - }); - }, - }); - - let now = new Date(2014, 5, 1, 12); - defineNow(gPolicy, now); - - let experimentName = "experiment-racybranch.xpi"; - let experimentPath = getExperimentPath(experimentName); - let experimentHash = "sha1:" + sha1File(experimentPath); - - gManifestObject = { - version: 1, - experiments: [ - { - id: "test-experiment-racybranch@tests.mozilla.org", - xpiURL: gDataRoot + "experiment-racybranch.xpi", - xpiHash: experimentHash, - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - startTime: dateToSeconds(futureDate(now, -MS_IN_ONE_DAY)), - endTime: dateToSeconds(futureDate(now, MS_IN_ONE_DAY)), - }, - ], - }; - - do_print("gManifestObject: " + JSON.stringify(gManifestObject)); - - // In order for the addon manager to work properly, we hack - // Experiments.instance which is used by the XPIProvider - let experiments = new Experiments.Experiments(gPolicy); - Assert.strictEqual(ExperimentsScope.gExperiments, null); - ExperimentsScope.gExperiments = experiments; - - yield experiments.updateManifest(); - let active = experiments._getActiveExperiment(); - Assert.ok(active); - Assert.equal(active.branch, "racy-set"); - Assert.ok(!experiments._dirty); -}); diff --git a/browser/experiments/test/xpcshell/test_conditions.js b/browser/experiments/test/xpcshell/test_conditions.js deleted file mode 100644 index 23c147fdb..000000000 --- a/browser/experiments/test/xpcshell/test_conditions.js +++ /dev/null @@ -1,325 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - - -Cu.import("resource:///modules/experiments/Experiments.jsm"); -Cu.import("resource://gre/modules/TelemetryController.jsm", this); - -const SEC_IN_ONE_DAY = 24 * 60 * 60; - -var gPolicy = null; - -function ManifestEntry(data) { - this.id = EXPERIMENT1_ID; - this.xpiURL = "http://localhost:1/dummy.xpi"; - this.xpiHash = EXPERIMENT1_XPI_SHA1; - this.startTime = new Date(2010, 0, 1, 12).getTime() / 1000; - this.endTime = new Date(9001, 0, 1, 12).getTime() / 1000; - this.maxActiveSeconds = SEC_IN_ONE_DAY; - this.appName = ["XPCShell"]; - this.channel = ["nightly"]; - - data = data || {}; - for (let k of Object.keys(data)) { - this[k] = data[k]; - } - - if (!this.endTime) { - this.endTime = this.startTime + 5 * SEC_IN_ONE_DAY; - } -} - -function applicableFromManifestData(data, policy) { - let manifestData = new ManifestEntry(data); - let entry = new Experiments.ExperimentEntry(policy); - entry.initFromManifestData(manifestData); - return entry.isApplicable(); -} - -function run_test() { - run_next_test(); -} - -add_task(function* test_setup() { - createAppInfo(); - do_get_profile(); - startAddonManagerOnly(); - yield TelemetryController.testSetup(); - gPolicy = new Experiments.Policy(); - - patchPolicy(gPolicy, { - updatechannel: () => "nightly", - locale: () => "en-US", - random: () => 0.5, - }); - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); - Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); -}); - -function arraysEqual(a, b) { - if (a.length !== b.length) { - return false; - } - - for (let i=0; i<a.length; ++i) { - if (a[i] !== b[i]) { - return false; - } - } - - return true; -} - -// This function exists solely to be .toSource()d -const sanityFilter = function filter(c) { - if (c.telemetryEnvironment === undefined) { - throw Error("No .telemetryEnvironment"); - } - if (c.telemetryEnvironment.build == undefined) { - throw Error("No .telemetryEnvironment.build"); - } - return true; -} - -// Utility function to generate build ID for previous/next date. -function addDate(buildId, diff) { - let m = /^([0-9]{4})([0-9]{2})([0-9]{2})(.*)$/.exec(buildId); - if (!m) { - throw Error("Unsupported build ID: " + buildId); - } - let year = Number.parseInt(m[1], 10); - let month = Number.parseInt(m[2], 10); - let date = Number.parseInt(m[3], 10); - let remainingParts = m[4]; - - let d = new Date(); - d.setUTCFullYear(year, month - 1, date); - d.setTime(d.getTime() + diff * 24 * 60 * 60 * 1000); - - let yearStr = String(d.getUTCFullYear()); - let monthStr = ("0" + String(d.getUTCMonth() + 1)).slice(-2); - let dateStr = ("0" + String(d.getUTCDate())).slice(-2); - return yearStr + monthStr + dateStr + remainingParts; -} -function prevDate(buildId) { - return addDate(buildId, -1); -} -function nextDate(buildId) { - return addDate(buildId, 1); -} - -add_task(function* test_simpleFields() { - let testData = [ - // "expected applicable?", failure reason or null, manifest data - - // misc. environment - - [false, ["appName"], {appName: []}], - [false, ["appName"], {appName: ["foo", gAppInfo.name + "-invalid"]}], - [true, null, {appName: ["not-an-app-name", gAppInfo.name]}], - - [false, ["os"], {os: []}], - [false, ["os"], {os: ["42", "abcdef"]}], - [true, null, {os: [gAppInfo.OS, "plan9"]}], - - [false, ["channel"], {channel: []}], - [false, ["channel"], {channel: ["foo", gPolicy.updatechannel() + "-invalid"]}], - [true, null, {channel: ["not-a-channel", gPolicy.updatechannel()]}], - - [false, ["locale"], {locale: []}], - [false, ["locale"], {locale: ["foo", gPolicy.locale + "-invalid"]}], - [true, null, {locale: ["not-a-locale", gPolicy.locale()]}], - - // version - - [false, ["version"], {version: []}], - [false, ["version"], {version: ["-1", gAppInfo.version + "-invalid", "asdf", "0,4", "99.99", "0.1.1.1"]}], - [true, null, {version: ["99999999.999", "-1", gAppInfo.version]}], - - [false, ["minVersion"], {minVersion: "1.0.1"}], - [true, null, {minVersion: "1.0b1"}], - [true, null, {minVersion: "1.0"}], - [true, null, {minVersion: "0.9"}], - - [false, ["maxVersion"], {maxVersion: "0.1"}], - [false, ["maxVersion"], {maxVersion: "0.9.9"}], - [false, ["maxVersion"], {maxVersion: "1.0b1"}], - [true, ["maxVersion"], {maxVersion: "1.0"}], - [true, ["maxVersion"], {maxVersion: "1.7pre"}], - - // build id - - [false, ["buildIDs"], {buildIDs: []}], - [false, ["buildIDs"], {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID + "-invalid"]}], - [true, null, {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID]}], - - [true, null, {minBuildID: prevDate(gAppInfo.platformBuildID)}], - [true, null, {minBuildID: gAppInfo.platformBuildID}], - [false, ["minBuildID"], {minBuildID: nextDate(gAppInfo.platformBuildID)}], - - [false, ["maxBuildID"], {maxBuildID: prevDate(gAppInfo.platformBuildID)}], - [true, null, {maxBuildID: gAppInfo.platformBuildID}], - [true, null, {maxBuildID: nextDate(gAppInfo.platformBuildID)}], - - // sample - - [false, ["sample"], {sample: -1 }], - [false, ["sample"], {sample: 0.0}], - [false, ["sample"], {sample: 0.1}], - [true, null, {sample: 0.5}], - [true, null, {sample: 0.6}], - [true, null, {sample: 1.0}], - [true, null, {sample: 0.5}], - - // experiment control - - [false, ["disabled"], {disabled: true}], - [true, null, {disabled: false}], - - [false, ["frozen"], {frozen: true}], - [true, null, {frozen: false}], - - [false, null, {frozen: true, disabled: true}], - [false, null, {frozen: true, disabled: false}], - [false, null, {frozen: false, disabled: true}], - [true, null, {frozen: false, disabled: false}], - - // jsfilter - - [true, null, {jsfilter: "function filter(c) { return true; }"}], - [false, ["jsfilter-false"], {jsfilter: "function filter(c) { return false; }"}], - [true, null, {jsfilter: "function filter(c) { return 123; }"}], // truthy - [false, ["jsfilter-false"], {jsfilter: "function filter(c) { return ''; }"}], // falsy - [false, ["jsfilter-false"], {jsfilter: "function filter(c) { var a = []; }"}], // undefined - [false, ["jsfilter-threw", "some error"], {jsfilter: "function filter(c) { throw new Error('some error'); }"}], - [false, ["jsfilter-evalfailed"], {jsfilter: "123, this won't work"}], - [true, null, {jsfilter: "var filter = " + sanityFilter.toSource()}], - ]; - - for (let i=0; i<testData.length; ++i) { - let entry = testData[i]; - let applicable; - let reason = null; - - yield applicableFromManifestData(entry[2], gPolicy).then( - value => applicable = value, - value => { - applicable = false; - reason = value; - } - ); - - Assert.equal(applicable, entry[0], - "Experiment entry applicability should match for test " - + i + ": " + JSON.stringify(entry[2])); - - let expectedReason = entry[1]; - if (!applicable && expectedReason) { - Assert.ok(arraysEqual(reason, expectedReason), - "Experiment rejection reasons should match for test " + i + ". " - + "Got " + JSON.stringify(reason) + ", expected " - + JSON.stringify(expectedReason)); - } - } -}); - -add_task(function* test_times() { - let now = new Date(2014, 5, 6, 12); - let nowSec = now.getTime() / 1000; - let testData = [ - // "expected applicable?", rejection reason or null, fake now date, manifest data - - // start time - - [true, null, now, - {startTime: nowSec - 5 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - [true, null, now, - {startTime: nowSec, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - [false, "startTime", now, - {startTime: nowSec + 5 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - - // end time - - [false, "endTime", now, - {startTime: nowSec - 5 * SEC_IN_ONE_DAY, - endTime: nowSec - 10 * SEC_IN_ONE_DAY}], - [false, "endTime", now, - {startTime: nowSec - 5 * SEC_IN_ONE_DAY, - endTime: nowSec - 5 * SEC_IN_ONE_DAY}], - - // max start time - - [false, "maxStartTime", now, - {maxStartTime: nowSec - 15 * SEC_IN_ONE_DAY, - startTime: nowSec - 10 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - [false, "maxStartTime", now, - {maxStartTime: nowSec - 1 * SEC_IN_ONE_DAY, - startTime: nowSec - 10 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - [false, "maxStartTime", now, - {maxStartTime: nowSec - 10 * SEC_IN_ONE_DAY, - startTime: nowSec - 10 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - [true, null, now, - {maxStartTime: nowSec, - startTime: nowSec - 10 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - [true, null, now, - {maxStartTime: nowSec + 1 * SEC_IN_ONE_DAY, - startTime: nowSec - 10 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - - // max active seconds - - [true, null, now, - {maxActiveSeconds: 5 * SEC_IN_ONE_DAY, - startTime: nowSec - 10 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - [true, null, now, - {maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - startTime: nowSec - 10 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - [true, null, now, - {maxActiveSeconds: 15 * SEC_IN_ONE_DAY, - startTime: nowSec - 10 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - [true, null, now, - {maxActiveSeconds: 20 * SEC_IN_ONE_DAY, - startTime: nowSec - 10 * SEC_IN_ONE_DAY, - endTime: nowSec + 10 * SEC_IN_ONE_DAY}], - ]; - - for (let i=0; i<testData.length; ++i) { - let entry = testData[i]; - let applicable; - let reason = null; - defineNow(gPolicy, entry[2]); - - yield applicableFromManifestData(entry[3], gPolicy).then( - value => applicable = value, - value => { - applicable = false; - reason = value; - } - ); - - Assert.equal(applicable, entry[0], - "Experiment entry applicability should match for test " - + i + ": " + JSON.stringify([entry[2], entry[3]])); - if (!applicable && entry[1]) { - Assert.equal(reason, entry[1], "Experiment rejection reason should match for test " + i); - } - } -}); - -add_task(function* test_shutdown() { - yield TelemetryController.testShutdown(); -}); diff --git a/browser/experiments/test/xpcshell/test_disableExperiments.js b/browser/experiments/test/xpcshell/test_disableExperiments.js deleted file mode 100644 index 8441b922d..000000000 --- a/browser/experiments/test/xpcshell/test_disableExperiments.js +++ /dev/null @@ -1,180 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://testing-common/httpd.js"); -Cu.import("resource://testing-common/AddonManagerTesting.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Experiments", - "resource:///modules/experiments/Experiments.jsm"); - -const MANIFEST_HANDLER = "manifests/handler"; - -const SEC_IN_ONE_DAY = 24 * 60 * 60; -const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; - -var gHttpServer = null; -var gHttpRoot = null; -var gDataRoot = null; -var gPolicy = null; -var gManifestObject = null; -var gManifestHandlerURI = null; - -function run_test() { - run_next_test(); -} - -add_task(function* test_setup() { - loadAddonManager(); - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let port = gHttpServer.identity.primaryPort; - gHttpRoot = "http://localhost:" + port + "/"; - gDataRoot = gHttpRoot + "data/"; - gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER; - gHttpServer.registerDirectory("/data/", do_get_cwd()); - gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify(gManifestObject)); - response.processAsync(); - response.finish(); - }); - do_register_cleanup(() => gHttpServer.stop(() => {})); - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); - Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); - Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI); - Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0); - - gPolicy = new Experiments.Policy(); - patchPolicy(gPolicy, { - updatechannel: () => "nightly", - oneshotTimer: (callback, timeout, thisObj, name) => {}, - }); -}); - -// Test disabling the feature stops current and future experiments. - -add_task(function* test_disableExperiments() { - const OBSERVER_TOPIC = "experiments-changed"; - let observerFireCount = 0; - let expectedObserverFireCount = 0; - let observer = () => ++observerFireCount; - Services.obs.addObserver(observer, OBSERVER_TOPIC, false); - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY); - let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY); - let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT2_ID, - xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, - xpiHash: EXPERIMENT2_XPI_SHA1, - startTime: dateToSeconds(startDate2), - endTime: dateToSeconds(endDate2), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate1), - endTime: dateToSeconds(endDate1), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - // Use updateManifest() to provide for coverage of that path. - - let now = baseDate; - defineNow(gPolicy, now); - - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed."); - - // Trigger update, clock set for experiment 1 to start. - - now = futureDate(startDate1, 5 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - - yield experiments.updateManifest(); - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - Assert.equal(list[0].active, true, "Experiment should be active."); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 1, "An experiment add-on was installed."); - - // Disable the experiments feature. Check that we stop the running experiment. - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, false); - yield experiments._mainTask; - - Assert.equal(observerFireCount, ++expectedObserverFireCount, - "Experiments observer should have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry."); - Assert.equal(list[0].active, false, "Experiment entry should not be active."); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled."); - - // Trigger update, clock set for experiment 2 to start. Verify we don't start it. - - now = startDate2; - defineNow(gPolicy, now); - - try { - yield experiments.updateManifest(); - } catch (e) { - // This exception is expected, we rethrow everything else - if (e.message != "experiments are disabled") { - throw e; - } - } - - experiments.notify(); - yield experiments._mainTask; - - Assert.equal(observerFireCount, expectedObserverFireCount, - "Experiments observer should not have been called."); - - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should still have 1 entry."); - Assert.equal(list[0].active, false, "Experiment entry should not be active."); - addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "There should still be no experiment add-on installed."); - - // Cleanup. - - Services.obs.removeObserver(observer, OBSERVER_TOPIC); - yield promiseRestartManager(); - yield removeCacheFile(); -}); diff --git a/browser/experiments/test/xpcshell/test_fetch.js b/browser/experiments/test/xpcshell/test_fetch.js deleted file mode 100644 index e8d76fa35..000000000 --- a/browser/experiments/test/xpcshell/test_fetch.js +++ /dev/null @@ -1,68 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://testing-common/httpd.js"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource:///modules/experiments/Experiments.jsm"); - -var gHttpServer = null; -var gHttpRoot = null; -var gPolicy = new Experiments.Policy(); - -function run_test() { - loadAddonManager(); - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let port = gHttpServer.identity.primaryPort; - gHttpRoot = "http://localhost:" + port + "/"; - gHttpServer.registerDirectory("/", do_get_cwd()); - do_register_cleanup(() => gHttpServer.stop(() => {})); - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); - Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); - - patchPolicy(gPolicy, { - updatechannel: () => "nightly", - }); - - run_next_test(); -} - -add_task(function* test_fetchAndCache() { - Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "experiments_1.manifest"); - let ex = new Experiments.Experiments(gPolicy); - - Assert.equal(ex._experiments, null, "There should be no cached experiments yet."); - yield ex.updateManifest(); - Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now."); - - yield promiseRestartManager(); -}); - -add_task(function* test_checkCache() { - let ex = new Experiments.Experiments(gPolicy); - yield ex.notify(); - Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now."); - - yield promiseRestartManager(); -}); - -add_task(function* test_fetchInvalid() { - yield removeCacheFile(); - - Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "experiments_1.manifest"); - let ex = new Experiments.Experiments(gPolicy); - yield ex.updateManifest(); - Assert.notEqual(ex._experiments.size, 0, "There should be experiments"); - - Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "invalid.manifest"); - yield ex.updateManifest() - Assert.notEqual(ex._experiments.size, 0, "There should still be experiments: fetch failure shouldn't remove them."); - - yield promiseRestartManager(); -}); diff --git a/browser/experiments/test/xpcshell/test_nethang_bug1012924.js b/browser/experiments/test/xpcshell/test_nethang_bug1012924.js deleted file mode 100644 index 7ef604901..000000000 --- a/browser/experiments/test/xpcshell/test_nethang_bug1012924.js +++ /dev/null @@ -1,47 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://testing-common/httpd.js"); -Cu.import("resource:///modules/experiments/Experiments.jsm"); - -const MANIFEST_HANDLER = "manifests/handler"; - -function run_test() { - run_next_test(); -} - -add_task(function* test_setup() { - loadAddonManager(); - do_get_profile(); - - let httpServer = new HttpServer(); - httpServer.start(-1); - let port = httpServer.identity.primaryPort; - let httpRoot = "http://localhost:" + port + "/"; - let handlerURI = httpRoot + MANIFEST_HANDLER; - httpServer.registerPathHandler("/" + MANIFEST_HANDLER, - (request, response) => { - response.processAsync(); - response.setStatus(null, 200, "OK"); - response.write("["); // never finish! - }); - - do_register_cleanup(() => httpServer.stop(() => {})); - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); - Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); - Services.prefs.setCharPref(PREF_MANIFEST_URI, handlerURI); - Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0); - - let experiments = Experiments.instance(); - experiments.updateManifest().then( - () => { - Assert.ok(true, "updateManifest finished successfully"); - }, - (e) => { - do_throw("updateManifest should not have failed: got error " + e); - }); - yield experiments.uninit(); -}); diff --git a/browser/experiments/test/xpcshell/test_previous_provider.js b/browser/experiments/test/xpcshell/test_previous_provider.js deleted file mode 100644 index f7186e159..000000000 --- a/browser/experiments/test/xpcshell/test_previous_provider.js +++ /dev/null @@ -1,179 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource:///modules/experiments/Experiments.jsm"); -Cu.import("resource://testing-common/httpd.js"); - -var gDataRoot; -var gHttpServer; -var gManifestObject; - -function run_test() { - run_next_test(); -} - -add_task(function test_setup() { - loadAddonManager(); - do_get_profile(); - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let httpRoot = "http://localhost:" + gHttpServer.identity.primaryPort + "/"; - gDataRoot = httpRoot + "data/"; - gHttpServer.registerDirectory("/data/", do_get_cwd()); - gHttpServer.registerPathHandler("/manifests/handler", (req, res) => { - res.setStatusLine(null, 200, "OK"); - res.write(JSON.stringify(gManifestObject)); - res.processAsync(); - res.finish(); - }); - do_register_cleanup(() => gHttpServer.stop(() => {})); - - Services.prefs.setBoolPref("experiments.enabled", true); - Services.prefs.setCharPref("experiments.manifest.uri", - httpRoot + "manifests/handler"); - Services.prefs.setBoolPref("experiments.logging.dump", true); - Services.prefs.setCharPref("experiments.logging.level", "Trace"); -}); - -add_task(function* test_provider_basic() { - let e = Experiments.instance(); - - let provider = new Experiments.PreviousExperimentProvider(e); - e._setPreviousExperimentsProvider(provider); - - let deferred = Promise.defer(); - provider.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - let experimentAddons = yield deferred.promise; - Assert.ok(Array.isArray(experimentAddons), "getAddonsByTypes returns an Array."); - Assert.equal(experimentAddons.length, 0, "No previous add-ons returned."); - - gManifestObject = { - version: 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: Date.now() / 1000 - 60, - endTime: Date.now() / 1000 + 60, - maxActiveSeconds: 60, - appName: ["XPCShell"], - channel: [e._policy.updatechannel()], - }, - ], - }; - - yield e.updateManifest(); - - deferred = Promise.defer(); - provider.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - experimentAddons = yield deferred.promise; - Assert.equal(experimentAddons.length, 0, "Still no previous experiment."); - - let experiments = yield e.getExperiments(); - Assert.equal(experiments.length, 1, "1 experiment present."); - Assert.ok(experiments[0].active, "It is active."); - - // Deactivate it. - defineNow(e._policy, new Date(gManifestObject.experiments[0].endTime * 1000 + 1000)); - yield e.updateManifest(); - - experiments = yield e.getExperiments(); - Assert.equal(experiments.length, 1, "1 experiment present."); - Assert.equal(experiments[0].active, false, "It isn't active."); - - deferred = Promise.defer(); - provider.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - experimentAddons = yield deferred.promise; - Assert.equal(experimentAddons.length, 1, "1 previous add-on known."); - Assert.equal(experimentAddons[0].id, EXPERIMENT1_ID, "ID matches expected."); - - deferred = Promise.defer(); - provider.getAddonByID(EXPERIMENT1_ID, (addon) => { - deferred.resolve(addon); - }); - let addon = yield deferred.promise; - Assert.ok(addon, "We got an add-on from its ID."); - Assert.equal(addon.id, EXPERIMENT1_ID, "ID matches expected."); - Assert.ok(addon.appDisabled, "Add-on is a previous experiment."); - Assert.ok(addon.userDisabled, "Add-on is disabled."); - Assert.equal(addon.type, "experiment", "Add-on is an experiment."); - Assert.equal(addon.isActive, false, "Add-on is not active."); - Assert.equal(addon.permissions, 0, "Add-on has no permissions."); - - deferred = Promise.defer(); - AddonManager.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - experimentAddons = yield deferred.promise; - Assert.equal(experimentAddons.length, 1, "Got 1 experiment from add-on manager."); - Assert.equal(experimentAddons[0].id, EXPERIMENT1_ID, "ID matches expected."); - Assert.ok(experimentAddons[0].appDisabled, "It is a previous experiment add-on."); -}); - -add_task(function* test_active_and_previous() { - // Building on the previous test, activate experiment 2. - let e = Experiments.instance(); - let provider = new Experiments.PreviousExperimentProvider(e); - e._setPreviousExperimentsProvider(provider); - - gManifestObject = { - version: 1, - experiments: [ - { - id: EXPERIMENT2_ID, - xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, - xpiHash: EXPERIMENT2_XPI_SHA1, - startTime: Date.now() / 1000 - 60, - endTime: Date.now() / 1000 + 60, - maxActiveSeconds: 60, - appName: ["XPCShell"], - channel: [e._policy.updatechannel()], - }, - ], - }; - - defineNow(e._policy, new Date()); - yield e.updateManifest(); - - let experiments = yield e.getExperiments(); - Assert.equal(experiments.length, 2, "2 experiments known."); - - let deferred = Promise.defer(); - provider.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - let experimentAddons = yield deferred.promise; - Assert.equal(experimentAddons.length, 1, "1 previous experiment."); - - deferred = Promise.defer(); - AddonManager.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - experimentAddons = yield deferred.promise; - Assert.equal(experimentAddons.length, 2, "2 experiment add-ons known."); - - for (let addon of experimentAddons) { - if (addon.id == EXPERIMENT1_ID) { - Assert.equal(addon.isActive, false, "Add-on is not active."); - Assert.ok(addon.appDisabled, "Should be a previous experiment."); - } - else if (addon.id == EXPERIMENT2_ID) { - Assert.ok(addon.isActive, "Add-on is active."); - Assert.ok(!addon.appDisabled, "Should not be a previous experiment."); - } - else { - throw new Error("Unexpected add-on ID: " + addon.id); - } - } -}); diff --git a/browser/experiments/test/xpcshell/test_telemetry.js b/browser/experiments/test/xpcshell/test_telemetry.js deleted file mode 100644 index 02bd15d2b..000000000 --- a/browser/experiments/test/xpcshell/test_telemetry.js +++ /dev/null @@ -1,294 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://testing-common/httpd.js"); -Cu.import("resource://gre/modules/TelemetryLog.jsm"); -var bsp = Cu.import("resource:///modules/experiments/Experiments.jsm"); - - -const MANIFEST_HANDLER = "manifests/handler"; - -const SEC_IN_ONE_DAY = 24 * 60 * 60; -const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; - - -var gHttpServer = null; -var gHttpRoot = null; -var gDataRoot = null; -var gPolicy = null; -var gManifestObject = null; -var gManifestHandlerURI = null; - -const TLOG = bsp.TELEMETRY_LOG; - -function checkEvent(event, id, data) -{ - do_print("Checking message " + id); - Assert.equal(event[0], id, "id should match"); - Assert.ok(event[1] > 0, "timestamp should be greater than 0"); - - if (data === undefined) { - Assert.equal(event.length, 2, "event array should have 2 entries"); - } else { - Assert.equal(event.length, data.length + 2, "event entry count should match expected count"); - for (var i = 0; i < data.length; ++i) { - Assert.equal(typeof(event[i + 2]), "string", "event entry should be a string"); - Assert.equal(event[i + 2], data[i], "event entry should match expected entry"); - } - } -} - -function run_test() { - run_next_test(); -} - -add_task(function* test_setup() { - loadAddonManager(); - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let port = gHttpServer.identity.primaryPort; - gHttpRoot = "http://localhost:" + port + "/"; - gDataRoot = gHttpRoot + "data/"; - gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER; - gHttpServer.registerDirectory("/data/", do_get_cwd()); - gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify(gManifestObject)); - response.processAsync(); - response.finish(); - }); - do_register_cleanup(() => gHttpServer.stop(() => {})); - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); - Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); - Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI); - Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0); - - gPolicy = new Experiments.Policy(); - let dummyTimer = { cancel: () => {}, clear: () => {} }; - patchPolicy(gPolicy, { - updatechannel: () => "nightly", - oneshotTimer: (callback, timeout, thisObj, name) => dummyTimer, - }); - - yield removeCacheFile(); -}); - -// Test basic starting and stopping of experiments. - -add_task(function* test_telemetryBasics() { - // Check TelemetryLog instead of TelemetrySession.getPayload().log because - // TelemetrySession gets Experiments.instance() and side-effects log entries. - - let expectedLogLength = 0; - - // Dates the following tests are based on. - - let baseDate = new Date(2014, 5, 1, 12); - let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY); - let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY); - let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY); - let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY); - - // The manifest data we test with. - - gManifestObject = { - "version": 1, - experiments: [ - { - id: EXPERIMENT1_ID, - xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, - xpiHash: EXPERIMENT1_XPI_SHA1, - startTime: dateToSeconds(startDate1), - endTime: dateToSeconds(endDate1), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - { - id: EXPERIMENT2_ID, - xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, - xpiHash: EXPERIMENT2_XPI_SHA1, - startTime: dateToSeconds(startDate2), - endTime: dateToSeconds(endDate2), - maxActiveSeconds: 10 * SEC_IN_ONE_DAY, - appName: ["XPCShell"], - channel: ["nightly"], - }, - ], - }; - - let experiments = new Experiments.Experiments(gPolicy); - - // Trigger update, clock set to before any activation. - // Use updateManifest() to provide for coverage of that path. - - let now = baseDate; - defineNow(gPolicy, now); - - yield experiments.updateManifest(); - let list = yield experiments.getExperiments(); - Assert.equal(list.length, 0, "Experiment list should be empty."); - - expectedLogLength += 2; - let log = TelemetryLog.entries(); - do_print("Telemetry log: " + JSON.stringify(log)); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); - checkEvent(log[log.length-2], TLOG.ACTIVATION_KEY, - [TLOG.ACTIVATION.REJECTED, EXPERIMENT1_ID, "startTime"]); - checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, - [TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]); - - // Trigger update, clock set for experiment 1 to start. - - now = futureDate(startDate1, 5 * MS_IN_ONE_DAY); - defineNow(gPolicy, now); - - yield experiments.updateManifest(); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); - - expectedLogLength += 1; - log = TelemetryLog.entries(); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries. Got " + log.toSource()); - checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, - [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT1_ID]); - - // Trigger update, clock set for experiment 1 to stop. - - now = futureDate(endDate1, 1000); - defineNow(gPolicy, now); - - yield experiments.updateManifest(); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entry."); - - expectedLogLength += 2; - log = TelemetryLog.entries(); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); - checkEvent(log[log.length-2], TLOG.TERMINATION_KEY, - [TLOG.TERMINATION.EXPIRED, EXPERIMENT1_ID]); - checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, - [TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]); - - // Trigger update, clock set for experiment 2 to start with invalid hash. - - now = startDate2; - defineNow(gPolicy, now); - gManifestObject.experiments[1].xpiHash = "sha1:0000000000000000000000000000000000000000"; - - yield experiments.updateManifest(); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 1, "Experiment list should have 1 entries."); - - expectedLogLength += 1; - log = TelemetryLog.entries(); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); - checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, - [TLOG.ACTIVATION.INSTALL_FAILURE, EXPERIMENT2_ID]); - - // Trigger update, clock set for experiment 2 to properly start now. - - now = futureDate(now, MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[1].xpiHash = EXPERIMENT2_XPI_SHA1; - - yield experiments.updateManifest(); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 2, "Experiment list should have 2 entries."); - - expectedLogLength += 1; - log = TelemetryLog.entries(); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); - checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, - [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT2_ID]); - - // Fake user uninstall of experiment via add-on manager. - - now = futureDate(now, MS_IN_ONE_DAY); - defineNow(gPolicy, now); - - yield experiments.disableExperiment(TLOG.TERMINATION.ADDON_UNINSTALLED); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 2, "Experiment list should have 2 entries."); - - expectedLogLength += 1; - log = TelemetryLog.entries(); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); - checkEvent(log[log.length-1], TLOG.TERMINATION_KEY, - [TLOG.TERMINATION.ADDON_UNINSTALLED, EXPERIMENT2_ID]); - - // Trigger update with experiment 1a ready to start. - - now = futureDate(now, MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].id = EXPERIMENT3_ID; - gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY)); - - yield experiments.updateManifest(); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 3, "Experiment list should have 3 entries."); - - expectedLogLength += 1; - log = TelemetryLog.entries(); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); - checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, - [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT3_ID]); - - // Trigger disable of an experiment via the API. - - now = futureDate(now, MS_IN_ONE_DAY); - defineNow(gPolicy, now); - - yield experiments.disableExperiment(TLOG.TERMINATION.FROM_API); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 3, "Experiment list should have 3 entries."); - - expectedLogLength += 1; - log = TelemetryLog.entries(); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); - checkEvent(log[log.length-1], TLOG.TERMINATION_KEY, - [TLOG.TERMINATION.FROM_API, EXPERIMENT3_ID]); - - // Trigger update with experiment 1a ready to start. - - now = futureDate(now, MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].id = EXPERIMENT4_ID; - gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY)); - - yield experiments.updateManifest(); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 4, "Experiment list should have 4 entries."); - - expectedLogLength += 1; - log = TelemetryLog.entries(); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); - checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, - [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT4_ID]); - - // Trigger experiment termination by something other than expiry via the manifest. - - now = futureDate(now, MS_IN_ONE_DAY); - defineNow(gPolicy, now); - gManifestObject.experiments[0].os = "Plan9"; - - yield experiments.updateManifest(); - list = yield experiments.getExperiments(); - Assert.equal(list.length, 4, "Experiment list should have 4 entries."); - - expectedLogLength += 1; - log = TelemetryLog.entries(); - Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); - checkEvent(log[log.length-1], TLOG.TERMINATION_KEY, - [TLOG.TERMINATION.RECHECK, EXPERIMENT4_ID, "os"]); - - // Cleanup. - - yield promiseRestartManager(); - yield removeCacheFile(); -}); diff --git a/browser/experiments/test/xpcshell/test_telemetry_disabled.js b/browser/experiments/test/xpcshell/test_telemetry_disabled.js deleted file mode 100644 index 74f85ccfc..000000000 --- a/browser/experiments/test/xpcshell/test_telemetry_disabled.js +++ /dev/null @@ -1,21 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource:///modules/experiments/Experiments.jsm"); - -add_test(function test_experiments_activation() { - do_get_profile(); - loadAddonManager(); - - Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); - Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, false); - - let experiments = Experiments.instance(); - Assert.ok(!experiments.enabled, "Experiments must be disabled if Telemetry is disabled."); - - // TODO: Test that Experiments are turned back on when bug 1232648 lands. - - run_next_test(); -}); diff --git a/browser/experiments/test/xpcshell/test_upgrade.js b/browser/experiments/test/xpcshell/test_upgrade.js deleted file mode 100644 index f094a406d..000000000 --- a/browser/experiments/test/xpcshell/test_upgrade.js +++ /dev/null @@ -1,52 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; -Cu.import("resource:///modules/experiments/Experiments.jsm"); - -var cacheData = { - _enabled: true, - _manifestData: { - id: "foobartestid", - xpiURL: "http://example.com/foo.xpi", - xpiHash: "sha256:abcde", - startTime: 0, - endTime: 2000000000, - maxActiveSeconds: 40000000, - appName: "TestApp", - channel: "test-foo", - }, - _needsUpdate: false, - _randomValue: 0.5, - _failedStart: false, - _name: "Foo", - _description: "Foobar", - _homepageURL: "", - _addonId: "foo@test", - _startDate: 0, - _endDate: 2000000000, - _branch: null -}; - -add_task(function* test_valid() { - let e = new Experiments.ExperimentEntry(); - Assert.ok(e.initFromCacheData(cacheData)); - Assert.ok(e.enabled); -}); - -add_task(function* test_upgrade() { - let e = new Experiments.ExperimentEntry(); - delete cacheData._branch; - Assert.ok(e.initFromCacheData(cacheData)); - Assert.ok(e.enabled); -}); - -add_task(function* test_missing() { - let e = new Experiments.ExperimentEntry(); - delete cacheData._name; - Assert.ok(!e.initFromCacheData(cacheData)); -}); - -function run_test() { - run_next_test(); -} diff --git a/browser/experiments/test/xpcshell/xpcshell.ini b/browser/experiments/test/xpcshell/xpcshell.ini deleted file mode 100644 index 5921c9c47..000000000 --- a/browser/experiments/test/xpcshell/xpcshell.ini +++ /dev/null @@ -1,31 +0,0 @@ -[DEFAULT] -head = head.js -tail = -tags = addons -firefox-appdir = browser -skip-if = toolkit == 'android' -support-files = - experiments_1.manifest - experiment-1.xpi - experiment-1a.xpi - experiment-2.xpi - experiment-racybranch.xpi - !/toolkit/mozapps/webextensions/test/xpcshell/head_addons.js -generated-files = - experiment-1.xpi - experiment-1a.xpi - experiment-2.xpi - experiment-racybranch.xpi - -[test_activate.js] -[test_api.js] -[test_cache.js] -[test_cacherace.js] -[test_conditions.js] -[test_disableExperiments.js] -[test_fetch.js] -[test_telemetry.js] -[test_telemetry_disabled.js] -[test_previous_provider.js] -[test_upgrade.js] -[test_nethang_bug1012924.js] diff --git a/browser/fonts/EmojiOneMozilla.ttf b/browser/fonts/EmojiOneMozilla.ttf Binary files differdeleted file mode 100644 index 50356509d..000000000 --- a/browser/fonts/EmojiOneMozilla.ttf +++ /dev/null diff --git a/browser/fonts/README.txt b/browser/fonts/README.txt index 188ea3fff..ac1f6d9de 100644 --- a/browser/fonts/README.txt +++ b/browser/fonts/README.txt @@ -1,9 +1,9 @@ -EmojiOne Mozilla +Twemoji Mozilla ================ -The upstream repository of EmojiOne Mozilla can be found at +The upstream repository of Twemoji Mozilla can be found at - https://github.com/mozilla/emojione-colr + https://github.com/mozilla/twemoji-colr Please refer commit history for the current version of the font. This file purposely omit the version, so there is no need to update it here. diff --git a/browser/fonts/TwemojiMozilla.ttf b/browser/fonts/TwemojiMozilla.ttf Binary files differnew file mode 100644 index 000000000..1933891d9 --- /dev/null +++ b/browser/fonts/TwemojiMozilla.ttf diff --git a/browser/fonts/moz.build b/browser/fonts/moz.build index b1a43e528..5cb98333b 100644 --- a/browser/fonts/moz.build +++ b/browser/fonts/moz.build @@ -7,5 +7,5 @@ if CONFIG['OS_ARCH'] in ('WINNT', 'Linux'): DIST_SUBDIR = '' FINAL_TARGET_FILES.fonts += [ - 'EmojiOneMozilla.ttf' + 'TwemojiMozilla.ttf' ] diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 015daa8c7..5540feed9 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -374,8 +374,6 @@ @RESPATH@/browser/components/devtools-startup.js @RESPATH@/browser/components/webideCli.js @RESPATH@/browser/components/webideComponents.manifest -@RESPATH@/browser/components/Experiments.manifest -@RESPATH@/browser/components/ExperimentsService.js @RESPATH@/browser/components/browser-newtab.xpt @RESPATH@/browser/components/aboutNewTabService.js @RESPATH@/browser/components/NewTabComponents.manifest @@ -781,28 +779,6 @@ bin/libfreebl_32int64_3.so @BINPATH@/maintenanceservice_installer.exe #endif -; [Crash Reporter] -; -#ifdef MOZ_CRASHREPORTER -@RESPATH@/components/CrashService.manifest -@RESPATH@/components/CrashService.js -@RESPATH@/components/toolkit_crashservice.xpt -#ifdef XP_MACOSX -@BINPATH@/crashreporter.app/ -#else -@BINPATH@/crashreporter@BIN_SUFFIX@ -@BINPATH@/minidump-analyzer@BIN_SUFFIX@ -@RESPATH@/crashreporter.ini -#ifdef XP_UNIX -@RESPATH@/Throbber-small.gif -#endif -#endif -@RESPATH@/browser/crashreporter-override.ini -#ifdef MOZ_CRASHREPORTER_INJECTOR -@BINPATH@/breakpadinjector.dll -#endif -#endif - @RESPATH@/components/dom_audiochannel.xpt ; Shutdown Terminator diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in index b3ecfd359..af200147f 100644 --- a/browser/locales/Makefile.in +++ b/browser/locales/Makefile.in @@ -165,11 +165,6 @@ else endif endif -ifdef MOZ_CRASHREPORTER -libs:: crashreporter-override.ini - $(SYSINSTALL) $(IFLAGS1) $^ $(FINAL_TARGET) -endif - ident: @printf 'fx_revision ' @$(PYTHON) $(topsrcdir)/config/printconfigsetting.py \ diff --git a/browser/modules/AboutHome.jsm b/browser/modules/AboutHome.jsm index 01cbafba9..639194c20 100644 --- a/browser/modules/AboutHome.jsm +++ b/browser/modules/AboutHome.jsm @@ -24,17 +24,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm"); -// Url to fetch snippets, in the urlFormatter service format. -const SNIPPETS_URL_PREF = "browser.aboutHomeSnippets.updateUrl"; - -// Should be bumped up if the snippets content format changes. -const STARTPAGE_VERSION = 4; +// Should be bumped up if any data content format changes. +const STARTPAGE_VERSION = 5; this.AboutHomeUtils = { - get snippetsVersion() { - return STARTPAGE_VERSION; - }, - /* * showKnowYourRights - Determines if the user should be shown the * about:rights notification. The notification should *not* be shown if @@ -54,7 +47,7 @@ this.AboutHomeUtils = { return !Services.prefs.getBoolPref("browser.EULA.override"); } catch (e) { } - if (!AppConstants.MOZILLA_OFFICIAL) { + if (!AppConstants.MC_OFFICIAL) { // Non-official builds shouldn't show the notification. return false; } @@ -77,16 +70,6 @@ this.AboutHomeUtils = { }; /** - * Returns the URL to fetch snippets from, in the urlFormatter service format. - */ -XPCOMUtils.defineLazyGetter(AboutHomeUtils, "snippetsURL", function() { - let updateURL = Services.prefs - .getCharPref(SNIPPETS_URL_PREF) - .replace("%STARTPAGE_VERSION%", STARTPAGE_VERSION); - return Services.urlFormatter.formatURL(updateURL); -}); - -/** * This code provides services to the about:home page. Whenever * about:home needs to do something chrome-privileged, it sends a * message that's handled here. @@ -169,9 +152,7 @@ var AboutHome = { ss.promiseInitialized.then(function() { let data = { showRestoreLastSession: ss.canRestoreLastSession, - snippetsURL: AboutHomeUtils.snippetsURL, - showKnowYourRights: AboutHomeUtils.showKnowYourRights, - snippetsVersion: AboutHomeUtils.snippetsVersion, + showKnowYourRights: AboutHomeUtils.showKnowYourRights }; if (AboutHomeUtils.showKnowYourRights) { diff --git a/browser/modules/ContentCrashHandlers.jsm b/browser/modules/ContentCrashHandlers.jsm index 2f755d142..488cc4f26 100644 --- a/browser/modules/ContentCrashHandlers.jsm +++ b/browser/modules/ContentCrashHandlers.jsm @@ -90,8 +90,6 @@ this.TabCrashHandler = { Services.telemetry .getHistogramById("FX_CONTENT_CRASH_DUMP_UNAVAILABLE") .add(1); - } else if (AppConstants.MOZ_CRASHREPORTER) { - this.childMap.set(childID, dumpID); } if (!this.flushCrashedBrowserQueue(childID)) { @@ -115,15 +113,6 @@ this.TabCrashHandler = { } } - // check for environment affecting crash reporting - let env = Cc["@mozilla.org/process/environment;1"] - .getService(Ci.nsIEnvironment); - let shutdown = env.exists("MOZ_CRASHREPORTER_SHUTDOWN"); - - if (shutdown) { - Services.startup.quit(Ci.nsIAppStartup.eForceQuit); - } - break; } case "oop-frameloader-crashed": { @@ -306,105 +295,10 @@ this.TabCrashHandler = { /** * Submits a crash report from about:tabcrashed, if the crash * reporter is enabled and a crash report can be found. - * - * @param aBrowser - * The <xul:browser> that the report was sent from. - * @param aFormData - * An Object with the following properties: - * - * includeURL (bool): - * Whether to include the URL that the user was on - * in the crashed tab before the crash occurred. - * URL (String) - * The URL that the user was on in the crashed tab - * before the crash occurred. - * emailMe (bool): - * Whether or not to include the user's email address - * in the crash report. - * email (String): - * The email address of the user. - * comments (String): - * Any additional comments from the user. - * - * Note that it is expected that all properties are set, - * even if they are empty. */ maybeSendCrashReport(message) { - if (!AppConstants.MOZ_CRASHREPORTER) { - return; - } - - if (!message.data.hasReport) { - // There was no report, so nothing to do. - return; - } - - let browser = message.target.browser; - - if (message.data.autoSubmit) { - // The user has opted in to autosubmitted backlogged - // crash reports in the future. - UnsubmittedCrashHandler.autoSubmit = true; - } - - let childID = this.browserMap.get(browser.permanentKey); - let dumpID = this.childMap.get(childID); - if (!dumpID) { - return; - } - - if (!message.data.sendReport) { - Services.telemetry.getHistogramById("FX_CONTENT_CRASH_NOT_SUBMITTED").add(1); - this.prefs.setBoolPref("sendReport", false); - return; - } - - let { - includeURL, - comments, - email, - emailMe, - URL, - } = message.data; - - let extraExtraKeyVals = { - "Comments": comments, - "Email": email, - "URL": URL, - }; - - // For the entries in extraExtraKeyVals, we only want to submit the - // extra data values where they are not the empty string. - for (let key in extraExtraKeyVals) { - let val = extraExtraKeyVals[key].trim(); - if (!val) { - delete extraExtraKeyVals[key]; - } - } - - // URL is special, since it's already been written to extra data by - // default. In order to make sure we don't send it, we overwrite it - // with the empty string. - if (!includeURL) { - extraExtraKeyVals["URL"] = ""; - } - - CrashSubmit.submit(dumpID, { - recordSubmission: true, - extraExtraKeyVals, - }).then(null, Cu.reportError); - - this.prefs.setBoolPref("sendReport", true); - this.prefs.setBoolPref("includeURL", includeURL); - this.prefs.setBoolPref("emailMe", emailMe); - if (emailMe) { - this.prefs.setCharPref("email", email); - } else { - this.prefs.setCharPref("email", ""); - } - - this.childMap.set(childID, null); // Avoid resubmission. - this.removeSubmitCheckboxesForSameCrash(childID); + /*** STUB ***/ + return; }, removeSubmitCheckboxesForSameCrash: function(childID) { @@ -518,17 +412,10 @@ this.TabCrashHandler = { /** * For some <xul:browser>, return a crash report dump ID for that browser * if we have been informed of one. Otherwise, return null. - * - * @param browser (<xul:browser) - * The browser to try to get the dump ID for - * @returns dumpID (String) */ getDumpID(browser) { - if (!AppConstants.MOZ_CRASHREPORTER) { - return null; - } - - return this.childMap.get(this.browserMap.get(browser.permanentKey)); + /*** STUB ***/ + return null; }, } diff --git a/browser/modules/ContentWebRTC.jsm b/browser/modules/ContentWebRTC.jsm index bfb98a868..fd50176a0 100644 --- a/browser/modules/ContentWebRTC.jsm +++ b/browser/modules/ContentWebRTC.jsm @@ -208,6 +208,7 @@ function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSec let request = { callID: aCallID, windowID: aWindowID, + origin: aContentWindow.origin, documentURI: aContentWindow.document.documentURI, secure: aSecure, requestTypes: requestTypes, diff --git a/browser/modules/PluginContent.jsm b/browser/modules/PluginContent.jsm index 1bbfa9a50..622d608bc 100644 --- a/browser/modules/PluginContent.jsm +++ b/browser/modules/PluginContent.jsm @@ -660,30 +660,8 @@ PluginContent.prototype = { }, submitReport: function submitReport(plugin) { - if (!AppConstants.MOZ_CRASHREPORTER) { - return; - } - if (!plugin) { - Cu.reportError("Attempted to submit crash report without an associated plugin."); - return; - } - if (!(plugin instanceof Ci.nsIObjectLoadingContent)) { - Cu.reportError("Attempted to submit crash report on plugin that does not" + - "implement nsIObjectLoadingContent."); - return; - } - - let runID = plugin.runID; - let submitURLOptIn = this.getPluginUI(plugin, "submitURLOptIn").checked; - let keyVals = {}; - let userComment = this.getPluginUI(plugin, "submitComment").value.trim(); - if (userComment) - keyVals.PluginUserComment = userComment; - if (submitURLOptIn) - keyVals.PluginContentURL = plugin.ownerDocument.URL; - - this.global.sendAsyncMessage("PluginContent:SubmitReport", - { runID, keyVals, submitURLOptIn }); + /*** STUB ***/ + return; }, reloadPage: function () { diff --git a/browser/modules/webrtcUI.jsm b/browser/modules/webrtcUI.jsm index b24135bfc..08de46bb3 100644 --- a/browser/modules/webrtcUI.jsm +++ b/browser/modules/webrtcUI.jsm @@ -290,7 +290,13 @@ function prompt(aBrowser, aRequest) { let {audioDevices: audioDevices, videoDevices: videoDevices, sharingScreen: sharingScreen, sharingAudio: sharingAudio, requestTypes: requestTypes} = aRequest; - let uri = Services.io.newURI(aRequest.documentURI, null, null); + let uri; + try { + // This fails for principals that serialize to "null", e.g. file URIs. + uri = Services.io.newURI(aRequest.origin, null, null); + } catch (e) { + uri = Services.io.newURI(aRequest.documentURI, null, null); + } let host = getHost(uri); let chromeDoc = aBrowser.ownerDocument; let chromeWin = chromeDoc.defaultView; diff --git a/browser/moz.build b/browser/moz.build index a691aeef2..0985148c0 100644 --- a/browser/moz.build +++ b/browser/moz.build @@ -11,7 +11,6 @@ SPHINX_TREES['browser'] = 'docs' DIRS += [ 'base', 'components', - 'experiments', 'fonts', 'locales', 'modules', diff --git a/browser/moz.configure b/browser/moz.configure index fba4603be..d5e7dba11 100644 --- a/browser/moz.configure +++ b/browser/moz.configure @@ -4,9 +4,4 @@ # 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/. -imply_option('MOZ_PLACES', True) -imply_option('MOZ_SERVICES_HEALTHREPORT', True) -imply_option('MOZ_SERVICES_SYNC', True) -imply_option('MOZ_SERVICES_CLOUDSYNC', True) - include('../toolkit/moz.configure') diff --git a/browser/themes/osx/shared.inc b/browser/themes/osx/shared.inc index b3ea4e199..3076450e2 100644 --- a/browser/themes/osx/shared.inc +++ b/browser/themes/osx/shared.inc @@ -1,4 +1,4 @@ -%include ../../../toolkit/themes/osx/global/shared.inc
+%include ../../../../toolkit/themes/osx/global/shared.inc
%include ../shared/browser.inc
%filter substitution
|