Skip to content

Commit

Permalink
Merge pull request #5819 from mikeconley/fix-step1-a11y
Browse files Browse the repository at this point in the history
Improve accessibility of switching-devices wizard step 1 autofocus behaviour.
  • Loading branch information
mikeconley authored Dec 14, 2023
2 parents f2a4dd5 + c314e6e commit 1f2fc0c
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 9 deletions.
15 changes: 13 additions & 2 deletions kitsune/sumo/static/sumo/js/form-wizard-sign-in-step.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class SignInStep extends BaseFormStep {
<label for="email">${gettext("Enter your email")}</label>
<div class="tooltip-container">
<aside id="email-error" class="tooltip">${gettext("Valid email required")}</aside>
<input id="email" name="email" type="email" required="true" placeholder="${gettext("[email protected]")}"/>
<input id="email" name="email" type="email" required="true" placeholder="${gettext("[email protected]")}" pending-autofocus="" />
</div>
<button id="continue" class="mzp-c-button mzp-t-product" type="submit" data-event-category="device-migration-wizard" data-event-action="click">${gettext("Continue")}</button>
Expand Down Expand Up @@ -69,7 +69,6 @@ export class SignInStep extends BaseFormStep {
this.#formEl.addEventListener("submit", this);
this.#emailEl.addEventListener("blur", this);
this.#emailEl.addEventListener("input", this);
this.#emailEl.focus();
}

disconnectedCallback() {
Expand Down Expand Up @@ -169,6 +168,18 @@ export class SignInStep extends BaseFormStep {
}
}

// If we've reached the point where we've got hasUpdatedFxAState set true,
// this means that we've confirmed that we're running on a non-disqualified
// version of Firefox Desktop. If focus hasn't moved anywhere else in the
// meantime, we'll move focus to the email field if it's never been moved
// there before.
if (this.state.hasUpdatedFxAState && this.#emailEl.hasAttribute("pending-autofocus")) {
this.#emailEl.removeAttribute("pending-autofocus");
if (document.activeElement == document.body) {
this.#emailEl.focus();
}
}

let alternativeLinks = this.shadowRoot.querySelectorAll(".alternative-link");
for (let link of alternativeLinks) {
link.href = this.state.linkHref;
Expand Down
15 changes: 13 additions & 2 deletions kitsune/sumo/static/sumo/js/form-wizard.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class FormWizard extends HTMLElement {
static get markup() {
return `
<template>
<div class="form-wizard-root">
<div id="form-wizard-root">
<div class="form-wizard-content">
<section>
<ul id="step-indicator"></ul>
Expand Down Expand Up @@ -117,6 +117,17 @@ export class FormWizard extends HTMLElement {
// If there's no active step, default to the first step.
this.activeStep = this.firstElementChild?.getAttribute("name");
}

// We set up our aria attributes here instead of in
// hook_device_migration_wizard.html where the <form-wizard> is put into
// the markup of the page because that hook is only ever rendered when the
// SUMO article embedding the wizard is updated. By adding the attributes here,
// we can ensure that the attributes will apply, even for instances of SUMO
// kb articles embedding the form-wizard that might not get updated in a while.
let root = this.shadowRoot.querySelector("#form-wizard-root");
root.setAttribute("role", "dialog");
root.setAttribute("aria-label", gettext("Backup assistant"));
this.setAttribute("aria-describedby", "form-wizard-root");
}

get activeStep() {
Expand Down Expand Up @@ -208,7 +219,7 @@ export class FormWizard extends HTMLElement {
child.toggleAttribute("active", child.getAttribute("reason") === reason);
}

let root = this.shadowRoot.querySelector(".form-wizard-root");
let root = this.shadowRoot.querySelector("#form-wizard-root");
root.toggleAttribute("inert", true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default class SwitchingDevicesWizardManager {
sumoEmail: "",
syncEnabled: false,
confirmedSyncChoices: false,
hasUpdatedFxAState: false,
};

#state = Object.assign({}, this.#defaultState);
Expand Down Expand Up @@ -127,6 +128,7 @@ export default class SwitchingDevicesWizardManager {
return {
fxaRoot: state.fxaRoot,
email: state.sumoEmail,
hasUpdatedFxAState: state.hasUpdatedFxAState,
linkHref: `${state.fxaRoot}?${linkParams}`,

...baseParams,
Expand Down Expand Up @@ -425,6 +427,10 @@ export default class SwitchingDevicesWizardManager {
UITour.getConfiguration("fxa", resolve);
});

this.#updateState({
hasUpdatedFxAState: true,
});

if (fxaConfig.setup && fxaConfig.accountStateOK) {
let syncEnabled = !!fxaConfig.browserServices?.sync?.setup;
// If the user had already confirmed their sync choices, but then sync
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,59 @@ describe("sign-in-step custom element", () => {
assertFormElements(form, EXPECTED_FORM_ELEMENTS_WITH_FLOW_METRICS);
});

it("should focus the email field automatically", () => {
it("should focus the email field automatically after the hasUpdatedFxAState property is set to true", () => {
let email = step.shadowRoot.querySelector("#email");
expect(step.shadowRoot.activeElement).to.not.equal(email);
expect(email.hasAttribute("pending-autofocus")).to.be.true;

const TEST_STATE = {
utm_source: "utm_source",
utm_campaign: "utm_campaign",
utm_medium: "utm_medium",
entrypoint: "entrypoint",
entrypoint_experiment: "entrypoint_experiment",
entrypoint_variation: "entrypoint_variation",
context: "context",
redirect_to: window.location.href,
redirect_immediately: true,
service: "sync",
action: "email",
hasUpdatedFxAState: true,
};
step.setState(TEST_STATE);

expect(step.shadowRoot.activeElement).to.equal(email);
expect(email.hasAttribute("pending-autofocus")).to.be.false;
});

it("should not focus the email field automatically if focus moved after the hasUpdatedFxAState property is set to true", () => {
let email = step.shadowRoot.querySelector("#email");
expect(step.shadowRoot.activeElement).to.not.equal(email);
expect(email.hasAttribute("pending-autofocus")).to.be.true;

let someButton = document.createElement("button");
someButton.textContent = "I'm focused first.";
document.body.append(someButton);
someButton.focus();

const TEST_STATE = {
utm_source: "utm_source",
utm_campaign: "utm_campaign",
utm_medium: "utm_medium",
entrypoint: "entrypoint",
entrypoint_experiment: "entrypoint_experiment",
entrypoint_variation: "entrypoint_variation",
context: "context",
redirect_to: window.location.href,
redirect_immediately: true,
service: "sync",
action: "email",
hasUpdatedFxAState: true,
};
step.setState(TEST_STATE);

expect(step.shadowRoot.activeElement).to.not.equal(email);
expect(email.hasAttribute("pending-autofocus")).to.be.false;
});

it("should set an email address if one is set in the state", () => {
Expand Down
2 changes: 1 addition & 1 deletion kitsune/sumo/static/sumo/js/tests/form-wizard-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ describe("form-wizard custom element", () => {
it("should put the root into the disqualified state and populate the header/message", () => {
wizard.disqualify("need-fx-desktop");

let root = wizard.shadowRoot.querySelector(".form-wizard-root");
let root = wizard.shadowRoot.querySelector("#form-wizard-root");
expect(root.hasAttribute("inert")).to.be.true;

let needFxDesktop = wizard.shadowRoot.querySelector(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ describe("k", () => {
context: "fx_desktop_v3",
redirect_to: window.location.href,
redirect_immediately: true,
hasUpdatedFxAState: true,
});
});

Expand Down Expand Up @@ -418,6 +419,7 @@ describe("k", () => {
sumoEmail: "[email protected]",
syncEnabled: false,
confirmedSyncChoices: false,
hasUpdatedFxAState: true,
};
const EXPECTED_PAYLOAD = {
service: "sync",
Expand All @@ -436,14 +438,17 @@ describe("k", () => {
redirect_to: window.location.href,
redirect_immediately: true,
linkHref: `${FAKE_FXA_ROOT}?service=sync&action=email&utm_source=support.mozilla.org&utm_campaign=migration&utm_medium=mozilla-websites&entrypoint=fx-new-device-sync&entrypoint_experiment=experiment&entrypoint_variation=variation&flow_id=${FAKE_FXA_FLOW_ID}&flow_begin_time=${FAKE_FXA_FLOW_BEGIN_TIME}&context=fx_desktop_v3&redirect_to=https%3A%2F%2Fexample.com%2F%23search&redirect_immediately=true`,
hasUpdatedFxAState: true,
};
expect(step.enter(TEST_STATE)).to.deep.equal(EXPECTED_PAYLOAD);
});

it("should send the user to the configure-sync step immediately if UITour says that the user is signed in", async () => {
let setStepCalled = new Promise((resolve) => {
gSetStepStub.callsFake((name, payload) => {
resolve({ name, payload });
if (name == "configure-sync") {
resolve({ name, payload });
}
});
});

Expand Down
4 changes: 2 additions & 2 deletions kitsune/sumo/static/sumo/scss/form-wizard.styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
box-shadow: 0px 2px 6px rgba(58, 57, 68, 0.2);
}

.form-wizard-root {
#form-wizard-root {
display: flex;
flex-direction: column;
position: relative;
}

:host([disqualified-mobile]) .form-wizard-content,
:host([disqualified-mobile]) #progress,
.form-wizard-root:not([inert]) > .form-wizard-disqualified {
#form-wizard-root:not([inert]) > .form-wizard-disqualified {
display: none;
}

Expand Down

0 comments on commit 1f2fc0c

Please sign in to comment.