diff options
109 files changed, 607 insertions, 9013 deletions
diff --git a/application/palemoon/base/content/browser-thumbnails.js b/application/palemoon/base/content/browser-thumbnails.js index d6fe25231..b06dfd503 100644 --- a/application/palemoon/base/content/browser-thumbnails.js +++ b/application/palemoon/base/content/browser-thumbnails.js @@ -95,7 +95,7 @@ let gBrowserThumbnails = { // Tycho: aCallback([browser.currentURI.spec for (browser of gBrowser.browsers)]); let result = []; for (let browser of gBrowser.browsers) { - result.push(browser.currentURL.spec); + result.push(browser.currentURI.spec); } aCallback(result); }, @@ -142,7 +142,7 @@ let gBrowserThumbnails = { // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as // that currently regresses Talos SVG tests. - if (doc instanceof SVGDocument || doc instanceof XMLDocument) + if (doc instanceof XMLDocument) return false; // There's no point in taking screenshot of loading pages. diff --git a/application/palemoon/base/content/browser.css b/application/palemoon/base/content/browser.css index 2c8ba3e69..e6a84421b 100644 --- a/application/palemoon/base/content/browser.css +++ b/application/palemoon/base/content/browser.css @@ -115,8 +115,17 @@ toolbar[printpreview="true"] { #titlebar { -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox"); + -moz-window-dragging: drag; } +%ifdef XP_WIN +#main-window[tabsontop="true"] #TabsToolbar, +#main-window[tabsontop="true"] #toolbar-menubar, +#main-window[tabsontop="true"] #navigator-toolbox > toolbar:-moz-lwtheme { + -moz-window-dragging: drag; +} +%endif + #titlebar-spacer { pointer-events: none; } diff --git a/application/palemoon/base/content/browser.js b/application/palemoon/base/content/browser.js index ffb133963..328cc4975 100644 --- a/application/palemoon/base/content/browser.js +++ b/application/palemoon/base/content/browser.js @@ -153,7 +153,11 @@ let gInitialPages = [ #include browser-plugins.js #include browser-tabPreviews.js #include browser-thumbnails.js + +#ifdef MOZ_WEBRTC #include browser-webrtcUI.js +#endif + #include browser-gestureSupport.js #ifdef MOZ_SERVICES_SYNC @@ -1020,7 +1024,9 @@ var gBrowserInit = { OfflineApps.init(); IndexedDBPromptHelper.init(); AddonManager.addAddonListener(AddonsMgrListener); +#ifdef MOZ_WEBRTC WebrtcIndicator.init(); +#endif // Ensure login manager is up and running. Services.logins; diff --git a/application/palemoon/base/content/browser.xul b/application/palemoon/base/content/browser.xul index 3044ce675..30b1838ea 100644 --- a/application/palemoon/base/content/browser.xul +++ b/application/palemoon/base/content/browser.xul @@ -440,8 +440,10 @@ <image id="alert-plugins-notification-icon" class="notification-anchor-icon" role="button"/> <image id="blocked-plugins-notification-icon" class="notification-anchor-icon" role="button"/> <image id="mixed-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/> +#ifdef MOZ_WEBRTC <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/> <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/> +#endif <image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/> <image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"/> </box> @@ -513,7 +515,7 @@ flex="100" persist="width" removable="true"> <searchbar id="searchbar" flex="1"/> </toolbaritem> - +#ifdef MOZ_WEBRTC <toolbarbutton id="webrtc-status-button" class="toolbarbutton-1 chromeclass-toolbar-additional" type="menu" @@ -525,7 +527,7 @@ onpopuphiding="WebrtcIndicator.clearPopup(this);" oncommand="WebrtcIndicator.menuCommand(event.target);"/> </toolbarbutton> - +#endif <toolbarbutton id="bookmarks-menu-button" class="toolbarbutton-1 chromeclass-toolbar-additional" persist="class" diff --git a/application/palemoon/base/content/popup-notifications.inc b/application/palemoon/base/content/popup-notifications.inc index 04f4cb5b7..3112de597 100644 --- a/application/palemoon/base/content/popup-notifications.inc +++ b/application/palemoon/base/content/popup-notifications.inc @@ -54,7 +54,7 @@ </hbox> </panel> - +#ifdef MOZ_WEBRTC <popupnotification id="webRTC-shareDevices-notification" hidden="true"> <popupnotificationcontent id="webRTC-selectCamera" orient="vertical"> <separator class="thin"/> @@ -75,7 +75,7 @@ </menulist> </popupnotificationcontent> </popupnotification> - +#endif <popupnotification id="servicesInstall-notification" hidden="true"> <popupnotificationcontent orient="vertical" align="start"> <!-- XXX bug 974146, tests are looking for this, can't remove yet. --> diff --git a/application/palemoon/base/content/tabbrowser.xml b/application/palemoon/base/content/tabbrowser.xml index 3e30c2ba1..ef0af5afb 100644 --- a/application/palemoon/base/content/tabbrowser.xml +++ b/application/palemoon/base/content/tabbrowser.xml @@ -707,7 +707,8 @@ let autocomplete = this.mTabBrowser._placesAutocomplete; if (this.mBrowser.registeredOpenURI) { - autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI); + autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI, + this.mBrowser.getAttribute("usercontextid") || 0); delete this.mBrowser.registeredOpenURI; } // Tabs in private windows aren't registered as "Open" so @@ -715,7 +716,8 @@ if (!isBlankPageURL(aLocation.spec) && (!PrivateBrowsingUtils.isWindowPrivate(window) || PrivateBrowsingUtils.permanentPrivateBrowsing)) { - autocomplete.registerOpenPage(aLocation); + autocomplete.registerOpenPage(aLocation, + this.mBrowser.getAttribute("usercontextid") || 0); this.mBrowser.registeredOpenURI = aLocation; } } @@ -1865,7 +1867,8 @@ this.mTabListeners[aTab._tPos].destroy(); if (browser.registeredOpenURI && !aTabWillBeMoved) { - this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI); + this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI, + browser.getAttribute("usercontextid") || 0); delete browser.registeredOpenURI; } @@ -2209,7 +2212,8 @@ <![CDATA[ // If the current URI is registered as open remove it from the list. if (aOurBrowser.registeredOpenURI) { - this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI); + this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI, + aOurBrowser.getAttribute("usercontextid") || 0); delete aOurBrowser.registeredOpenURI; } @@ -3066,7 +3070,8 @@ for (var i = 0; i < this.mTabListeners.length; ++i) { let browser = this.getBrowserAtIndex(i); if (browser.registeredOpenURI) { - this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI); + this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI, + browser.getAttribute("usercontextid") || 0); delete browser.registeredOpenURI; } browser.webProgress.removeProgressListener(this.mTabFilters[i]); diff --git a/application/palemoon/components/about/AboutRedirector.cpp b/application/palemoon/components/about/AboutRedirector.cpp index b5dd4abfb..d52b873b9 100644 --- a/application/palemoon/components/about/AboutRedirector.cpp +++ b/application/palemoon/components/about/AboutRedirector.cpp @@ -30,17 +30,47 @@ struct RedirEntry { URI_SAFE_FOR_UNTRUSTED_CONTENT. */ static RedirEntry kRedirMap[] = { - { "certerror", "chrome://browser/content/certerror/aboutCertError.xhtml", + { + "certerror", "chrome://browser/content/certerror/aboutCertError.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 + }, + { + "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "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::ALLOW_SCRIPT }, - { "rights", + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "home", "chrome://browser/content/abouthome/aboutHome.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::MAKE_LINKABLE | + nsIAboutModule::ALLOW_SCRIPT + }, + { + "newtab", "chrome://browser/content/newtab/newTab.xul", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "palemoon", "chrome://global/content/memoriam.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "permissions", "chrome://browser/content/preferences/aboutPermissions.xul", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "privatebrowsing", "chrome://browser/content/aboutPrivateBrowsing.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "rights", #ifdef MOZ_OFFICIAL_BRANDING "chrome://global/content/aboutRights.xhtml", #else @@ -48,34 +78,27 @@ static RedirEntry kRedirMap[] = { #endif nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::MAKE_LINKABLE | - nsIAboutModule::ALLOW_SCRIPT }, - { "palemoon", "chrome://global/content/palemoon.xhtml", - nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, - { "logopage", "chrome://global/content/logopage.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "robots", "chrome://browser/content/aboutRobots.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT }, - { "robots", "chrome://browser/content/aboutRobots.xhtml", - nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::ALLOW_SCRIPT }, - { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml", - nsIAboutModule::ALLOW_SCRIPT }, + nsIAboutModule::ALLOW_SCRIPT + }, + { + "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, #ifdef MOZ_SERVICES_SYNC - { "sync-progress", "chrome://browser/content/sync/progress.xhtml", - nsIAboutModule::ALLOW_SCRIPT }, - { "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul", - nsIAboutModule::ALLOW_SCRIPT }, + { + "sync-progress", "chrome://browser/content/sync/progress.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul", + nsIAboutModule::ALLOW_SCRIPT + }, #endif - { "home", "chrome://browser/content/abouthome/aboutHome.xhtml", - nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::MAKE_LINKABLE | - nsIAboutModule::ALLOW_SCRIPT }, - { "newtab", "chrome://browser/content/newtab/newTab.xul", - nsIAboutModule::ALLOW_SCRIPT }, - { "permissions", "chrome://browser/content/preferences/aboutPermissions.xul", - nsIAboutModule::ALLOW_SCRIPT }, - { "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul", - nsIAboutModule::ALLOW_SCRIPT }, }; static const int kRedirTotal = ArrayLength(kRedirMap); diff --git a/application/palemoon/components/build/nsModule.cpp b/application/palemoon/components/build/nsModule.cpp index d5b79b455..fad87d831 100644 --- a/application/palemoon/components/build/nsModule.cpp +++ b/application/palemoon/components/build/nsModule.cpp @@ -91,7 +91,6 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = { { NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "palemoon", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, - { NS_ABOUT_MODULE_CONTRACTID_PREFIX "logopage", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "robots", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sessionrestore", &kNS_BROWSER_ABOUT_REDIRECTOR_CID }, #ifdef MOZ_SERVICES_SYNC diff --git a/application/palemoon/components/downloads/DownloadsCommon.jsm b/application/palemoon/components/downloads/DownloadsCommon.jsm index b90baaf9c..0921f8400 100644 --- a/application/palemoon/components/downloads/DownloadsCommon.jsm +++ b/application/palemoon/components/downloads/DownloadsCommon.jsm @@ -867,9 +867,19 @@ DownloadsDataCtor.prototype = { // Sort backwards by start time, ensuring that the most recent // downloads are added first regardless of their state. - let loadedItemsArray = [dataItem - for each (dataItem in this.dataItems) - if (dataItem)]; + // Tycho: + //let loadedItemsArray = [dataItem + // for each (dataItem in this.dataItems) + // if (dataItem)]; + + let loadedItemsArray = []; + + for each (let dataItem in this.dataItems) { + if (dataItem) { + loadedItemsArray.push(dataItem); + } + } + loadedItemsArray.sort(function(a, b) b.startTime - a.startTime); loadedItemsArray.forEach( function (dataItem) aView.onDataItemAdded(dataItem, false) diff --git a/application/palemoon/components/nsBrowserGlue.js b/application/palemoon/components/nsBrowserGlue.js index 5b7fdde33..aafdced9f 100644 --- a/application/palemoon/components/nsBrowserGlue.js +++ b/application/palemoon/components/nsBrowserGlue.js @@ -13,59 +13,29 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", - "resource://gre/modules/AddonManager.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "UserAgentOverrides", - "resource://gre/modules/UserAgentOverrides.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", - "resource://gre/modules/PlacesUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils", - "resource://gre/modules/BookmarkHTMLUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils", - "resource://gre/modules/BookmarkJSONUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs", - "resource://gre/modules/PageThumbs.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils", - "resource://gre/modules/NewTabUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "BrowserNewTabPreloader", - "resource:///modules/BrowserNewTabPreloader.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "webrtcUI", - "resource:///modules/webrtcUI.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", - "resource:///modules/RecentWindow.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups", - "resource://gre/modules/PlacesBackups.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent", - "resource://gre/modules/LoginManagerParent.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler", - "resource:///modules/FormValidationHandler.jsm"); +// Define Lazy Module Gitters +[ + ["AddonManager", "resource://gre/modules/AddonManager.jsm"], + ["NetUtil", "resource://gre/modules/NetUtil.jsm"], + ["UserAgentOverrides", "resource://gre/modules/UserAgentOverrides.jsm"], + ["FileUtils", "resource://gre/modules/FileUtils.jsm"], + ["PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"], + ["BookmarkHTMLUtils", "resource://gre/modules/BookmarkHTMLUtils.jsm"], + ["BookmarkJSONUtils", "resource://gre/modules/BookmarkJSONUtils.jsm"], + ["PageThumbs", "resource://gre/modules/PageThumbs.jsm"], + ["NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"], + ["BrowserNewTabPreloader", "resource:///modules/BrowserNewTabPreloader.jsm"], +#ifdef MOZ_WEBRTC + ["webrtcUI", "resource:///modules/webrtcUI.jsm"], +#endif + ["PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"], + ["RecentWindow", "resource:///modules/RecentWindow.jsm"], + ["Task", "resource://gre/modules/Task.jsm"], + ["PlacesBackups", "resource://gre/modules/PlacesBackups.jsm"], + ["OS", "resource://gre/modules/osfile.jsm"], + ["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"], + ["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"], +].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource)); const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; const PREF_PLUGINS_UPDATEURL = "plugins.update.url"; @@ -427,7 +397,9 @@ BrowserGlue.prototype = { PageThumbs.init(); NewTabUtils.init(); BrowserNewTabPreloader.init(); +#ifdef MOZ_WEBRTC webrtcUI.init(); +#endif FormValidationHandler.init(); LoginManagerParent.init(); @@ -534,7 +506,9 @@ BrowserGlue.prototype = { _onProfileShutdown: function BG__onProfileShutdown() { BrowserNewTabPreloader.uninit(); UserAgentOverrides.uninit(); +#ifdef MOZ_WEBRTC webrtcUI.uninit(); +#endif FormValidationHandler.uninit(); this._dispose(); }, diff --git a/application/palemoon/locales/en-US/chrome/browser/browser.dtd b/application/palemoon/locales/en-US/chrome/browser/browser.dtd index 0225f422b..65cc34fa0 100644 --- a/application/palemoon/locales/en-US/chrome/browser/browser.dtd +++ b/application/palemoon/locales/en-US/chrome/browser/browser.dtd @@ -642,8 +642,10 @@ just addresses the organization to follow, e.g. "This site is run by " --> <!ENTITY getUserMedia.selectMicrophone.label "Microphone to share:"> <!ENTITY getUserMedia.selectMicrophone.accesskey "M"> +#ifdef MOZ_WEBRTC <!ENTITY webrtcIndicatorButton.label "Camera / Microphone Access"> <!ENTITY webrtcIndicatorButton.tooltip "Display sites you are currently sharing your camera or microphone with"> +#endif <!ENTITY mixedContentBlocked.moreinfo "Most websites will still work properly even when this content is blocked."> diff --git a/application/palemoon/locales/jar.mn b/application/palemoon/locales/jar.mn index 451a86ab7..6512c683e 100644 --- a/application/palemoon/locales/jar.mn +++ b/application/palemoon/locales/jar.mn @@ -16,7 +16,7 @@ locale/browser/syncProgress.dtd (%chrome/browser/syncProgress.dtd) locale/browser/aboutSyncTabs.dtd (%chrome/browser/aboutSyncTabs.dtd) #endif - locale/browser/browser.dtd (%chrome/browser/browser.dtd) +* locale/browser/browser.dtd (%chrome/browser/browser.dtd) locale/browser/baseMenuOverlay.dtd (%chrome/browser/baseMenuOverlay.dtd) locale/browser/charsetOverlay.dtd (%chrome/browser/charsetOverlay.dtd) locale/browser/browser.properties (%chrome/browser/browser.properties) diff --git a/application/palemoon/modules/WindowsPreviewPerTab.jsm b/application/palemoon/modules/WindowsPreviewPerTab.jsm index 41b38f0cf..e186bcad2 100644 --- a/application/palemoon/modules/WindowsPreviewPerTab.jsm +++ b/application/palemoon/modules/WindowsPreviewPerTab.jsm @@ -510,7 +510,14 @@ TabWindow.prototype = { // Previews are internally stored using a map, so we need to iterate over // the tabbrowser's array of tabs to retrieve previews in the same order. - let inorder = [previews.get(t) for (t of tabs) if (previews.has(t))]; + // Tycho: let inorder = [previews.get(t) for (t of tabs) if (previews.has(t))]; + let inorder = []; + + for (let t of tabs) { + if (previews.has(t)) { + inorder.push(previews.get(t)); + } + } // Since the internal taskbar array has not yet been updated, we must force // the sorting order of our local array on it. To do so, we must walk diff --git a/application/palemoon/modules/moz.build b/application/palemoon/modules/moz.build index b3459bcd5..7620b9862 100644 --- a/application/palemoon/modules/moz.build +++ b/application/palemoon/modules/moz.build @@ -19,10 +19,12 @@ EXTRA_JS_MODULES += [ 'PageMenu.jsm', 'PopupNotifications.jsm', 'QuotaManager.jsm', - 'SharedFrame.jsm', - 'webrtcUI.jsm' + 'SharedFrame.jsm' ] +if CONFIG['MOZ_WEBRTC']: + EXTRA_JS_MODULES += ['webrtcUI.jsm'] + if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': EXTRA_JS_MODULES += [ 'Windows8WindowFrameColor.jsm', diff --git a/application/palemoon/themes/linux/browser.css b/application/palemoon/themes/linux/browser.css index 07e9dae9c..33c242909 100644 --- a/application/palemoon/themes/linux/browser.css +++ b/application/palemoon/themes/linux/browser.css @@ -728,9 +728,11 @@ toolbar[mode="full"] .toolbarbutton-1 > .toolbarbutton-menubutton-button { opacity: .4; } +%ifdef MOZ_WEBRTC #webrtc-status-button { -moz-image-region: rect(0px 192px 24px 168px); } +%endif /* 16px primary toolbar buttons */ toolbar[iconsize="small"] .toolbarbutton-1:not([type="menu-button"]) { @@ -812,7 +814,9 @@ toolbar[iconsize="small"] #downloads-button { -moz-image-region: rect(0px 16px 16px 0px); } +%ifdef MOZ_WEBRTC toolbar[iconsize="small"] #webrtc-status-button /* temporary placeholder (bug 824825) */, +%endif toolbar[iconsize="small"] #history-button, toolbar[iconsize="small"] #history-menu-button { -moz-image-region: rect(0px 32px 16px 16px); @@ -884,9 +888,11 @@ toolbar[iconsize="small"] #feed-button { -moz-image-region: rect(0px 112px 16px 96px); } +%ifdef MOZ_WEBRTC toolbar[iconsize="small"] #webrtc-status-button { -moz-image-region: rect(0px 128px 16px 112px); } +%endif /* Fullscreen window controls */ #window-controls { @@ -1226,10 +1232,12 @@ toolbar[iconsize="small"] #webrtc-status-button { list-style-image: url(chrome://browser/skin/mixed-content-blocked-64.png); } +%ifdef MOZ_WEBRTC .popup-notification-icon[popupid="webRTC-sharingDevices"], .popup-notification-icon[popupid="webRTC-shareDevices"] { list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png); } +%endif .popup-notification-icon[popupid="pointerLock"] { list-style-image: url(chrome://browser/skin/pointerLock-64.png); @@ -1352,6 +1360,7 @@ toolbar[iconsize="small"] #webrtc-status-button { list-style-image: url(chrome://browser/skin/mixed-content-blocked-16.png); } +%ifdef MOZ_WEBRTC .webRTC-shareDevices-notification-icon, #webRTC-shareDevices-notification-icon { list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16.png); @@ -1361,6 +1370,7 @@ toolbar[iconsize="small"] #webrtc-status-button { #webRTC-sharingDevices-notification-icon { list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16.png); } +%endif .web-notifications-notification-icon, #web-notifications-notification-icon { diff --git a/application/palemoon/themes/linux/jar.mn b/application/palemoon/themes/linux/jar.mn index a756edbc3..44d837778 100644 --- a/application/palemoon/themes/linux/jar.mn +++ b/application/palemoon/themes/linux/jar.mn @@ -53,9 +53,11 @@ browser.jar: skin/classic/browser/Toolbar.png skin/classic/browser/Toolbar-small.png skin/classic/browser/urlbar-arrow.png +#ifdef MOZ_WEBRTC skin/classic/browser/webRTC-shareDevice-16.png skin/classic/browser/webRTC-shareDevice-64.png skin/classic/browser/webRTC-sharingDevice-16.png +#endif skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/download-glow.png (downloads/download-glow.png) skin/classic/browser/downloads/download-glow-small.png (downloads/download-glow-small.png) diff --git a/application/palemoon/themes/osx/browser.css b/application/palemoon/themes/osx/browser.css index 58348a408..882a0c39c 100644 --- a/application/palemoon/themes/osx/browser.css +++ b/application/palemoon/themes/osx/browser.css @@ -801,10 +801,11 @@ toolbar[brighttext] #bookmarks-menu-button.bookmark-item { -moz-image-region: rect(0, 342px, 18px, 324px); } +%ifdef MOZ_WEBRTC #webrtc-status-button { -moz-image-region: rect(0, 360px, 18px, 342px); } - +%endif /* ::::: fullscreen window controls ::::: */ @@ -1955,11 +1956,13 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { list-style-image: url(chrome://browser/skin/mixed-content-blocked-64.png); } +%ifdef MOZ_WEBRTC .popup-notification-icon[popupid="webRTC-sharingDevices"], .popup-notification-icon[popupid="webRTC-shareDevices"] { list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png); } +%endif .popup-notification-icon[popupid="pointerLock"] { list-style-image: url(chrome://browser/skin/pointerLock-64.png); } @@ -2079,6 +2082,7 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { list-style-image: url(chrome://browser/skin/mixed-content-blocked-16.png); } +%ifdef MOZ_WEBRTC .webRTC-shareDevices-notification-icon, #webRTC-shareDevices-notification-icon { list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16.png); @@ -2088,6 +2092,7 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { #webRTC-sharingDevices-notification-icon { list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16.png); } +%endif .web-notifications-notification-icon, #web-notifications-notification-icon { diff --git a/application/palemoon/themes/osx/jar.mn b/application/palemoon/themes/osx/jar.mn index 00575bac8..8742f1b87 100644 --- a/application/palemoon/themes/osx/jar.mn +++ b/application/palemoon/themes/osx/jar.mn @@ -72,9 +72,11 @@ browser.jar: skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png) skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png) skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png) +#ifdef MOZ_WEBRTC skin/classic/browser/webRTC-shareDevice-16.png skin/classic/browser/webRTC-shareDevice-64.png skin/classic/browser/webRTC-sharingDevice-16.png +#endif skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/download-glow.png (downloads/download-glow.png) skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png) diff --git a/application/palemoon/themes/shared/browser.inc b/application/palemoon/themes/shared/browser.inc index cd17903ce..18e69ad43 100644 --- a/application/palemoon/themes/shared/browser.inc +++ b/application/palemoon/themes/shared/browser.inc @@ -1,3 +1,8 @@ %filter substitution +%ifndef MOZ_WEBRTC +%define primaryToolbarButtons #back-button, #forward-button, #reload-button, #stop-button, #home-button, #print-button, #downloads-button, #downloads-indicator, #history-button, #history-menu-button, #bookmarks-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #cut-button, #copy-button, #paste-button, #fullscreen-button, #zoom-out-button, #zoom-in-button, #sync-button, #feed-button, #alltabs-button +%else %define primaryToolbarButtons #back-button, #forward-button, #reload-button, #stop-button, #home-button, #print-button, #downloads-button, #downloads-indicator, #history-button, #history-menu-button, #bookmarks-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #cut-button, #copy-button, #paste-button, #fullscreen-button, #zoom-out-button, #zoom-in-button, #sync-button, #feed-button, #alltabs-button, #webrtc-status-button +%endif + diff --git a/application/palemoon/themes/windows/browser.css b/application/palemoon/themes/windows/browser.css index 7c837764d..75e144a39 100644 --- a/application/palemoon/themes/windows/browser.css +++ b/application/palemoon/themes/windows/browser.css @@ -1192,10 +1192,11 @@ toolbar[brighttext] #bookmarks-menu-button.bookmark-item { -moz-image-region: rect(0, 342px, 18px, 324px); } +%ifdef MOZ_WEBRTC #webrtc-status-button { -moz-image-region: rect(0, 360px, 18px, 342px); } - +%endif /* ::::: fullscreen window controls ::::: */ @@ -2443,10 +2444,12 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { list-style-image: url(chrome://browser/skin/mixed-content-blocked-64.png); } +%ifdef MOZ_WEBRTC .popup-notification-icon[popupid="webRTC-sharingDevices"], .popup-notification-icon[popupid="webRTC-shareDevices"] { list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png); } +%endif .popup-notification-icon[popupid="pointerLock"] { list-style-image: url(chrome://browser/skin/pointerLock-64.png); @@ -2567,6 +2570,7 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { list-style-image: url(chrome://browser/skin/mixed-content-blocked-16.png); } +%ifdef MOZ_WEBRTC .webRTC-shareDevices-notification-icon, #webRTC-shareDevices-notification-icon { list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16.png); @@ -2576,6 +2580,7 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] { #webRTC-sharingDevices-notification-icon { list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16.png); } +%endif .web-notifications-notification-icon, #web-notifications-notification-icon { diff --git a/application/palemoon/themes/windows/jar.mn b/application/palemoon/themes/windows/jar.mn index 1c1f139de..f708d39c0 100644 --- a/application/palemoon/themes/windows/jar.mn +++ b/application/palemoon/themes/windows/jar.mn @@ -74,9 +74,11 @@ browser.jar: skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png) skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png) skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png) +#ifdef MOZ_WEBRTC skin/classic/browser/webRTC-shareDevice-16.png skin/classic/browser/webRTC-shareDevice-64.png skin/classic/browser/webRTC-sharingDevice-16.png +#endif skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/download-glow.png (downloads/download-glow.png) skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png) 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/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.js b/browser/base/content/browser.js index d41e94ae6..53cc3c735 100755 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -53,8 +53,6 @@ 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"], - ["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"], - ["gDevToolsBrowser", "resource://devtools/client/framework/gDevTools.jsm"], ["webrtcUI", "resource:///modules/webrtcUI.jsm", ] ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource)); @@ -81,12 +79,6 @@ if (AppConstants.MOZ_CRASHREPORTER) { } -XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() { - let tmp = {}; - Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", tmp); - return tmp.BrowserToolboxProcess; -}); - XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() { return Services.strings.createBundle('chrome://browser/locale/browser.properties'); }); @@ -214,7 +206,8 @@ var gInitialPages = [ "about:home", "about:privatebrowsing", "about:welcomeback", - "about:sessionrestore" + "about:sessionrestore", + "about:logopage" ]; function* browserWindows() { @@ -7893,15 +7886,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 +7943,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/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/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/modules/AboutHome.jsm b/browser/modules/AboutHome.jsm index 01cbafba9..8c0fc4c15 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 @@ -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/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/build/directive4.py b/build/directive4.py index 8f05eeed5..dd8c111cf 100644 --- a/build/directive4.py +++ b/build/directive4.py @@ -49,6 +49,7 @@ if ('MOZ_OFFICIAL_BRANDING' in listConfig) or (strBrandingDirectory.endswith("br # Applies to Pale Moon Only if 'MC_PALEMOON' in listConfig: listViolations += [ + 'MOZ_EME', 'MOZ_WEBRTC' ] diff --git a/devtools/client/commandline/test/browser_cmd_csscoverage_startstop.js b/devtools/client/commandline/test/browser_cmd_csscoverage_startstop.js index 2bdb86d86..c48136989 100644 --- a/devtools/client/commandline/test/browser_cmd_csscoverage_startstop.js +++ b/devtools/client/commandline/test/browser_cmd_csscoverage_startstop.js @@ -4,6 +4,7 @@ // Tests that the addon commands works as they should const csscoverage = require("devtools/shared/fronts/csscoverage"); +const {gDevTools} = require("devtools/client/framework/devtools"); const PAGE_1 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page1.html"; const PAGE_2 = TEST_BASE_HTTPS + "browser_cmd_csscoverage_page2.html"; diff --git a/devtools/client/framework/browser-menus.js b/devtools/client/framework/browser-menus.js index 3d6c4def6..15d2ec1b6 100644 --- a/devtools/client/framework/browser-menus.js +++ b/devtools/client/framework/browser-menus.js @@ -156,6 +156,12 @@ function createToolMenuElements(toolDefinition, doc) { }); } + let appmenuitem = createMenuItem({ + doc, + id: "appmenuitem_" + id, + label: toolDefinition.menuLabel || toolDefinition.label + }); + let menuitem = createMenuItem({ doc, id: "menuitem_" + id, @@ -170,6 +176,7 @@ function createToolMenuElements(toolDefinition, doc) { return { key, + appmenuitem, menuitem }; } @@ -186,12 +193,24 @@ function createToolMenuElements(toolDefinition, doc) { * The tool definition after which the tool menu item is to be added. */ function insertToolMenuElements(doc, toolDefinition, prevDef) { - let { key, menuitem } = createToolMenuElements(toolDefinition, doc); + let { key, appmenuitem, menuitem } = createToolMenuElements(toolDefinition, doc); if (key) { attachKeybindingsToBrowser(doc, key); } + let amp; + if (prevDef) { + let menuitem = doc.getElementById("appmenuitem_" + prevDef.id); + ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null; + } else { + ref = doc.getElementById("appmenu_devtools_separator"); + } + + if (ref) { + amp.parentNode.insertBefore(menuitem, ref); + } + let ref; if (prevDef) { let menuitem = doc.getElementById("menuitem_" + prevDef.id); @@ -220,6 +239,11 @@ function removeToolFromMenu(toolId, doc) { key.remove(); } + let appmenuitem = doc.getElementById("appmenuitem_" + toolId); + if (appmenuitem) { + appmenuitem.remove(); + } + let menuitem = doc.getElementById("menuitem_" + toolId); if (menuitem) { menuitem.remove(); @@ -235,6 +259,7 @@ exports.removeToolFromMenu = removeToolFromMenu; */ function addAllToolsToMenu(doc) { let fragKeys = doc.createDocumentFragment(); + let fragAppMenuItems = doc.createDocumentFragment(); let fragMenuItems = doc.createDocumentFragment(); for (let toolDefinition of gDevTools.getToolDefinitionArray()) { @@ -251,11 +276,17 @@ function addAllToolsToMenu(doc) { if (elements.key) { fragKeys.appendChild(elements.key); } + fragAppMenuItems.appendChild(elements.appmenuitem); fragMenuItems.appendChild(elements.menuitem); } attachKeybindingsToBrowser(doc, fragKeys); + let amps = doc.getElementById("appmenu_devtools_separator"); + if (amps) { + amps.parentNode.insertBefore(fragAppMenuItems, amps); + } + let mps = doc.getElementById("menu_devtools_separator"); if (mps) { mps.parentNode.insertBefore(fragMenuItems, mps); @@ -270,18 +301,28 @@ function addAllToolsToMenu(doc) { */ function addTopLevelItems(doc) { let keys = doc.createDocumentFragment(); + let appmenuItems = doc.createDocumentFragment(); let menuItems = doc.createDocumentFragment(); let { menuitems } = require("../menus"); for (let item of menuitems) { if (item.separator) { + let appseparator = doc.createElement("menuseparator"); + appseparator.id = "app" + item.id; let separator = doc.createElement("menuseparator"); separator.id = item.id; + appmenuItems.appendChild(appseparator); menuItems.appendChild(separator); } else { let { id, l10nKey } = item; // Create a <menuitem> + let appmenuitem = createMenuItem({ + doc, + id: "app" + id, + label: l10n(l10nKey + ".label"), + isCheckbox: item.checkbox + }); let menuitem = createMenuItem({ doc, id, @@ -289,7 +330,9 @@ function addTopLevelItems(doc) { accesskey: l10n(l10nKey + ".accesskey"), isCheckbox: item.checkbox }); + appmenuitem.addEventListener("command", item.oncommand); menuitem.addEventListener("command", item.oncommand); + appmenuItems.appendChild(appmenuitem); menuItems.appendChild(menuitem); if (item.key && l10nKey) { @@ -330,6 +373,9 @@ function addTopLevelItems(doc) { for (let node of keys.children) { nodes.push(node); } + for (let node of appmenuItems.children) { + nodes.push(node); + } for (let node of menuItems.children) { nodes.push(node); } @@ -337,15 +383,27 @@ function addTopLevelItems(doc) { attachKeybindingsToBrowser(doc, keys); + let appmenu = doc.getElementById("appmenu_webDeveloper_popup"); + if (appmenu) { + appmenu.appendChild(appmenuItems); + + // There is still "Page Source" menuitem hardcoded into browser.xul. Instead + // of manually inserting everything around it, move it to the expected + // position. + let appmenu_pageSource = doc.getElementById("appmenu_pageSource"); + let appmenu_endSeparator = doc.getElementById("appmenu_devToolsEndSeparator"); + appmenu.insertBefore(appmenu_pageSource, appmenu_endSeparator); + } + let menu = doc.getElementById("menuWebDeveloperPopup"); menu.appendChild(menuItems); // There is still "Page Source" menuitem hardcoded into browser.xul. Instead // of manually inserting everything around it, move it to the expected // position. - let pageSource = doc.getElementById("menu_pageSource"); - let endSeparator = doc.getElementById("devToolsEndSeparator"); - menu.insertBefore(pageSource, endSeparator); + let menu_pageSource = doc.getElementById("menu_pageSource"); + let menu_endSeparator = doc.getElementById("menu_devToolsEndSeparator"); + menu.insertBefore(menu_pageSource, menu_endSeparator); } /** diff --git a/devtools/client/framework/devtools-browser.js b/devtools/client/framework/devtools-browser.js index b9f4d92ba..fe40cdb32 100644 --- a/devtools/client/framework/devtools-browser.js +++ b/devtools/client/framework/devtools-browser.js @@ -8,7 +8,7 @@ * This is the main module loaded in Firefox desktop that handles browser * windows and coordinates devtools around each window. * - * This module is loaded lazily by devtools-clhandler.js, once the first + * This module is loaded lazily by devtools-startup.js, once the first * browser window is ready (i.e. fired browser-delayed-startup-finished event) **/ diff --git a/devtools/client/framework/test/browser_keybindings_01.js b/devtools/client/framework/test/browser_keybindings_01.js index 4e4effb07..134fb127c 100644 --- a/devtools/client/framework/test/browser_keybindings_01.js +++ b/devtools/client/framework/test/browser_keybindings_01.js @@ -8,6 +8,9 @@ const TEST_URL = "data:text/html,<html><head><title>Test for the " + "highlighter keybindings</title></head><body>" + "<h1>Keybindings!</h1></body></html>" + +const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser"); + function test() { waitForExplicitFinish(); diff --git a/devtools/client/menus.js b/devtools/client/menus.js index 7e36839da..1d2168967 100644 --- a/devtools/client/menus.js +++ b/devtools/client/menus.js @@ -183,9 +183,9 @@ exports.menuitems = [ } }, { separator: true, - id: "devToolsEndSeparator" + id: "menu_devToolsEndSeparator" }, - { id: "getMoreDevtools", + { id: "menu_getMoreDevtools", l10nKey: "getMoreDevtoolsCmd", oncommand(event) { let window = event.target.ownerDocument.defaultView; diff --git a/devtools/client/scratchpad/test/head.js b/devtools/client/scratchpad/test/head.js index 15619a169..955c037d7 100644 --- a/devtools/client/scratchpad/test/head.js +++ b/devtools/client/scratchpad/test/head.js @@ -9,6 +9,7 @@ const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {}); const {console} = Cu.import("resource://gre/modules/Console.jsm", {}); const {ScratchpadManager} = Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm", {}); const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const {gDevTools} = require("devtools/client/framework/devtools"); const Services = require("Services"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const flags = require("devtools/shared/flags"); diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/head.js b/devtools/client/webconsole/new-console-output/test/mochitest/head.js index b71eaec4f..049f3d1ce 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js @@ -14,6 +14,7 @@ Services.scriptloader.loadSubScript( var {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils"); const WEBCONSOLE_STRINGS_URI = "devtools/client/locales/webconsole.properties"; +var {HUDService} = require("devtools/client/webconsole/hudservice"); var WCUL10n = new WebConsoleUtils.L10n(WEBCONSOLE_STRINGS_URI); Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true); diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp index e7d362864..845669951 100644 --- a/docshell/base/nsAboutRedirector.cpp +++ b/docshell/base/nsAboutRedirector.cpp @@ -42,12 +42,12 @@ static RedirEntry kRedirMap[] = { { "buildconfig", "chrome://global/content/buildconfig.html", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::MAKE_LINKABLE + nsIAboutModule::MAKE_LINKABLE }, { "checkerboard", "chrome://global/content/aboutCheckerboard.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::ALLOW_SCRIPT + nsIAboutModule::ALLOW_SCRIPT }, { "config", "chrome://global/content/config.xul", 0 }, #ifdef MOZ_CRASHREPORTER @@ -67,7 +67,7 @@ static RedirEntry kRedirMap[] = { { "license", "chrome://global/content/license.html", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::MAKE_LINKABLE + nsIAboutModule::MAKE_LINKABLE }, { "logo", "chrome://branding/content/about.png", @@ -75,6 +75,13 @@ static RedirEntry kRedirMap[] = { // Linkable for testing reasons. nsIAboutModule::MAKE_LINKABLE }, +#ifdef MOZ_PHOENIX + { + "logopage", "chrome://global/content/logopage.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, +#endif { "memory", "chrome://global/content/aboutMemory.xhtml", nsIAboutModule::ALLOW_SCRIPT @@ -86,9 +93,9 @@ static RedirEntry kRedirMap[] = { { "neterror", "chrome://global/content/netError.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::URI_CAN_LOAD_IN_CHILD | - nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | + nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT }, { "networking", "chrome://global/content/aboutNetworking.xhtml", @@ -97,7 +104,7 @@ static RedirEntry kRedirMap[] = { { "newaddon", "chrome://mozapps/content/extensions/newaddon.xul", nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT + nsIAboutModule::HIDE_FROM_ABOUTABOUT }, { "performance", "chrome://global/content/aboutPerformance.xhtml", @@ -124,10 +131,10 @@ static RedirEntry kRedirMap[] = { { "srcdoc", "about:blank", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::HIDE_FROM_ABOUTABOUT | - // Needs to be linkable so content can touch its own srcdoc frames - nsIAboutModule::MAKE_LINKABLE | - nsIAboutModule::URI_CAN_LOAD_IN_CHILD + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + // Needs to be linkable so content can touch its own srcdoc frames + nsIAboutModule::MAKE_LINKABLE | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD }, { "support", "chrome://global/content/aboutSupport.xhtml", diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp index d43c305f9..e37ced4a9 100644 --- a/docshell/build/nsDocShellModule.cpp +++ b/docshell/build/nsDocShellModule.cpp @@ -174,6 +174,9 @@ const mozilla::Module::ContractIDEntry kDocShellContracts[] = { #endif { NS_ABOUT_MODULE_CONTRACTID_PREFIX "license", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "logo", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, +#ifdef MOZ_PHOENIX + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "logopage", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, +#endif { NS_ABOUT_MODULE_CONTRACTID_PREFIX "memory", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "mozilla", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 40fd008b9..710c7a76c 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -1159,7 +1159,7 @@ bool ModuleBuilder::processExport(frontend::ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_EXPORT) || pn->isKind(PNK_EXPORT_DEFAULT)); - MOZ_ASSERT(pn->getArity() == pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY); + MOZ_ASSERT(pn->getArity() == (pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY)); bool isDefault = pn->getKind() == PNK_EXPORT_DEFAULT; ParseNode* kid = isDefault ? pn->pn_left : pn->pn_kid; diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index eef6fd7ec..beff58e13 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2140,7 +2140,7 @@ ASTSerializer::exportDeclaration(ParseNode* pn, MutableHandleValue dst) MOZ_ASSERT(pn->isKind(PNK_EXPORT) || pn->isKind(PNK_EXPORT_FROM) || pn->isKind(PNK_EXPORT_DEFAULT)); - MOZ_ASSERT(pn->getArity() == pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY); + MOZ_ASSERT(pn->getArity() == (pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY)); MOZ_ASSERT_IF(pn->isKind(PNK_EXPORT_FROM), pn->pn_right->isKind(PNK_STRING)); RootedValue decl(cx, NullValue()); diff --git a/old-configure.in b/old-configure.in index 7c61ebf74..bd65c9940 100644 --- a/old-configure.in +++ b/old-configure.in @@ -5820,6 +5820,7 @@ MOZ_BRANDING_DIRECTORY=$MOZ_BRANDING_DIRECTORY MC_BASILISK=$MC_BASILISK MC_PALEMOON=$MC_PALEMOON MOZ_SANDBOX=$MOZ_SANDBOX +MOZ_EME=$MOZ_EME MOZ_WEBRTC=$MOZ_WEBRTC MOZ_SYSTEM_LIBEVENT=$MOZ_SYSTEM_LIBEVENT MOZ_SYSTEM_NSS=$MOZ_SYSTEM_NSS diff --git a/toolkit/components/protobuf/moz.build b/toolkit/components/protobuf/moz.build index b5015eb67..8cca3514c 100644 --- a/toolkit/components/protobuf/moz.build +++ b/toolkit/components/protobuf/moz.build @@ -117,10 +117,13 @@ DEFINES['GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER'] = True # Suppress warnings in third-party code. if CONFIG['GNU_CXX']: CXXFLAGS += [ - '-Wno-null-conversion', '-Wno-return-type', '-Wno-sign-compare', ] + if CONFIG['CLANG_CXX']: + CXXFLAGS += [ + '-Wno-null-conversion', + ] elif CONFIG['_MSC_VER']: CXXFLAGS += [ '-wd4005', # 'WIN32_LEAN_AND_MEAN' : macro redefinition diff --git a/toolkit/components/telemetry/TelemetryEnvironment.jsm b/toolkit/components/telemetry/TelemetryEnvironment.jsm index 2f4ac81ba..910d804ae 100644 --- a/toolkit/components/telemetry/TelemetryEnvironment.jsm +++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm @@ -153,7 +153,6 @@ const DEFAULT_ENVIRONMENT_PREFS = new Map([ ["dom.ipc.plugins.enabled", {what: RECORD_PREF_VALUE}], ["dom.ipc.processCount", {what: RECORD_PREF_VALUE, requiresRestart: true}], ["dom.max_script_run_time", {what: RECORD_PREF_VALUE}], - ["experiments.manifest.uri", {what: RECORD_PREF_VALUE}], ["extensions.autoDisableScopes", {what: RECORD_PREF_VALUE}], ["extensions.enabledScopes", {what: RECORD_PREF_VALUE}], ["extensions.blocklist.enabled", {what: RECORD_PREF_VALUE}], @@ -209,7 +208,6 @@ const PREF_E10S_COHORT = "e10s.rollout.cohort"; const COMPOSITOR_CREATED_TOPIC = "compositor:created"; const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = "distribution-customization-complete"; -const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed"; const GFX_FEATURES_READY_TOPIC = "gfx-features-ready"; const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified"; const SEARCH_SERVICE_TOPIC = "browser-search-service"; @@ -465,7 +463,6 @@ EnvironmentAddonBuilder.prototype = { watchForChanges: function() { this._loaded = true; AddonManager.addAddonListener(this); - Services.obs.addObserver(this, EXPERIMENTS_CHANGED_TOPIC, false); }, // AddonListener @@ -490,7 +487,6 @@ EnvironmentAddonBuilder.prototype = { // nsIObserver observe: function (aSubject, aTopic, aData) { this._environment._log.trace("observe - Topic " + aTopic); - this._checkForChanges("experiment-changed"); }, _checkForChanges: function(changeReason) { @@ -515,7 +511,6 @@ EnvironmentAddonBuilder.prototype = { _shutdownBlocker: function() { if (this._loaded) { AddonManager.removeAddonListener(this); - Services.obs.removeObserver(this, EXPERIMENTS_CHANGED_TOPIC); } return this._pendingTask; }, @@ -545,7 +540,6 @@ EnvironmentAddonBuilder.prototype = { theme: yield this._getActiveTheme(), activePlugins: this._getActivePlugins(), activeGMPlugins: yield this._getActiveGMPlugins(), - activeExperiment: this._getActiveExperiment(), persona: personaId, }; @@ -718,29 +712,7 @@ EnvironmentAddonBuilder.prototype = { } return activeGMPlugins; - }), - - /** - * Get the active experiment data in object form. - * @return Object containing the active experiment data. - */ - _getActiveExperiment: function () { - let experimentInfo = {}; - try { - let scope = {}; - Cu.import("resource:///modules/experiments/Experiments.jsm", scope); - let experiments = scope.Experiments.instance(); - let activeExperiment = experiments.getActiveExperimentID(); - if (activeExperiment) { - experimentInfo.id = activeExperiment; - experimentInfo.branch = experiments.getActiveExperimentBranch(); - } - } catch (e) { - // If this is not Firefox, the import will fail. - } - - return experimentInfo; - }, + }) }; function EnvironmentCache() { diff --git a/toolkit/content/aboutSupport.xhtml b/toolkit/content/aboutSupport.xhtml index e2885c8b8..a043dfe3b 100644 --- a/toolkit/content/aboutSupport.xhtml +++ b/toolkit/content/aboutSupport.xhtml @@ -504,39 +504,6 @@ </table> - <h2 class="major-section"> - &aboutSupport.experimentsTitle; - </h2> - - <table> - <thead> - <tr> - <th> - &aboutSupport.experimentName; - </th> - <th> - &aboutSupport.experimentId; - </th> - <th> - &aboutSupport.experimentDescription; - </th> - <th> - &aboutSupport.experimentActive; - </th> - <th> - &aboutSupport.experimentEndDate; - </th> - <th> - &aboutSupport.experimentHomepage; - </th> - <th> - &aboutSupport.experimentBranch; - </th> - </tr> - </thead> - <tbody id="experiments-tbody"> - </tbody> - </table> <!-- - - - - - - - - - - - - - - - - - - - - --> #if defined(MOZ_SANDBOX) diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index f1a222a58..946e499f1 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -54,8 +54,12 @@ toolkit.jar: #endif content/global/filepicker.properties content/global/globalOverlay.js + content/global/memoriam.xhtml +* content/global/mozilla.css content/global/mozilla.xhtml +#ifdef MOZ_PHOENIX content/global/logopage.xhtml +#endif content/global/process-content.js content/global/resetProfile.css content/global/resetProfile.js diff --git a/toolkit/content/memoriam.xhtml b/toolkit/content/memoriam.xhtml new file mode 100644 index 000000000..f1a1b474d --- /dev/null +++ b/toolkit/content/memoriam.xhtml @@ -0,0 +1,76 @@ +<!DOCTYPE html +[ + <!ENTITY % directionDTD SYSTEM "chrome://global/locale/global.dtd" > + %directionDTD; + <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> + %brandDTD; +]> + +<!-- 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/. --> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta charset='utf-8' /> + <title>Mozilla: In Memoriam</title> + +<style> +html { + background: maroon radial-gradient( circle, #a01010 0%, #800000 80%) center center / cover no-repeat; + color: white; + font-style: italic; + text-rendering: optimizeLegibility; + min-height: 100%; +} + +#moztext { + margin-top: 15%; + font-size: 1.1em; + font-family: serif; + text-align: center; + line-height: 1.5; +} + +#from { + font-size: 1.0em; + font-family: serif; + text-align: right; +} + +em { + font-size: 1.3em; + line-height: 0; +} + +a { + text-decoration: none; + color: white; +} +</style> +</head> + +<body dir="&locale.dir;"> + +<section> + <p id="moztext"> + <h1>Mozilla: In Memoriam</h1> + <br/> + Dedicated to the tireless developers who have come and gone.<br/> + To those who have put their heart and soul into Mozilla products.<br/> + To those who have seen their good intentions and hard work squandered.<br/> + To those who really cared about the user, and cared about usability.<br/> + To those who truly understood us and desired freedom, but were unheard.<br/> + To those who knew that change is inevitable, but loss of vision is not.<br/> + To those who were forced to give up the good fight.<br/> + <br/> + <em>Thank you.</em> &brandFullName; would not have been possible without you.<br/> + <br/> + </p> + + <p id="from"> + </p> +</section> + +</body> +</html>
\ No newline at end of file diff --git a/toolkit/content/mozilla.css b/toolkit/content/mozilla.css new file mode 100644 index 000000000..d5eae6415 --- /dev/null +++ b/toolkit/content/mozilla.css @@ -0,0 +1,36 @@ +html { +%ifdef MC_PALEMOON + background: #333399 radial-gradient( circle at 75% 25%, #6666b0 0%, #333399 40%, #111177 80%) center center / cover no-repeat; +%else + background: maroon radial-gradient( circle, #a01010 0%, #800000 80%) center center / cover no-repeat; +%endif + + color: white; + font-style: italic; + text-rendering: optimizeLegibility; + min-height: 100%; +} + +#moztext { + margin-top: 15%; + font-size: 1.1em; + font-family: serif; + text-align: center; + line-height: 1.5; +} + +#from { + font-size: 1.95em; + font-family: serif; + text-align: right; +} + +em { + font-size: 1.3em; + line-height: 0; +} + +a { + text-decoration: none; + color: white; +}
\ No newline at end of file diff --git a/toolkit/content/mozilla.xhtml b/toolkit/content/mozilla.xhtml index 2acfc9f5d..8c79b5ff9 100644 --- a/toolkit/content/mozilla.xhtml +++ b/toolkit/content/mozilla.xhtml @@ -15,39 +15,8 @@ <meta charset='utf-8' /> <title>&chronicles.title.55.2;</title> -<style> -html { - background: maroon radial-gradient( circle, #a01010 0%, #800000 80%) center center / cover no-repeat; - color: white; - font-style: italic; - text-rendering: optimizeLegibility; - min-height: 100%; -} - -#moztext { - margin-top: 15%; - font-size: 1.1em; - font-family: serif; - text-align: center; - line-height: 1.5; -} - -#from { - font-size: 1.95em; - font-family: serif; - text-align: right; -} - -em { - font-size: 1.3em; - line-height: 0; -} - -a { - text-decoration: none; - color: white; -} -</style> + <link rel="stylesheet" href="chrome://global/content/mozilla.css" + type="text/css"/> </head> <body dir="&locale.dir;"> diff --git a/toolkit/modules/Troubleshoot.jsm b/toolkit/modules/Troubleshoot.jsm index cc545b4c4..f259d9ae9 100644 --- a/toolkit/modules/Troubleshoot.jsm +++ b/toolkit/modules/Troubleshoot.jsm @@ -12,13 +12,6 @@ Cu.import("resource://gre/modules/AddonManager.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/AppConstants.jsm"); -var Experiments; -try { - Experiments = Cu.import("resource:///modules/experiments/Experiments.jsm").Experiments; -} -catch (e) { -} - // We use a preferences whitelist to make sure we only show preferences that // are useful for support and won't compromise the user's privacy. Note that // entries are *prefixes*: for example, "accessibility." applies to all prefs @@ -263,18 +256,6 @@ var dataProviders = { }); }, - experiments: function experiments(done) { - if (Experiments === undefined) { - done([]); - return; - } - - // getExperiments promises experiment history - Experiments.instance().getExperiments().then( - experiments => done(experiments) - ); - }, - modifiedPreferences: function modifiedPreferences(done) { done(getPrefList(name => Services.prefs.prefHasUserValue(name))); }, diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index a799eeebb..1d8d5bb0a 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -22,8 +22,6 @@ XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () { return Cu.import("resource://gre/modules/devtools/ToolboxProcess.jsm", {}). BrowserToolboxProcess; }); -XPCOMUtils.defineLazyModuleGetter(this, "Experiments", - "resource:///modules/experiments/Experiments.jsm"); const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; @@ -216,23 +214,6 @@ function isDiscoverEnabled() { return true; } -function getExperimentEndDate(aAddon) { - if (!("@mozilla.org/browser/experiments-service;1" in Cc)) { - return 0; - } - - if (!aAddon.isActive) { - return aAddon.endDate; - } - - let experiment = Experiments.instance().getActiveExperiment(); - if (!experiment) { - return 0; - } - - return experiment.endDate; -} - /** * Obtain the main DOMWindow for the current context. */ @@ -1316,28 +1297,7 @@ var gViewController = { doCommand: function cmd_neverActivateItem_doCommand(aAddon) { aAddon.userDisabled = true; } - }, - - cmd_experimentsLearnMore: { - isEnabled: function cmd_experimentsLearnMore_isEnabled() { - let mainWindow = getMainWindow(); - return mainWindow && "switchToTabHavingURI" in mainWindow; - }, - doCommand: function cmd_experimentsLearnMore_doCommand() { - let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL"); - openOptionsInTab(url); - }, - }, - - cmd_experimentsOpenTelemetryPreferences: { - isEnabled: function cmd_experimentsOpenTelemetryPreferences_isEnabled() { - return !!getMainWindowWithPreferencesPane(); - }, - doCommand: function cmd_experimentsOpenTelemetryPreferences_doCommand() { - let mainWindow = getMainWindowWithPreferencesPane(); - mainWindow.openAdvancedPreferences("dataChoicesTab"); - }, - }, + } }, supportsCommand: function gVC_supportsCommand(aCommand) { @@ -1468,10 +1428,6 @@ function createItem(aObj, aIsInstall, aIsRemote) { // the binding handles the rest item.setAttribute("value", aObj.id); - if (aObj.type == "experiment") { - item.endDate = getExperimentEndDate(aObj); - } - return item; } @@ -2679,13 +2635,6 @@ var gListView = { // the existing item if (aInstall.existingAddon) this.removeItem(aInstall, true); - - if (aInstall.addon.type == "experiment") { - let item = this.getListItemForID(aInstall.addon.id); - if (item) { - item.endDate = getExperimentEndDate(aInstall.addon); - } - } }, addItem: function gListView_addItem(aObj, aIsInstall) { @@ -2945,34 +2894,6 @@ var gDetailView = { } } - if (this._addon.type == "experiment") { - let prefix = "details.experiment."; - let active = this._addon.isActive; - - let stateKey = prefix + "state." + (active ? "active" : "complete"); - let node = document.getElementById("detail-experiment-state"); - node.value = gStrings.ext.GetStringFromName(stateKey); - - let now = Date.now(); - let end = getExperimentEndDate(this._addon); - let days = Math.abs(end - now) / (24 * 60 * 60 * 1000); - - let timeKey = prefix + "time."; - let timeMessage; - if (days < 1) { - timeKey += (active ? "endsToday" : "endedToday"); - timeMessage = gStrings.ext.GetStringFromName(timeKey); - } else { - timeKey += (active ? "daysRemaining" : "daysPassed"); - days = Math.round(days); - let timeString = gStrings.ext.GetStringFromName(timeKey); - timeMessage = PluralForm.get(days, timeString) - .replace("#1", days); - } - - document.getElementById("detail-experiment-time").value = timeMessage; - } - this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() { this.updateState(); gViewController.notifyViewChanged(); diff --git a/toolkit/mozapps/extensions/test/browser/browser_experiments.js b/toolkit/mozapps/extensions/test/browser/browser_experiments.js deleted file mode 100644 index 72d0ca83e..000000000 --- a/toolkit/mozapps/extensions/test/browser/browser_experiments.js +++ /dev/null @@ -1,645 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -Components.utils.import("resource://gre/modules/Promise.jsm", this); - -let {AddonTestUtils} = Components.utils.import("resource://testing-common/AddonManagerTesting.jsm", {}); -let {HttpServer} = Components.utils.import("resource://testing-common/httpd.js", {}); - -let gManagerWindow; -let gCategoryUtilities; -let gExperiments; -let gHttpServer; - -let gSavedManifestURI; -let gIsEnUsLocale; - -const SEC_IN_ONE_DAY = 24 * 60 * 60; -const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; - -function getExperimentAddons() { - let deferred = Promise.defer(); - AddonManager.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - return deferred.promise; -} - -function getInstallItem() { - let doc = gManagerWindow.document; - let view = doc.getElementById("view-port").selectedPanel; - let list = doc.getElementById("addon-list"); - - let node = list.firstChild; - while (node) { - if (node.getAttribute("status") == "installing") { - return node; - } - node = node.nextSibling; - } - - return null; -} - -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 openDetailsView(aId) { - let item = get_addon_element(gManagerWindow, aId); - Assert.ok(item, "Should have got add-on element."); - is_element_visible(item, "Add-on element should be visible."); - - EventUtils.synthesizeMouseAtCenter(item, { clickCount: 1 }, gManagerWindow); - EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, gManagerWindow); - - let deferred = Promise.defer(); - wait_for_view_load(gManagerWindow, deferred.resolve); - return deferred.promise; -} - -function clickRemoveButton(addonElement) { - let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "remove-btn"); - if (!btn) { - return Promise.reject(); - } - - EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow); - let deferred = Promise.defer(); - setTimeout(deferred.resolve, 0); - return deferred; -} - -function clickUndoButton(addonElement) { - let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "undo-btn"); - if (!btn) { - return Promise.reject(); - } - - EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow); - let deferred = Promise.defer(); - setTimeout(deferred.resolve, 0); - return deferred; -} - -add_task(function* initializeState() { - gManagerWindow = yield open_manager(); - gCategoryUtilities = new CategoryUtilities(gManagerWindow); - - registerCleanupFunction(() => { - Services.prefs.clearUserPref("experiments.enabled"); - if (gHttpServer) { - gHttpServer.stop(() => {}); - if (gSavedManifestURI !== undefined) { - Services.prefs.setCharPref("experments.manifest.uri", gSavedManifestURI); - } - } - if (gExperiments) { - let tmp = {}; - Cu.import("resource:///modules/experiments/Experiments.jsm", tmp); - gExperiments._policy = new tmp.Experiments.Policy(); - } - }); - - let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry); - gIsEnUsLocale = chrome.getSelectedLocale("global") == "en-US"; - - // The Experiments Manager will interfere with us by preventing installs - // of experiments it doesn't know about. We remove it from the equation - // because here we are only concerned with core Addon Manager operation, - // not the superset Experiments Manager has imposed. - if ("@mozilla.org/browser/experiments-service;1" in Components.classes) { - let tmp = {}; - Cu.import("resource:///modules/experiments/Experiments.jsm", tmp); - // There is a race condition between XPCOM service initialization and - // this test running. We have to initialize the instance first, then - // uninitialize it to prevent this. - gExperiments = tmp.Experiments.instance(); - yield gExperiments._mainTask; - yield gExperiments.uninit(); - } -}); - -// On an empty profile with no experiments, the experiment category -// should be hidden. -add_task(function* testInitialState() { - Assert.ok(gCategoryUtilities.get("experiment", false), "Experiment tab is defined."); - Assert.ok(!gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab hidden by default."); -}); - -add_task(function* testExperimentInfoNotVisible() { - yield gCategoryUtilities.openType("extension"); - let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0]; - is_element_hidden(el, "Experiment info not visible on other types."); -}); - -// If we have an active experiment, we should see the experiments tab -// and that tab should have some messages. -add_task(function* testActiveExperiment() { - let addon = yield install_addon("addons/browser_experiment1.xpi"); - - Assert.ok(addon.userDisabled, "Add-on is disabled upon initial install."); - Assert.equal(addon.isActive, false, "Add-on is not active."); - - Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible."); - - yield gCategoryUtilities.openType("experiment"); - let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0]; - is_element_visible(el, "Experiment info is visible on experiment tab."); -}); - -add_task(function* testExperimentLearnMore() { - // Actual URL is irrelevant. - Services.prefs.setCharPref("toolkit.telemetry.infoURL", - "http://mochi.test:8888/server.js"); - - yield gCategoryUtilities.openType("experiment"); - let btn = gManagerWindow.document.getElementById("experiments-learn-more"); - - if (!gUseInContentUI) { - is_element_hidden(btn, "Learn more button hidden if not using in-content UI."); - Services.prefs.clearUserPref("toolkit.telemetry.infoURL"); - - return; - } - - is_element_visible(btn, "Learn more button visible."); - - let deferred = Promise.defer(); - window.addEventListener("DOMContentLoaded", function onLoad(event) { - info("Telemetry privacy policy window opened."); - window.removeEventListener("DOMContentLoaded", onLoad, false); - - let browser = gBrowser.selectedBrowser; - let expected = Services.prefs.getCharPref("toolkit.telemetry.infoURL"); - Assert.equal(browser.currentURI.spec, expected, "New tab should have loaded privacy policy."); - browser.contentWindow.close(); - - Services.prefs.clearUserPref("toolkit.telemetry.infoURL"); - - deferred.resolve(); - }, false); - - info("Opening telemetry privacy policy."); - EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow); - - yield deferred.promise; -}); - -add_task(function* testOpenPreferences() { - yield gCategoryUtilities.openType("experiment"); - let btn = gManagerWindow.document.getElementById("experiments-change-telemetry"); - if (!gUseInContentUI) { - is_element_hidden(btn, "Change telemetry button not enabled in out of window UI."); - info("Skipping preferences open test because not using in-content UI."); - return; - } - - is_element_visible(btn, "Change telemetry button visible in in-content UI."); - - let deferred = Promise.defer(); - Services.obs.addObserver(function observer(prefWin, topic, data) { - Services.obs.removeObserver(observer, "advanced-pane-loaded"); - info("Advanced preference pane opened."); - executeSoon(function() { - // We want this test to fail if the preferences pane changes. - let el = prefWin.document.getElementById("dataChoicesPanel"); - is_element_visible(el); - - prefWin.close(); - info("Closed preferences pane."); - - deferred.resolve(); - }); - }, "advanced-pane-loaded", false); - - info("Loading preferences pane."); - EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow); - - yield deferred.promise; -}); - -add_task(function* testButtonPresence() { - yield gCategoryUtilities.openType("experiment"); - let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn"); - // Corresponds to the uninstall permission. - is_element_visible(el, "Remove button is visible."); - // Corresponds to lack of disable permission. - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn"); - is_element_hidden(el, "Disable button not visible."); - // Corresponds to lack of enable permission. - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn"); - is_element_hidden(el, "Enable button not visible."); -}); - -// Remove the add-on we've been testing with. -add_task(function* testCleanup() { - yield AddonTestUtils.uninstallAddonByID("test-experiment1@experiments.mozilla.org"); - // Verify some conditions, just in case. - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "No experiment add-ons are installed."); -}); - -// The following tests should ideally live in browser/experiments/. However, -// they rely on some of the helper functions from head.js, which can't easily -// be consumed from other directories. So, they live here. - -add_task(function* testActivateExperiment() { - if (!gExperiments) { - info("Skipping experiments test because that feature isn't available."); - return; - } - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let root = "http://localhost:" + gHttpServer.identity.primaryPort + "/"; - gHttpServer.registerPathHandler("/manifest", (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify({ - "version": 1, - "experiments": [ - { - id: "experiment-1", - xpiURL: TESTROOT + "addons/browser_experiment1.xpi", - xpiHash: "IRRELEVANT", - startTime: Date.now() / 1000 - 3600, - endTime: Date.now() / 1000 + 3600, - maxActiveSeconds: 600, - appName: [Services.appinfo.name], - channel: [gExperiments._policy.updatechannel()], - }, - ], - })); - response.processAsync(); - response.finish(); - }); - - gSavedManifestURI = Services.prefs.getCharPref("experiments.manifest.uri"); - Services.prefs.setCharPref("experiments.manifest.uri", root + "manifest"); - - // We need to remove the cache file to help ensure consistent state. - yield OS.File.remove(gExperiments._cacheFilePath); - - Services.prefs.setBoolPref("experiments.enabled", true); - - info("Initializing experiments service."); - yield gExperiments.init(); - info("Experiments service finished first run."); - - // Check conditions, just to be sure. - let experiments = yield gExperiments.getExperiments(); - Assert.equal(experiments.length, 0, "No experiments known to the service."); - - // This makes testing easier. - gExperiments._policy.ignoreHashes = true; - - info("Manually updating experiments manifest."); - yield gExperiments.updateManifest(); - info("Experiments update complete."); - - let deferred = Promise.defer(); - gHttpServer.stop(() => { - gHttpServer = null; - - info("getting experiment by ID"); - AddonManager.getAddonByID("test-experiment1@experiments.mozilla.org", (addon) => { - Assert.ok(addon, "Add-on installed via Experiments manager."); - - deferred.resolve(); - }); - }); - - yield deferred.promise; - - Assert.ok(gCategoryUtilities.isTypeVisible, "experiment", "Experiment tab visible."); - yield gCategoryUtilities.openType("experiment"); - let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0]; - is_element_visible(el, "Experiment info is visible on experiment tab."); -}); - -add_task(function testDeactivateExperiment() { - if (!gExperiments) { - return; - } - - // Fake an empty manifest to purge data from previous manifest. - yield gExperiments._updateExperiments({ - "version": 1, - "experiments": [], - }); - - yield gExperiments.disableExperiment("testing"); - - // We should have a record of the previously-active experiment. - let experiments = yield gExperiments.getExperiments(); - Assert.equal(experiments.length, 1, "1 experiment is known."); - Assert.equal(experiments[0].active, false, "Experiment is not active."); - - // We should have a previous experiment in the add-ons manager. - let deferred = Promise.defer(); - AddonManager.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - let addons = yield deferred.promise; - Assert.equal(addons.length, 1, "1 experiment add-on known."); - Assert.ok(addons[0].appDisabled, "It is a previous experiment."); - Assert.equal(addons[0].id, "experiment-1", "Add-on ID matches expected."); - - // Verify the UI looks sane. - - Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible."); - let item = get_addon_element(gManagerWindow, "experiment-1"); - Assert.ok(item, "Got add-on element."); - Assert.ok(!item.active, "Element should not be active."); - item.parentNode.ensureElementIsVisible(item); - - // User control buttons should not be present because previous experiments - // should have no permissions. - let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn"); - is_element_hidden(el, "Remove button is not visible."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn"); - is_element_hidden(el, "Disable button is not visible."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn"); - is_element_hidden(el, "Enable button is not visible."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "preferences-btn"); - is_element_hidden(el, "Preferences button is not visible."); -}); - -add_task(function testActivateRealExperiments() { - if (!gExperiments) { - info("Skipping experiments test because that feature isn't available."); - return; - } - - yield gExperiments._updateExperiments({ - "version": 1, - "experiments": [ - { - id: "experiment-2", - xpiURL: TESTROOT + "addons/browser_experiment1.xpi", - xpiHash: "IRRELEVANT", - startTime: Date.now() / 1000 - 3600, - endTime: Date.now() / 1000 + 3600, - maxActiveSeconds: 600, - appName: [Services.appinfo.name], - channel: [gExperiments._policy.updatechannel()], - }, - ], - }); - yield gExperiments._run(); - - // Check the active experiment. - - let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Active"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Less than a day remaining"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container"); - is_element_hidden(el, "error-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container"); - is_element_hidden(el, "warning-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container"); - is_element_hidden(el, "pending-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version"); - is_element_hidden(el, "version should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix"); - is_element_hidden(el, "disabled-postfix should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix"); - is_element_hidden(el, "update-postfix should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet"); - is_element_visible(el, "experiment-bullet should be visible."); - - // Check the previous experiment. - - item = get_addon_element(gManagerWindow, "experiment-1"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Complete"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Less than a day ago"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container"); - is_element_hidden(el, "error-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container"); - is_element_hidden(el, "warning-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container"); - is_element_hidden(el, "pending-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version"); - is_element_hidden(el, "version should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix"); - is_element_hidden(el, "disabled-postfix should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix"); - is_element_hidden(el, "update-postfix should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet"); - is_element_visible(el, "experiment-bullet should be visible."); - - // Install an "older" experiment. - - yield gExperiments.disableExperiment("experiment-2"); - - let now = Date.now(); - let fakeNow = now - 5 * MS_IN_ONE_DAY; - defineNow(gExperiments._policy, fakeNow); - - yield gExperiments._updateExperiments({ - "version": 1, - "experiments": [ - { - id: "experiment-3", - xpiURL: TESTROOT + "addons/browser_experiment1.xpi", - xpiHash: "IRRELEVANT", - startTime: fakeNow / 1000 - SEC_IN_ONE_DAY, - endTime: now / 1000 + 10 * SEC_IN_ONE_DAY, - maxActiveSeconds: 100 * SEC_IN_ONE_DAY, - appName: [Services.appinfo.name], - channel: [gExperiments._policy.updatechannel()], - }, - ], - }); - yield gExperiments._run(); - - // Check the active experiment. - - item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Active"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "10 days remaining"); - } - - // Disable it and check it's previous experiment entry. - - yield gExperiments.disableExperiment("experiment-3"); - - item = get_addon_element(gManagerWindow, "experiment-3"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Complete"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "5 days ago"); - } -}); - -add_task(function testDetailView() { - if (!gExperiments) { - info("Skipping experiments test because that feature isn't available."); - return; - } - - defineNow(gExperiments._policy, Date.now()); - yield gExperiments._updateExperiments({ - "version": 1, - "experiments": [ - { - id: "experiment-4", - xpiURL: TESTROOT + "addons/browser_experiment1.xpi", - xpiHash: "IRRELEVANT", - startTime: Date.now() / 1000 - 3600, - endTime: Date.now() / 1000 + 3600, - maxActiveSeconds: 600, - appName: [Services.appinfo.name], - channel: [gExperiments._policy.updatechannel()], - }, - ], - }); - yield gExperiments._run(); - - // Check active experiment. - - yield openDetailsView("test-experiment1@experiments.mozilla.org"); - - let el = gManagerWindow.document.getElementById("detail-experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Active"); - } - - el = gManagerWindow.document.getElementById("detail-experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Less than a day remaining"); - } - - el = gManagerWindow.document.getElementById("detail-version"); - is_element_hidden(el, "detail-version should be hidden."); - el = gManagerWindow.document.getElementById("detail-creator"); - is_element_hidden(el, "detail-creator should be hidden."); - el = gManagerWindow.document.getElementById("detail-experiment-bullet"); - is_element_visible(el, "experiment-bullet should be visible."); - - // Check previous experiment. - - yield gCategoryUtilities.openType("experiment"); - yield openDetailsView("experiment-3"); - - el = gManagerWindow.document.getElementById("detail-experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Complete"); - } - - el = gManagerWindow.document.getElementById("detail-experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "5 days ago"); - } - - el = gManagerWindow.document.getElementById("detail-version"); - is_element_hidden(el, "detail-version should be hidden."); - el = gManagerWindow.document.getElementById("detail-creator"); - is_element_hidden(el, "detail-creator should be hidden."); - el = gManagerWindow.document.getElementById("detail-experiment-bullet"); - is_element_visible(el, "experiment-bullet should be visible."); -}); - -add_task(function* testRemoveAndUndo() { - if (!gExperiments) { - info("Skipping experiments test because that feature isn't available."); - return; - } - - yield gCategoryUtilities.openType("experiment"); - - let addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(addon, "Got add-on element."); - - yield clickRemoveButton(addon); - addon.parentNode.ensureElementIsVisible(addon); - - let el = gManagerWindow.document.getAnonymousElementByAttribute(addon, "class", "pending"); - is_element_visible(el, "Uninstall undo information should be visible."); - - yield clickUndoButton(addon); - addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(addon, "Got add-on element."); -}); - -add_task(function* testCleanup() { - if (gExperiments) { - Services.prefs.clearUserPref("experiments.enabled"); - Services.prefs.setCharPref("experiments.manifest.uri", gSavedManifestURI); - - // We perform the uninit/init cycle to purge any leftover state. - yield OS.File.remove(gExperiments._cacheFilePath); - yield gExperiments.uninit(); - yield gExperiments.init(); - } - - // Check post-conditions. - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "No experiment add-ons are installed."); - - yield close_manager(gManagerWindow); -}); diff --git a/toolkit/mozapps/webextensions/content/extensions.js b/toolkit/mozapps/webextensions/content/extensions.js index 5e428fe17..3159eb1e1 100644 --- a/toolkit/mozapps/webextensions/content/extensions.js +++ b/toolkit/mozapps/webextensions/content/extensions.js @@ -29,9 +29,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", XPCOMUtils.defineLazyModuleGetter(this, "Preferences", "resource://gre/modules/Preferences.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Experiments", - "resource:///modules/experiments/Experiments.jsm"); - const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; const PREF_XPI_ENABLED = "xpinstall.enabled"; @@ -297,23 +294,6 @@ function isDiscoverEnabled() { return true; } -function getExperimentEndDate(aAddon) { - if (!("@mozilla.org/browser/experiments-service;1" in Cc)) { - return 0; - } - - if (!aAddon.isActive) { - return aAddon.endDate; - } - - let experiment = Experiments.instance().getActiveExperiment(); - if (!experiment) { - return 0; - } - - return experiment.endDate; -} - /** * Obtain the main DOMWindow for the current context. */ @@ -1444,27 +1424,6 @@ var gViewController = { } }, - cmd_experimentsLearnMore: { - isEnabled: function() { - let mainWindow = getMainWindow(); - return mainWindow && "switchToTabHavingURI" in mainWindow; - }, - doCommand: function() { - let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL"); - openOptionsInTab(url); - }, - }, - - cmd_experimentsOpenTelemetryPreferences: { - isEnabled: function() { - return !!getMainWindowWithPreferencesPane(); - }, - doCommand: function() { - let mainWindow = getMainWindowWithPreferencesPane(); - mainWindow.openAdvancedPreferences("dataChoicesTab"); - }, - }, - cmd_showUnsignedExtensions: { isEnabled: function() { return true; @@ -1577,10 +1536,6 @@ function shouldShowVersionNumber(aAddon) { if (!aAddon.version) return false; - // The version number is hidden for experiments. - if (aAddon.type == "experiment") - return false; - // The version number is hidden for lightweight themes. if (aAddon.type == "theme") return !/@personas\.mozilla\.org$/.test(aAddon.id); @@ -1614,10 +1569,6 @@ function createItem(aObj, aIsInstall, aIsRemote) { // the binding handles the rest item.setAttribute("value", aObj.id); - if (aObj.type == "experiment") { - item.endDate = getExperimentEndDate(aObj); - } - return item; } @@ -2861,13 +2812,6 @@ var gListView = { // the existing item if (aInstall.existingAddon) this.removeItem(aInstall, true); - - if (aInstall.addon.type == "experiment") { - let item = this.getListItemForID(aInstall.addon.id); - if (item) { - item.endDate = getExperimentEndDate(aInstall.addon); - } - } }, addItem: function(aObj, aIsInstall) { @@ -3126,34 +3070,6 @@ var gDetailView = { } } - if (this._addon.type == "experiment") { - let prefix = "details.experiment."; - let active = this._addon.isActive; - - let stateKey = prefix + "state." + (active ? "active" : "complete"); - let node = document.getElementById("detail-experiment-state"); - node.value = gStrings.ext.GetStringFromName(stateKey); - - let now = Date.now(); - let end = getExperimentEndDate(this._addon); - let days = Math.abs(end - now) / (24 * 60 * 60 * 1000); - - let timeKey = prefix + "time."; - let timeMessage; - if (days < 1) { - timeKey += (active ? "endsToday" : "endedToday"); - timeMessage = gStrings.ext.GetStringFromName(timeKey); - } else { - timeKey += (active ? "daysRemaining" : "daysPassed"); - days = Math.round(days); - let timeString = gStrings.ext.GetStringFromName(timeKey); - timeMessage = PluralForm.get(days, timeString) - .replace("#1", days); - } - - document.getElementById("detail-experiment-time").value = timeMessage; - } - this.fillSettingsRows(aScrollToPreferences, (function() { this.updateState(); gViewController.notifyViewChanged(); diff --git a/toolkit/mozapps/webextensions/internal/XPIProvider.jsm b/toolkit/mozapps/webextensions/internal/XPIProvider.jsm index 87e09cef1..7c3cb6763 100644 --- a/toolkit/mozapps/webextensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/webextensions/internal/XPIProvider.jsm @@ -175,6 +175,8 @@ const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest"; const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; const TOOLKIT_ID = "toolkit@mozilla.org"; +const WEBEXTENSIONS_ID = "webextensions@mozilla.org"; +const WEBEXTENSIONS_VERSION = "52.0"; const XPI_SIGNATURE_CHECK_PERIOD = 24 * 60 * 60; @@ -1037,7 +1039,7 @@ var loadManifestFromWebManifest = Task.async(function*(aUri) { delete addon.defaultLocale.locales; addon.targetApplications = [{ - id: TOOLKIT_ID, + id: WEBEXTENSIONS_ID, minVersion: bss.strict_min_version, maxVersion: bss.strict_max_version, }]; @@ -7228,6 +7230,8 @@ AddonInternal.prototype = { version = aAppVersion; else if (app.id == TOOLKIT_ID) version = aPlatformVersion + else if (app.id == WEBEXTENSIONS_ID) + version = WEBEXTENSIONS_VERSION // Only extensions and dictionaries can be compatible by default; themes // and language packs always use strict compatibility checking. @@ -7250,7 +7254,7 @@ AddonInternal.prototype = { let minCompatVersion; if (app.id == Services.appinfo.ID) minCompatVersion = XPIProvider.minCompatibleAppVersion; - else if (app.id == TOOLKIT_ID) + else if (app.id == TOOLKIT_ID || app.id == WEBEXTENSIONS_ID) minCompatVersion = XPIProvider.minCompatiblePlatformVersion; if (minCompatVersion && @@ -7269,7 +7273,7 @@ AddonInternal.prototype = { for (let targetApp of this.targetApplications) { if (targetApp.id == Services.appinfo.ID) return targetApp; - if (targetApp.id == TOOLKIT_ID) + if (targetApp.id == TOOLKIT_ID || targetApp.id == WEBEXTENSIONS_ID) app = targetApp; } return app; diff --git a/toolkit/mozapps/webextensions/test/browser/browser_experiments.js b/toolkit/mozapps/webextensions/test/browser/browser_experiments.js deleted file mode 100644 index 18a548de5..000000000 --- a/toolkit/mozapps/webextensions/test/browser/browser_experiments.js +++ /dev/null @@ -1,654 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -Components.utils.import("resource://gre/modules/Promise.jsm", this); - -var {AddonManagerTesting} = Components.utils.import("resource://testing-common/AddonManagerTesting.jsm", {}); -var {HttpServer} = Components.utils.import("resource://testing-common/httpd.js", {}); - -var gManagerWindow; -var gCategoryUtilities; -var gExperiments; -var gHttpServer; - -var gSavedManifestURI; -var gIsEnUsLocale; - -const SEC_IN_ONE_DAY = 24 * 60 * 60; -const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; - -function getExperimentAddons() { - let deferred = Promise.defer(); - AddonManager.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - return deferred.promise; -} - -function getInstallItem() { - let doc = gManagerWindow.document; - let view = get_current_view(gManagerWindow); - let list = doc.getElementById("addon-list"); - - let node = list.firstChild; - while (node) { - if (node.getAttribute("status") == "installing") { - return node; - } - node = node.nextSibling; - } - - return null; -} - -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 openDetailsView(aId) { - let item = get_addon_element(gManagerWindow, aId); - Assert.ok(item, "Should have got add-on element."); - is_element_visible(item, "Add-on element should be visible."); - - EventUtils.synthesizeMouseAtCenter(item, { clickCount: 1 }, gManagerWindow); - EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, gManagerWindow); - - let deferred = Promise.defer(); - wait_for_view_load(gManagerWindow, deferred.resolve); - return deferred.promise; -} - -function clickRemoveButton(addonElement) { - let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "remove-btn"); - if (!btn) { - return Promise.reject(); - } - - EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow); - let deferred = Promise.defer(); - setTimeout(deferred.resolve, 0); - return deferred; -} - -function clickUndoButton(addonElement) { - let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "undo-btn"); - if (!btn) { - return Promise.reject(); - } - - EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow); - let deferred = Promise.defer(); - setTimeout(deferred.resolve, 0); - return deferred; -} - -add_task(function* initializeState() { - gManagerWindow = yield open_manager(); - gCategoryUtilities = new CategoryUtilities(gManagerWindow); - - registerCleanupFunction(() => { - Services.prefs.clearUserPref("experiments.enabled"); - Services.prefs.clearUserPref("toolkit.telemetry.enabled"); - if (gHttpServer) { - gHttpServer.stop(() => {}); - if (gSavedManifestURI !== undefined) { - Services.prefs.setCharPref("experments.manifest.uri", gSavedManifestURI); - } - } - if (gExperiments) { - let tmp = {}; - Cu.import("resource:///modules/experiments/Experiments.jsm", tmp); - gExperiments._policy = new tmp.Experiments.Policy(); - } - }); - - let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry); - gIsEnUsLocale = chrome.getSelectedLocale("global") == "en-US"; - - // The Experiments Manager will interfere with us by preventing installs - // of experiments it doesn't know about. We remove it from the equation - // because here we are only concerned with core Addon Manager operation, - // not the superset Experiments Manager has imposed. - if ("@mozilla.org/browser/experiments-service;1" in Components.classes) { - let tmp = {}; - Cu.import("resource:///modules/experiments/Experiments.jsm", tmp); - // There is a race condition between XPCOM service initialization and - // this test running. We have to initialize the instance first, then - // uninitialize it to prevent this. - gExperiments = tmp.Experiments.instance(); - yield gExperiments._mainTask; - yield gExperiments.uninit(); - } -}); - -// On an empty profile with no experiments, the experiment category -// should be hidden. -add_task(function* testInitialState() { - Assert.ok(gCategoryUtilities.get("experiment", false), "Experiment tab is defined."); - Assert.ok(!gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab hidden by default."); -}); - -add_task(function* testExperimentInfoNotVisible() { - yield gCategoryUtilities.openType("extension"); - let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0]; - is_element_hidden(el, "Experiment info not visible on other types."); -}); - -// If we have an active experiment, we should see the experiments tab -// and that tab should have some messages. -add_task(function* testActiveExperiment() { - let addon = yield install_addon("addons/browser_experiment1.xpi"); - - Assert.ok(addon.userDisabled, "Add-on is disabled upon initial install."); - Assert.equal(addon.isActive, false, "Add-on is not active."); - - Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible."); - - yield gCategoryUtilities.openType("experiment"); - let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0]; - is_element_visible(el, "Experiment info is visible on experiment tab."); -}); - -add_task(function* testExperimentLearnMore() { - // Actual URL is irrelevant. - Services.prefs.setCharPref("toolkit.telemetry.infoURL", - "http://mochi.test:8888/server.js"); - - yield gCategoryUtilities.openType("experiment"); - let btn = gManagerWindow.document.getElementById("experiments-learn-more"); - - if (!gUseInContentUI) { - is_element_hidden(btn, "Learn more button hidden if not using in-content UI."); - Services.prefs.clearUserPref("toolkit.telemetry.infoURL"); - - return; - } - - is_element_visible(btn, "Learn more button visible."); - - let deferred = Promise.defer(); - window.addEventListener("DOMContentLoaded", function onLoad(event) { - info("Telemetry privacy policy window opened."); - window.removeEventListener("DOMContentLoaded", onLoad, false); - - let browser = gBrowser.selectedBrowser; - let expected = Services.prefs.getCharPref("toolkit.telemetry.infoURL"); - Assert.equal(browser.currentURI.spec, expected, "New tab should have loaded privacy policy."); - browser.contentWindow.close(); - - Services.prefs.clearUserPref("toolkit.telemetry.infoURL"); - - deferred.resolve(); - }, false); - - info("Opening telemetry privacy policy."); - EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow); - - yield deferred.promise; -}); - -add_task(function* testOpenPreferences() { - yield gCategoryUtilities.openType("experiment"); - let btn = gManagerWindow.document.getElementById("experiments-change-telemetry"); - if (!gUseInContentUI) { - is_element_hidden(btn, "Change telemetry button not enabled in out of window UI."); - info("Skipping preferences open test because not using in-content UI."); - return; - } - - is_element_visible(btn, "Change telemetry button visible in in-content UI."); - - let deferred = Promise.defer(); - Services.obs.addObserver(function observer(prefWin, topic, data) { - Services.obs.removeObserver(observer, "advanced-pane-loaded"); - info("Advanced preference pane opened."); - executeSoon(function() { - // We want this test to fail if the preferences pane changes. - let el = prefWin.document.getElementById("dataChoicesPanel"); - is_element_visible(el); - - prefWin.close(); - info("Closed preferences pane."); - - deferred.resolve(); - }); - }, "advanced-pane-loaded", false); - - info("Loading preferences pane."); - // We need to focus before synthesizing the mouse event (bug 1240052) as - // synthesizeMouseAtCenter currently only synthesizes the mouse in the child process. - // This can cause some subtle differences if the child isn't focused. - yield SimpleTest.promiseFocus(); - yield BrowserTestUtils.synthesizeMouseAtCenter("#experiments-change-telemetry", {}, - gBrowser.selectedBrowser); - - yield deferred.promise; -}); - -add_task(function* testButtonPresence() { - yield gCategoryUtilities.openType("experiment"); - let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn"); - // Corresponds to the uninstall permission. - is_element_visible(el, "Remove button is visible."); - // Corresponds to lack of disable permission. - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn"); - is_element_hidden(el, "Disable button not visible."); - // Corresponds to lack of enable permission. - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn"); - is_element_hidden(el, "Enable button not visible."); -}); - -// Remove the add-on we've been testing with. -add_task(function* testCleanup() { - yield AddonManagerTesting.uninstallAddonByID("test-experiment1@experiments.mozilla.org"); - // Verify some conditions, just in case. - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "No experiment add-ons are installed."); -}); - -// The following tests should ideally live in browser/experiments/. However, -// they rely on some of the helper functions from head.js, which can't easily -// be consumed from other directories. So, they live here. - -add_task(function* testActivateExperiment() { - if (!gExperiments) { - info("Skipping experiments test because that feature isn't available."); - return; - } - - gHttpServer = new HttpServer(); - gHttpServer.start(-1); - let root = "http://localhost:" + gHttpServer.identity.primaryPort + "/"; - gHttpServer.registerPathHandler("/manifest", (request, response) => { - response.setStatusLine(null, 200, "OK"); - response.write(JSON.stringify({ - "version": 1, - "experiments": [ - { - id: "experiment-1", - xpiURL: TESTROOT + "addons/browser_experiment1.xpi", - xpiHash: "IRRELEVANT", - startTime: Date.now() / 1000 - 3600, - endTime: Date.now() / 1000 + 3600, - maxActiveSeconds: 600, - appName: [Services.appinfo.name], - channel: [gExperiments._policy.updatechannel()], - }, - ], - })); - response.processAsync(); - response.finish(); - }); - - gSavedManifestURI = Services.prefs.getCharPref("experiments.manifest.uri"); - Services.prefs.setCharPref("experiments.manifest.uri", root + "manifest"); - - // We need to remove the cache file to help ensure consistent state. - yield OS.File.remove(gExperiments._cacheFilePath); - - Services.prefs.setBoolPref("toolkit.telemetry.enabled", true); - Services.prefs.setBoolPref("experiments.enabled", true); - - info("Initializing experiments service."); - yield gExperiments.init(); - info("Experiments service finished first run."); - - // Check conditions, just to be sure. - let experiments = yield gExperiments.getExperiments(); - Assert.equal(experiments.length, 0, "No experiments known to the service."); - - // This makes testing easier. - gExperiments._policy.ignoreHashes = true; - - info("Manually updating experiments manifest."); - yield gExperiments.updateManifest(); - info("Experiments update complete."); - - let deferred = Promise.defer(); - gHttpServer.stop(() => { - gHttpServer = null; - - info("getting experiment by ID"); - AddonManager.getAddonByID("test-experiment1@experiments.mozilla.org", (addon) => { - Assert.ok(addon, "Add-on installed via Experiments manager."); - - deferred.resolve(); - }); - }); - - yield deferred.promise; - - Assert.ok(gCategoryUtilities.isTypeVisible, "experiment", "Experiment tab visible."); - yield gCategoryUtilities.openType("experiment"); - let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0]; - is_element_visible(el, "Experiment info is visible on experiment tab."); -}); - -add_task(function* testDeactivateExperiment() { - if (!gExperiments) { - return; - } - - // Fake an empty manifest to purge data from previous manifest. - yield gExperiments._updateExperiments({ - "version": 1, - "experiments": [], - }); - - yield gExperiments.disableExperiment("testing"); - - // We should have a record of the previously-active experiment. - let experiments = yield gExperiments.getExperiments(); - Assert.equal(experiments.length, 1, "1 experiment is known."); - Assert.equal(experiments[0].active, false, "Experiment is not active."); - - // We should have a previous experiment in the add-ons manager. - let deferred = Promise.defer(); - AddonManager.getAddonsByTypes(["experiment"], (addons) => { - deferred.resolve(addons); - }); - let addons = yield deferred.promise; - Assert.equal(addons.length, 1, "1 experiment add-on known."); - Assert.ok(addons[0].appDisabled, "It is a previous experiment."); - Assert.equal(addons[0].id, "experiment-1", "Add-on ID matches expected."); - - // Verify the UI looks sane. - - Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible."); - let item = get_addon_element(gManagerWindow, "experiment-1"); - Assert.ok(item, "Got add-on element."); - Assert.ok(!item.active, "Element should not be active."); - item.parentNode.ensureElementIsVisible(item); - - // User control buttons should not be present because previous experiments - // should have no permissions. - let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn"); - is_element_hidden(el, "Remove button is not visible."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn"); - is_element_hidden(el, "Disable button is not visible."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn"); - is_element_hidden(el, "Enable button is not visible."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "preferences-btn"); - is_element_hidden(el, "Preferences button is not visible."); -}); - -add_task(function* testActivateRealExperiments() { - if (!gExperiments) { - info("Skipping experiments test because that feature isn't available."); - return; - } - - yield gExperiments._updateExperiments({ - "version": 1, - "experiments": [ - { - id: "experiment-2", - xpiURL: TESTROOT + "addons/browser_experiment1.xpi", - xpiHash: "IRRELEVANT", - startTime: Date.now() / 1000 - 3600, - endTime: Date.now() / 1000 + 3600, - maxActiveSeconds: 600, - appName: [Services.appinfo.name], - channel: [gExperiments._policy.updatechannel()], - }, - ], - }); - yield gExperiments._run(); - - // Check the active experiment. - - let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Active"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Less than a day remaining"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container"); - is_element_hidden(el, "error-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container"); - is_element_hidden(el, "warning-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container"); - is_element_hidden(el, "pending-container should be hidden."); - let { version } = yield get_tooltip_info(item); - Assert.equal(version, undefined, "version should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix"); - is_element_hidden(el, "disabled-postfix should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix"); - is_element_hidden(el, "update-postfix should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet"); - is_element_visible(el, "experiment-bullet should be visible."); - - // Check the previous experiment. - - item = get_addon_element(gManagerWindow, "experiment-1"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Complete"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Less than a day ago"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container"); - is_element_hidden(el, "error-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container"); - is_element_hidden(el, "warning-container should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container"); - is_element_hidden(el, "pending-container should be hidden."); - ({ version } = yield get_tooltip_info(item)); - Assert.equal(version, undefined, "version should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix"); - is_element_hidden(el, "disabled-postfix should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix"); - is_element_hidden(el, "update-postfix should be hidden."); - el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet"); - is_element_visible(el, "experiment-bullet should be visible."); - - // Install an "older" experiment. - - yield gExperiments.disableExperiment("experiment-2"); - - let now = Date.now(); - let fakeNow = now - 5 * MS_IN_ONE_DAY; - defineNow(gExperiments._policy, fakeNow); - - yield gExperiments._updateExperiments({ - "version": 1, - "experiments": [ - { - id: "experiment-3", - xpiURL: TESTROOT + "addons/browser_experiment1.xpi", - xpiHash: "IRRELEVANT", - startTime: fakeNow / 1000 - SEC_IN_ONE_DAY, - endTime: now / 1000 + 10 * SEC_IN_ONE_DAY, - maxActiveSeconds: 100 * SEC_IN_ONE_DAY, - appName: [Services.appinfo.name], - channel: [gExperiments._policy.updatechannel()], - }, - ], - }); - yield gExperiments._run(); - - // Check the active experiment. - - item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Active"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "10 days remaining"); - } - - // Disable it and check it's previous experiment entry. - - yield gExperiments.disableExperiment("experiment-3"); - - item = get_addon_element(gManagerWindow, "experiment-3"); - Assert.ok(item, "Got add-on element."); - item.parentNode.ensureElementIsVisible(item); - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Complete"); - } - - el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "5 days ago"); - } -}); - -add_task(function* testDetailView() { - if (!gExperiments) { - info("Skipping experiments test because that feature isn't available."); - return; - } - - defineNow(gExperiments._policy, Date.now()); - yield gExperiments._updateExperiments({ - "version": 1, - "experiments": [ - { - id: "experiment-4", - xpiURL: TESTROOT + "addons/browser_experiment1.xpi", - xpiHash: "IRRELEVANT", - startTime: Date.now() / 1000 - 3600, - endTime: Date.now() / 1000 + 3600, - maxActiveSeconds: 600, - appName: [Services.appinfo.name], - channel: [gExperiments._policy.updatechannel()], - }, - ], - }); - yield gExperiments._run(); - - // Check active experiment. - - yield openDetailsView("test-experiment1@experiments.mozilla.org"); - - let el = gManagerWindow.document.getElementById("detail-experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Active"); - } - - el = gManagerWindow.document.getElementById("detail-experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Less than a day remaining"); - } - - el = gManagerWindow.document.getElementById("detail-version"); - is_element_hidden(el, "detail-version should be hidden."); - el = gManagerWindow.document.getElementById("detail-creator"); - is_element_hidden(el, "detail-creator should be hidden."); - el = gManagerWindow.document.getElementById("detail-experiment-bullet"); - is_element_visible(el, "experiment-bullet should be visible."); - - // Check previous experiment. - - yield gCategoryUtilities.openType("experiment"); - yield openDetailsView("experiment-3"); - - el = gManagerWindow.document.getElementById("detail-experiment-state"); - is_element_visible(el, "Experiment state label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "Complete"); - } - - el = gManagerWindow.document.getElementById("detail-experiment-time"); - is_element_visible(el, "Experiment time label should be visible."); - if (gIsEnUsLocale) { - Assert.equal(el.value, "5 days ago"); - } - - el = gManagerWindow.document.getElementById("detail-version"); - is_element_hidden(el, "detail-version should be hidden."); - el = gManagerWindow.document.getElementById("detail-creator"); - is_element_hidden(el, "detail-creator should be hidden."); - el = gManagerWindow.document.getElementById("detail-experiment-bullet"); - is_element_visible(el, "experiment-bullet should be visible."); -}); - -add_task(function* testRemoveAndUndo() { - if (!gExperiments) { - info("Skipping experiments test because that feature isn't available."); - return; - } - - yield gCategoryUtilities.openType("experiment"); - - let addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(addon, "Got add-on element."); - - yield clickRemoveButton(addon); - addon.parentNode.ensureElementIsVisible(addon); - - let el = gManagerWindow.document.getAnonymousElementByAttribute(addon, "class", "pending"); - is_element_visible(el, "Uninstall undo information should be visible."); - - yield clickUndoButton(addon); - addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org"); - Assert.ok(addon, "Got add-on element."); -}); - -add_task(function* testCleanup() { - if (gExperiments) { - Services.prefs.clearUserPref("experiments.enabled"); - Services.prefs.setCharPref("experiments.manifest.uri", gSavedManifestURI); - - // We perform the uninit/init cycle to purge any leftover state. - yield OS.File.remove(gExperiments._cacheFilePath); - yield gExperiments.uninit(); - yield gExperiments.init(); - - Services.prefs.clearUserPref("toolkit.telemetry.enabled"); - } - - // Check post-conditions. - let addons = yield getExperimentAddons(); - Assert.equal(addons.length, 0, "No experiment add-ons are installed."); - - yield close_manager(gManagerWindow); -}); |