Bug 1505848 - change the way accessible object is determined when using accessible highlighter. r=pbro

MozReview-Commit-ID: IiUGsFojZP1

Differential Revision: https://phabricator.services.mozilla.com/D11366

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Yura Zenevich 2018-11-16 03:57:25 +00:00
Родитель 62dd9713e0
Коммит 33bdb435b4
2 изменённых файлов: 104 добавлений и 28 удалений

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

@ -241,7 +241,7 @@ const AccessibleActor = ActorClassWithSpec(accessibleSpec, {
let targetAcc;
try {
targetAcc = this.walker.attachAccessible(target, doc);
targetAcc = this.walker.attachAccessible(target, doc.rawAccessible);
} catch (e) {
// Target is not available.
}

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

@ -13,12 +13,16 @@ loader.lazyRequireGetter(this, "AccessibleActor", "devtools/server/actors/access
loader.lazyRequireGetter(this, "CustomHighlighterActor", "devtools/server/actors/highlighters", true);
loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(this, "events", "devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "getCurrentZoom", "devtools/shared/layout/utils", true);
loader.lazyRequireGetter(this, "InspectorUtils", "InspectorUtils");
loader.lazyRequireGetter(this, "isDefunct", "devtools/server/actors/utils/accessibility", true);
loader.lazyRequireGetter(this, "isTypeRegistered", "devtools/server/actors/highlighters", true);
loader.lazyRequireGetter(this, "isWindowIncluded", "devtools/shared/layout/utils", true);
loader.lazyRequireGetter(this, "isXUL", "devtools/server/actors/highlighters/utils/markup", true);
loader.lazyRequireGetter(this, "register", "devtools/server/actors/highlighters", true);
const kStateHover = 0x00000004; // NS_EVENT_STATE_HOVER
const nsIAccessibleEvent = Ci.nsIAccessibleEvent;
const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent;
const nsIAccessibleRole = Ci.nsIAccessibleRole;
@ -126,6 +130,7 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
this.setA11yServiceGetter();
this.onPick = this.onPick.bind(this);
this.onHovered = this.onHovered.bind(this);
this._preventContentEvent = this._preventContentEvent.bind(this);
this.onKey = this.onKey.bind(this);
this.onHighlighterEvent = this.onHighlighterEvent.bind(this);
},
@ -513,6 +518,24 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
_preventContentEvent(event) {
event.stopPropagation();
event.preventDefault();
const target = event.originalTarget || event.target;
if (target !== this._currentTarget) {
this._resetStateAndReleaseTarget();
this._currentTarget = target;
// We use InspectorUtils to save the original hover content state of the target
// element (that includes its hover state). In order to not trigger any visual
// changes to the element that depend on its hover state we remove the state while
// the element is the most current target of the highlighter.
//
// TODO: This logic can be removed if/when we can use elementsAtPoint API for
// determining topmost DOMNode that corresponds to specific coordinates. We would
// then be able to use a highlighter overlay that would prevent all pointer events
// to content but still render highlighter for the node/element correctly.
this._currentTargetHoverState =
InspectorUtils.getContentState(target) & kStateHover;
InspectorUtils.removeContentState(target, kStateHover);
}
},
/**
@ -521,7 +544,7 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
* @param {Object} event
* Current click event.
*/
async onPick(event) {
onPick(event) {
if (!this._isPicking) {
return;
}
@ -535,16 +558,16 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
// the client, but don't stop picking.
if (event.shiftKey) {
if (!this._currentAccessible) {
this._currentAccessible = await this._findAndAttachAccessible(event);
this._currentAccessible = this._findAndAttachAccessible(event);
}
events.emit(this, "picker-accessible-previewed", this._currentAccessible);
return;
}
this._stopPickerListeners();
this._unsetPickerEnvironment();
this._isPicking = false;
if (!this._currentAccessible) {
this._currentAccessible = await this._findAndAttachAccessible(event);
this._currentAccessible = this._findAndAttachAccessible(event);
}
events.emit(this, "picker-accessible-picked", this._currentAccessible);
},
@ -555,7 +578,7 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
* @param {Object} event
* Current hover event.
*/
async onHovered(event) {
onHovered(event) {
if (!this._isPicking) {
return;
}
@ -565,7 +588,7 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
return;
}
const accessible = await this._findAndAttachAccessible(event);
const accessible = this._findAndAttachAccessible(event);
if (!accessible) {
return;
}
@ -626,7 +649,7 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
pick: function() {
if (!this._isPicking) {
this._isPicking = true;
this._startPickerListeners();
this._setPickerEnvironment();
}
},
@ -651,7 +674,7 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
// defunct and accessing it via parent property will throw.
try {
let parent = accessible;
while (parent && parent != accessibleDocument) {
while (parent && parent.rawAccessible != accessibleDocument) {
parent = parent.parentAcc;
}
} catch (error) {
@ -662,39 +685,59 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
},
/**
* Find accessible object that corresponds to a DOMNode and attach (lookup its
* ancestry to the root doc) to the AccessibilityWalker tree.
* When RDM is used, users can set custom DPR values that are different from the device
* they are using. Store true screenPixelsPerCSSPixel value to be able to use accessible
* highlighter features correctly.
*/
get pixelRatio() {
const { contentViewer } = this.targetActor.docShell;
const { windowUtils } = this.rootWin;
const overrideDPPX = contentViewer.overrideDPPX;
let ratio;
if (overrideDPPX) {
contentViewer.overrideDPPX = 0;
ratio = windowUtils.screenPixelsPerCSSPixel;
contentViewer.overrideDPPX = overrideDPPX;
} else {
ratio = windowUtils.screenPixelsPerCSSPixel;
}
return ratio;
},
/**
* Find deepest accessible object that corresponds to the screen coordinates of the
* mouse pointer and attach it to the AccessibilityWalker tree.
*
* @param {Object} event
* Correspoinding content event.
* @return {null|Object}
* Accessible object, if available, that corresponds to a DOM node.
*/
async _findAndAttachAccessible(event) {
let target = event.originalTarget || event.target;
let rawAccessible;
// Find a first accessible object in the target's ancestry, including
// target. Note: not all DOM nodes have corresponding accessible objects
// (for example, a <DIV> element that is used as a container for other
// things) thus we need to find one that does.
while (!rawAccessible && target) {
rawAccessible = this.getRawAccessibleFor(target);
target = target.parentNode;
}
const doc = await this.getDocument();
return this.attachAccessible(rawAccessible, doc);
_findAndAttachAccessible(event) {
const target = event.originalTarget || event.target;
const docAcc = this.getRawAccessibleFor(this.rootDoc);
const win = target.ownerGlobal;
const scale = this.pixelRatio / getCurrentZoom(win);
const rawAccessible = docAcc.getDeepestChildAtPoint(
event.screenX * scale,
event.screenY * scale);
return this.attachAccessible(rawAccessible, docAcc);
},
/**
* Start picker content listeners.
*/
_startPickerListeners: function() {
_setPickerEnvironment: function() {
const target = this.targetActor.chromeEventHandler;
target.addEventListener("mousemove", this.onHovered, true);
target.addEventListener("click", this.onPick, true);
target.addEventListener("mousedown", this._preventContentEvent, true);
target.addEventListener("mouseup", this._preventContentEvent, true);
target.addEventListener("mouseover", this._preventContentEvent, true);
target.addEventListener("mouseout", this._preventContentEvent, true);
target.addEventListener("mouseleave", this._preventContentEvent, true);
target.addEventListener("mouseenter", this._preventContentEvent, true);
target.addEventListener("dblclick", this._preventContentEvent, true);
target.addEventListener("keydown", this.onKey, true);
target.addEventListener("keyup", this._preventContentEvent, true);
@ -703,7 +746,7 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
/**
* If content is still alive, stop picker content listeners.
*/
_stopPickerListeners: function() {
_unsetPickerEnvironment: function() {
const target = this.targetActor.chromeEventHandler;
if (!target) {
@ -714,9 +757,42 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
target.removeEventListener("click", this.onPick, true);
target.removeEventListener("mousedown", this._preventContentEvent, true);
target.removeEventListener("mouseup", this._preventContentEvent, true);
target.removeEventListener("mouseover", this._preventContentEvent, true);
target.removeEventListener("mouseout", this._preventContentEvent, true);
target.removeEventListener("mouseleave", this._preventContentEvent, true);
target.removeEventListener("mouseenter", this._preventContentEvent, true);
target.removeEventListener("dblclick", this._preventContentEvent, true);
target.removeEventListener("keydown", this.onKey, true);
target.removeEventListener("keyup", this._preventContentEvent, true);
this._resetStateAndReleaseTarget();
},
/**
* When using accessibility highlighter, we keep track of the most current event pointer
* event target. In order to update or release the target, we need to make sure we set
* the content state (using InspectorUtils) to its original value.
*
* TODO: This logic can be removed if/when we can use elementsAtPoint API for
* determining topmost DOMNode that corresponds to specific coordinates. We would then
* be able to use a highlighter overlay that would prevent all pointer events to content
* but still render highlighter for the node/element correctly.
*/
_resetStateAndReleaseTarget() {
if (!this._currentTarget) {
return;
}
try {
if (this._currentTargetHoverState) {
InspectorUtils.setContentState(this._currentTarget, kStateHover);
}
} catch (e) {
// DOMNode is already dead.
}
this._currentTarget = null;
this._currentTargetState = null;
},
/**
@ -728,7 +804,7 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
}
if (this._isPicking) {
this._stopPickerListeners();
this._unsetPickerEnvironment();
this._isPicking = false;
this._currentAccessible = null;
}