Bug 818342 - Introduce announcement output. r=davidb

This commit is contained in:
Eitan Isaacson 2012-12-07 10:39:17 -08:00
Родитель b7be18150c
Коммит f7d7e441ed
4 изменённых файлов: 164 добавлений и 51 удалений

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

@ -28,3 +28,35 @@
top: 0px; top: 0px;
left: 0px; left: 0px;
} }
#announce-box {
position: fixed;
width: 7.5em;
height: 5em;
top: calc(100% - 50% - 2.5em);
left: calc(100% - 50% - 3.75em);
pointer-events: none;
display: table;
font-size: 28pt;
font-weight: 700;
color: orange;
background-color: black;
border-radius: 0.25em;
}
#announce-box:not(.showing) {
opacity: 0.0;
margin: 0.1em;
-moz-transition: opacity 0.4s linear;
}
#announce-box.showing {
opacity: 1.0;
-moz-transition: opacity 0.2s linear;
}
#announce-box * {
text-align: center;
display: table-cell;
vertical-align: middle;
}

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

@ -72,6 +72,7 @@ this.AccessFu = {
Cu.import('resource://gre/modules/accessibility/Utils.jsm'); Cu.import('resource://gre/modules/accessibility/Utils.jsm');
Cu.import('resource://gre/modules/accessibility/TouchAdapter.jsm'); Cu.import('resource://gre/modules/accessibility/TouchAdapter.jsm');
Cu.import('resource://gre/modules/accessibility/Presentation.jsm');
Logger.info('enable'); Logger.info('enable');
@ -145,26 +146,30 @@ this.AccessFu = {
switch (aMessage.name) { switch (aMessage.name) {
case 'AccessFu:Ready': case 'AccessFu:Ready':
let mm = Utils.getMessageManager(aMessage.target); let mm = Utils.getMessageManager(aMessage.target);
mm.sendAsyncMessage('AccessFu:Start', mm.sendAsyncMessage('AccessFu:Start',
{method: 'start', buildApp: Utils.MozBuildApp}); {method: 'start', buildApp: Utils.MozBuildApp});
break; break;
case 'AccessFu:Present': case 'AccessFu:Present':
this._output(aMessage.json, aMessage.target);
break;
case 'AccessFu:Input':
Input.setEditState(aMessage.json);
break;
}
},
_output: function _output(aPresentationData, aBrowser) {
try { try {
for each (let presenter in aMessage.json) { for each (let presenter in aPresentationData) {
if (!presenter) if (!presenter)
continue; continue;
Output[presenter.type](presenter.details, aMessage.target); Output[presenter.type](presenter.details, aBrowser);
} }
} catch (x) { } catch (x) {
Logger.logException(x); Logger.logException(x);
} }
break;
case 'AccessFu:Input':
Input.setEditState(aMessage.json);
break;
}
}, },
_loadFrameScript: function _loadFrameScript(aMessageManager) { _loadFrameScript: function _loadFrameScript(aMessageManager) {
@ -243,6 +248,11 @@ this.AccessFu = {
} }
}, },
announce: function announce(aAnnouncement) {
this._output(Presentation.announce(aAnnouncement),
Utils.getCurrentBrowser(this.chromeWin));
},
// So we don't enable/disable twice // So we don't enable/disable twice
_enabled: false, _enabled: false,
@ -262,34 +272,71 @@ var Output = {
}, },
Visual: function Visual(aDetails, aBrowser) { Visual: function Visual(aDetails, aBrowser) {
if (!this.highlightBox) { switch (aDetails.method) {
// Add highlight box case 'showBounds':
this.highlightBox = this.chromeWin.document. {
createElementNS('http://www.w3.org/1999/xhtml', 'div'); if (!this.highlightBox) {
this.chromeWin.document.documentElement.appendChild(this.highlightBox); // Add highlight box
this.highlightBox.id = 'virtual-cursor-box'; this.highlightBox = this.chromeWin.document.
createElementNS('http://www.w3.org/1999/xhtml', 'div');
this.chromeWin.document.documentElement.appendChild(this.highlightBox);
this.highlightBox.id = 'virtual-cursor-box';
// Add highlight inset for inner shadow // Add highlight inset for inner shadow
let inset = this.chromeWin.document. let inset = this.chromeWin.document.
createElementNS('http://www.w3.org/1999/xhtml', 'div'); createElementNS('http://www.w3.org/1999/xhtml', 'div');
inset.id = 'virtual-cursor-inset'; inset.id = 'virtual-cursor-inset';
this.highlightBox.appendChild(inset); this.highlightBox.appendChild(inset);
} }
if (aDetails.method == 'show') { let padding = aDetails.padding;
let padding = aDetails.padding; let r = this._adjustBounds(aDetails.bounds, aBrowser);
let r = this._adjustBounds(aDetails.bounds, aBrowser);
// First hide it to avoid flickering when changing the style. // First hide it to avoid flickering when changing the style.
this.highlightBox.style.display = 'none'; this.highlightBox.style.display = 'none';
this.highlightBox.style.top = (r.top - padding) + 'px'; this.highlightBox.style.top = (r.top - padding) + 'px';
this.highlightBox.style.left = (r.left - padding) + 'px'; this.highlightBox.style.left = (r.left - padding) + 'px';
this.highlightBox.style.width = (r.width + padding*2) + 'px'; this.highlightBox.style.width = (r.width + padding*2) + 'px';
this.highlightBox.style.height = (r.height + padding*2) + 'px'; this.highlightBox.style.height = (r.height + padding*2) + 'px';
this.highlightBox.style.display = 'block'; this.highlightBox.style.display = 'block';
} else if (aDetails.method == 'hide') {
this.highlightBox.style.display = 'none'; break;
}
case 'hideBounds':
{
if (this.highlightBox)
this.highlightBox.style.display = 'none';
break;
}
case 'showAnnouncement':
{
if (!this.announceBox) {
this.announceBox = this.chromeWin.document.
createElementNS('http://www.w3.org/1999/xhtml', 'div');
this.announceBox.id = 'announce-box';
this.chromeWin.document.documentElement.appendChild(this.announceBox);
}
this.announceBox.innerHTML = '<div>' + aDetails.text + '</div>';
this.announceBox.classList.add('showing');
if (this._announceHideTimeout)
this.chromeWin.clearTimeout(this._announceHideTimeout);
if (aDetails.duration > 0)
this._announceHideTimeout = this.chromeWin.setTimeout(
function () {
this.announceBox.classList.remove('showing');
this._announceHideTimeout = 0;
}.bind(this), aDetails.duration);
break;
}
case 'hideAnnouncement':
{
this.announceBox.classList.remove('showing');
break;
}
} }
}, },

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

@ -100,7 +100,12 @@ Presenter.prototype = {
/** /**
* We have entered or left text editing mode. * We have entered or left text editing mode.
*/ */
editingModeChanged: function editingModeChanged(aIsEditing) {} editingModeChanged: function editingModeChanged(aIsEditing) {},
/**
* Announce something. Typically an app state change.
*/
announce: function announce(aAnnouncement) {}
}; };
/** /**
@ -125,7 +130,7 @@ VisualPresenter.prototype = {
return { return {
type: this.type, type: this.type,
details: { details: {
method: 'show', method: 'showBounds',
bounds: context.bounds, bounds: context.bounds,
padding: this.BORDER_PADDING padding: this.BORDER_PADDING
} }
@ -139,7 +144,7 @@ VisualPresenter.prototype = {
this._currentAccessible = aContext.accessible; this._currentAccessible = aContext.accessible;
if (!aContext.accessible) if (!aContext.accessible)
return {type: this.type, details: {method: 'hide'}}; return {type: this.type, details: {method: 'hideBounds'}};
try { try {
aContext.accessible.scrollTo( aContext.accessible.scrollTo(
@ -147,7 +152,7 @@ VisualPresenter.prototype = {
return { return {
type: this.type, type: this.type,
details: { details: {
method: 'show', method: 'showBounds',
bounds: aContext.bounds, bounds: aContext.bounds,
padding: this.BORDER_PADDING padding: this.BORDER_PADDING
} }
@ -165,9 +170,20 @@ VisualPresenter.prototype = {
tabStateChanged: function VisualPresenter_tabStateChanged(aDocObj, tabStateChanged: function VisualPresenter_tabStateChanged(aDocObj,
aPageState) { aPageState) {
if (aPageState == 'newdoc') if (aPageState == 'newdoc')
return {type: this.type, details: {method: 'hide'}}; return {type: this.type, details: {method: 'hideBounds'}};
return null; return null;
},
announce: function VisualPresenter_announce(aAnnouncement) {
return {
type: this.type,
details: {
method: 'showAnnouncement',
text: aAnnouncement,
duration: 1000
}
};
} }
}; };
@ -257,8 +273,8 @@ AndroidPresenter.prototype = {
tabStateChanged: function AndroidPresenter_tabStateChanged(aDocObj, tabStateChanged: function AndroidPresenter_tabStateChanged(aDocObj,
aPageState) { aPageState) {
return this._appAnnounce( return this.announce(
UtteranceGenerator.genForTabStateChange(aDocObj, aPageState)); UtteranceGenerator.genForTabStateChange(aDocObj, aPageState).join(' '));
}, },
textChanged: function AndroidPresenter_textChanged(aIsInserted, aStart, textChanged: function AndroidPresenter_textChanged(aIsInserted, aStart,
@ -303,20 +319,18 @@ AndroidPresenter.prototype = {
}, },
editingModeChanged: function AndroidPresenter_editingModeChanged(aIsEditing) { editingModeChanged: function AndroidPresenter_editingModeChanged(aIsEditing) {
return this._appAnnounce(UtteranceGenerator.genForEditingMode(aIsEditing)); return this.announce(
UtteranceGenerator.genForEditingMode(aIsEditing).join(' '));
}, },
_appAnnounce: function _appAnnounce(aUtterance) { announce: function AndroidPresenter_announce(aAnnouncement) {
if (!aUtterance.length)
return null;
return { return {
type: this.type, type: this.type,
details: [{ details: [{
eventType: (Utils.AndroidSdkVersion >= 16) ? eventType: (Utils.AndroidSdkVersion >= 16) ?
this.ANDROID_ANNOUNCEMENT : this.ANDROID_VIEW_TEXT_CHANGED, this.ANDROID_ANNOUNCEMENT : this.ANDROID_VIEW_TEXT_CHANGED,
text: aUtterance, text: [aAnnouncement],
addedCount: aUtterance.join(' ').length, addedCount: aAnnouncement.length,
removedCount: 0, removedCount: 0,
fromIndex: 0 fromIndex: 0
}] }]
@ -500,7 +514,6 @@ PresenterContext.prototype = {
} }
}; };
this.Presentation = { this.Presentation = {
get presenters() { get presenters() {
delete this.presenters; delete this.presenters;
@ -550,5 +563,12 @@ this.Presentation = {
editingModeChanged: function Presentation_editingModeChanged(aIsEditing) { editingModeChanged: function Presentation_editingModeChanged(aIsEditing) {
return [p.editingModeChanged(aIsEditing) return [p.editingModeChanged(aIsEditing)
for each (p in this.presenters)]; for each (p in this.presenters)];
},
announce: function Presentation_announce(aAnnouncement) {
// XXX: Typically each presenter uses the UtteranceGenerator,
// but there really isn't a point here.
return [p.announce(UtteranceGenerator.genForAnnouncement(aAnnouncement)[0])
for each (p in this.presenters)];
} }
}; };

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

@ -97,6 +97,20 @@ this.UtteranceGenerator = {
return [gStringBundle.GetStringFromName(this.gActionMap[aActionName])]; return [gStringBundle.GetStringFromName(this.gActionMap[aActionName])];
}, },
/**
* Generates an utterance for an announcement. Basically attempts to localize
* the announcement string.
* @param {string} aAnnouncement unlocalized announcement.
* @return {Array} A one string array with the announcement.
*/
genForAnnouncement: function genForAnnouncement(aAnnouncement) {
try {
return [gStringBundle.GetStringFromName(aAnnouncement)];
} catch (x) {
return [aAnnouncement];
}
},
/** /**
* Generates an utterance for a tab state change. * Generates an utterance for a tab state change.
* @param {nsIAccessible} aAccessible accessible object of the tab's attached * @param {nsIAccessible} aAccessible accessible object of the tab's attached
@ -309,7 +323,7 @@ this.UtteranceGenerator = {
return stateUtterances; return stateUtterances;
}, },
_getListUtterance: function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) { _getListUtterance: function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : ''; let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : '';
let desc = []; let desc = [];