author | Eitan Isaacson <eitan@monotonous.org> |
Mon, 07 May 2012 09:44:44 -0700 | |
changeset 93350 | 1bb9382bcbd116ef0993d835cd62af39674c149e |
parent 93349 | 7fc170b6f470d5e09068688fb647727bfce7cbe5 |
child 93351 | bc7f3a9deba8d737c54369270a00fcb2b091cecc |
push id | 9092 |
push user | eisaacson@mozilla.com |
push date | Mon, 07 May 2012 16:52:51 +0000 |
treeherder | mozilla-inbound@b58c6e5156d7 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | surkov |
bugs | 745986 |
milestone | 15.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/accessible/src/jsat/AccessFu.jsm +++ b/accessible/src/jsat/AccessFu.jsm @@ -67,38 +67,34 @@ var AccessFu = { 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); - this.chromeWin.addEventListener('TabSelect', this, true); - this.chromeWin.addEventListener('TabClosed', this, true); }, /** * Disable AccessFu and return to default interaction mode. */ disable: function disable() { dump('AccessFu disable'); - this.presenters.forEach(function(p) {p.detach();}); + this.presenters.forEach(function(p) { p.detach(); }); this.presenters = []; VirtualCursorController.detach(); Services.obs.addObserver(this, 'accessible-event', false); this.chromeWin.removeEventListener('DOMActivate', this); this.chromeWin.removeEventListener('resize', this); this.chromeWin.removeEventListener('scroll', this); this.chromeWin.removeEventListener('TabOpen', this); - this.chromeWin.removeEventListener('TabSelect', this); - this.chromeWin.removeEventListener('TabClose', this); }, amINeeded: function(aPref) { switch (aPref) { case ACCESSFU_ENABLE: return true; case ACCESSFU_AUTO: if (Services.appinfo.OS == 'Android') { @@ -120,24 +116,27 @@ var AccessFu = { addPresenter: function addPresenter(presenter) { this.presenters.push(presenter); presenter.attach(this.chromeWin); }, handleEvent: function handleEvent(aEvent) { switch (aEvent.type) { - case 'TabSelect': - { - this.getDocAccessible( - function(docAcc) { - this.presenters.forEach(function(p) {p.tabSelected(docAcc);}); - }); - break; - } + 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; + this.presenters.forEach(function(p) { p.tabStateChanged(null, 'newtab'); }); + break; + } 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 @@ -148,35 +147,22 @@ var AccessFu = { this.presenters.forEach(function(p) { p.actionInvoked(activatedAcc, 'click'); }); break; } case 'scroll': case 'resize': { - this.presenters.forEach(function(p) {p.viewportChanged();}); + this.presenters.forEach(function(p) { p.viewportChanged(); }); break; } } }, - getDocAccessible: function getDocAccessible(aCallback) { - let browserApp = (Services.appinfo.OS == 'Android') ? - this.chromeWin.BrowserApp : this.chromeWin.gBrowser; - - let docAcc = getAccessible(browserApp.selectedBrowser.contentDocument); - if (!docAcc) { - // Wait for a reorder event fired by the parent of the new doc. - this._pendingDocuments[browserApp.selectedBrowser] = aCallback; - } else { - aCallback.apply(this, [docAcc]); - } - }, - observe: function observe(aSubject, aTopic, aData) { switch (aTopic) { case 'nsPref:changed': if (aData == 'accessfu') { if (this.amINeeded(this.prefsBranch.getIntPref('accessfu'))) this.enable(); else this.disable(); @@ -218,26 +204,96 @@ var AccessFu = { !(event.isExtraState())) { this.presenters.forEach( function(p) { p.actionInvoked(aEvent.accessible, event.isEnabled() ? 'check' : 'uncheck'); } ); } + 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'); + } + ); + } + } break; } case Ci.nsIAccessibleEvent.EVENT_REORDER: { - let node = aEvent.accessible.DOMNode; - let callback = this._pendingDocuments[node]; - if (callback && aEvent.accessible.childCount) { - // We have a callback associated with a document. - callback.apply(this, [aEvent.accessible.getChildAt(0)]); - delete this._pendingDocuments[node]; + 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. + // If this is not a new tab, don't bother because we sent 'loading' + // when the previous doc changed its state to BUSY. + let state = {}; + docAcc.getState(state, {}); + if (state.value & Ci.nsIAccessibleStates.STATE_BUSY && + this.isNotChromeDoc(docAcc)) + this.presenters.forEach( + function(p) { p.tabStateChanged(docAcc, 'loading'); } + ); + delete this._pendingDocuments[aEvent.DOMNode]; + } + if (this.isBrowserDoc(docAcc)) + // 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: + { + if (this.isNotChromeDoc(aEvent.accessible)) { + 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; + } + case Ci.nsIAccessibleEvent.EVENT_FOCUS: + { + if (this.isBrowserDoc(aEvent.accessible)) { + // The document recieved focus, call tabSelected to present current tab. + this.presenters.forEach( + function(p) { p.tabSelected(aEvent.accessible); }); } break; } case Ci.nsIAccessibleEvent.EVENT_TEXT_INSERTED: case Ci.nsIAccessibleEvent.EVENT_TEXT_REMOVED: { if (aEvent.isFromUserInput) { // XXX support live regions as well. @@ -264,16 +320,48 @@ var AccessFu = { } break; } default: break; } }, + /** + * 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. + */ + isBrowserDoc: function isBrowserDoc(aDocAcc) { + 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. + */ + isNotChromeDoc: function isNotChromeDoc(aDocument) { + let location = aDocument.DOMNode.location; + if (!location) + return false; + + return location.protocol != "about:"; + }, + getNewContext: function getNewContext(aOldObject, aNewObject) { let newLineage = []; let oldLineage = []; let parent = aNewObject; while ((parent = parent.parent)) newLineage.push(parent);
--- a/accessible/src/jsat/Presenters.jsm +++ b/accessible/src/jsat/Presenters.jsm @@ -61,25 +61,31 @@ Presenter.prototype = { /** * Selection has changed. TODO. * @param {nsIAccessible} aObject the object that has been selected. */ selectionChanged: function selectionChanged(aObject) {}, /** - * The page state has changed, loading, stopped loading, etc. TODO. + * The tab, or the tab's document state has changed. + * @param {nsIAccessible} aDocObj the tab document accessible that has had its + * state changed, or null if the tab has no associated document yet. + * @param {string} aPageState the state name for the tab, valid states are: + * 'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'. */ - pageStateChanged: function pageStateChanged() {}, + tabStateChanged: function tabStateChanged(aDocObj, aPageState) {}, /** - * The tab has changed. - * @param {nsIAccessible} aObject the document contained in the tab. + * The current tab has changed. + * @param {nsIAccessible} aObject the document contained by the tab + * accessible, or null if it is a new tab with no attached + * document yet. */ - tabSelected: function tabSelected(aObject) {}, + tabSelected: function tabSelected(aDocObj) {}, /** * The viewport has changed, either a scroll, pan, zoom, or * landscape/portrait toggle. */ viewportChanged: function viewportChanged() {} }; @@ -142,19 +148,27 @@ VisualPresenter.prototype.pivotChanged = aObject.scrollTo(Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE); this.highlight(aObject); } catch (e) { dump('Error getting bounds: ' + e); return; } }; -VisualPresenter.prototype.tabSelected = function(aObject) { - let vcDoc = aObject.QueryInterface(Ci.nsIAccessibleCursorable); - this.pivotChanged(vcDoc.virtualCursor.position); +VisualPresenter.prototype.tabSelected = function(aDocObj) { + let vcPos = aDocObj ? + aDocObj.QueryInterface(Ci.nsIAccessibleCursorable).virtualCursor.position : + null; + + this.pivotChanged(vcPos); +}; + +VisualPresenter.prototype.tabStateChanged = function(aDocObj, aPageState) { + if (aPageState == "newdoc") + this.pivotChanged(null); }; // Internals VisualPresenter.prototype.hide = function hide() { this.highlightBox.style.display = 'none'; }; @@ -237,26 +251,50 @@ AndroidPresenter.prototype.actionInvoked gecko: { type: 'Accessibility:Event', eventType: ANDROID_TYPE_VIEW_CLICKED, text: UtteranceGenerator.genForAction(aObject, aActionName) } }); }; -AndroidPresenter.prototype.tabSelected = function(aObject) { - let vcDoc = aObject.QueryInterface(Ci.nsIAccessibleCursorable); +AndroidPresenter.prototype.tabSelected = function(aDocObj) { + // Send a pivot change message with the full context utterance for this doc. + let vcDoc = aDocObj.QueryInterface(Ci.nsIAccessibleCursorable); let context = []; - let parent = vcDoc.virtualCursor.position || aObject; - while ((parent = parent.parent)) + let parent = vcDoc.virtualCursor.position || aDocObj; + while ((parent = parent.parent)) { context.push(parent); + if (parent == aDocObj) + break; + } + context.reverse(); - this.pivotChanged(vcDoc.virtualCursor.position || aObject, context); + this.pivotChanged(vcDoc.virtualCursor.position || aDocObj, context); +}; + +AndroidPresenter.prototype.tabStateChanged = function(aDocObj, aPageState) { + let stateUtterance = UtteranceGenerator. + genForTabStateChange(aDocObj, aPageState); + + if (!stateUtterance.length) + return; + + this.sendMessageToJava({ + gecko: { + type: 'Accessibility:Event', + eventType: ANDROID_TYPE_VIEW_TEXT_CHANGED, + text: stateUtterance, + addedCount: stateUtterance.join(' ').length, + removedCount: 0, + fromIndex: 0 + } + }); }; AndroidPresenter.prototype.textChanged = function(aIsInserted, aStart, aLength, aText, aModifiedText) { let androidEvent = { type: 'Accessibility:Event', eventType: ANDROID_TYPE_VIEW_TEXT_CHANGED, text: [aText], fromIndex: aStart
--- a/accessible/src/jsat/UtteranceGenerator.jsm +++ b/accessible/src/jsat/UtteranceGenerator.jsm @@ -52,16 +52,34 @@ var UtteranceGenerator = { return func.apply(this, [aAccessible, roleString, flags]); }, genForAction: function(aObject, aActionName) { return [gStringBundle.GetStringFromName(this.gActionMap[aActionName])]; }, + genForTabStateChange: function (aObject, aTabState) { + switch (aTabState) { + case 'newtab': + return [gStringBundle.GetStringFromName('tabNew')]; + case 'loading': + return [gStringBundle.GetStringFromName('tabLoading')]; + case 'loaded': + return [aObject.name || '', + gStringBundle.GetStringFromName('tabLoaded')]; + case 'loadstopped': + return [gStringBundle.GetStringFromName('tabLoadStopped')]; + case 'reload': + return [gStringBundle.GetStringFromName('tabReload')]; + default: + return []; + } + }, + verbosityRoleMap: { 'menubar': INCLUDE_ROLE, 'scrollbar': INCLUDE_ROLE, 'grip': INCLUDE_ROLE, 'alert': INCLUDE_ROLE, 'menupopup': INCLUDE_ROLE, 'menuitem': INCLUDE_ROLE, 'tooltip': INCLUDE_ROLE,
--- a/dom/locales/en-US/chrome/accessibility/AccessFu.properties +++ b/dom/locales/en-US/chrome/accessibility/AccessFu.properties @@ -86,9 +86,16 @@ uncheckAction = unchecked selectAction = selected openAction = opened closeAction = closed switchAction = switched clickAction = clicked collapseAction = collapsed expandAction = expanded activateAction = activated -cycleAction = cycled \ No newline at end of file +cycleAction = cycled + +# Tab states +tabLoading = loading +tabLoaded = loaded +tabNew = new tab +tabLoadStopped = loading stopped +tabReload = reloading