Bug 751769 - Dynamically attach/detach windows in AccessFu. r=yzen

This commit is contained in:
Eitan Isaacson 2018-05-30 12:43:00 -04:00
Родитель 8587557096
Коммит 0c48554698
8 изменённых файлов: 114 добавлений и 224 удалений

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

@ -28,31 +28,6 @@ const GECKOVIEW_MESSAGE = {
};
var AccessFu = {
/**
* Initialize chrome-layer accessibility functionality.
* If accessibility is enabled on the platform, then a special accessibility
* mode is started.
*/
attach: function attach(aWindow, aInTest = false) {
Utils.init(aWindow);
if (!aInTest) {
this._enable();
}
},
/**
* Shut down chrome-layer accessibility functionality from the outside.
*/
detach: function detach() {
// Avoid disabling twice.
if (this._enabled) {
this._disable();
}
Utils.uninit();
},
/**
* A lazy getter for event handler that binds the scope to AccessFu object.
*/
@ -63,10 +38,9 @@ var AccessFu = {
},
/**
* Start AccessFu mode, this primarily means controlling the virtual cursor
* with arrow keys.
* Start AccessFu mode.
*/
_enable: function _enable() {
enable: function enable() {
if (this._enabled) {
return;
}
@ -75,25 +49,18 @@ var AccessFu = {
ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
ChromeUtils.import("resource://gre/modules/accessibility/Presentation.jsm");
for (let mm of Utils.AllMessageManagers) {
this._addMessageListeners(mm);
this._loadFrameScript(mm);
}
// Check for output notification
this._notifyOutputPref =
new PrefCache("accessibility.accessfu.notify_output");
if (Utils.MozBuildApp === "mobile/android") {
Utils.win.WindowEventDispatcher.registerListener(this,
Object.values(GECKOVIEW_MESSAGE));
}
Services.obs.addObserver(this, "remote-browser-shown");
Services.obs.addObserver(this, "inprocess-browser-shown");
Utils.win.addEventListener("TabOpen", this);
Utils.win.addEventListener("TabClose", this);
Utils.win.addEventListener("TabSelect", this);
Services.ww.registerNotification(this);
let windows = Services.wm.getEnumerator(null);
while (windows.hasMoreElements()) {
this._attachWindow(windows.getNext());
}
if (this.readyCallback) {
this.readyCallback();
@ -106,28 +73,20 @@ var AccessFu = {
/**
* Disable AccessFu and return to default interaction mode.
*/
_disable: function _disable() {
disable: function disable() {
if (!this._enabled) {
return;
}
this._enabled = false;
for (let mm of Utils.AllMessageManagers) {
mm.sendAsyncMessage("AccessFu:Stop");
this._removeMessageListeners(mm);
}
Utils.win.removeEventListener("TabOpen", this);
Utils.win.removeEventListener("TabClose", this);
Utils.win.removeEventListener("TabSelect", this);
Services.obs.removeObserver(this, "remote-browser-shown");
Services.obs.removeObserver(this, "inprocess-browser-shown");
Services.ww.unregisterNotification(this);
if (Utils.MozBuildApp === "mobile/android") {
Utils.win.WindowEventDispatcher.unregisterListener(this,
Object.values(GECKOVIEW_MESSAGE));
let windows = Services.wm.getEnumerator(null);
while (windows.hasMoreElements()) {
this._detachWindow(windows.getNext());
}
delete this._notifyOutputPref;
@ -157,11 +116,49 @@ var AccessFu = {
this._output(aMessage.json, aMessage.target);
break;
case "AccessFu:DoScroll":
this.Input.doScroll(aMessage.json);
this.Input.doScroll(aMessage.json, aMessage.target);
break;
}
},
_attachWindow: function _attachWindow(win) {
let wtype = win.document.documentElement.getAttribute("windowtype");
if (wtype != "navigator:browser" && wtype != "navigator:geckoview") {
// Don't attach to non-browser or geckoview windows.
return;
}
for (let mm of Utils.getAllMessageManagers(win)) {
this._addMessageListeners(mm);
this._loadFrameScript(mm);
}
win.addEventListener("TabOpen", this);
win.addEventListener("TabClose", this);
win.addEventListener("TabSelect", this);
if (win.WindowEventDispatcher) {
// desktop mochitests don't have this.
win.WindowEventDispatcher.registerListener(this,
Object.values(GECKOVIEW_MESSAGE));
}
},
_detachWindow: function _detachWindow(win) {
for (let mm of Utils.getAllMessageManagers(win)) {
mm.sendAsyncMessage("AccessFu:Stop");
this._removeMessageListeners(mm);
}
win.removeEventListener("TabOpen", this);
win.removeEventListener("TabClose", this);
win.removeEventListener("TabSelect", this);
if (win.WindowEventDispatcher) {
// desktop mochitests don't have this.
win.WindowEventDispatcher.unregisterListener(this,
Object.values(GECKOVIEW_MESSAGE));
}
},
_output: function _output(aPresentationData, aBrowser) {
if (!aPresentationData) {
// Either no android events to send or a string used for testing only.
@ -172,15 +169,20 @@ var AccessFu = {
return;
}
let win = aBrowser.ownerGlobal;
for (let evt of aPresentationData) {
if (typeof evt == "string") {
continue;
}
Utils.win.WindowEventDispatcher.sendRequest({
...evt,
type: "GeckoView:AccessibilityEvent"
});
if (win.WindowEventDispatcher) {
// desktop mochitests don't have this.
win.WindowEventDispatcher.sendRequest({
...evt,
type: "GeckoView:AccessibilityEvent"
});
}
}
if (this._notifyOutputPref.value) {
@ -281,6 +283,10 @@ var AccessFu = {
this._handleMessageManager(frameLoader.messageManager);
break;
}
case "domwindowopened": {
this._attachWindow(aSubject.QueryInterface(Ci.nsIDOMWindow));
break;
}
}
},
@ -322,12 +328,12 @@ var AccessFu = {
},
autoMove: function autoMove(aOptions) {
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
let mm = Utils.getMessageManager();
mm.sendAsyncMessage("AccessFu:AutoMove", aOptions);
},
announce: function announce(aAnnouncement) {
this._output(Presentation.announce(aAnnouncement), Utils.CurrentBrowser);
this._output(Presentation.announce(aAnnouncement), Utils.getCurrentBrowser());
},
// So we don't enable/disable twice
@ -344,38 +350,29 @@ var AccessFu = {
* Adjusts the given bounds that are defined in device display pixels
* to client-relative CSS pixels of the chrome window.
* @param {Rect} aJsonBounds the bounds to adjust
* @param {Window} aWindow the window containing the item
*/
screenToClientBounds(aJsonBounds) {
screenToClientBounds(aJsonBounds, aWindow) {
let bounds = new Rect(aJsonBounds.left, aJsonBounds.top,
aJsonBounds.right - aJsonBounds.left,
aJsonBounds.bottom - aJsonBounds.top);
let win = Utils.win;
let dpr = win.devicePixelRatio;
let { devicePixelRatio, mozInnerScreenX, mozInnerScreenY } = aWindow;
bounds = bounds.scale(1 / dpr, 1 / dpr);
bounds = bounds.translate(-win.mozInnerScreenX, -win.mozInnerScreenY);
bounds = bounds.scale(1 / devicePixelRatio, 1 / devicePixelRatio);
bounds = bounds.translate(-mozInnerScreenX, -mozInnerScreenY);
return bounds.expandToIntegers();
}
};
var Input = {
moveToPoint: function moveToPoint(aRule, aX, aY) {
// XXX: Bug 1013408 - There is no alignment between the chrome window's
// viewport size and the content viewport size in Android. This makes
// sending mouse events beyond its bounds impossible.
if (Utils.MozBuildApp === "mobile/android") {
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
mm.sendAsyncMessage("AccessFu:MoveToPoint",
{rule: aRule, x: aX, y: aY, origin: "top"});
} else {
let win = Utils.win;
Utils.winUtils.sendMouseEvent("mousemove",
aX - win.mozInnerScreenX, aY - win.mozInnerScreenY, 0, 0, 0);
}
let mm = Utils.getMessageManager();
mm.sendAsyncMessage("AccessFu:MoveToPoint",
{rule: aRule, x: aX, y: aY, origin: "top"});
},
moveCursor: function moveCursor(aAction, aRule, aInputType, aAdjustRange) {
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
let mm = Utils.getMessageManager();
mm.sendAsyncMessage("AccessFu:MoveCursor",
{ action: aAction, rule: aRule,
origin: "top", inputType: aInputType,
@ -383,18 +380,18 @@ var Input = {
},
androidScroll: function androidScroll(aDirection) {
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
let mm = Utils.getMessageManager();
mm.sendAsyncMessage("AccessFu:AndroidScroll",
{ direction: aDirection, origin: "top" });
},
moveByGranularity: function moveByGranularity(aDetails) {
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
let mm = Utils.getMessageManager();
mm.sendAsyncMessage("AccessFu:MoveByGranularity", aDetails);
},
activateCurrent: function activateCurrent(aData, aActivateIfKey = false) {
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
let mm = Utils.getMessageManager();
let offset = 0;
mm.sendAsyncMessage("AccessFu:Activate",
@ -408,18 +405,21 @@ var Input = {
},
sendScrollMessage: function sendScrollMessage(aPage, aHorizontal) {
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
let mm = Utils.getMessageManager();
mm.sendAsyncMessage("AccessFu:Scroll",
{page: aPage, horizontal: aHorizontal, origin: "top"});
},
doScroll: function doScroll(aDetails) {
doScroll: function doScroll(aDetails, aBrowser) {
let horizontal = aDetails.horizontal;
let page = aDetails.page;
let p = AccessFu.screenToClientBounds(aDetails.bounds).center();
Utils.winUtils.sendWheelEvent(p.x, p.y,
let win = aBrowser.ownerGlobal;
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
Ci.nsIDOMWindowUtils);
let p = AccessFu.screenToClientBounds(aDetails.bounds, win).center();
winUtils.sendWheelEvent(p.x, p.y,
horizontal ? page : 0, horizontal ? 0 : page, 0,
Utils.win.WheelEvent.DOM_DELTA_PAGE, 0, 0, 0, 0);
win.WheelEvent.DOM_DELTA_PAGE, 0, 0, 0, 0);
}
};
AccessFu.Input = Input;

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

@ -19,6 +19,10 @@ ChromeUtils.defineModuleGetter(this, "Events",
"resource://gre/modules/accessibility/Constants.jsm");
ChromeUtils.defineModuleGetter(this, "States",
"resource://gre/modules/accessibility/Constants.jsm");
ChromeUtils.defineModuleGetter(this, "clearTimeout",
"resource://gre/modules/Timer.jsm");
ChromeUtils.defineModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
var EXPORTED_SYMBOLS = ["EventManager"];
@ -447,7 +451,7 @@ this.EventManager.prototype = {
let queue = this._liveEventQueue.get(domNode);
let nextEvent = queue[0];
if (nextEvent.eventType === aEventType) {
Utils.win.clearTimeout(nextEvent.timeoutID);
clearTimeout(nextEvent.timeoutID);
queue.shift();
if (queue.length === 0) {
this._liveEventQueue.delete(domNode);
@ -462,7 +466,7 @@ this.EventManager.prototype = {
}
let eventHandler = {
eventType: aEventType,
timeoutID: Utils.win.setTimeout(this.present.bind(this),
timeoutID: setTimeout(this.present.bind(this),
20, // Wait for a possible EVENT_SHOW or EVENT_TEXT_INSERTED event.
Presentation.liveRegion(aLiveRegion, aIsPolite, true, aModifiedText))
};

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

@ -32,37 +32,6 @@ var Utils = { // jshint ignore:line
"{aa3c5121-dab2-40e2-81ca-7ea25febc110}": "mobile/android"
},
init: function Utils_init(aWindow) {
if (this._win) {
// XXX: only supports attaching to one window now.
throw new Error("Only one top-level window could used with AccessFu");
}
this._win = Cu.getWeakReference(aWindow);
},
uninit: function Utils_uninit() {
if (!this._win) {
return;
}
delete this._win;
},
get win() {
if (!this._win) {
return null;
}
return this._win.get();
},
get winUtils() {
let win = this.win;
if (!win) {
return null;
}
return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
Ci.nsIDOMWindowUtils);
},
get AccService() {
if (!this._AccService) {
this._AccService = Cc["@mozilla.org/accessibilityService;1"].
@ -83,20 +52,6 @@ var Utils = { // jshint ignore:line
return this._buildApp;
},
get OS() {
if (!this._OS) {
this._OS = Services.appinfo.OS;
}
return this._OS;
},
get widgetToolkit() {
if (!this._widgetToolkit) {
this._widgetToolkit = Services.appinfo.widgetToolkit;
}
return this._widgetToolkit;
},
get ScriptName() {
if (!this._ScriptName) {
this._ScriptName =
@ -123,39 +78,14 @@ var Utils = { // jshint ignore:line
this._AndroidSdkVersion = value;
},
get BrowserApp() {
if (!this.win) {
return null;
}
switch (this.MozBuildApp) {
case "mobile/android":
return this.win.BrowserApp;
case "browser":
return this.win.gBrowser;
case "b2g":
return this.win.shell;
default:
return null;
}
getCurrentBrowser: function getCurrentBrowser(aWindow) {
let win = aWindow ||
Services.wm.getMostRecentWindow("navigator:browser") ||
Services.wm.getMostRecentWindow("navigator:geckoview");
return win.document.querySelector("browser[type=content][primary=true]");
},
get CurrentBrowser() {
if (!this.BrowserApp) {
// Get the first content browser element when no 'BrowserApp' exists.
return this.win.document.querySelector("browser[type=content]");
}
if (this.MozBuildApp == "b2g") {
return this.BrowserApp.contentBrowser;
}
return this.BrowserApp.selectedBrowser;
},
get CurrentContentDoc() {
let browser = this.CurrentBrowser;
return browser ? browser.contentDocument : null;
},
get AllMessageManagers() {
getAllMessageManagers: function getAllMessageManagers(aWindow) {
let messageManagers = new Set();
function collectLeafMessageManagers(mm) {
@ -170,18 +100,12 @@ var Utils = { // jshint ignore:line
}
}
collectLeafMessageManagers(this.win.messageManager);
collectLeafMessageManagers(aWindow.messageManager);
let document = this.CurrentContentDoc;
let browser = this.getCurrentBrowser(aWindow);
let document = browser ? browser.contentDocument : null;
if (document) {
if (document.location.host === "b2g") {
// The document is a b2g app chrome (ie. Mulet).
let contentBrowser = this.win.content.shell.contentBrowser;
messageManagers.add(this.getMessageManager(contentBrowser));
document = contentBrowser.contentDocument;
}
let remoteframes = document.querySelectorAll("iframe");
for (let i = 0; i < remoteframes.length; ++i) {
@ -249,8 +173,9 @@ var Utils = { // jshint ignore:line
},
getMessageManager: function getMessageManager(aBrowser) {
let browser = aBrowser || this.getCurrentBrowser();
try {
return aBrowser.frameLoader.messageManager;
return browser.frameLoader.messageManager;
} catch (x) {
return null;
}
@ -319,15 +244,6 @@ var Utils = { // jshint ignore:line
return new Rect(objX.value, objY.value, objW.value, objH.value);
},
/**
* Get current display DPI.
*/
get dpi() {
delete this.dpi;
this.dpi = this.winUtils.displayDPI;
return this.dpi;
},
isInSubtree: function isInSubtree(aAccessible, aSubTreeRoot) {
let acc = aAccessible;
@ -478,29 +394,6 @@ var Utils = { // jshint ignore:line
aStaticText.indexInParent === 0;
},
dispatchChromeEvent: function dispatchChromeEvent(aType, aDetails) {
let details = {
type: aType,
details: JSON.stringify(
typeof aDetails === "string" ? { eventType: aDetails } : aDetails)
};
let window = this.win;
let shell = window.shell || window.content.shell;
if (shell) {
// On B2G device.
shell.sendChromeEvent(details);
} else {
// Dispatch custom event to have support for desktop and screen reader
// emulator add-on.
window.dispatchEvent(new window.CustomEvent(aType, {
bubbles: true,
cancelable: true,
detail: details
}));
}
},
isActivatableOnFingerUp: function isActivatableOnFingerUp(aAccessible) {
if (aAccessible.role === Roles.KEY) {
return true;

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

@ -109,7 +109,8 @@ var AccessFuTest = {
Logger.logLevel = Logger.INFO;
// Finish through idle callback to let AccessFu._disable complete.
SimpleTest.executeSoon(function() {
AccessFu.detach();
// May be redundant, but for cleanup's sake.
AccessFu.disable();
SimpleTest.finish();
});
},
@ -141,20 +142,12 @@ var AccessFuTest = {
// Start AccessFu and put it in stand-by.
ChromeUtils.import("resource://gre/modules/accessibility/AccessFu.jsm");
let chromeWin = getMainChromeWindow(window);
chromeWin.WindowEventDispatcher = {
dispatch: () => {},
sendRequest: () => {}
};
AccessFu.readyCallback = function readyCallback() {
// Enable logging to the console service.
Logger.test = true;
Logger.logLevel = Logger.DEBUG;
};
AccessFu.attach(chromeWin, true);
var prefs = [["accessibility.accessfu.notify_output", 1]];
prefs.push.apply(prefs, aAdditionalPrefs);

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

@ -23,7 +23,7 @@
ok(AccessFu._enabled, "AccessFu was enabled again.");
AccessFuTest.nextTest();
});
AccessFu._enable();
AccessFu.enable();
}
// Make sure EventManager is started again.
@ -36,7 +36,7 @@
isnot(AccessFu._enabled, "AccessFu was disabled.");
AccessFuTest.finish();
});
AccessFu._disable();
AccessFu.disable();
}
function doTest() {

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

@ -14,12 +14,12 @@
<script type="application/javascript">
function startAccessFu() {
AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
AccessFu._enable();
AccessFu.enable();
}
function stopAccessFu() {
AccessFuTest.once_log("EventManager.stop", () => AccessFuTest.finish());
AccessFu._disable();
AccessFu.disable();
}
function hide(id) {

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

@ -489,9 +489,9 @@ var BrowserApp = {
InitLater(() => GlobalEventDispatcher.dispatch("GeckoView:AccessibilityReady"));
GlobalEventDispatcher.registerListener((aEvent, aData, aCallback) => {
if (aData.enabled) {
AccessFu.attach(window);
AccessFu.enable();
} else {
AccessFu.detach();
AccessFu.disable();
}
}, "GeckoView:AccessibilitySettings");
}

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

@ -19,9 +19,9 @@ class GeckoViewAccessibility extends GeckoViewModule {
EventDispatcher.instance.dispatch("GeckoView:AccessibilityReady");
EventDispatcher.instance.registerListener((aEvent, aData, aCallback) => {
if (aData.enabled) {
AccessFu.attach(this.window);
AccessFu.enable();
} else {
AccessFu.detach();
AccessFu.disable();
}
}, "GeckoView:AccessibilitySettings");
}