mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-08 08:17:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
528
accessible/jsat/ContentControl.jsm
Normal file
528
accessible/jsat/ContentControl.jsm
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
/* 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/. */
|
||||
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Services',
|
||||
'resource://gre/modules/Services.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
|
||||
'resource://gre/modules/accessibility/Constants.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
|
||||
'resource://gre/modules/accessibility/Traversal.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'TraversalHelper',
|
||||
'resource://gre/modules/accessibility/Traversal.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
|
||||
'resource://gre/modules/accessibility/Presentation.jsm');
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['ContentControl'];
|
||||
|
||||
const MOVEMENT_GRANULARITY_CHARACTER = 1;
|
||||
const MOVEMENT_GRANULARITY_WORD = 2;
|
||||
const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
|
||||
|
||||
this.ContentControl = function ContentControl(aContentScope) {
|
||||
this._contentScope = Cu.getWeakReference(aContentScope);
|
||||
this._childMessageSenders = new WeakMap();
|
||||
};
|
||||
|
||||
this.ContentControl.prototype = {
|
||||
messagesOfInterest: ['AccessFu:MoveCursor',
|
||||
'AccessFu:ClearCursor',
|
||||
'AccessFu:MoveToPoint',
|
||||
'AccessFu:AutoMove',
|
||||
'AccessFu:Activate',
|
||||
'AccessFu:MoveCaret',
|
||||
'AccessFu:MoveByGranularity',
|
||||
'AccessFu:AndroidScroll'],
|
||||
|
||||
start: function cc_start() {
|
||||
let cs = this._contentScope.get();
|
||||
for (let message of this.messagesOfInterest) {
|
||||
cs.addMessageListener(message, this);
|
||||
}
|
||||
cs.addEventListener('mousemove', this);
|
||||
},
|
||||
|
||||
stop: function cc_stop() {
|
||||
let cs = this._contentScope.get();
|
||||
for (let message of this.messagesOfInterest) {
|
||||
cs.removeMessageListener(message, this);
|
||||
}
|
||||
cs.removeEventListener('mousemove', this);
|
||||
},
|
||||
|
||||
get document() {
|
||||
return this._contentScope.get().content.document;
|
||||
},
|
||||
|
||||
get window() {
|
||||
return this._contentScope.get().content;
|
||||
},
|
||||
|
||||
get vc() {
|
||||
return Utils.getVirtualCursor(this.document);
|
||||
},
|
||||
|
||||
receiveMessage: function cc_receiveMessage(aMessage) {
|
||||
Logger.debug(() => {
|
||||
return ['ContentControl.receiveMessage',
|
||||
aMessage.name,
|
||||
JSON.stringify(aMessage.json)];
|
||||
});
|
||||
|
||||
// If we get an explicit message, we should immediately cancel any autoMove
|
||||
this.cancelAutoMove();
|
||||
|
||||
try {
|
||||
let func = this['handle' + aMessage.name.slice(9)]; // 'AccessFu:'.length
|
||||
if (func) {
|
||||
func.bind(this)(aMessage);
|
||||
} else {
|
||||
Logger.warning('ContentControl: Unhandled message:', aMessage.name);
|
||||
}
|
||||
} catch (x) {
|
||||
Logger.logException(
|
||||
x, 'Error handling message: ' + JSON.stringify(aMessage.json));
|
||||
}
|
||||
},
|
||||
|
||||
handleAndroidScroll: function cc_handleAndroidScroll(aMessage) {
|
||||
let vc = this.vc;
|
||||
let position = vc.position;
|
||||
|
||||
if (aMessage.json.origin != 'child' && this.sendToChild(vc, aMessage)) {
|
||||
// Forwarded succesfully to child cursor.
|
||||
return;
|
||||
}
|
||||
|
||||
// Counter-intuitive, but scrolling backward (ie. up), actually should
|
||||
// increase range values.
|
||||
if (this.adjustRange(position, aMessage.json.direction === 'backward')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._contentScope.get().sendAsyncMessage('AccessFu:DoScroll',
|
||||
{ bounds: Utils.getBounds(position, true),
|
||||
page: aMessage.json.direction === 'forward' ? 1 : -1,
|
||||
horizontal: false });
|
||||
},
|
||||
|
||||
handleMoveCursor: function cc_handleMoveCursor(aMessage) {
|
||||
let origin = aMessage.json.origin;
|
||||
let action = aMessage.json.action;
|
||||
let adjustRange = aMessage.json.adjustRange;
|
||||
let vc = this.vc;
|
||||
|
||||
if (origin != 'child' && this.sendToChild(vc, aMessage)) {
|
||||
// Forwarded succesfully to child cursor.
|
||||
return;
|
||||
}
|
||||
|
||||
if (adjustRange && this.adjustRange(vc.position, action === 'moveNext')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let moved = TraversalHelper.move(vc, action, aMessage.json.rule);
|
||||
|
||||
if (moved) {
|
||||
if (origin === 'child') {
|
||||
// We just stepped out of a child, clear child cursor.
|
||||
Utils.getMessageManager(aMessage.target).sendAsyncMessage(
|
||||
'AccessFu:ClearCursor', {});
|
||||
} else {
|
||||
// We potentially landed on a new child cursor. If so, we want to
|
||||
// either be on the first or last item in the child doc.
|
||||
let childAction = action;
|
||||
if (action === 'moveNext') {
|
||||
childAction = 'moveFirst';
|
||||
} else if (action === 'movePrevious') {
|
||||
childAction = 'moveLast';
|
||||
}
|
||||
|
||||
// Attempt to forward move to a potential child cursor in our
|
||||
// new position.
|
||||
this.sendToChild(vc, aMessage, { action: childAction }, true);
|
||||
}
|
||||
} else if (!this._childMessageSenders.has(aMessage.target) &&
|
||||
origin !== 'top') {
|
||||
// We failed to move, and the message is not from a parent, so forward
|
||||
// to it.
|
||||
this.sendToParent(aMessage);
|
||||
} else {
|
||||
this._contentScope.get().sendAsyncMessage('AccessFu:Present',
|
||||
Presentation.noMove(action));
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function cc_handleEvent(aEvent) {
|
||||
if (aEvent.type === 'mousemove') {
|
||||
this.handleMoveToPoint(
|
||||
{ json: { x: aEvent.screenX, y: aEvent.screenY, rule: 'Simple' } });
|
||||
}
|
||||
if (!Utils.getMessageManager(aEvent.target)) {
|
||||
aEvent.preventDefault();
|
||||
} else {
|
||||
aEvent.target.focus();
|
||||
}
|
||||
},
|
||||
|
||||
handleMoveToPoint: function cc_handleMoveToPoint(aMessage) {
|
||||
let [x, y] = [aMessage.json.x, aMessage.json.y];
|
||||
let rule = TraversalRules[aMessage.json.rule];
|
||||
|
||||
let dpr = this.window.devicePixelRatio;
|
||||
this.vc.moveToPoint(rule, x * dpr, y * dpr, true);
|
||||
},
|
||||
|
||||
handleClearCursor: function cc_handleClearCursor(aMessage) {
|
||||
let forwarded = this.sendToChild(this.vc, aMessage);
|
||||
this.vc.position = null;
|
||||
if (!forwarded) {
|
||||
this._contentScope.get().sendAsyncMessage('AccessFu:CursorCleared');
|
||||
}
|
||||
this.document.activeElement.blur();
|
||||
},
|
||||
|
||||
handleAutoMove: function cc_handleAutoMove(aMessage) {
|
||||
this.autoMove(null, aMessage.json);
|
||||
},
|
||||
|
||||
handleActivate: function cc_handleActivate(aMessage) {
|
||||
let activateAccessible = (aAccessible) => {
|
||||
Logger.debug(() => {
|
||||
return ['activateAccessible', Logger.accessibleToString(aAccessible)];
|
||||
});
|
||||
try {
|
||||
if (aMessage.json.activateIfKey &&
|
||||
!Utils.isActivatableOnFingerUp(aAccessible)) {
|
||||
// Only activate keys, don't do anything on other objects.
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// accessible is invalid. Silently fail.
|
||||
return;
|
||||
}
|
||||
|
||||
if (aAccessible.actionCount > 0) {
|
||||
aAccessible.doAction(0);
|
||||
} else {
|
||||
let control = Utils.getEmbeddedControl(aAccessible);
|
||||
if (control && control.actionCount > 0) {
|
||||
control.doAction(0);
|
||||
}
|
||||
|
||||
// XXX Some mobile widget sets do not expose actions properly
|
||||
// (via ARIA roles, etc.), so we need to generate a click.
|
||||
// Could possibly be made simpler in the future. Maybe core
|
||||
// engine could expose nsCoreUtiles::DispatchMouseEvent()?
|
||||
let docAcc = Utils.AccService.getAccessibleFor(this.document);
|
||||
let docX = {}, docY = {}, docW = {}, docH = {};
|
||||
docAcc.getBounds(docX, docY, docW, docH);
|
||||
|
||||
let objX = {}, objY = {}, objW = {}, objH = {};
|
||||
aAccessible.getBounds(objX, objY, objW, objH);
|
||||
|
||||
let x = Math.round((objX.value - docX.value) + objW.value / 2);
|
||||
let y = Math.round((objY.value - docY.value) + objH.value / 2);
|
||||
|
||||
let node = aAccessible.DOMNode || aAccessible.parent.DOMNode;
|
||||
|
||||
for (let eventType of ['mousedown', 'mouseup']) {
|
||||
let evt = this.document.createEvent('MouseEvents');
|
||||
evt.initMouseEvent(eventType, true, true, this.window,
|
||||
x, y, 0, 0, 0, false, false, false, false, 0, null);
|
||||
node.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Utils.isActivatableOnFingerUp(aAccessible)) {
|
||||
// Keys will typically have a sound of their own.
|
||||
this._contentScope.get().sendAsyncMessage('AccessFu:Present',
|
||||
Presentation.actionInvoked(aAccessible, 'click'));
|
||||
}
|
||||
};
|
||||
|
||||
let focusedAcc = Utils.AccService.getAccessibleFor(
|
||||
this.document.activeElement);
|
||||
if (focusedAcc && this.vc.position === focusedAcc
|
||||
&& focusedAcc.role === Roles.ENTRY) {
|
||||
let accText = focusedAcc.QueryInterface(Ci.nsIAccessibleText);
|
||||
let oldOffset = accText.caretOffset;
|
||||
let newOffset = aMessage.json.offset;
|
||||
let text = accText.getText(0, accText.characterCount);
|
||||
|
||||
if (newOffset >= 0 && newOffset <= accText.characterCount) {
|
||||
accText.caretOffset = newOffset;
|
||||
}
|
||||
|
||||
this.presentCaretChange(text, oldOffset, accText.caretOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
// recursively find a descendant that is activatable.
|
||||
let getActivatableDescendant = (aAccessible) => {
|
||||
if (aAccessible.actionCount > 0) {
|
||||
return aAccessible;
|
||||
}
|
||||
|
||||
for (let acc = aAccessible.firstChild; acc; acc = acc.nextSibling) {
|
||||
let activatable = getActivatableDescendant(acc);
|
||||
if (activatable) {
|
||||
return activatable;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
let vc = this.vc;
|
||||
if (!this.sendToChild(vc, aMessage, null, true)) {
|
||||
let position = vc.position;
|
||||
activateAccessible(getActivatableDescendant(position) || position);
|
||||
}
|
||||
},
|
||||
|
||||
adjustRange: function cc_adjustRange(aAccessible, aStepUp) {
|
||||
let acc = Utils.getEmbeddedControl(aAccessible) || aAccessible;
|
||||
try {
|
||||
acc.QueryInterface(Ci.nsIAccessibleValue);
|
||||
} catch (x) {
|
||||
// This is not an adjustable, return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
let elem = acc.DOMNode;
|
||||
if (!elem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elem.tagName === 'INPUT' && elem.type === 'range') {
|
||||
elem[aStepUp ? 'stepDown' : 'stepUp']();
|
||||
let evt = this.document.createEvent('UIEvent');
|
||||
evt.initEvent('change', true, true);
|
||||
elem.dispatchEvent(evt);
|
||||
} else {
|
||||
let evt = this.document.createEvent('KeyboardEvent');
|
||||
let keycode = aStepUp ? evt.DOM_VK_DOWN : evt.DOM_VK_UP;
|
||||
evt.initKeyEvent(
|
||||
"keypress", false, true, null, false, false, false, false, keycode, 0);
|
||||
elem.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
handleMoveByGranularity: function cc_handleMoveByGranularity(aMessage) {
|
||||
// XXX: Add sendToChild. Right now this is only used in Android, so no need.
|
||||
let direction = aMessage.json.direction;
|
||||
let granularity;
|
||||
|
||||
switch(aMessage.json.granularity) {
|
||||
case MOVEMENT_GRANULARITY_CHARACTER:
|
||||
granularity = Ci.nsIAccessiblePivot.CHAR_BOUNDARY;
|
||||
break;
|
||||
case MOVEMENT_GRANULARITY_WORD:
|
||||
granularity = Ci.nsIAccessiblePivot.WORD_BOUNDARY;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (direction === 'Previous') {
|
||||
this.vc.movePreviousByText(granularity);
|
||||
} else if (direction === 'Next') {
|
||||
this.vc.moveNextByText(granularity);
|
||||
}
|
||||
},
|
||||
|
||||
presentCaretChange: function cc_presentCaretChange(
|
||||
aText, aOldOffset, aNewOffset) {
|
||||
if (aOldOffset !== aNewOffset) {
|
||||
let msg = Presentation.textSelectionChanged(aText, aNewOffset, aNewOffset,
|
||||
aOldOffset, aOldOffset, true);
|
||||
this._contentScope.get().sendAsyncMessage('AccessFu:Present', msg);
|
||||
}
|
||||
},
|
||||
|
||||
handleMoveCaret: function cc_handleMoveCaret(aMessage) {
|
||||
let direction = aMessage.json.direction;
|
||||
let granularity = aMessage.json.granularity;
|
||||
let accessible = this.vc.position;
|
||||
let accText = accessible.QueryInterface(Ci.nsIAccessibleText);
|
||||
let oldOffset = accText.caretOffset;
|
||||
let text = accText.getText(0, accText.characterCount);
|
||||
|
||||
let start = {}, end = {};
|
||||
if (direction === 'Previous' && !aMessage.json.atStart) {
|
||||
switch (granularity) {
|
||||
case MOVEMENT_GRANULARITY_CHARACTER:
|
||||
accText.caretOffset--;
|
||||
break;
|
||||
case MOVEMENT_GRANULARITY_WORD:
|
||||
accText.getTextBeforeOffset(accText.caretOffset,
|
||||
Ci.nsIAccessibleText.BOUNDARY_WORD_START, start, end);
|
||||
accText.caretOffset = end.value === accText.caretOffset ?
|
||||
start.value : end.value;
|
||||
break;
|
||||
case MOVEMENT_GRANULARITY_PARAGRAPH:
|
||||
let startOfParagraph = text.lastIndexOf('\n', accText.caretOffset - 1);
|
||||
accText.caretOffset = startOfParagraph !== -1 ? startOfParagraph : 0;
|
||||
break;
|
||||
}
|
||||
} else if (direction === 'Next' && !aMessage.json.atEnd) {
|
||||
switch (granularity) {
|
||||
case MOVEMENT_GRANULARITY_CHARACTER:
|
||||
accText.caretOffset++;
|
||||
break;
|
||||
case MOVEMENT_GRANULARITY_WORD:
|
||||
accText.getTextAtOffset(accText.caretOffset,
|
||||
Ci.nsIAccessibleText.BOUNDARY_WORD_END, start, end);
|
||||
accText.caretOffset = end.value;
|
||||
break;
|
||||
case MOVEMENT_GRANULARITY_PARAGRAPH:
|
||||
accText.caretOffset = text.indexOf('\n', accText.caretOffset + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.presentCaretChange(text, oldOffset, accText.caretOffset);
|
||||
},
|
||||
|
||||
getChildCursor: function cc_getChildCursor(aAccessible) {
|
||||
let acc = aAccessible || this.vc.position;
|
||||
if (Utils.isAliveAndVisible(acc) && acc.role === Roles.INTERNAL_FRAME) {
|
||||
let domNode = acc.DOMNode;
|
||||
let mm = this._childMessageSenders.get(domNode, null);
|
||||
if (!mm) {
|
||||
mm = Utils.getMessageManager(domNode);
|
||||
mm.addWeakMessageListener('AccessFu:MoveCursor', this);
|
||||
this._childMessageSenders.set(domNode, mm);
|
||||
}
|
||||
|
||||
return mm;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
sendToChild: function cc_sendToChild(aVirtualCursor, aMessage, aReplacer,
|
||||
aFocus) {
|
||||
let position = aVirtualCursor.position;
|
||||
let mm = this.getChildCursor(position);
|
||||
if (!mm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aFocus) {
|
||||
position.takeFocus();
|
||||
}
|
||||
|
||||
// XXX: This is a silly way to make a deep copy
|
||||
let newJSON = JSON.parse(JSON.stringify(aMessage.json));
|
||||
newJSON.origin = 'parent';
|
||||
for (let attr in aReplacer) {
|
||||
newJSON[attr] = aReplacer[attr];
|
||||
}
|
||||
|
||||
mm.sendAsyncMessage(aMessage.name, newJSON);
|
||||
return true;
|
||||
},
|
||||
|
||||
sendToParent: function cc_sendToParent(aMessage) {
|
||||
// XXX: This is a silly way to make a deep copy
|
||||
let newJSON = JSON.parse(JSON.stringify(aMessage.json));
|
||||
newJSON.origin = 'child';
|
||||
aMessage.target.sendAsyncMessage(aMessage.name, newJSON);
|
||||
},
|
||||
|
||||
/**
|
||||
* Move cursor and/or present its location.
|
||||
* aOptions could have any of these fields:
|
||||
* - delay: in ms, before actual move is performed. Another autoMove call
|
||||
* would cancel it. Useful if we want to wait for a possible trailing
|
||||
* focus move. Default 0.
|
||||
* - noOpIfOnScreen: if accessible is alive and visible, don't do anything.
|
||||
* - forcePresent: present cursor location, whether we move or don't.
|
||||
* - moveToFocused: if there is a focused accessible move to that. This takes
|
||||
* precedence over given anchor.
|
||||
* - moveMethod: pivot move method to use, default is 'moveNext',
|
||||
*/
|
||||
autoMove: function cc_autoMove(aAnchor, aOptions = {}) {
|
||||
this.cancelAutoMove();
|
||||
|
||||
let moveFunc = () => {
|
||||
let vc = this.vc;
|
||||
let acc = aAnchor;
|
||||
let rule = aOptions.onScreenOnly ?
|
||||
TraversalRules.SimpleOnScreen : TraversalRules.Simple;
|
||||
let forcePresentFunc = () => {
|
||||
if (aOptions.forcePresent) {
|
||||
this._contentScope.get().sendAsyncMessage(
|
||||
'AccessFu:Present', Presentation.pivotChanged(
|
||||
vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE,
|
||||
vc.startOffset, vc.endOffset, false));
|
||||
}
|
||||
};
|
||||
|
||||
if (aOptions.noOpIfOnScreen &&
|
||||
Utils.isAliveAndVisible(vc.position, true)) {
|
||||
forcePresentFunc();
|
||||
return;
|
||||
}
|
||||
|
||||
if (aOptions.moveToFocused) {
|
||||
acc = Utils.AccService.getAccessibleFor(
|
||||
this.document.activeElement) || acc;
|
||||
}
|
||||
|
||||
let moved = false;
|
||||
let moveMethod = aOptions.moveMethod || 'moveNext'; // default is moveNext
|
||||
let moveFirstOrLast = moveMethod in ['moveFirst', 'moveLast'];
|
||||
if (!moveFirstOrLast || acc) {
|
||||
// We either need next/previous or there is an anchor we need to use.
|
||||
moved = vc[moveFirstOrLast ? 'moveNext' : moveMethod](rule, acc, true,
|
||||
false);
|
||||
}
|
||||
if (moveFirstOrLast && !moved) {
|
||||
// We move to first/last after no anchor move happened or succeeded.
|
||||
moved = vc[moveMethod](rule, false);
|
||||
}
|
||||
|
||||
let sentToChild = this.sendToChild(vc, {
|
||||
name: 'AccessFu:AutoMove',
|
||||
json: {
|
||||
moveMethod: aOptions.moveMethod,
|
||||
moveToFocused: aOptions.moveToFocused,
|
||||
noOpIfOnScreen: true,
|
||||
forcePresent: true
|
||||
}
|
||||
}, null, true);
|
||||
|
||||
if (!moved && !sentToChild) {
|
||||
forcePresentFunc();
|
||||
}
|
||||
};
|
||||
|
||||
if (aOptions.delay) {
|
||||
this._autoMove = this.window.setTimeout(moveFunc, aOptions.delay);
|
||||
} else {
|
||||
moveFunc();
|
||||
}
|
||||
},
|
||||
|
||||
cancelAutoMove: function cc_cancelAutoMove() {
|
||||
this.window.clearTimeout(this._autoMove);
|
||||
this._autoMove = 0;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
|
||||
Ci.nsIMessageListener
|
||||
])
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue