Bug 1428430 - added enable, disable, highlighter, picker a11y functionality. r=pbro, ochameau

MozReview-Commit-ID: 7QsY75oJCtW
This commit is contained in:
Yura Zenevich 2018-03-01 09:48:41 -05:00
Родитель e8f5150467
Коммит 8dccbf98a3
11 изменённых файлов: 1388 добавлений и 273 удалений

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

@ -0,0 +1,232 @@
/* 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 { Cc, Ci } = require("chrome");
const Services = require("Services");
const PREF_ACCESSIBILITY_FORCE_DISABLED = "accessibility.force_disabled";
/**
* A helper class that does all the work related to accessibility service
* lifecycle (initialization, shutdown, consumer changes, etc) in parent
* parent process. It is not guaranteed that the AccessibilityActor starts in
* parent process and thus triggering these lifecycle functions directly is
* extremely unreliable.
*/
class AccessibilityParent {
constructor(mm, prefix) {
this._msgName = `debug:${prefix}accessibility`;
this.onAccessibilityMessage = this.onAccessibilityMessage.bind(this);
this.setMessageManager(mm);
this.userPref = Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
Services.obs.addObserver(this, "a11y-consumers-changed");
Services.prefs.addObserver(PREF_ACCESSIBILITY_FORCE_DISABLED, this);
if (this.enabled && !this.accService) {
// Set a local reference to an accessibility service if accessibility was
// started elsewhere to ensure that parent process a11y service does not
// get GC'ed away.
this.accService = Cc["@mozilla.org/accessibilityService;1"].getService(
Ci.nsIAccessibilityService);
}
this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
topic: "initialized",
data: {
canBeDisabled: this.canBeDisabled,
canBeEnabled: this.canBeEnabled
}
});
}
/**
* Set up message manager listener to listen for messages coming from the
* AccessibilityActor when it is instantiated in the child process.
*
* @param {Object} mm
* Message manager that corresponds to the current content tab.
*/
setMessageManager(mm) {
if (this.messageManager === mm) {
return;
}
if (this.messageManager) {
// If the browser was swapped we need to reset the message manager.
let oldMM = this.messageManager;
oldMM.removeMessageListener(this._msgName, this.onAccessibilityMessage);
}
this.messageManager = mm;
if (mm) {
mm.addMessageListener(this._msgName, this.onAccessibilityMessage);
}
}
/**
* Content AccessibilityActor message listener.
*
* @param {String} msg
* Name of the action to perform.
*/
onAccessibilityMessage(msg) {
let { action } = msg.json;
switch (action) {
case "enable":
this.enable();
break;
case "disable":
this.disable();
break;
case "disconnect":
this.destroy();
break;
default:
break;
}
}
observe(subject, topic, data) {
if (topic === "a11y-consumers-changed") {
// This event is fired when accessibility service consumers change. Since
// this observer lives in parent process there are 2 possible consumers of
// a11y service: XPCOM and PlatformAPI (e.g. screen readers). We only care
// about PlatformAPI consumer changes because when set, we can no longer
// disable accessibility service.
let { PlatformAPI } = JSON.parse(data);
this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
topic: "can-be-disabled-change",
data: !PlatformAPI
});
} else if (!this.disabling && topic === "nsPref:changed" &&
data === PREF_ACCESSIBILITY_FORCE_DISABLED) {
// PREF_ACCESSIBILITY_FORCE_DISABLED preference change event. When set to
// >=1, it means that the user wants to disable accessibility service and
// prevent it from starting in the future. Note: we also check
// this.disabling state when handling this pref change because this is how
// we disable the accessibility inspector itself.
this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
topic: "can-be-enabled-change",
data: Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED) < 1
});
}
}
/**
* A getter that indicates if accessibility service is enabled.
*
* @return {Boolean}
* True if accessibility service is on.
*/
get enabled() {
return Services.appinfo.accessibilityEnabled;
}
/**
* A getter that indicates if the accessibility service can be disabled.
*
* @return {Boolean}
* True if accessibility service can be disabled.
*/
get canBeDisabled() {
if (this.enabled) {
let a11yService = Cc["@mozilla.org/accessibilityService;1"].getService(
Ci.nsIAccessibilityService);
let { PlatformAPI } = JSON.parse(a11yService.getConsumers());
return !PlatformAPI;
}
return true;
}
/**
* A getter that indicates if the accessibility service can be enabled.
*
* @return {Boolean}
* True if accessibility service can be enabled.
*/
get canBeEnabled() {
return Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED) < 1;
}
/**
* Enable accessibility service (via XPCOM service).
*/
enable() {
if (this.enabled || !this.canBeEnabled) {
return;
}
this.accService = Cc["@mozilla.org/accessibilityService;1"].getService(
Ci.nsIAccessibilityService);
}
/**
* Force disable accessibility service. This method removes the reference to
* the XPCOM a11y service object and flips the
* PREF_ACCESSIBILITY_FORCE_DISABLED preference on and off to shutdown a11y
* service.
*/
disable() {
if (!this.enabled || !this.canBeDisabled) {
return;
}
this.disabling = true;
this.accService = null;
// Set PREF_ACCESSIBILITY_FORCE_DISABLED to 1 to force disable
// accessibility service. This is the only way to guarantee an immediate
// accessibility service shutdown in all processes. This also prevents
// accessibility service from starting up in the future.
Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1);
// Set PREF_ACCESSIBILITY_FORCE_DISABLED back to previous default or user
// set value. This will not start accessibility service until the user
// activates it again. It simply ensures that accessibility service can
// start again (when value is below 1).
Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, this.userPref);
delete this.disabling;
}
/**
* Destroy thie helper class, remove all listeners and if possible disable
* accessibility service in the parent process.
*/
destroy() {
Services.obs.removeObserver(this, "a11y-consumers-changed");
Services.prefs.removeObserver(PREF_ACCESSIBILITY_FORCE_DISABLED, this);
this.setMessageManager(null);
this.accService = null;
}
}
/**
* Setup function that runs in parent process and setups AccessibleActor bits
* that must always run in parent process.
*
* @param {Object} options.mm
* Message manager that corresponds to the current content tab.
* @param {String} options.prefix
* Unique prefix for message manager messages.
* @return {Object}
* Defines event listeners for when client disconnects or browser gets
* swapped.
*/
function setupParentProcess({ mm, prefix }) {
let accessibility = new AccessibilityParent(mm, prefix);
return {
onBrowserSwap: newMM => accessibility.setMessageManager(newMM),
onDisconnected: () => {
accessibility.destroy();
accessibility = null;
}
};
}
exports.setupParentProcess = setupParentProcess;

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -13,6 +13,7 @@ DIRS += [
]
DevToolsModules(
'accessibility-parent.js',
'accessibility.js',
'actor-registry.js',
'addon.js',

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

@ -27,8 +27,8 @@ support-files =
!/devtools/server/tests/mochitest/hello-actor.js
!/devtools/client/framework/test/shared-head.js
[browser_accessibility_node_events.js]
[browser_accessibility_node.js]
[browser_accessibility_node_events.js]
[browser_accessibility_simple.js]
[browser_accessibility_walker.js]
[browser_animation_emitMutations.js]

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

@ -10,7 +10,8 @@ add_task(async function () {
let {client, walker, accessibility} =
await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
let a11yWalker = await accessibility.getWalker(walker);
let a11yWalker = await accessibility.getWalker();
await accessibility.enable();
let buttonNode = await walker.querySelector(walker.rootNode, "#button");
let accessibleFront = await a11yWalker.getAccessibleFor(buttonNode);
@ -22,38 +23,23 @@ add_task(async function () {
help: "",
keyboardShortcut: "",
childCount: 1,
domNodeType: 1
domNodeType: 1,
indexInParent: 1,
states: ["focusable", "selectable text", "opaque", "enabled", "sensitive"],
actions: [ "Press" ],
attributes: {
"margin-top": "0px",
display: "inline-block",
"text-align": "center",
"text-indent": "0px",
"margin-left": "0px",
tag: "button",
"margin-right": "0px",
id: "button",
"margin-bottom": "0px"
}
});
info("Actions");
let actions = await accessibleFront.getActions();
is(actions.length, 1, "Accessible Front has correct number of actions");
is(actions[0], "Press", "Accessible Front default action is correct");
info("Index in parent");
let index = await accessibleFront.getIndexInParent();
is(index, 1, "Accessible Front has correct index in parent");
info("State");
let state = await accessibleFront.getState();
SimpleTest.isDeeply(state,
["focusable", "selectable text", "opaque", "enabled", "sensitive"],
"Accessible Front has correct states");
info("Attributes");
let attributes = await accessibleFront.getAttributes();
SimpleTest.isDeeply(attributes, {
"margin-top": "0px",
display: "inline-block",
"text-align": "center",
"text-indent": "0px",
"margin-left": "0px",
tag: "button",
"margin-right": "0px",
id: "button",
"margin-bottom": "0px"
}, "Accessible Front has correct attributes");
info("Children");
let children = await accessibleFront.children();
is(children.length, 1, "Accessible Front has correct number of children");
@ -62,13 +48,8 @@ add_task(async function () {
role: "text leaf"
});
info("DOM Node");
let node = await accessibleFront.getDOMNode(walker);
is(node, buttonNode, "Accessible Front has correct DOM node");
let a11yShutdown = waitForA11yShutdown();
await accessibility.disable();
await waitForA11yShutdown();
await client.close();
forceCollections();
await a11yShutdown;
gBrowser.removeCurrentTab();
});

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

@ -10,8 +10,10 @@ add_task(async function () {
let {client, walker, accessibility} =
await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
let a11yWalker = await accessibility.getWalker(walker);
let a11yDoc = await a11yWalker.getDocument();
let a11yWalker = await accessibility.getWalker();
await accessibility.enable();
let rootNode = await walker.getRootNode();
let a11yDoc = await a11yWalker.getAccessibleFor(rootNode);
let buttonNode = await walker.querySelector(walker.rootNode, "#button");
let accessibleFront = await a11yWalker.getAccessibleFor(buttonNode);
let sliderNode = await walker.querySelector(walker.rootNode, "#slider");
@ -26,7 +28,21 @@ add_task(async function () {
help: "",
keyboardShortcut: "",
childCount: 1,
domNodeType: 1
domNodeType: 1,
indexInParent: 1,
states: ["focusable", "selectable text", "opaque", "enabled", "sensitive"],
actions: [ "Press" ],
attributes: {
"margin-top": "0px",
display: "inline-block",
"text-align": "center",
"text-indent": "0px",
"margin-left": "0px",
tag: "button",
"margin-right": "0px",
id: "button",
"margin-bottom": "0px"
}
});
info("Name change event");
@ -45,27 +61,35 @@ add_task(async function () {
content.document.getElementById("button").removeAttribute("aria-describedby")));
info("State change event");
let states = await accessibleFront.getState();
let expectedStates = ["unavailable", "selectable text", "opaque"];
SimpleTest.isDeeply(states, ["focusable", "selectable text", "opaque",
"enabled", "sensitive"], "States are correct");
await emitA11yEvent(accessibleFront, "state-change",
newStates => SimpleTest.isDeeply(newStates, expectedStates,
"States are updated"),
() => ContentTask.spawn(browser, null, () =>
await emitA11yEvent(accessibleFront, "states-change",
newStates => {
checkA11yFront(accessibleFront, { states: expectedStates });
SimpleTest.isDeeply(newStates, expectedStates, "States are updated");
}, () => ContentTask.spawn(browser, null, () =>
content.document.getElementById("button").setAttribute("disabled", true)));
states = await accessibleFront.getState();
SimpleTest.isDeeply(states, expectedStates, "States are updated");
info("Attributes change event");
let attrs = await accessibleFront.getAttributes();
ok(!attrs.live, "Attribute is not present");
await emitA11yEvent(accessibleFront, "attributes-change",
newAttrs => is(newAttrs.live, "polite", "Attributes are updated"),
() => ContentTask.spawn(browser, null, () =>
newAttrs => {
checkA11yFront(accessibleFront, { attributes: {
"container-live": "polite",
display: "inline-block",
"event-from-input": "false",
"explicit-name": "true",
id: "button",
live: "polite",
"margin-bottom": "0px",
"margin-left": "0px",
"margin-right": "0px",
"margin-top": "0px",
tag: "button",
"text-align": "center",
"text-indent": "0px"
}});
is(newAttrs.live, "polite", "Attributes are updated");
}, () => ContentTask.spawn(browser, null, () =>
content.document.getElementById("button").setAttribute("aria-live", "polite")));
attrs = await accessibleFront.getAttributes();
is(attrs.live, "polite", "Attributes are updated");
info("Value change event");
checkA11yFront(accessibleSliderFront, { value: "5" });
@ -76,18 +100,29 @@ add_task(async function () {
info("Reorder event");
is(accessibleSliderFront.childCount, 1, "Slider has only 1 child");
let [firstChild, ] = await accessibleSliderFront.children();
is(firstChild.indexInParent, 0, "Slider's first child has correct index in parent");
await emitA11yEvent(accessibleSliderFront, "reorder",
childCount => is(childCount, 2, "Child count is updated"),
() => ContentTask.spawn(browser, null, () => {
let button = content.document.createElement("button");
childCount => {
is(childCount, 2, "Child count is updated");
is(accessibleSliderFront.childCount, 2, "Child count is updated");
is(firstChild.indexInParent, 1,
"Slider's first child has an updated index in parent");
}, () => ContentTask.spawn(browser, null, () => {
let doc = content.document;
let slider = doc.getElementById("slider");
let button = doc.createElement("button");
button.innerText = "Slider button";
content.document.getElementById("slider").appendChild(button);
content.document.getElementById("slider").insertBefore(button, slider.firstChild);
}));
is(accessibleSliderFront.childCount, 2, "Child count is updated");
let a11yShutdown = waitForA11yShutdown();
await emitA11yEvent(firstChild, "index-in-parent-change", indexInParent =>
is(indexInParent, 0, "Slider's first child has an updated index in parent"), () =>
ContentTask.spawn(browser, null, () =>
content.document.getElementById("slider").firstChild.remove()));
await accessibility.disable();
await waitForA11yShutdown();
await client.close();
forceCollections();
await a11yShutdown;
gBrowser.removeCurrentTab();
});

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

@ -4,10 +4,19 @@
"use strict";
const PREF_ACCESSIBILITY_FORCE_DISABLED = "accessibility.force_disabled";
function checkAccessibilityState(accessibility, expected) {
let { enabled, canBeDisabled, canBeEnabled } = accessibility;
is(enabled, expected.enabled, "Enabled state is correct.");
is(canBeDisabled, expected.canBeDisabled, "canBeDisabled state is correct.");
is(canBeEnabled, expected.canBeEnabled, "canBeEnabled state is correct.");
}
// Simple checks for the AccessibilityActor and AccessibleWalkerActor
add_task(async function () {
let {client, accessibility} = await initAccessibilityFrontForUrl(
let { walker: domWalker, client, accessibility} = await initAccessibilityFrontForUrl(
"data:text/html;charset=utf-8,<title>test</title><div></div>");
ok(accessibility, "The AccessibilityFront was created");
@ -16,6 +25,44 @@ add_task(async function () {
let a11yWalker = await accessibility.getWalker();
ok(a11yWalker, "The AccessibleWalkerFront was returned");
checkAccessibilityState(accessibility,
{ enabled: false, canBeDisabled: true, canBeEnabled: true });
info("Force disable accessibility service: updates canBeEnabled flag");
let onEvent = accessibility.once("can-be-enabled-change");
Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1);
await onEvent;
checkAccessibilityState(accessibility,
{ enabled: false, canBeDisabled: true, canBeEnabled: false });
info("Clear force disable accessibility service: updates canBeEnabled flag");
onEvent = accessibility.once("can-be-enabled-change");
Services.prefs.clearUserPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
await onEvent;
checkAccessibilityState(accessibility,
{ enabled: false, canBeDisabled: true, canBeEnabled: true });
info("Initialize accessibility service");
let initEvent = accessibility.once("init");
await accessibility.enable();
await waitForA11yInit();
await initEvent;
checkAccessibilityState(accessibility,
{ enabled: true, canBeDisabled: true, canBeEnabled: true });
a11yWalker = await accessibility.getWalker();
let rootNode = await domWalker.getRootNode();
let a11yDoc = await a11yWalker.getAccessibleFor(rootNode);
ok(a11yDoc, "Accessible document actor is created");
info("Shutdown accessibility service");
let shutdownEvent = accessibility.once("shutdown");
await accessibility.disable();
await waitForA11yShutdown();
await shutdownEvent;
checkAccessibilityState(accessibility,
{ enabled: false, canBeDisabled: true, canBeEnabled: true });
await client.close();
gBrowser.removeCurrentTab();
});

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

@ -10,10 +10,12 @@ add_task(async function () {
let {client, walker, accessibility} =
await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
let a11yWalker = await accessibility.getWalker(walker);
let a11yWalker = await accessibility.getWalker();
ok(a11yWalker, "The AccessibleWalkerFront was returned");
let a11yDoc = await a11yWalker.getDocument();
await accessibility.enable();
let rootNode = await walker.getRootNode();
let a11yDoc = await a11yWalker.getAccessibleFor(rootNode);
ok(a11yDoc, "The AccessibleFront for root doc is created");
let children = await a11yWalker.children();
@ -30,6 +32,15 @@ add_task(async function () {
role: "pushbutton"
});
let ancestry = await a11yWalker.getAncestry(accessibleFront);
is(ancestry.length, 1, "Button is a direct child of a root document.");
is(ancestry[0].accessible, a11yDoc,
"Button's only ancestor is a root document");
is(ancestry[0].children.length, 3,
"Root doc should have correct number of children");
ok(ancestry[0].children.includes(accessibleFront),
"Button accessible front is in root doc's children");
let browser = gBrowser.selectedBrowser;
// Ensure name-change event is emitted by walker when cached accessible's name
@ -67,9 +78,55 @@ add_task(async function () {
() => ContentTask.spawn(browser, null, () =>
content.document.getElementById("button").remove()));
let a11yShutdown = waitForA11yShutdown();
let shown = await a11yWalker.highlightAccessible(docChildren[0]);
ok(shown, "AccessibleHighlighter highlighted the node");
shown = await a11yWalker.highlightAccessible(a11yDoc);
ok(!shown, "AccessibleHighlighter does not highlight an accessible with no bounds");
await a11yWalker.unhighlight();
info("Checking AccessibleWalker picker functionality");
ok(a11yWalker.pick, "AccessibleWalker pick method exists");
ok(a11yWalker.pickAndFocus, "AccessibleWalker pickAndFocus method exists");
ok(a11yWalker.cancelPick, "AccessibleWalker cancelPick method exists");
let onPickerEvent = a11yWalker.once("picker-accessible-hovered");
await a11yWalker.pick();
await BrowserTestUtils.synthesizeMouseAtCenter("#h1", { type: "mousemove" }, browser);
let acc = await onPickerEvent;
checkA11yFront(acc, { name: "Accessibility Test" }, docChildren[0]);
onPickerEvent = a11yWalker.once("picker-accessible-previewed");
await BrowserTestUtils.synthesizeMouseAtCenter("#h1", { shiftKey: true }, browser);
acc = await onPickerEvent;
checkA11yFront(acc, { name: "Accessibility Test" }, docChildren[0]);
onPickerEvent = a11yWalker.once("picker-accessible-canceled");
await BrowserTestUtils.synthesizeKey("VK_ESCAPE", { type: "keydown" }, browser);
await onPickerEvent;
onPickerEvent = a11yWalker.once("picker-accessible-hovered");
await a11yWalker.pick();
await BrowserTestUtils.synthesizeMouseAtCenter("#h1", { type: "mousemove" }, browser);
await onPickerEvent;
onPickerEvent = a11yWalker.once("picker-accessible-picked");
await BrowserTestUtils.synthesizeMouseAtCenter("#h1", { }, browser);
acc = await onPickerEvent;
checkA11yFront(acc, { name: "Accessibility Test" }, docChildren[0]);
await a11yWalker.cancelPick();
info("Checking document-ready event fired by walker when top level accessible " +
"document is recreated.");
let reloaded = BrowserTestUtils.browserLoaded(browser);
let documentReady = a11yWalker.once("document-ready");
browser.reload();
await reloaded;
await documentReady;
await accessibility.disable();
await waitForA11yShutdown();
await client.close();
forceCollections();
await a11yShutdown;
gBrowser.removeCurrentTab();
});

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

@ -89,6 +89,8 @@ async function initAccessibilityFrontForUrl(url) {
let walker = await inspector.getWalker();
let accessibility = AccessibilityFront(client, form);
await accessibility.bootstrap();
return {inspector, walker, accessibility, client};
}
@ -308,24 +310,47 @@ function checkA11yFront(front, expected, expectedFront) {
}
for (let key in expected) {
is(front[key], expected[key], `accessibility front has correct ${key}`);
if (["actions", "states", "attributes"].includes(key)) {
SimpleTest.isDeeply(front[key], expected[key],
`Accessible Front has correct ${key}`);
} else {
is(front[key], expected[key], `accessibility front has correct ${key}`);
}
}
}
function getA11yInitOrShutdownPromise() {
return new Promise(resolve => {
let observe = (subject, topic, data) => {
Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
resolve(data);
};
Services.obs.addObserver(observe, "a11y-init-or-shutdown");
});
}
/**
* Wait for accessibility service to shut down. We consider it shut down when
* an "a11y-init-or-shutdown" event is received with a value of "0".
*/
async function waitForA11yShutdown() {
await ContentTask.spawn(gBrowser.selectedBrowser, {}, () =>
new Promise(resolve => {
let observe = (subject, topic, data) => {
Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
if (!Services.appinfo.accessibilityEnabled) {
return;
}
if (data === "0") {
resolve();
}
};
Services.obs.addObserver(observe, "a11y-init-or-shutdown");
}));
await getA11yInitOrShutdownPromise().then(data =>
data === "0" ? Promise.resolve() : Promise.reject());
}
/**
* Wait for accessibility service to initialize. We consider it initialized when
* an "a11y-init-or-shutdown" event is received with a value of "1".
*/
async function waitForA11yInit() {
if (Services.appinfo.accessibilityEnabled) {
return;
}
await getA11yInitOrShutdownPromise().then(data =>
data === "1" ? Promise.resolve() : Promise.reject());
}

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

@ -3,50 +3,74 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const {
custom,
Front,
FrontClassWithSpec,
preEvent,
types
preEvent
} = require("devtools/shared/protocol.js");
const {
accessibleSpec,
accessibleWalkerSpec,
accessibilitySpec
} = require("devtools/shared/specs/accessibility");
const events = require("devtools/shared/event-emitter");
const ACCESSIBLE_PROPERTIES = [
"role",
"name",
"value",
"description",
"help",
"keyboardShortcut",
"childCount",
"domNodeType"
];
const AccessibleFront = FrontClassWithSpec(accessibleSpec, {
initialize(client, form) {
Front.prototype.initialize.call(this, client, form);
// Define getters for accesible properties that are received from the actor.
// Note: we would like accessible properties to be iterable for a11y
// clients.
for (let key of ACCESSIBLE_PROPERTIES) {
Object.defineProperty(this, key, {
get() {
return this._form[key];
},
enumerable: true
});
}
},
marshallPool() {
return this.walker;
return this.parent();
},
get role() {
return this._form.role;
},
get name() {
return this._form.name;
},
get value() {
return this._form.value;
},
get description() {
return this._form.description;
},
get help() {
return this._form.help;
},
get keyboardShortcut() {
return this._form.keyboardShortcut;
},
get childCount() {
return this._form.childCount;
},
get domNodeType() {
return this._form.domNodeType;
},
get indexInParent() {
return this._form.indexInParent;
},
get states() {
return this._form.states;
},
get actions() {
return this._form.actions;
},
get attributes() {
return this._form.attributes;
},
form(form, detail) {
@ -57,25 +81,14 @@ const AccessibleFront = FrontClassWithSpec(accessibleSpec, {
this.actorID = form.actor;
this._form = form;
DevToolsUtils.defineLazyGetter(this, "walker", () =>
types.getType("accessiblewalker").read(this._form.walker, this));
},
/**
* Get a dom node front from accessible actor's raw accessible object's
* DONNode property.
*/
getDOMNode(domWalker) {
return domWalker.getNodeFromActor(this.actorID,
["rawAccessible", "DOMNode"]);
},
nameChange: preEvent("name-change", function (name, parent) {
nameChange: preEvent("name-change", function (name, parent, walker) {
this._form.name = name;
// Name change event affects the tree rendering, we fire this event on
// accessibility walker as the point of interaction for UI.
if (this.walker) {
events.emit(this.walker, "name-change", this, parent);
if (walker) {
events.emit(walker, "name-change", this, parent);
}
}),
@ -95,21 +108,37 @@ const AccessibleFront = FrontClassWithSpec(accessibleSpec, {
this._form.keyboardShortcut = keyboardShortcut;
}),
reorder: preEvent("reorder", function (childCount) {
reorder: preEvent("reorder", function (childCount, walker) {
this._form.childCount = childCount;
// Reorder event affects the tree rendering, we fire this event on
// accessibility walker as the point of interaction for UI.
if (this.walker) {
events.emit(this.walker, "reorder", this);
if (walker) {
events.emit(walker, "reorder", this);
}
}),
textChange: preEvent("text-change", function () {
textChange: preEvent("text-change", function (walker) {
// Text event affects the tree rendering, we fire this event on
// accessibility walker as the point of interaction for UI.
if (this.walker) {
events.emit(this.walker, "text-change", this);
if (walker) {
events.emit(walker, "text-change", this);
}
}),
indexInParentChange: preEvent("index-in-parent-change", function (indexInParent) {
this._form.indexInParent = indexInParent;
}),
statesChange: preEvent("states-change", function (states) {
this._form.states = states;
}),
actionsChange: preEvent("actions-change", function (actions) {
this._form.actions = actions;
}),
attributesChange: preEvent("attributes-change", function (attributes) {
this._form.attributes = attributes;
})
});
@ -120,7 +149,17 @@ const AccessibleWalkerFront = FrontClassWithSpec(accessibleWalkerSpec, {
form(json) {
this.actorID = json.actor;
}
},
pick: custom(function (doFocus) {
if (doFocus) {
return this.pickAndFocus();
}
return this._pick();
}, {
impl: "_pick"
})
});
const AccessibilityFront = FrontClassWithSpec(accessibilitySpec, {
@ -128,7 +167,33 @@ const AccessibilityFront = FrontClassWithSpec(accessibilitySpec, {
Front.prototype.initialize.call(this, client, form);
this.actorID = form.accessibilityActor;
this.manage(this);
}
},
bootstrap: custom(function () {
return this._bootstrap().then(state => {
this.enabled = state.enabled;
this.canBeEnabled = state.canBeEnabled;
this.canBeDisabled = state.canBeDisabled;
});
}, {
impl: "_bootstrap"
}),
init: preEvent("init", function () {
this.enabled = true;
}),
shutdown: preEvent("shutdown", function () {
this.enabled = false;
}),
canBeEnabled: preEvent("can-be-enabled-change", function (canBeEnabled) {
this.canBeEnabled = canBeEnabled;
}),
canBeDisabled: preEvent("can-be-disabled-change", function (canBeDisabled) {
this.canBeDisabled = canBeDisabled;
})
});
exports.AccessibleFront = AccessibleFront;

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

@ -9,6 +9,17 @@ const { Arg, generateActorSpec, RetVal, types } = protocol;
types.addActorType("accessible");
/**
* Accessible with children listed in the ancestry structure calculated by the
* walker.
*/
types.addDictType("accessibleWithChildren", {
// Accessible
accessible: "accessible",
// Accessible's children
children: "array:accessible"
});
const accessibleSpec = generateActorSpec({
typeName: "accessible",
@ -20,7 +31,8 @@ const accessibleSpec = generateActorSpec({
"name-change": {
type: "nameChange",
name: Arg(0, "string"),
parent: Arg(1, "nullable:accessible")
parent: Arg(1, "nullable:accessible"),
walker: Arg(2, "nullable:accessiblewalker")
},
"value-change": {
type: "valueChange",
@ -30,13 +42,13 @@ const accessibleSpec = generateActorSpec({
type: "descriptionChange",
description: Arg(0, "string")
},
"state-change": {
type: "stateChange",
"states-change": {
type: "statesChange",
states: Arg(0, "array:string")
},
"attributes-change": {
type: "attributesChange",
states: Arg(0, "json")
attributes: Arg(0, "json")
},
"help-change": {
type: "helpChange",
@ -48,38 +60,20 @@ const accessibleSpec = generateActorSpec({
},
"reorder": {
type: "reorder",
childCount: Arg(0, "number")
childCount: Arg(0, "number"),
walker: Arg(1, "nullable:accessiblewalker")
},
"text-change": {
type: "textChange"
type: "textChange",
walker: Arg(0, "nullable:accessiblewalker")
},
"index-in-parent-change": {
type: "indexInParentChange",
indexInParent: Arg(0, "number")
}
},
methods: {
getActions: {
request: {},
response: {
actions: RetVal("array:string")
}
},
getIndexInParent: {
request: {},
response: {
indexInParent: RetVal("number")
}
},
getState: {
request: {},
response: {
states: RetVal("array:string")
}
},
getAttributes: {
request: {},
response: {
attributes: RetVal("json")
}
},
children: {
request: {},
response: {
@ -96,6 +90,24 @@ const accessibleWalkerSpec = generateActorSpec({
"accessible-destroy": {
type: "accessibleDestroy",
accessible: Arg(0, "accessible")
},
"document-ready": {
type: "documentReady",
},
"picker-accessible-picked": {
type: "pickerAccessiblePicked",
accessible: Arg(0, "nullable:accessible")
},
"picker-accessible-previewed": {
type: "pickerAccessiblePreviewed",
accessible: Arg(0, "nullable:accessible")
},
"picker-accessible-hovered": {
type: "pickerAccessibleHovered",
accessible: Arg(0, "nullable:accessible")
},
"picker-accessible-canceled": {
type: "pickerAccessibleCanceled"
}
},
@ -106,30 +118,76 @@ const accessibleWalkerSpec = generateActorSpec({
children: RetVal("array:accessible")
}
},
getDocument: {
request: {},
response: {
document: RetVal("accessible")
}
},
getAccessibleFor: {
request: { node: Arg(0, "domnode") },
response: {
accessible: RetVal("accessible")
}
}
},
getAncestry: {
request: { accessible: Arg(0, "accessible") },
response: {
ancestry: RetVal("array:accessibleWithChildren")
}
},
highlightAccessible: {
request: {
accessible: Arg(0, "accessible"),
options: Arg(1, "nullable:json")
},
response: {
value: RetVal("nullable:boolean")
}
},
unhighlight: {
request: {}
},
pick: {},
pickAndFocus: {},
cancelPick: {}
}
});
const accessibilitySpec = generateActorSpec({
typeName: "accessibility",
events: {
"init": {
type: "init"
},
"shutdown": {
type: "shutdown"
},
"can-be-disabled-change": {
type: "canBeDisabledChange",
canBeDisabled: Arg(0, "boolean")
},
"can-be-enabled-change": {
type: "canBeEnabledChange",
canBeEnabled: Arg(0, "boolean")
}
},
methods: {
bootstrap: {
request: {},
response: {
state: RetVal("json")
}
},
getWalker: {
request: {},
response: {
walker: RetVal("accessiblewalker")
}
},
enable: {
request: {},
response: {}
},
disable: {
request: {},
response: {}
}
}
});