зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1586841 - Port ShieldFrameChild.jsm to JSWindowActors. r=Gijs
These are the actors that handle messaging on the about:studies page. Differential Revision: https://phabricator.services.mozilla.com/D48911 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
a457606700
Коммит
8719f5bc3e
|
@ -147,6 +147,21 @@ let ACTORS = {
|
|||
allFrames: true,
|
||||
},
|
||||
|
||||
ShieldFrame: {
|
||||
parent: {
|
||||
moduleURI: "resource://normandy-content/ShieldFrameParent.jsm",
|
||||
},
|
||||
child: {
|
||||
moduleURI: "resource://normandy-content/ShieldFrameChild.jsm",
|
||||
events: {
|
||||
pageshow: {},
|
||||
pagehide: {},
|
||||
ShieldPageEvent: { wantUntrusted: true },
|
||||
},
|
||||
},
|
||||
matches: ["about:studies"],
|
||||
},
|
||||
|
||||
SwitchDocumentDirection: {
|
||||
child: {
|
||||
moduleURI: "resource:///actors/SwitchDocumentDirectionChild.jsm",
|
||||
|
@ -322,16 +337,6 @@ let LEGACY_ACTORS = {
|
|||
},
|
||||
},
|
||||
|
||||
ShieldFrame: {
|
||||
child: {
|
||||
module: "resource://normandy-content/ShieldFrameChild.jsm",
|
||||
events: {
|
||||
ShieldPageEvent: { wantUntrusted: true },
|
||||
},
|
||||
matches: ["about:studies"],
|
||||
},
|
||||
},
|
||||
|
||||
UITour: {
|
||||
child: {
|
||||
module: "resource:///modules/UITourChild.jsm",
|
||||
|
|
|
@ -18,11 +18,6 @@ ChromeUtils.defineModuleGetter(
|
|||
"AddonStudyAction",
|
||||
"resource://normandy/actions/AddonStudyAction.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"CleanupManager",
|
||||
"resource://normandy/lib/CleanupManager.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"PreferenceExperiments",
|
||||
|
@ -85,30 +80,18 @@ AboutPage.prototype.QueryInterface = ChromeUtils.generateQI([
|
|||
/**
|
||||
* The module exported by this file.
|
||||
*/
|
||||
var AboutPages = {
|
||||
async init() {
|
||||
// Load scripts in content processes and tabs
|
||||
|
||||
// Register about: pages and their listeners
|
||||
this.aboutStudies.registerParentListeners();
|
||||
|
||||
CleanupManager.addCleanupHandler(() => {
|
||||
// Stop loading process scripts and notify existing scripts to clean up.
|
||||
Services.ppmm.broadcastAsyncMessage("Shield:ShuttingDown");
|
||||
Services.mm.broadcastAsyncMessage("Shield:ShuttingDown");
|
||||
|
||||
// Clean up about pages
|
||||
this.aboutStudies.unregisterParentListeners();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
let AboutPages = {};
|
||||
/**
|
||||
* The weak set that keeps track of which browsing contexts
|
||||
* have an about:studies page.
|
||||
*/
|
||||
let BrowsingContexts = new WeakSet();
|
||||
/**
|
||||
* about:studies page for displaying in-progress and past Shield studies.
|
||||
* @type {AboutPage}
|
||||
* @implements {nsIMessageListener}
|
||||
*/
|
||||
XPCOMUtils.defineLazyGetter(this.AboutPages, "aboutStudies", () => {
|
||||
XPCOMUtils.defineLazyGetter(AboutPages, "aboutStudies", () => {
|
||||
const aboutStudies = new AboutPage({
|
||||
chromeUrl: "resource://normandy-content/about-studies/about-studies.html",
|
||||
aboutHost: "studies",
|
||||
|
@ -122,126 +105,57 @@ XPCOMUtils.defineLazyGetter(this.AboutPages, "aboutStudies", () => {
|
|||
|
||||
// Extra methods for about:study-specific behavior.
|
||||
Object.assign(aboutStudies, {
|
||||
/**
|
||||
* Register listeners for messages from the content processes.
|
||||
getAddonStudyList() {
|
||||
return AddonStudies.getAll();
|
||||
},
|
||||
|
||||
getPreferenceStudyList() {
|
||||
return PreferenceExperiments.getAll();
|
||||
},
|
||||
|
||||
/** Add a browsing context to the weak set;
|
||||
* this weak set keeps track of all contexts
|
||||
* that are housing an about:studies page.
|
||||
*/
|
||||
registerParentListeners() {
|
||||
Services.mm.addMessageListener("Shield:GetAddonStudyList", this);
|
||||
Services.mm.addMessageListener("Shield:GetPreferenceStudyList", this);
|
||||
Services.mm.addMessageListener("Shield:RemoveAddonStudy", this);
|
||||
Services.mm.addMessageListener("Shield:RemovePreferenceStudy", this);
|
||||
Services.mm.addMessageListener("Shield:OpenDataPreferences", this);
|
||||
Services.mm.addMessageListener("Shield:GetStudiesEnabled", this);
|
||||
addToWeakSet(browsingContext) {
|
||||
BrowsingContexts.add(browsingContext);
|
||||
},
|
||||
/** Remove a browsing context to the weak set;
|
||||
* this weak set keeps track of all contexts
|
||||
* that are housing an about:studies page.
|
||||
*/
|
||||
removeFromWeakSet(browsingContext) {
|
||||
BrowsingContexts.delete(browsingContext);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregister listeners for messages from the content process.
|
||||
* Sends a message to every about:studies page,
|
||||
* by iterating over the BrowsingContexts weakset.
|
||||
* @param {string} message The message string to send to.
|
||||
* @param {object} data The data object to send.
|
||||
*/
|
||||
unregisterParentListeners() {
|
||||
Services.mm.removeMessageListener("Shield:GetAddonStudyList", this);
|
||||
Services.mm.removeMessageListener("Shield:GetPreferenceStudyList", this);
|
||||
Services.mm.removeMessageListener("Shield:RemoveAddonStudy", this);
|
||||
Services.mm.removeMessageListener("Shield:RemovePreferenceStudy", this);
|
||||
Services.mm.removeMessageListener("Shield:OpenDataPreferences", this);
|
||||
Services.mm.removeMessageListener("Shield:GetStudiesEnabled", this);
|
||||
_sendToAll(message, data) {
|
||||
ChromeUtils.nondeterministicGetWeakSetKeys(BrowsingContexts).forEach(
|
||||
browser =>
|
||||
browser.currentWindowGlobal
|
||||
.getActor("ShieldFrame")
|
||||
.sendAsyncMessage(message, data)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatch messages from the content process to the appropriate handler.
|
||||
* @param {Object} message
|
||||
* See the nsIMessageListener documentation for details about this object.
|
||||
*/
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "Shield:GetAddonStudyList":
|
||||
this.sendAddonStudyList(message.target);
|
||||
break;
|
||||
case "Shield:GetPreferenceStudyList":
|
||||
this.sendPreferenceStudyList(message.target);
|
||||
break;
|
||||
case "Shield:RemoveAddonStudy":
|
||||
this.removeAddonStudy(message.data.recipeId, message.data.reason);
|
||||
break;
|
||||
case "Shield:RemovePreferenceStudy":
|
||||
this.removePreferenceStudy(
|
||||
message.data.experimentName,
|
||||
message.data.reason
|
||||
);
|
||||
break;
|
||||
case "Shield:OpenDataPreferences":
|
||||
this.openDataPreferences();
|
||||
break;
|
||||
case "Shield:GetStudiesEnabled":
|
||||
this.sendStudiesEnabled(message.target);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch a list of add-on studies from storage and send it to the process
|
||||
* that requested them.
|
||||
* @param {<browser>} target
|
||||
* XUL <browser> element for the tab containing the about:studies page
|
||||
* that requested a study list.
|
||||
*/
|
||||
async sendAddonStudyList(target) {
|
||||
try {
|
||||
target.messageManager.sendAsyncMessage("Shield:ReceiveAddonStudyList", {
|
||||
studies: await AddonStudies.getAll(),
|
||||
});
|
||||
} catch (err) {
|
||||
// The child process might be gone, so no need to throw here.
|
||||
Cu.reportError(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch a list of preference studies from storage and send it to the
|
||||
* process that requested them.
|
||||
* @param {<browser>} target
|
||||
* XUL <browser> element for the tab containing the about:studies page
|
||||
* that requested a study list.
|
||||
*/
|
||||
async sendPreferenceStudyList(target) {
|
||||
try {
|
||||
target.messageManager.sendAsyncMessage(
|
||||
"Shield:ReceivePreferenceStudyList",
|
||||
{
|
||||
studies: await PreferenceExperiments.getAll(),
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
// The child process might be gone, so no need to throw here.
|
||||
Cu.reportError(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get if studies are enabled and send it to the process that
|
||||
* requested them. This has to be in the parent process, since
|
||||
* RecipeRunner is stateful, and can't be interacted with from
|
||||
* Get if studies are enabled. This has to be in the parent process,
|
||||
* since RecipeRunner is stateful, and can't be interacted with from
|
||||
* content processes safely.
|
||||
*
|
||||
* @param {<browser>} target
|
||||
* XUL <browser> element for the tab containing the about:studies page
|
||||
* that requested a study list.
|
||||
*/
|
||||
sendStudiesEnabled(target) {
|
||||
RecipeRunner.checkPrefs();
|
||||
const studiesEnabled = RecipeRunner.enabled && gOptOutStudiesEnabled;
|
||||
try {
|
||||
target.messageManager.sendAsyncMessage("Shield:ReceiveStudiesEnabled", {
|
||||
studiesEnabled,
|
||||
});
|
||||
} catch (err) {
|
||||
// The child process might be gone, so no need to throw here.
|
||||
Cu.reportError(err);
|
||||
}
|
||||
getStudiesEnabled() {
|
||||
return RecipeRunner.enabled && gOptOutStudiesEnabled;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable an active add-on study and remove its add-on.
|
||||
* @param {String} studyName
|
||||
* @param {String} recipeId the id of the addon to remove
|
||||
* @param {String} reason the reason for removal
|
||||
*/
|
||||
async removeAddonStudy(recipeId, reason) {
|
||||
try {
|
||||
|
@ -256,15 +170,16 @@ XPCOMUtils.defineLazyGetter(this.AboutPages, "aboutStudies", () => {
|
|||
} finally {
|
||||
// Update any open tabs with the new study list now that it has changed,
|
||||
// even if the above failed.
|
||||
Services.mm.broadcastAsyncMessage("Shield:ReceiveAddonStudyList", {
|
||||
studies: await AddonStudies.getAll(),
|
||||
});
|
||||
this.getAddonStudyList().then(list =>
|
||||
this._sendToAll("Shield:UpdateAddonStudyList", list)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable an active preference study
|
||||
* @param {String} studyName
|
||||
* Disable an active preference study.
|
||||
* @param {String} experimentName the name of the experiment to remove
|
||||
* @param {String} reason the reason for removal
|
||||
*/
|
||||
async removePreferenceStudy(experimentName, reason) {
|
||||
try {
|
||||
|
@ -278,9 +193,9 @@ XPCOMUtils.defineLazyGetter(this.AboutPages, "aboutStudies", () => {
|
|||
} finally {
|
||||
// Update any open tabs with the new study list now that it has changed,
|
||||
// even if the above failed.
|
||||
Services.mm.broadcastAsyncMessage("Shield:ReceivePreferenceStudyList", {
|
||||
studies: await PreferenceExperiments.getAll(),
|
||||
});
|
||||
this.getPreferenceStudyList().then(list =>
|
||||
this._sendToAll("Shield:UpdatePreferenceStudyList", list)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -10,14 +10,8 @@ var EXPORTED_SYMBOLS = ["ShieldFrameChild"];
|
|||
* privileged actions in response to them. If we need to do anything that the
|
||||
* content process can't handle (such as reading IndexedDB), we send a message
|
||||
* to the parent process and handle it there.
|
||||
*
|
||||
* This file is loaded as a frame script. It will be loaded once per tab that
|
||||
* is opened.
|
||||
*/
|
||||
|
||||
const { ActorChild } = ChromeUtils.import(
|
||||
"resource://gre/modules/ActorChild.jsm"
|
||||
);
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
|
@ -43,41 +37,58 @@ XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
|
|||
});
|
||||
|
||||
/**
|
||||
* Handles incoming events from the parent process and about:studies.
|
||||
* @implements nsIMessageListener
|
||||
* @implements EventListener
|
||||
* Listen for DOM events bubbling up from the about:studies page, and perform
|
||||
* privileged actions in response to them. If we need to do anything that the
|
||||
* content process can't handle (such as reading IndexedDB), we send a message
|
||||
* to the parent process and handle it there.
|
||||
*/
|
||||
class ShieldFrameChild extends ActorChild {
|
||||
handleEvent(event) {
|
||||
// We waited until after we received an event to register message listeners
|
||||
// in order to save resources for tabs that don't ever load about:studies.
|
||||
this.mm.addMessageListener("Shield:ShuttingDown", this);
|
||||
this.mm.addMessageListener("Shield:ReceiveAddonStudyList", this);
|
||||
this.mm.addMessageListener("Shield:ReceivePreferenceStudyList", this);
|
||||
this.mm.addMessageListener("Shield:ReceiveStudiesEnabled", this);
|
||||
class ShieldFrameChild extends JSWindowActorChild {
|
||||
async handleEvent(event) {
|
||||
// On page show or page hide,
|
||||
// add this child to the WeakSet in AboutStudies.
|
||||
switch (event.type) {
|
||||
case "pageshow":
|
||||
this.sendAsyncMessage("Shield:AddToWeakSet");
|
||||
return;
|
||||
|
||||
case "pagehide":
|
||||
this.sendAsyncMessage("Shield:RemoveFromWeakSet");
|
||||
return;
|
||||
}
|
||||
switch (event.detail.action) {
|
||||
// Actions that require the parent process
|
||||
case "GetRemoteValue:AddonStudyList":
|
||||
this.mm.sendAsyncMessage("Shield:GetAddonStudyList");
|
||||
let addonStudies = await this.sendQuery("Shield:GetAddonStudyList");
|
||||
this.triggerPageCallback(
|
||||
"ReceiveRemoteValue:AddonStudyList",
|
||||
addonStudies
|
||||
);
|
||||
break;
|
||||
case "GetRemoteValue:PreferenceStudyList":
|
||||
this.mm.sendAsyncMessage("Shield:GetPreferenceStudyList");
|
||||
let prefStudies = await this.sendQuery("Shield:GetPreferenceStudyList");
|
||||
this.triggerPageCallback(
|
||||
"ReceiveRemoteValue:PreferenceStudyList",
|
||||
prefStudies
|
||||
);
|
||||
break;
|
||||
case "RemoveAddonStudy":
|
||||
this.mm.sendAsyncMessage("Shield:RemoveAddonStudy", event.detail.data);
|
||||
this.sendAsyncMessage("Shield:RemoveAddonStudy", event.detail.data);
|
||||
break;
|
||||
case "RemovePreferenceStudy":
|
||||
this.mm.sendAsyncMessage(
|
||||
this.sendAsyncMessage(
|
||||
"Shield:RemovePreferenceStudy",
|
||||
event.detail.data
|
||||
);
|
||||
break;
|
||||
case "GetRemoteValue:StudiesEnabled":
|
||||
this.mm.sendAsyncMessage("Shield:GetStudiesEnabled");
|
||||
let studiesEnabled = await this.sendQuery("Shield:GetStudiesEnabled");
|
||||
this.triggerPageCallback(
|
||||
"ReceiveRemoteValue:StudiesEnabled",
|
||||
studiesEnabled
|
||||
);
|
||||
break;
|
||||
case "NavigateToDataPreferences":
|
||||
this.mm.sendAsyncMessage("Shield:OpenDataPreferences");
|
||||
this.sendAsyncMessage("Shield:OpenDataPreferences");
|
||||
break;
|
||||
// Actions that can be performed in the content process
|
||||
case "GetRemoteValue:ShieldLearnMoreHref":
|
||||
|
@ -105,56 +116,30 @@ class ShieldFrameChild extends ActorChild {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle messages from the parent process.
|
||||
* @param {Object} message
|
||||
* See the nsIMessageListener docs.
|
||||
*/
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "Shield:ReceiveAddonStudyList":
|
||||
this.triggerPageCallback(
|
||||
"ReceiveRemoteValue:AddonStudyList",
|
||||
message.data.studies
|
||||
);
|
||||
receiveMessage(msg) {
|
||||
switch (msg.name) {
|
||||
case "Shield:UpdateAddonStudyList":
|
||||
this.triggerPageCallback("ReceiveRemoteValue:AddonStudyList", msg.data);
|
||||
break;
|
||||
case "Shield:ReceivePreferenceStudyList":
|
||||
case "Shield:UpdatePreferenceStudyList":
|
||||
this.triggerPageCallback(
|
||||
"ReceiveRemoteValue:PreferenceStudyList",
|
||||
message.data.studies
|
||||
msg.data
|
||||
);
|
||||
break;
|
||||
case "Shield:ReceiveStudiesEnabled":
|
||||
this.triggerPageCallback(
|
||||
"ReceiveRemoteValue:StudiesEnabled",
|
||||
message.data.studiesEnabled
|
||||
);
|
||||
break;
|
||||
case "Shield:ShuttingDown":
|
||||
this.onShutdown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event to communicate with the unprivileged about: page.
|
||||
* @param {String} type
|
||||
* @param {Object} detail
|
||||
* Trigger an event to communicate with the unprivileged about:studies page.
|
||||
* @param {String} type The type of event to trigger.
|
||||
* @param {Object} detail The data to pass along to the event.
|
||||
*/
|
||||
triggerPageCallback(type, detail) {
|
||||
let { content } = this.mm;
|
||||
|
||||
// Clone details and use the event class from the unprivileged context.
|
||||
const event = new content.document.defaultView.CustomEvent(type, {
|
||||
const event = new this.document.defaultView.CustomEvent(type, {
|
||||
bubbles: true,
|
||||
detail: Cu.cloneInto(detail, content.document.defaultView),
|
||||
detail: Cu.cloneInto(detail, this.document.defaultView),
|
||||
});
|
||||
content.document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
onShutdown() {
|
||||
this.mm.removeMessageListener("Shield:SendStudyList", this);
|
||||
this.mm.removeMessageListener("Shield:ShuttingDown", this);
|
||||
this.mm.removeEventListener("Shield", this);
|
||||
this.document.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/* 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/. */
|
||||
|
||||
var EXPORTED_SYMBOLS = ["ShieldFrameParent"];
|
||||
|
||||
const frameGlobal = {};
|
||||
ChromeUtils.defineModuleGetter(
|
||||
frameGlobal,
|
||||
"AboutPages",
|
||||
"resource://normandy-content/AboutPages.jsm"
|
||||
);
|
||||
|
||||
class ShieldFrameParent extends JSWindowActorParent {
|
||||
async receiveMessage(msg) {
|
||||
let { aboutStudies } = frameGlobal.AboutPages;
|
||||
switch (msg.name) {
|
||||
case "Shield:AddToWeakSet":
|
||||
aboutStudies.addToWeakSet(this.browsingContext);
|
||||
break;
|
||||
case "Shield:RemoveFromWeakSet":
|
||||
aboutStudies.removeFromWeakSet(this.browsingContext);
|
||||
break;
|
||||
case "Shield:GetAddonStudyList":
|
||||
return aboutStudies.getAddonStudyList();
|
||||
case "Shield:GetPreferenceStudyList":
|
||||
return aboutStudies.getPreferenceStudyList();
|
||||
case "Shield:RemoveAddonStudy":
|
||||
aboutStudies.removeAddonStudy(msg.data.recipeId, msg.data.reason);
|
||||
break;
|
||||
case "Shield:RemovePreferenceStudy":
|
||||
aboutStudies.removePreferenceStudy(
|
||||
msg.data.experimentName,
|
||||
msg.data.reason
|
||||
);
|
||||
break;
|
||||
case "Shield:OpenDataPreferences":
|
||||
aboutStudies.openDataPreferences();
|
||||
break;
|
||||
case "Shield:GetStudiesEnabled":
|
||||
return aboutStudies.getStudiesEnabled();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm", this);
|
|||
ChromeUtils.import("resource://normandy/lib/PreferenceRollouts.jsm", this);
|
||||
ChromeUtils.import("resource://normandy/lib/RecipeRunner.jsm", this);
|
||||
ChromeUtils.import("resource://normandy/lib/TelemetryEvents.jsm", this);
|
||||
ChromeUtils.import("resource://normandy-content/AboutPages.jsm", this);
|
||||
|
||||
const experimentPref1 = "test.initExperimentPrefs1";
|
||||
const experimentPref2 = "test.initExperimentPrefs2";
|
||||
|
@ -16,7 +15,6 @@ const experimentPref4 = "test.initExperimentPrefs4";
|
|||
|
||||
function withStubInits(testFunction) {
|
||||
return decorate(
|
||||
withStub(AboutPages, "init"),
|
||||
withStub(AddonRollouts, "init"),
|
||||
withStub(AddonStudies, "init"),
|
||||
withStub(PreferenceRollouts, "init"),
|
||||
|
@ -198,7 +196,6 @@ decorate_task(
|
|||
decorate_task(withStubInits, async function testStartup() {
|
||||
const initObserved = TestUtils.topicObserved("shield-init-complete");
|
||||
await Normandy.finishInit();
|
||||
ok(AboutPages.init.called, "startup calls AboutPages.init");
|
||||
ok(AddonStudies.init.called, "startup calls AddonStudies.init");
|
||||
ok(
|
||||
PreferenceExperiments.init.called,
|
||||
|
@ -212,23 +209,6 @@ decorate_task(withStubInits, async function testStartupPrefInitFail() {
|
|||
PreferenceExperiments.init.rejects();
|
||||
|
||||
await Normandy.finishInit();
|
||||
ok(AboutPages.init.called, "startup calls AboutPages.init");
|
||||
ok(AddonStudies.init.called, "startup calls AddonStudies.init");
|
||||
ok(AddonRollouts.init.called, "startup calls AddonRollouts.init");
|
||||
ok(
|
||||
PreferenceExperiments.init.called,
|
||||
"startup calls PreferenceExperiments.init"
|
||||
);
|
||||
ok(RecipeRunner.init.called, "startup calls RecipeRunner.init");
|
||||
ok(TelemetryEvents.init.called, "startup calls TelemetryEvents.init");
|
||||
ok(PreferenceRollouts.init.called, "startup calls PreferenceRollouts.init");
|
||||
});
|
||||
|
||||
decorate_task(withStubInits, async function testStartupAboutPagesInitFail() {
|
||||
AboutPages.init.rejects();
|
||||
|
||||
await Normandy.finishInit();
|
||||
ok(AboutPages.init.called, "startup calls AboutPages.init");
|
||||
ok(AddonStudies.init.called, "startup calls AddonStudies.init");
|
||||
ok(AddonRollouts.init.called, "startup calls AddonRollouts.init");
|
||||
ok(
|
||||
|
@ -244,7 +224,6 @@ decorate_task(withStubInits, async function testStartupAddonStudiesInitFail() {
|
|||
AddonStudies.init.rejects();
|
||||
|
||||
await Normandy.finishInit();
|
||||
ok(AboutPages.init.called, "startup calls AboutPages.init");
|
||||
ok(AddonStudies.init.called, "startup calls AddonStudies.init");
|
||||
ok(AddonRollouts.init.called, "startup calls AddonRollouts.init");
|
||||
ok(
|
||||
|
@ -262,7 +241,6 @@ decorate_task(
|
|||
TelemetryEvents.init.throws();
|
||||
|
||||
await Normandy.finishInit();
|
||||
ok(AboutPages.init.called, "startup calls AboutPages.init");
|
||||
ok(AddonStudies.init.called, "startup calls AddonStudies.init");
|
||||
ok(AddonRollouts.init.called, "startup calls AddonRollouts.init");
|
||||
ok(
|
||||
|
@ -281,7 +259,6 @@ decorate_task(
|
|||
PreferenceRollouts.init.throws();
|
||||
|
||||
await Normandy.finishInit();
|
||||
ok(AboutPages.init.called, "startup calls AboutPages.init");
|
||||
ok(AddonStudies.init.called, "startup calls AddonStudies.init");
|
||||
ok(AddonRollouts.init.called, "startup calls AddonRollouts.init");
|
||||
ok(
|
||||
|
|
|
@ -465,3 +465,155 @@ decorate_task(
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Test that clicking remove on a study updates even about:studies pages
|
||||
// that are not currently in focus.
|
||||
decorate_task(
|
||||
AddonStudies.withStudies([
|
||||
addonStudyFactory({
|
||||
slug: "fake-addon-study",
|
||||
userFacingName: "Fake Add-on Study",
|
||||
active: true,
|
||||
userFacingDescription: "A fake description",
|
||||
studyStartDate: new Date(2018, 0, 4),
|
||||
}),
|
||||
]),
|
||||
PreferenceExperiments.withMockExperiments([
|
||||
preferenceStudyFactory({
|
||||
slug: "fake-pref-study",
|
||||
userFacingName: "Fake Preference Study",
|
||||
lastSeen: new Date(2018, 0, 3),
|
||||
expired: false,
|
||||
}),
|
||||
]),
|
||||
withAboutStudies,
|
||||
async function testOtherTabsUpdated([addonStudy], [prefStudy], browser) {
|
||||
// Ensure that both our studies are active in the current tab.
|
||||
await ContentTask.spawn(
|
||||
browser,
|
||||
{ addonStudy, prefStudy },
|
||||
async ({ addonStudy, prefStudy }) => {
|
||||
const doc = content.document;
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => doc.querySelectorAll(".remove-button").length == 2,
|
||||
"waiting for page to load"
|
||||
);
|
||||
let activeNames = Array.from(
|
||||
doc.querySelectorAll(".active-study-list .study")
|
||||
).map(row => row.dataset.studySlug);
|
||||
let inactiveNames = Array.from(
|
||||
doc.querySelectorAll(".inactive-study-list .study")
|
||||
).map(row => row.dataset.studySlug);
|
||||
|
||||
Assert.deepEqual(
|
||||
activeNames,
|
||||
[addonStudy.slug, prefStudy.slug],
|
||||
"Both studies should be listed as active"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
inactiveNames,
|
||||
[],
|
||||
"No studies should be listed as inactive"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Open a new about:studies tab.
|
||||
await BrowserTestUtils.withNewTab("about:studies", async browser => {
|
||||
// Delete both studies in this tab; this should pass if previous tests have passed.
|
||||
await ContentTask.spawn(
|
||||
browser,
|
||||
{ addonStudy, prefStudy },
|
||||
async ({ addonStudy, prefStudy }) => {
|
||||
const doc = content.document;
|
||||
|
||||
function getStudyRow(docElem, slug) {
|
||||
return docElem.querySelector(`.study[data-study-slug="${slug}"]`);
|
||||
}
|
||||
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => doc.querySelectorAll(".remove-button").length == 2,
|
||||
"waiting for page to load"
|
||||
);
|
||||
let activeNames = Array.from(
|
||||
doc.querySelectorAll(".active-study-list .study")
|
||||
).map(row => row.dataset.studySlug);
|
||||
let inactiveNames = Array.from(
|
||||
doc.querySelectorAll(".inactive-study-list .study")
|
||||
).map(row => row.dataset.studySlug);
|
||||
|
||||
Assert.deepEqual(
|
||||
activeNames,
|
||||
[addonStudy.slug, prefStudy.slug],
|
||||
"Both studies should be listed as active in the new tab"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
inactiveNames,
|
||||
[],
|
||||
"No studies should be listed as inactive in the new tab"
|
||||
);
|
||||
|
||||
const activeAddonStudy = getStudyRow(doc, addonStudy.slug);
|
||||
const activePrefStudy = getStudyRow(doc, prefStudy.slug);
|
||||
|
||||
activeAddonStudy.querySelector(".remove-button").click();
|
||||
await ContentTaskUtils.waitForCondition(() =>
|
||||
getStudyRow(doc, addonStudy.slug).matches(".study.disabled")
|
||||
);
|
||||
ok(
|
||||
getStudyRow(doc, addonStudy.slug).matches(".study.disabled"),
|
||||
"Clicking the remove button updates the UI in the new tab"
|
||||
);
|
||||
|
||||
activePrefStudy.querySelector(".remove-button").click();
|
||||
await ContentTaskUtils.waitForCondition(() =>
|
||||
getStudyRow(doc, prefStudy.slug).matches(".study.disabled")
|
||||
);
|
||||
ok(
|
||||
getStudyRow(doc, prefStudy.slug).matches(".study.disabled"),
|
||||
"Clicking the remove button updates the UI in the new tab"
|
||||
);
|
||||
|
||||
activeNames = Array.from(
|
||||
doc.querySelectorAll(".active-study-list .study")
|
||||
).map(row => row.dataset.studySlug);
|
||||
|
||||
Assert.deepEqual(
|
||||
activeNames,
|
||||
[],
|
||||
"No studies should be listed as active"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Ensure that the original tab has updated correctly.
|
||||
await ContentTask.spawn(
|
||||
browser,
|
||||
{ addonStudy, prefStudy },
|
||||
async ({ addonStudy, prefStudy }) => {
|
||||
const doc = content.document;
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => doc.querySelectorAll(".inactive-study-list .study").length == 2,
|
||||
"Two studies should load into the inactive list, since they were disabled in a different tab"
|
||||
);
|
||||
let activeNames = Array.from(
|
||||
doc.querySelectorAll(".active-study-list .study")
|
||||
).map(row => row.dataset.studySlug);
|
||||
let inactiveNames = Array.from(
|
||||
doc.querySelectorAll(".inactive-study-list .study")
|
||||
).map(row => row.dataset.studySlug);
|
||||
Assert.deepEqual(
|
||||
activeNames,
|
||||
[],
|
||||
"No studies should be listed as active, since they were disabled in a different tab"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
inactiveNames,
|
||||
[addonStudy.slug, prefStudy.slug],
|
||||
"Both studies should be listed as inactive, since they were disabled in a different tab"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
Загрузка…
Ссылка в новой задаче