From 0c48554698df5d40c091164ebdb5fa415441d598 Mon Sep 17 00:00:00 2001 From: Eitan Isaacson Date: Wed, 30 May 2018 12:43:00 -0400 Subject: [PATCH] Bug 751769 - Dynamically attach/detach windows in AccessFu. r=yzen --- accessible/jsat/AccessFu.jsm | 174 +++++++++--------- accessible/jsat/EventManager.jsm | 8 +- accessible/jsat/Utils.jsm | 129 ++----------- accessible/tests/mochitest/jsat/jsatcommon.js | 11 +- .../tests/mochitest/jsat/test_alive.html | 4 +- .../mochitest/jsat/test_live_regions.html | 4 +- mobile/android/chrome/content/browser.js | 4 +- .../geckoview/GeckoViewAccessibility.jsm | 4 +- 8 files changed, 114 insertions(+), 224 deletions(-) diff --git a/accessible/jsat/AccessFu.jsm b/accessible/jsat/AccessFu.jsm index d9038ff6ccf3..899d98ea3458 100644 --- a/accessible/jsat/AccessFu.jsm +++ b/accessible/jsat/AccessFu.jsm @@ -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; diff --git a/accessible/jsat/EventManager.jsm b/accessible/jsat/EventManager.jsm index 76f4dfcb2322..7b0373e2f29c 100644 --- a/accessible/jsat/EventManager.jsm +++ b/accessible/jsat/EventManager.jsm @@ -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)) }; diff --git a/accessible/jsat/Utils.jsm b/accessible/jsat/Utils.jsm index f3090225b09c..b3ce7155a77c 100644 --- a/accessible/jsat/Utils.jsm +++ b/accessible/jsat/Utils.jsm @@ -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; diff --git a/accessible/tests/mochitest/jsat/jsatcommon.js b/accessible/tests/mochitest/jsat/jsatcommon.js index 8023ddb992d3..0cc552442807 100644 --- a/accessible/tests/mochitest/jsat/jsatcommon.js +++ b/accessible/tests/mochitest/jsat/jsatcommon.js @@ -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); diff --git a/accessible/tests/mochitest/jsat/test_alive.html b/accessible/tests/mochitest/jsat/test_alive.html index 42f870a0eb10..9aacab7995d2 100644 --- a/accessible/tests/mochitest/jsat/test_alive.html +++ b/accessible/tests/mochitest/jsat/test_alive.html @@ -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() { diff --git a/accessible/tests/mochitest/jsat/test_live_regions.html b/accessible/tests/mochitest/jsat/test_live_regions.html index 0cffb4fb758e..c5b77e6e12f0 100644 --- a/accessible/tests/mochitest/jsat/test_live_regions.html +++ b/accessible/tests/mochitest/jsat/test_live_regions.html @@ -14,12 +14,12 @@