Bug 1686343 - Ask user to pin Firefox during windows about:welcome onboarding r=pdahiya

Support pin special action and add a new action property to wait for default browser that changes styles and content.

Differential Revision: https://phabricator.services.mozilla.com/D105653
This commit is contained in:
Ed Lee 2021-02-19 10:25:54 +00:00
parent d3ac263f8c
commit f348f8d342
10 changed files with 202 additions and 6 deletions

View File

@ -198,6 +198,10 @@ class AboutWelcomeChild extends JSWindowActorChild {
defineAs: "AWGetRegion",
});
Cu.exportFunction(this.AWIsDefaultBrowser.bind(this), window, {
defineAs: "AWIsDefaultBrowser",
});
Cu.exportFunction(this.AWSelectTheme.bind(this), window, {
defineAs: "AWSelectTheme",
});
@ -338,6 +342,10 @@ class AboutWelcomeChild extends JSWindowActorChild {
return this.wrapPromise(getSelectedTheme(this));
}
AWIsDefaultBrowser() {
return this.wrapPromise(this.sendQuery("AWPage:IS_DEFAULT_BROWSER"));
}
/**
* Send Event Telemetry
* @param {object} eventData

View File

@ -267,6 +267,8 @@ class AboutWelcomeParent extends JSWindowActorParent {
this.RegionHomeObserver = new RegionHomeObserver(this);
}
return this.RegionHomeObserver.promiseRegionHome();
case "AWPage:IS_DEFAULT_BROWSER":
return window.getShellService().isDefaultBrowser();
case "AWPage:WAIT_FOR_MIGRATION_CLOSE":
return new Promise(resolve =>
Services.ww.registerNotification(function observer(subject, topic) {

View File

@ -453,6 +453,9 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
constructor(props) {
super(props);
this.handleAction = this.handleAction.bind(this);
this.state = {
alternateContent: ""
};
}
handleOpenURL(action, flowParams, UTMTerm) {
@ -521,6 +524,23 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
await window.AWWaitForMigrationClose();
_lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].sendActionTelemetry(props.messageId, "migrate_close");
}
} // Wait until we become default browser to continue rest of action.
if (action.waitForDefault) {
// Update the UI to show additional "waiting" content.
this.setState({
alternateContent: "waiting_for_default"
}); // Keep checking frequently as we want the UI to be responsive.
await new Promise(resolve => async function checkDefault() {
if (await window.AWIsDefaultBrowser()) {
resolve();
} else {
setTimeout(checkDefault, 100);
}
}());
_lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].sendActionTelemetry(props.messageId, "default_browser");
} // A special tiles.action.theme value indicates we should use the event's value vs provided value.
@ -668,10 +688,16 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
}
render() {
// Use the provided content or switch to an alternate one.
const {
content,
topSites
} = this.props;
if (content[this.state.alternateContent]) {
Object.assign(content, content[this.state.alternateContent]);
}
const showImportableSitesDisclaimer = content.tiles && content.tiles.type === "topsites" && topSites && topSites.showImportable;
return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("main", {
className: `screen ${this.props.id}`
@ -690,7 +716,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
className: "primary",
value: "primary_button",
onClick: this.handleAction
}))), content.secondary_button ? this.renderSecondaryCTA() : null, content.help_text && content.help_text.position === "default" ? this.renderHelpText() : null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("nav", {
}))), content.help_text && content.help_text.position === "default" ? this.renderHelpText() : null, content.secondary_button ? this.renderSecondaryCTA() : null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("nav", {
className: content.help_text && content.help_text.position === "footer" || showImportableSitesDisclaimer ? "steps has-helptext" : "steps",
"data-l10n-id": "onboarding-welcome-steps-indicator",
"data-l10n-args": `{"current": ${parseInt(this.props.order, 10) + 1}, "total": ${this.props.totalNumberOfScreens}}`

View File

@ -457,6 +457,12 @@ body {
margin: 0; }
.onboardingContainer .tiles-media-section.privacy.media {
opacity: 0; }
.onboardingContainer .tiles-delayed {
animation: fadein 0.4s; }
@keyframes fadein {
from {
opacity: 0; } }
.onboardingContainer button {
font-family: inherit;
cursor: pointer;

View File

@ -556,6 +556,14 @@ body {
}
}
.tiles-delayed {
animation: fadein 0.4s;
}
@keyframes fadein {
from { opacity: 0; }
}
button {
font-family: inherit;
cursor: pointer;

View File

@ -136,6 +136,7 @@ export class WelcomeScreen extends React.PureComponent {
constructor(props) {
super(props);
this.handleAction = this.handleAction.bind(this);
this.state = { alternateContent: "" };
}
handleOpenURL(action, flowParams, UTMTerm) {
@ -193,6 +194,25 @@ export class WelcomeScreen extends React.PureComponent {
}
}
// Wait until we become default browser to continue rest of action.
if (action.waitForDefault) {
// Update the UI to show additional "waiting" content.
this.setState({ alternateContent: "waiting_for_default" });
// Keep checking frequently as we want the UI to be responsive.
await new Promise(resolve =>
(async function checkDefault() {
if (await window.AWIsDefaultBrowser()) {
resolve();
} else {
setTimeout(checkDefault, 100);
}
})()
);
AboutWelcomeUtils.sendActionTelemetry(props.messageId, "default_browser");
}
// A special tiles.action.theme value indicates we should use the event's value vs provided value.
if (action.theme) {
let themeToUse =
@ -383,7 +403,12 @@ export class WelcomeScreen extends React.PureComponent {
}
render() {
// Use the provided content or switch to an alternate one.
const { content, topSites } = this.props;
if (content[this.state.alternateContent]) {
Object.assign(content, content[this.state.alternateContent]);
}
const showImportableSitesDisclaimer =
content.tiles &&
content.tiles.type === "topsites" &&
@ -416,10 +441,10 @@ export class WelcomeScreen extends React.PureComponent {
/>
</Localized>
</div>
{content.secondary_button ? this.renderSecondaryCTA() : null}
{content.help_text && content.help_text.position === "default"
? this.renderHelpText()
: null}
{content.secondary_button ? this.renderSecondaryCTA() : null}
<nav
className={
(content.help_text && content.help_text.position === "footer") ||

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@ -189,10 +189,10 @@ module.exports = function(config) {
branches: 70,
},
"content-src/aboutwelcome/**/*.jsx": {
statements: 50,
lines: 50,
functions: 76,
branches: 0,
statements: 62,
lines: 60,
functions: 83,
branches: 50,
},
"content-src/components/**/*.jsx": {
statements: 51.1,

View File

@ -214,5 +214,102 @@ describe("MultiStageAboutWelcome module", () => {
);
});
});
describe("#handleAction", () => {
let SCREEN_PROPS;
let TEST_ACTION;
beforeEach(() => {
SCREEN_PROPS = {
content: {
primary_button: {
action: {},
label: "test button",
},
},
navigate: sandbox.stub(),
setActiveTheme: sandbox.stub(),
UTMTerm: "you_tee_emm",
};
TEST_ACTION = SCREEN_PROPS.content.primary_button.action;
sandbox.stub(AboutWelcomeUtils, "handleUserAction");
});
it("should handle navigate", () => {
TEST_ACTION.navigate = true;
const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
wrapper.find(".primary").simulate("click");
assert.calledOnce(SCREEN_PROPS.navigate);
});
it("should handle theme", () => {
TEST_ACTION.theme = "test";
const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
wrapper.find(".primary").simulate("click");
assert.calledWith(SCREEN_PROPS.setActiveTheme, "test");
});
it("should handle SHOW_FIREFOX_ACCOUNTS", () => {
TEST_ACTION.type = "SHOW_FIREFOX_ACCOUNTS";
const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
wrapper.find(".primary").simulate("click");
assert.calledWith(AboutWelcomeUtils.handleUserAction, {
data: {
extraParams: {
utm_campaign: "firstrun",
utm_medium: "referral",
utm_source: "activity-stream",
utm_term: "aboutwelcome-you_tee_emm-screen",
},
},
type: "SHOW_FIREFOX_ACCOUNTS",
});
});
it("should handle SHOW_MIGRATION_WIZARD", () => {
TEST_ACTION.type = "SHOW_MIGRATION_WIZARD";
const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
wrapper.find(".primary").simulate("click");
assert.calledWith(AboutWelcomeUtils.handleUserAction, {
type: "SHOW_MIGRATION_WIZARD",
});
});
it("should handle waitForDefault", () => {
TEST_ACTION.waitForDefault = true;
const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
wrapper.find(".primary").simulate("click");
assert.propertyVal(
wrapper.state(),
"alternateContent",
"waiting_for_default"
);
});
});
describe("alternate content", () => {
const SCREEN_PROPS = {
content: {
title: "Original",
alternate: {
title: "Alternate",
},
},
};
it("should show original title", () => {
const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
assert.equal(wrapper.find(".welcome-text").text(), "Original");
});
it("should show alternate title", () => {
const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
wrapper.setState({ alternateContent: "alternate" });
assert.equal(wrapper.find(".welcome-text").text(), "Alternate");
});
});
});
});

View File

@ -79,6 +79,23 @@ const SpecialMessageActions = {
}
},
/**
* Pin Firefox to taskbar.
*
* @param {Window} window Reference to a window object
*/
pinFirefoxToTaskbar(window) {
try {
// Currently this only works on certain Windows versions.
window
.getShellService()
.QueryInterface(Ci.nsIWindowsShellService)
.pinCurrentAppToTaskbar();
} catch (e) {
Cu.reportError(e);
}
},
/**
* Set browser as the operating system default browser.
*
@ -229,6 +246,13 @@ const SpecialMessageActions = {
action.data.telemetrySource
);
break;
case "PIN_FIREFOX_TO_TASKBAR":
this.pinFirefoxToTaskbar(window);
break;
case "PIN_AND_DEFAULT":
this.pinFirefoxToTaskbar(window);
this.setDefaultBrowser(window);
break;
case "SET_DEFAULT_BROWSER":
this.setDefaultBrowser(window);
break;