Bug 1813716 - Encapsulate aboutwelcome container and make its ID more unique. r=omc-reviewers,fxview-reviewers,sfoster,jprickett

Differential Revision: https://phabricator.services.mozilla.com/D172493
This commit is contained in:
Shane Hughes 2023-03-29 21:51:09 +00:00
Родитель 1a1ba61c29
Коммит 78935b829e
10 изменённых файлов: 77 добавлений и 85 удалений

Просмотреть файл

@ -49,7 +49,7 @@ function renderMultistage(ready) {
);
document.body.classList.add("onboardingContainer");
document.body.id = "root";
document.body.id = "multi-stage-message-root";
// This value is reported as the "page" in telemetry
document.body.dataset.page = "spotlight";

Просмотреть файл

@ -45,7 +45,7 @@ const RECENTLY_CLOSED_STATE_PREF =
const TAB_PICKUP_STATE_PREF =
"browser.tabs.firefox-view.ui-state.tab-pickup.open";
const calloutId = "root";
const calloutId = "multi-stage-message-root";
const calloutSelector = `#${calloutId}.featureCallout`;
const primaryButtonSelector = `#${calloutId} .primary`;

Просмотреть файл

@ -28,13 +28,15 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export */ "AboutWelcomeUtils": () => (/* binding */ AboutWelcomeUtils),
/* harmony export */ "DEFAULT_RTAMO_CONTENT": () => (/* binding */ DEFAULT_RTAMO_CONTENT)
/* harmony export */ });
var _document$querySelect;
/* 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/. */
// If the container has a "page" data attribute, then this is
// a Spotlight modal or Feature Callout. Otherwise, this is
// about:welcome and we should return the current page.
const page = document.querySelector("#root.onboardingContainer[data-page]") ? document.querySelector("#root[data-page]").dataset.page : document.location.href;
const page = ((_document$querySelect = document.querySelector("#multi-stage-message-root.onboardingContainer[data-page]")) === null || _document$querySelect === void 0 ? void 0 : _document$querySelect.dataset.page) || document.location.href;
const AboutWelcomeUtils = {
handleUserAction(action) {
window.AWSendToParent("SPECIAL_ACTION", action);
@ -2200,7 +2202,7 @@ async function mount() {
react_dom__WEBPACK_IMPORTED_MODULE_1___default().render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(AboutWelcome, _extends({
messageId: messageId,
UTMTerm: UTMTerm
}, aboutWelcomeProps)), document.getElementById("root"));
}, aboutWelcomeProps)), document.getElementById("multi-stage-message-root"));
}
performance.mark("mount");

Просмотреть файл

@ -19,7 +19,7 @@
<link rel="localization" href="browser/spotlight.ftl"/>
</head>
<body>
<div id="root" class="welcome-container" role="presentation">
<div id="multi-stage-message-root" class="welcome-container" role="presentation">
</div>
<script src="resource://activity-stream/vendor/react.js"></script>
<script src="resource://activity-stream/vendor/react-dom.js"></script>

Просмотреть файл

@ -132,7 +132,7 @@ async function mount() {
UTMTerm={UTMTerm}
{...aboutWelcomeProps}
/>,
document.getElementById("root")
document.getElementById("multi-stage-message-root")
);
}

Просмотреть файл

@ -5,9 +5,10 @@
// If the container has a "page" data attribute, then this is
// a Spotlight modal or Feature Callout. Otherwise, this is
// about:welcome and we should return the current page.
const page = document.querySelector("#root.onboardingContainer[data-page]")
? document.querySelector("#root[data-page]").dataset.page
: document.location.href;
const page =
document.querySelector(
"#multi-stage-message-root.onboardingContainer[data-page]"
)?.dataset.page || document.location.href;
export const AboutWelcomeUtils = {
handleUserAction(action) {

Просмотреть файл

@ -730,32 +730,28 @@ add_setup(async function() {
});
});
// Test Fxaccounts MetricsFlowURI
test_newtab(
{
async before({ pushPrefs }) {
await pushPrefs(["browser.aboutwelcome.enabled", true]);
},
test: async function test_startBrowsing() {
add_task(async function test_FxA_metricsFlowURI() {
let browser = await openAboutWelcome();
await ContentTask.spawn(browser, {}, async () => {
Assert.ok(
await ContentTaskUtils.waitForCondition(
() => content.document.querySelector("div.onboardingContainer"),
"Wait for about:welcome to load"
);
},
after() {
Assert.ok(
FxAccounts.config.promiseMetricsFlowURI.called,
"Stub was called"
);
Assert.equal(
FxAccounts.config.promiseMetricsFlowURI.firstCall.args[0],
"aboutwelcome",
"Called by AboutWelcomeParent"
);
},
},
"about:welcome"
);
),
"about:welcome loaded"
);
});
Assert.ok(FxAccounts.config.promiseMetricsFlowURI.called, "Stub was called");
Assert.equal(
FxAccounts.config.promiseMetricsFlowURI.firstCall.args[0],
"aboutwelcome",
"Called by AboutWelcomeParent"
);
SpecialPowers.popPrefEnv();
});
add_task(async function test_send_aboutwelcome_as_page_in_event_telemetry() {
const sandbox = sinon.createSandbox();

Просмотреть файл

@ -7,7 +7,7 @@ const { ASRouter } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouter.jsm"
);
const calloutId = "root";
const calloutId = "multi-stage-message-root";
const calloutSelector = `#${calloutId}.featureCallout`;
const primaryButtonSelector = `#${calloutId} .primary`;
const PDF_TEST_URL =
@ -187,7 +187,9 @@ add_task(
const tab2 = await openURLInNewTab(win, "about:preferences");
tab2.focus();
await BrowserTestUtils.waitForCondition(() => {
return !doc.body.querySelector("#root.featureCallout");
return !doc.body.querySelector(
"#multi-stage-message-root.featureCallout"
);
});
ok(

Просмотреть файл

@ -2,8 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*eslint-env browser*/
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
@ -14,7 +12,7 @@ XPCOMUtils.defineLazyModuleGetters(lazy, {
});
const TRANSITION_MS = 500;
const CONTAINER_ID = "root";
const CONTAINER_ID = "multi-stage-message-root";
const BUNDLE_SRC =
"resource://activity-stream/aboutwelcome/aboutwelcome.bundle.js";
@ -157,13 +155,12 @@ export class FeatureCallout {
this.currentScreen = null;
} else if (prefVal.screen !== this.currentScreen?.id) {
this.ready = false;
const container = this.doc.getElementById(CONTAINER_ID);
container?.classList.add("hidden");
this._container?.classList.add("hidden");
this._pageEventManager?.clear();
// wait for fade out transition
this.win.setTimeout(async () => {
await this._loadConfig();
container?.remove();
this._container?.remove();
this._removePositionListeners();
this.doc.querySelector(`[src="${BUNDLE_SRC}"]`)?.remove();
await this._renderCallout();
@ -174,16 +171,16 @@ export class FeatureCallout {
handleEvent(event) {
switch (event.type) {
case "focus": {
let container = this.doc.getElementById(CONTAINER_ID);
if (!container) {
if (!this._container) {
return;
}
// If focus has fired on the feature callout window itself, or on something
// contained in that window, ignore it, as we can't possibly place the focus
// on it after the callout is closd.
if (
event.target.id === CONTAINER_ID ||
(Node.isInstance(event.target) && container.contains(event.target))
event.target === this._container ||
(Node.isInstance(event.target) &&
this._container.contains(event.target))
) {
return;
}
@ -198,8 +195,7 @@ export class FeatureCallout {
if (event.key !== "Escape") {
return;
}
let container = this.doc.getElementById(CONTAINER_ID);
if (!container) {
if (!this._container) {
return;
}
let focusedElement =
@ -211,7 +207,7 @@ export class FeatureCallout {
!focusedElement ||
focusedElement === this.doc.body ||
focusedElement === this.browser ||
container.contains(focusedElement)
this._container.contains(focusedElement)
) {
this.win.AWSendEventTelemetry?.({
event: "DISMISS",
@ -290,31 +286,33 @@ export class FeatureCallout {
return false;
}
let container = this.doc.createElement("div");
container.classList.add(
"onboardingContainer",
"featureCallout",
"callout-arrow",
"hidden"
);
container.id = CONTAINER_ID;
// This value is reported as the "page" in about:welcome telemetry
container.dataset.page = this.page;
container.setAttribute(
"aria-describedby",
`#${CONTAINER_ID} .welcome-text`
);
container.tabIndex = 0;
this._applyTheme(container);
this.doc.body.prepend(container);
return container;
if (!this._container?.parentElement) {
this._container = this.doc.createElement("div");
this._container.classList.add(
"onboardingContainer",
"featureCallout",
"callout-arrow",
"hidden"
);
this._container.id = CONTAINER_ID;
// This value is reported as the "page" in about:welcome telemetry
this._container.dataset.page = this.page;
this._container.setAttribute(
"aria-describedby",
`#${CONTAINER_ID} .welcome-text`
);
this._container.tabIndex = 0;
this._applyTheme();
this.doc.body.prepend(this._container);
}
return this._container;
}
/**
* Set callout's position relative to parent element
*/
_positionCallout() {
const container = this.doc.getElementById(CONTAINER_ID);
const container = this._container;
const parentEl = this.doc.querySelector(
this.currentScreen?.parent_selector
);
@ -727,12 +725,11 @@ export class FeatureCallout {
delete this.featureTourProgress;
this.ready = false;
// wait for fade out transition
let container = this.doc.getElementById(CONTAINER_ID);
container?.classList.add("hidden");
this._container?.classList.add("hidden");
this._clearWindowFunctions();
this.win.setTimeout(
() => {
container?.remove();
this._container?.remove();
this.renderObserver?.disconnect();
this._removePositionListeners();
this.doc.querySelector(`[src="${BUNDLE_SRC}"]`)?.remove();
@ -961,7 +958,7 @@ export class FeatureCallout {
this.renderObserver = new this.win.MutationObserver(() => {
// Check if the Feature Callout screen has loaded for the first time
if (!this.ready && this.doc.querySelector(`#${CONTAINER_ID} .screen`)) {
if (!this.ready && this._container.querySelector(".screen")) {
// Once the screen element is added to the DOM, wait for the
// animation frame after next to ensure that _positionCallout
// has access to the rendered screen with the correct height
@ -973,9 +970,7 @@ export class FeatureCallout {
);
this.win.addEventListener("keypress", this, { capture: true });
this._positionCallout();
let button = this.doc
.getElementById(CONTAINER_ID)
.querySelector(".primary");
let button = this._container.querySelector(".primary");
button.focus();
this.win.addEventListener("focus", this, {
capture: true, // get the event before retargeting
@ -988,8 +983,7 @@ export class FeatureCallout {
this._pageEventManager?.clear();
this.ready = false;
const container = this.doc.getElementById(CONTAINER_ID);
container?.remove();
this._container?.remove();
// If user has disabled CFR, don't show any callouts. But make sure we load
// the necessary stylesheets first, since re-enabling CFR should allow
@ -1049,16 +1043,15 @@ export class FeatureCallout {
* custom properties in inline styles. These custom properties are consumed by
* _feature-callout-theme.scss, which is bundled with the other styles that
* are loaded by {@link FeatureCallout.prototype._addCalloutLinkElements}.
* @param {Element} [container] Root element of the feature callout
*/
_applyTheme(container = this.doc?.getElementById(CONTAINER_ID)) {
if (container) {
_applyTheme() {
if (this._container) {
// This tells the stylesheets to use -moz-content-prefers-color-scheme
// instead of prefers-color-scheme, in order to follow the content color
// scheme instead of the chrome color scheme, in case of a mismatch when
// the feature callout exists in the chrome but is meant to look like it's
// part of the content of a page in a browser tab (like PDF.js).
container.classList.toggle(
this._container.classList.toggle(
"simulateContent",
this.page === "chrome" && this.theme.simulateContent
);
@ -1066,7 +1059,6 @@ export class FeatureCallout {
const scheme = this.theme[type];
for (const name of FeatureCallout.themePropNames) {
this._setThemeVariable(
container,
`--fc-${name}-${type}`,
scheme?.[name] || this.theme.all[name]
);
@ -1077,15 +1069,14 @@ export class FeatureCallout {
/**
* Set or remove a CSS custom property on the feature callout container
* @param {Element} container Root element of the feature callout
* @param {String} name Name of the CSS custom property
* @param {String|void} [value] Value of the property, or omit to remove it
*/
_setThemeVariable(container, name, value) {
_setThemeVariable(name, value) {
if (value) {
container.style.setProperty(name, value);
this._container.style.setProperty(name, value);
} else {
container.style.removeProperty(name);
this._container.style.removeProperty(name);
}
}

Просмотреть файл

@ -84,7 +84,7 @@ add_task(async function test_CLICK_ELEMENT() {
await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow;
const calloutSelector = "#root.featureCallout";
const calloutSelector = "#multi-stage-message-root.featureCallout";
await BrowserTestUtils.waitForCondition(() => {
return document.querySelector(