/* 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 gClickEvents = ["mousedown", "mouseup", "click"]; const gActionDescrMap = { jump: "Jump", press: "Press", check: "Check", uncheck: "Uncheck", select: "Select", open: "Open", close: "Close", switch: "Switch", click: "Click", collapse: "Collapse", expand: "Expand", activate: "Activate", cycle: "Cycle", clickAncestor: "Click ancestor", }; async function testActions(browser, docAcc, id, expectedActions, domEvents) { info(`Testing element ${id}`); const acc = findAccessibleChildByID(docAcc, id); is(acc.actionCount, expectedActions.length, "Correct action count"); let actionNames = []; let actionDescriptions = []; for (let i = 0; i < acc.actionCount; i++) { actionNames.push(acc.getActionName(i)); actionDescriptions.push(acc.getActionDescription(i)); } is(actionNames.join(","), expectedActions.join(","), "Correct action names"); is( actionDescriptions.join(","), expectedActions.map(a => gActionDescrMap[a]).join(","), "Correct action descriptions" ); if (!domEvents) { return; } // We need to set up the listener, and wait for the promise in two separate // content tasks. await invokeContentTask(browser, [id, domEvents], (_id, _domEvents) => { let promises = _domEvents.map( evtName => new Promise(resolve => { const listener = e => { if (e.target.id == _id) { content.removeEventListener(evtName, listener); resolve(42); } }; content.addEventListener(evtName, listener); }) ); content.evtPromise = Promise.all(promises); }); acc.doAction(0); let eventFired = await invokeContentTask(browser, [], async () => { await content.evtPromise; content.evtPromise = null; return true; }); ok(eventFired, `DOM events fired '${domEvents}'`); } addAccessibleTask( `
linkable textleaf accessible
p in clickable div
`,
async function (browser, docAcc) {
is(docAcc.actionCount, 0, "Doc should not have any actions");
const _testActions = async (id, expectedActions, domEvents) => {
await testActions(browser, docAcc, id, expectedActions, domEvents);
};
await _testActions("li_clickable1", ["click"], gClickEvents);
await _testActions("li_clickable2", ["click"], gClickEvents);
await _testActions("li_clickable3", ["click"], gClickEvents);
await _testActions("onclick_img", ["click"], gClickEvents);
await _testActions("link1", ["jump"], gClickEvents);
await _testActions("link2", ["click"], gClickEvents);
await _testActions("link3", ["jump"], gClickEvents);
await _testActions("link3img", ["clickAncestor"], gClickEvents);
await _testActions("link4", ["jump"], gClickEvents);
await _testActions("link4img", ["clickAncestor"], gClickEvents);
await _testActions("link5", ["click"], gClickEvents);
await _testActions("link5img", ["clickAncestor"], gClickEvents);
await _testActions("link6", ["click"], gClickEvents);
await _testActions("link6img", ["clickAncestor"], gClickEvents);
await _testActions("link7", ["click"], gClickEvents);
await _testActions("link7img", ["clickAncestor"], gClickEvents);
await _testActions("label1", ["click"], gClickEvents);
await _testActions("p_in_clickable_div", ["clickAncestor"], gClickEvents);
await _testActions("area", ["jump"], gClickEvents);
await invokeContentTask(browser, [], () => {
content.document.getElementById("li_clickable1").onclick = null;
});
let acc = findAccessibleChildByID(docAcc, "li_clickable1");
await untilCacheIs(() => acc.actionCount, 0, "li has no actions");
let thrown = false;
try {
acc.doAction(0);
} catch (e) {
thrown = true;
}
ok(thrown, "doAction should throw exception");
// Remove 'for' from label
await invokeContentTask(browser, [], () => {
content.document.getElementById("label1").removeAttribute("for");
});
acc = findAccessibleChildByID(docAcc, "label1");
await untilCacheIs(() => acc.actionCount, 0, "label has no actions");
thrown = false;
try {
acc.doAction(0);
ok(false, "doAction should throw exception");
} catch (e) {
thrown = true;
}
ok(thrown, "doAction should throw exception");
// Add 'longdesc' to image
await invokeContentTask(browser, [], () => {
content.document
.getElementById("onclick_img")
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
.setAttribute("longdesc", "http://example.com");
});
acc = findAccessibleChildByID(docAcc, "onclick_img");
await untilCacheIs(() => acc.actionCount, 2, "img has 2 actions");
await _testActions("onclick_img", ["click", "showlongdesc"]);
// Remove 'onclick' from image with 'longdesc'
await invokeContentTask(browser, [], () => {
content.document.getElementById("onclick_img").onclick = null;
});
acc = findAccessibleChildByID(docAcc, "onclick_img");
await untilCacheIs(() => acc.actionCount, 1, "img has 1 actions");
await _testActions("onclick_img", ["showlongdesc"]);
// Remove 'href' from link and test linkable child
let link1Acc = findAccessibleChildByID(docAcc, "link1");
is(
link1Acc.firstChild.getActionName(0),
"clickAncestor",
"linkable child has clickAncestor action"
);
let onRecreation = waitForEvents({
expected: [
[EVENT_HIDE, link1Acc],
[EVENT_SHOW, "link1"],
],
});
await invokeContentTask(browser, [], () => {
let link1 = content.document.getElementById("link1");
link1.removeAttribute("href");
});
await onRecreation;
link1Acc = findAccessibleChildByID(docAcc, "link1");
await untilCacheIs(() => link1Acc.actionCount, 0, "link has no actions");
is(link1Acc.firstChild.actionCount, 0, "linkable child's actions removed");
// Add a click handler to the body. Ensure it propagates to descendants.
await invokeContentTask(browser, [], () => {
content.document.body.onclick = () => {};
});
await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action");
await _testActions("link1", ["clickAncestor"]);
await invokeContentTask(browser, [], () => {
content.document.body.onclick = null;
});
await untilCacheIs(() => docAcc.actionCount, 0, "Doc has no actions");
is(link1Acc.actionCount, 0, "link has no actions");
// Add a click handler to the root element. Ensure it propagates to
// descendants.
await invokeContentTask(browser, [], () => {
content.document.documentElement.onclick = () => {};
});
await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action");
await _testActions("link1", ["clickAncestor"]);
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
contentSetup: async function contentSetup() {
// Attach dummy event handlers here, because inline event handler attributes
// will be blocked in the chrome context.
for (const el of content.document.querySelectorAll("[data-event]")) {
el["on" + el.dataset.event] = () => {};
}
},
}
);
/**
* Test access key.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const noKey = findAccessibleChildByID(docAcc, "noKey");
is(noKey.accessKey, "", "noKey has no accesskey");
const key = findAccessibleChildByID(docAcc, "key");
is(key.accessKey, MAC ? "⌃⌥a" : "Alt+Shift+a", "key has correct accesskey");
info("Changing accesskey");
await invokeContentTask(browser, [], () => {
content.document.getElementById("key").accessKey = "b";
});
await untilCacheIs(
() => key.accessKey,
MAC ? "⌃⌥b" : "Alt+Shift+b",
"Correct accesskey after change"
);
info("Removing accesskey");
await invokeContentTask(browser, [], () => {
content.document.getElementById("key").removeAttribute("accesskey");
});
await untilCacheIs(
() => key.accessKey,
"",
"Empty accesskey after removal"
);
info("Adding accesskey");
await invokeContentTask(browser, [], () => {
content.document.getElementById("key").accessKey = "c";
});
await untilCacheIs(
() => key.accessKey,
MAC ? "⌃⌥c" : "Alt+Shift+c",
"Correct accesskey after addition"
);
},
{
chrome: true,
topLevel: true,
iframe: false, // Bug 1796846
remoteIframe: false, // Bug 1796846
}
);