2012-04-13 16:18:57 -07:00
|
|
|
/* 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 = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
const Cu = Components.utils;
|
|
|
|
const Cr = Components.results;
|
|
|
|
|
|
|
|
var EXPORTED_SYMBOLS = ['AccessFu'];
|
|
|
|
|
|
|
|
Cu.import('resource://gre/modules/Services.jsm');
|
|
|
|
|
2012-06-20 14:07:51 -07:00
|
|
|
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
|
2012-04-13 16:18:57 -07:00
|
|
|
Cu.import('resource://gre/modules/accessibility/Presenters.jsm');
|
|
|
|
Cu.import('resource://gre/modules/accessibility/VirtualCursorController.jsm');
|
2012-08-17 15:49:34 -07:00
|
|
|
Cu.import('resource://gre/modules/accessibility/TouchAdapter.jsm');
|
2012-04-13 16:18:57 -07:00
|
|
|
|
2012-05-03 19:28:35 -07:00
|
|
|
const ACCESSFU_DISABLE = 0;
|
|
|
|
const ACCESSFU_ENABLE = 1;
|
|
|
|
const ACCESSFU_AUTO = 2;
|
|
|
|
|
2012-04-13 16:18:57 -07:00
|
|
|
var AccessFu = {
|
|
|
|
/**
|
|
|
|
* Attach chrome-layer accessibility functionality to the given chrome window.
|
|
|
|
* If accessibility is enabled on the platform (currently Android-only), then
|
|
|
|
* a special accessibility mode is started (see startup()).
|
|
|
|
* @param {ChromeWindow} aWindow Chrome window to attach to.
|
|
|
|
* @param {boolean} aForceEnabled Skip platform accessibility check and enable
|
|
|
|
* AccessFu.
|
|
|
|
*/
|
|
|
|
attach: function attach(aWindow) {
|
2012-05-03 19:28:35 -07:00
|
|
|
if (this.chromeWin)
|
|
|
|
// XXX: only supports attaching to one window now.
|
|
|
|
throw new Error('Only one window could be attached to AccessFu');
|
|
|
|
|
2012-06-20 14:07:51 -07:00
|
|
|
Logger.info('attach');
|
2012-04-13 16:18:57 -07:00
|
|
|
this.chromeWin = aWindow;
|
|
|
|
this.presenters = [];
|
|
|
|
|
2012-05-03 19:28:35 -07:00
|
|
|
this.prefsBranch = Cc['@mozilla.org/preferences-service;1']
|
2012-05-18 11:56:38 -07:00
|
|
|
.getService(Ci.nsIPrefService).getBranch('accessibility.accessfu.');
|
|
|
|
this.prefsBranch.addObserver('activate', this, false);
|
2012-06-13 12:22:52 -07:00
|
|
|
this.prefsBranch.addObserver('explorebytouch', this, false);
|
2012-05-03 19:28:35 -07:00
|
|
|
|
2012-08-17 15:49:34 -07:00
|
|
|
this.touchAdapter = TouchAdapter;
|
|
|
|
|
|
|
|
switch(Utils.MozBuildApp) {
|
|
|
|
case 'mobile/android':
|
|
|
|
Services.obs.addObserver(this, 'Accessibility:Settings', false);
|
|
|
|
this.touchAdapter = AndroidTouchAdapter;
|
|
|
|
break;
|
|
|
|
case 'b2g':
|
|
|
|
aWindow.addEventListener(
|
|
|
|
'ContentStart',
|
|
|
|
(function(event) {
|
|
|
|
let content = aWindow.shell.contentBrowser.contentWindow;
|
|
|
|
content.addEventListener('mozContentEvent', this, false, true);
|
|
|
|
}).bind(this), false);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2012-08-17 15:49:34 -07:00
|
|
|
|
2012-06-13 12:22:52 -07:00
|
|
|
this._processPreferences();
|
2012-04-13 16:18:57 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-05-07 09:44:44 -07:00
|
|
|
* Start AccessFu mode, this primarily means controlling the virtual cursor
|
|
|
|
* with arrow keys.
|
2012-04-13 16:18:57 -07:00
|
|
|
*/
|
2012-05-14 14:21:59 -07:00
|
|
|
_enable: function _enable() {
|
2012-05-10 10:33:12 -07:00
|
|
|
if (this._enabled)
|
|
|
|
return;
|
|
|
|
this._enabled = true;
|
|
|
|
|
2012-06-20 14:07:51 -07:00
|
|
|
Logger.info('enable');
|
2012-08-17 15:49:34 -07:00
|
|
|
|
|
|
|
// Add stylesheet
|
|
|
|
let stylesheetURL = 'chrome://global/content/accessibility/AccessFu.css';
|
|
|
|
this.stylesheet = this.chromeWin.document.createProcessingInstruction(
|
|
|
|
'xml-stylesheet', 'href="' + stylesheetURL + '" type="text/css"');
|
|
|
|
this.chromeWin.document.insertBefore(this.stylesheet, this.chromeWin.document.firstChild);
|
|
|
|
|
2012-04-13 16:18:57 -07:00
|
|
|
this.addPresenter(new VisualPresenter());
|
|
|
|
|
|
|
|
// Implicitly add the Android presenter on Android.
|
2012-07-20 09:46:54 -07:00
|
|
|
if (Utils.MozBuildApp == 'mobile/android')
|
2012-04-13 16:18:57 -07:00
|
|
|
this.addPresenter(new AndroidPresenter());
|
2012-07-20 09:46:54 -07:00
|
|
|
else if (Utils.MozBuildApp == 'b2g')
|
|
|
|
this.addPresenter(new SpeechPresenter());
|
2012-04-13 16:18:57 -07:00
|
|
|
|
|
|
|
VirtualCursorController.attach(this.chromeWin);
|
|
|
|
|
|
|
|
Services.obs.addObserver(this, 'accessible-event', false);
|
|
|
|
this.chromeWin.addEventListener('DOMActivate', this, true);
|
|
|
|
this.chromeWin.addEventListener('resize', this, true);
|
|
|
|
this.chromeWin.addEventListener('scroll', this, true);
|
|
|
|
this.chromeWin.addEventListener('TabOpen', this, true);
|
2012-05-22 13:44:17 -07:00
|
|
|
this.chromeWin.addEventListener('focus', this, true);
|
2012-04-13 16:18:57 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disable AccessFu and return to default interaction mode.
|
|
|
|
*/
|
2012-05-14 14:21:59 -07:00
|
|
|
_disable: function _disable() {
|
2012-05-10 10:33:12 -07:00
|
|
|
if (!this._enabled)
|
|
|
|
return;
|
|
|
|
this._enabled = false;
|
|
|
|
|
2012-06-20 14:07:51 -07:00
|
|
|
Logger.info('disable');
|
2012-04-13 16:18:57 -07:00
|
|
|
|
2012-08-17 15:49:34 -07:00
|
|
|
this.chromeWin.document.removeChild(this.stylesheet);
|
|
|
|
|
2012-05-07 09:44:44 -07:00
|
|
|
this.presenters.forEach(function(p) { p.detach(); });
|
2012-04-13 16:18:57 -07:00
|
|
|
this.presenters = [];
|
|
|
|
|
|
|
|
VirtualCursorController.detach();
|
|
|
|
|
2012-05-07 09:44:44 -07:00
|
|
|
Services.obs.removeObserver(this, 'accessible-event');
|
|
|
|
this.chromeWin.removeEventListener('DOMActivate', this, true);
|
|
|
|
this.chromeWin.removeEventListener('resize', this, true);
|
|
|
|
this.chromeWin.removeEventListener('scroll', this, true);
|
|
|
|
this.chromeWin.removeEventListener('TabOpen', this, true);
|
2012-05-22 13:44:17 -07:00
|
|
|
this.chromeWin.removeEventListener('focus', this, true);
|
2012-04-13 16:18:57 -07:00
|
|
|
},
|
|
|
|
|
2012-06-13 12:22:52 -07:00
|
|
|
_processPreferences: function _processPreferences(aEnabled, aTouchEnabled) {
|
|
|
|
let accessPref = ACCESSFU_DISABLE;
|
|
|
|
try {
|
|
|
|
accessPref = (aEnabled == undefined) ?
|
|
|
|
this.prefsBranch.getIntPref('activate') : aEnabled;
|
|
|
|
} catch (x) {
|
|
|
|
}
|
|
|
|
|
|
|
|
let ebtPref = ACCESSFU_DISABLE;
|
|
|
|
try {
|
|
|
|
ebtPref = (aTouchEnabled == undefined) ?
|
|
|
|
this.prefsBranch.getIntPref('explorebytouch') : aTouchEnabled;
|
|
|
|
} catch (x) {
|
|
|
|
}
|
|
|
|
|
2012-08-15 13:40:12 -07:00
|
|
|
if (Utils.MozBuildApp == 'mobile/android') {
|
2012-06-13 12:22:52 -07:00
|
|
|
if (accessPref == ACCESSFU_AUTO) {
|
2012-05-10 10:33:12 -07:00
|
|
|
Cc['@mozilla.org/android/bridge;1'].
|
|
|
|
getService(Ci.nsIAndroidBridge).handleGeckoMessage(
|
|
|
|
JSON.stringify({ gecko: { type: 'Accessibility:Ready' } }));
|
|
|
|
return;
|
|
|
|
}
|
2012-05-03 19:28:35 -07:00
|
|
|
}
|
2012-05-10 10:33:12 -07:00
|
|
|
|
2012-06-13 12:22:52 -07:00
|
|
|
if (accessPref == ACCESSFU_ENABLE)
|
2012-05-14 14:21:59 -07:00
|
|
|
this._enable();
|
2012-05-10 10:33:12 -07:00
|
|
|
else
|
2012-05-14 14:21:59 -07:00
|
|
|
this._disable();
|
2012-06-13 12:22:52 -07:00
|
|
|
|
2012-08-17 15:49:34 -07:00
|
|
|
if (ebtPref == ACCESSFU_ENABLE)
|
|
|
|
this.touchAdapter.attach(this.chromeWin);
|
|
|
|
else
|
|
|
|
this.touchAdapter.detach(this.chromeWin);
|
2012-05-03 19:28:35 -07:00
|
|
|
},
|
|
|
|
|
2012-04-13 16:18:57 -07:00
|
|
|
addPresenter: function addPresenter(presenter) {
|
|
|
|
this.presenters.push(presenter);
|
|
|
|
presenter.attach(this.chromeWin);
|
|
|
|
},
|
|
|
|
|
|
|
|
handleEvent: function handleEvent(aEvent) {
|
|
|
|
switch (aEvent.type) {
|
2012-05-22 13:44:17 -07:00
|
|
|
case 'focus':
|
|
|
|
{
|
|
|
|
if (aEvent.target instanceof Ci.nsIDOMWindow) {
|
|
|
|
let docAcc = getAccessible(aEvent.target.document);
|
|
|
|
let docContext = new PresenterContext(docAcc, null);
|
|
|
|
let cursorable = docAcc.QueryInterface(Ci.nsIAccessibleCursorable);
|
|
|
|
let vcContext = new PresenterContext(
|
|
|
|
(cursorable) ? cursorable.virtualCursor.position : null, null);
|
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) { p.tabSelected(docContext, vcContext); });
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2012-05-07 09:44:44 -07:00
|
|
|
case 'TabOpen':
|
|
|
|
{
|
|
|
|
let browser = aEvent.target.linkedBrowser || aEvent.target;
|
|
|
|
// Store the new browser node. We will need to check later when a new
|
|
|
|
// content document is attached if it has been attached to this new tab.
|
|
|
|
// If it has, than we will need to send a 'loading' message along with
|
|
|
|
// the usual 'newdoc' to presenters.
|
|
|
|
this._pendingDocuments[browser] = true;
|
2012-06-20 14:07:51 -07:00
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) {
|
|
|
|
p.tabStateChanged(null, 'newtab');
|
|
|
|
}
|
|
|
|
);
|
2012-05-07 09:44:44 -07:00
|
|
|
break;
|
|
|
|
}
|
2012-04-13 16:18:57 -07:00
|
|
|
case 'DOMActivate':
|
|
|
|
{
|
|
|
|
let activatedAcc = getAccessible(aEvent.originalTarget);
|
|
|
|
let state = {};
|
|
|
|
activatedAcc.getState(state, {});
|
|
|
|
|
|
|
|
// Checkable objects will have a state changed event that we will use
|
|
|
|
// instead of this hackish DOMActivate. We will also know the true
|
|
|
|
// action that was taken.
|
|
|
|
if (state.value & Ci.nsIAccessibleStates.STATE_CHECKABLE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.presenters.forEach(function(p) {
|
|
|
|
p.actionInvoked(activatedAcc, 'click');
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'scroll':
|
|
|
|
case 'resize':
|
|
|
|
{
|
2012-05-07 09:44:44 -07:00
|
|
|
this.presenters.forEach(function(p) { p.viewportChanged(); });
|
2012-04-13 16:18:57 -07:00
|
|
|
break;
|
|
|
|
}
|
2012-08-17 15:49:34 -07:00
|
|
|
case 'mozContentEvent':
|
|
|
|
{
|
|
|
|
if (aEvent.detail.type == 'accessibility-screenreader') {
|
|
|
|
let pref = aEvent.detail.enabled + 0;
|
|
|
|
this._processPreferences(pref, pref);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2012-04-13 16:18:57 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
observe: function observe(aSubject, aTopic, aData) {
|
|
|
|
switch (aTopic) {
|
2012-05-10 10:33:12 -07:00
|
|
|
case 'Accessibility:Settings':
|
2012-06-13 12:22:52 -07:00
|
|
|
this._processPreferences(JSON.parse(aData).enabled + 0,
|
|
|
|
JSON.parse(aData).exploreByTouch + 0);
|
2012-05-10 10:33:12 -07:00
|
|
|
break;
|
2012-05-03 19:28:35 -07:00
|
|
|
case 'nsPref:changed':
|
2012-06-13 12:22:52 -07:00
|
|
|
this._processPreferences(this.prefsBranch.getIntPref('activate'),
|
|
|
|
this.prefsBranch.getIntPref('explorebytouch'));
|
2012-05-03 19:28:35 -07:00
|
|
|
break;
|
2012-04-13 16:18:57 -07:00
|
|
|
case 'accessible-event':
|
|
|
|
let event;
|
|
|
|
try {
|
|
|
|
event = aSubject.QueryInterface(Ci.nsIAccessibleEvent);
|
2012-05-14 14:21:59 -07:00
|
|
|
this._handleAccEvent(event);
|
2012-04-13 16:18:57 -07:00
|
|
|
} catch (ex) {
|
2012-06-20 14:07:51 -07:00
|
|
|
Logger.error(ex);
|
2012-04-13 16:18:57 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-05-14 14:21:59 -07:00
|
|
|
_handleAccEvent: function _handleAccEvent(aEvent) {
|
2012-06-20 14:07:51 -07:00
|
|
|
if (Logger.logLevel <= Logger.DEBUG)
|
|
|
|
Logger.debug(Logger.eventToString(aEvent),
|
|
|
|
Logger.accessibleToString(aEvent.accessible));
|
|
|
|
|
2012-04-13 16:18:57 -07:00
|
|
|
switch (aEvent.eventType) {
|
|
|
|
case Ci.nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED:
|
|
|
|
{
|
|
|
|
let pivot = aEvent.accessible.
|
|
|
|
QueryInterface(Ci.nsIAccessibleCursorable).virtualCursor;
|
|
|
|
let event = aEvent.
|
|
|
|
QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
|
2012-05-15 10:41:26 -07:00
|
|
|
let position = pivot.position;
|
|
|
|
let doc = aEvent.DOMNode;
|
|
|
|
|
2012-05-30 15:14:23 -07:00
|
|
|
let presenterContext =
|
|
|
|
new PresenterContext(position, event.oldAccessible);
|
2012-06-25 10:34:52 -07:00
|
|
|
let reason = event.reason;
|
2012-05-30 15:14:23 -07:00
|
|
|
this.presenters.forEach(
|
2012-06-25 10:34:52 -07:00
|
|
|
function(p) { p.pivotChanged(presenterContext, reason); });
|
2012-05-30 15:14:23 -07:00
|
|
|
|
2012-04-13 16:18:57 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE:
|
|
|
|
{
|
|
|
|
let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
|
|
|
|
if (event.state == Ci.nsIAccessibleStates.STATE_CHECKED &&
|
|
|
|
!(event.isExtraState())) {
|
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) {
|
|
|
|
p.actionInvoked(aEvent.accessible,
|
|
|
|
event.isEnabled() ? 'check' : 'uncheck');
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2012-05-07 09:44:44 -07:00
|
|
|
else if (event.state == Ci.nsIAccessibleStates.STATE_BUSY &&
|
|
|
|
!(event.isExtraState()) && event.isEnabled()) {
|
|
|
|
let role = event.accessible.role;
|
|
|
|
if ((role == Ci.nsIAccessibleRole.ROLE_DOCUMENT ||
|
|
|
|
role == Ci.nsIAccessibleRole.ROLE_APPLICATION)) {
|
|
|
|
// An existing document has changed to state "busy", this means
|
|
|
|
// something is loading. Send a 'loading' message to presenters.
|
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) {
|
|
|
|
p.tabStateChanged(event.accessible, 'loading');
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2012-04-13 16:18:57 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Ci.nsIAccessibleEvent.EVENT_REORDER:
|
|
|
|
{
|
2012-05-07 09:44:44 -07:00
|
|
|
let acc = aEvent.accessible;
|
|
|
|
if (acc.childCount) {
|
|
|
|
let docAcc = acc.getChildAt(0);
|
|
|
|
if (this._pendingDocuments[aEvent.DOMNode]) {
|
|
|
|
// This is a document in a new tab. Check if it is
|
|
|
|
// in a BUSY state (i.e. loading), and inform presenters.
|
|
|
|
// We need to do this because a state change event will not be
|
|
|
|
// fired when an object is created with the BUSY state.
|
2012-06-20 14:07:51 -07:00
|
|
|
// If this is not a new tab, don't bother because we sent
|
|
|
|
// 'loading' when the previous doc changed its state to BUSY.
|
2012-05-07 09:44:44 -07:00
|
|
|
let state = {};
|
|
|
|
docAcc.getState(state, {});
|
|
|
|
if (state.value & Ci.nsIAccessibleStates.STATE_BUSY &&
|
2012-05-14 14:21:59 -07:00
|
|
|
this._isNotChromeDoc(docAcc))
|
2012-05-07 09:44:44 -07:00
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) { p.tabStateChanged(docAcc, 'loading'); }
|
|
|
|
);
|
|
|
|
delete this._pendingDocuments[aEvent.DOMNode];
|
|
|
|
}
|
2012-05-14 14:21:59 -07:00
|
|
|
if (this._isBrowserDoc(docAcc))
|
2012-05-07 09:44:44 -07:00
|
|
|
// A new top-level content document has been attached
|
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) { p.tabStateChanged(docAcc, 'newdoc'); }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Ci.nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE:
|
|
|
|
{
|
2012-05-14 14:21:59 -07:00
|
|
|
if (this._isNotChromeDoc(aEvent.accessible)) {
|
2012-05-07 09:44:44 -07:00
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) {
|
|
|
|
p.tabStateChanged(aEvent.accessible, 'loaded');
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Ci.nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_STOPPED:
|
|
|
|
{
|
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) {
|
|
|
|
p.tabStateChanged(aEvent.accessible, 'loadstopped');
|
|
|
|
}
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Ci.nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD:
|
|
|
|
{
|
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) {
|
|
|
|
p.tabStateChanged(aEvent.accessible, 'reload');
|
|
|
|
}
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
2012-05-02 22:10:55 -07:00
|
|
|
case Ci.nsIAccessibleEvent.EVENT_TEXT_INSERTED:
|
|
|
|
case Ci.nsIAccessibleEvent.EVENT_TEXT_REMOVED:
|
|
|
|
{
|
|
|
|
if (aEvent.isFromUserInput) {
|
|
|
|
// XXX support live regions as well.
|
|
|
|
let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent);
|
|
|
|
let isInserted = event.isInserted();
|
2012-06-20 14:07:51 -07:00
|
|
|
let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
|
2012-05-02 22:10:55 -07:00
|
|
|
|
|
|
|
let text = '';
|
|
|
|
try {
|
2012-06-20 14:07:51 -07:00
|
|
|
text = txtIface.
|
2012-05-02 22:10:55 -07:00
|
|
|
getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
|
|
|
|
} catch (x) {
|
|
|
|
// XXX we might have gotten an exception with of a
|
|
|
|
// zero-length text. If we did, ignore it (bug #749810).
|
2012-06-20 14:07:51 -07:00
|
|
|
if (txtIface.characterCount)
|
2012-05-02 22:10:55 -07:00
|
|
|
throw x;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) {
|
2012-06-20 14:07:51 -07:00
|
|
|
p.textChanged(isInserted, event.start, event.length,
|
|
|
|
text, event.modifiedText);
|
2012-05-02 22:10:55 -07:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2012-06-03 19:02:56 -07:00
|
|
|
case Ci.nsIAccessibleEvent.EVENT_SCROLLING_START:
|
|
|
|
{
|
2012-07-20 09:46:54 -07:00
|
|
|
VirtualCursorController.moveCursorToObject(
|
|
|
|
Utils.getVirtualCursor(aEvent.accessibleDocument), aEvent.accessible);
|
2012-06-03 19:02:56 -07:00
|
|
|
break;
|
|
|
|
}
|
2012-06-06 10:02:24 -07:00
|
|
|
case Ci.nsIAccessibleEvent.EVENT_FOCUS:
|
|
|
|
{
|
|
|
|
let acc = aEvent.accessible;
|
|
|
|
let doc = aEvent.accessibleDocument;
|
|
|
|
if (acc.role != Ci.nsIAccessibleRole.ROLE_DOCUMENT &&
|
|
|
|
doc.role != Ci.nsIAccessibleRole.ROLE_CHROME_WINDOW)
|
2012-07-20 09:46:54 -07:00
|
|
|
VirtualCursorController.moveCursorToObject(
|
|
|
|
Utils.getVirtualCursor(doc), acc);
|
2012-07-10 16:10:15 -07:00
|
|
|
|
|
|
|
let [,extState] = Utils.getStates(acc);
|
|
|
|
let editableState = extState &
|
|
|
|
(Ci.nsIAccessibleStates.EXT_STATE_EDITABLE |
|
|
|
|
Ci.nsIAccessibleStates.EXT_STATE_MULTI_LINE);
|
|
|
|
|
|
|
|
if (editableState != VirtualCursorController.editableState) {
|
|
|
|
if (!VirtualCursorController.editableState)
|
|
|
|
this.presenters.forEach(
|
|
|
|
function(p) {
|
|
|
|
p.editingModeChanged(true);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
VirtualCursorController.editableState = editableState;
|
2012-06-06 10:02:24 -07:00
|
|
|
break;
|
|
|
|
}
|
2012-04-13 16:18:57 -07:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-05-07 09:44:44 -07:00
|
|
|
/**
|
|
|
|
* Check if accessible is a top-level content document (i.e. a child of a XUL
|
|
|
|
* browser node).
|
|
|
|
* @param {nsIAccessible} aDocAcc the accessible to check.
|
|
|
|
* @return {boolean} true if this is a top-level content document.
|
|
|
|
*/
|
2012-05-14 14:21:59 -07:00
|
|
|
_isBrowserDoc: function _isBrowserDoc(aDocAcc) {
|
2012-05-07 09:44:44 -07:00
|
|
|
let parent = aDocAcc.parent;
|
|
|
|
if (!parent)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let domNode = parent.DOMNode;
|
|
|
|
if (!domNode)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const ns = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
|
|
|
|
return (domNode.localName == 'browser' && domNode.namespaceURI == ns);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if document is not a local "chrome" document, like about:home.
|
|
|
|
* @param {nsIDOMDocument} aDocument the document to check.
|
|
|
|
* @return {boolean} true if this is not a chrome document.
|
|
|
|
*/
|
2012-05-14 14:21:59 -07:00
|
|
|
_isNotChromeDoc: function _isNotChromeDoc(aDocument) {
|
2012-05-07 09:44:44 -07:00
|
|
|
let location = aDocument.DOMNode.location;
|
|
|
|
if (!location)
|
|
|
|
return false;
|
|
|
|
|
2012-06-20 14:07:51 -07:00
|
|
|
return location.protocol != 'about:';
|
2012-05-07 09:44:44 -07:00
|
|
|
},
|
|
|
|
|
2012-04-13 16:18:57 -07:00
|
|
|
// A hash of documents that don't yet have an accessible tree.
|
2012-05-10 10:33:12 -07:00
|
|
|
_pendingDocuments: {},
|
|
|
|
|
|
|
|
// So we don't enable/disable twice
|
2012-06-13 12:22:52 -07:00
|
|
|
_enabled: false
|
2012-04-13 16:18:57 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
function getAccessible(aNode) {
|
|
|
|
try {
|
|
|
|
return Cc['@mozilla.org/accessibleRetrieval;1'].
|
|
|
|
getService(Ci.nsIAccessibleRetrieval).getAccessibleFor(aNode);
|
|
|
|
} catch (e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|