Bug 818342 - Introduce announcement output. r=davidb

This commit is contained in:
Eitan Isaacson 2012-12-07 10:39:17 -08:00
parent 723100f2cc
commit 085bf7b9c8
4 changed files with 164 additions and 51 deletions

View File

@ -28,3 +28,35 @@
top: 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;
}

View File

@ -72,6 +72,7 @@ this.AccessFu = {
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
Cu.import('resource://gre/modules/accessibility/TouchAdapter.jsm');
Cu.import('resource://gre/modules/accessibility/Presentation.jsm');
Logger.info('enable');
@ -145,26 +146,30 @@ this.AccessFu = {
switch (aMessage.name) {
case 'AccessFu:Ready':
let mm = Utils.getMessageManager(aMessage.target);
mm.sendAsyncMessage('AccessFu:Start',
{method: 'start', buildApp: Utils.MozBuildApp});
break;
let mm = Utils.getMessageManager(aMessage.target);
mm.sendAsyncMessage('AccessFu:Start',
{method: 'start', buildApp: Utils.MozBuildApp});
break;
case 'AccessFu:Present':
this._output(aMessage.json, aMessage.target);
break;
case 'AccessFu:Input':
Input.setEditState(aMessage.json);
break;
}
},
_output: function _output(aPresentationData, aBrowser) {
try {
for each (let presenter in aMessage.json) {
for each (let presenter in aPresentationData) {
if (!presenter)
continue;
Output[presenter.type](presenter.details, aMessage.target);
Output[presenter.type](presenter.details, aBrowser);
}
} catch (x) {
Logger.logException(x);
}
break;
case 'AccessFu:Input':
Input.setEditState(aMessage.json);
break;
}
},
_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
_enabled: false,
@ -262,34 +272,71 @@ var Output = {
},
Visual: function Visual(aDetails, aBrowser) {
if (!this.highlightBox) {
// Add highlight 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';
switch (aDetails.method) {
case 'showBounds':
{
if (!this.highlightBox) {
// Add highlight 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
let inset = this.chromeWin.document.
createElementNS('http://www.w3.org/1999/xhtml', 'div');
inset.id = 'virtual-cursor-inset';
// Add highlight inset for inner shadow
let inset = this.chromeWin.document.
createElementNS('http://www.w3.org/1999/xhtml', 'div');
inset.id = 'virtual-cursor-inset';
this.highlightBox.appendChild(inset);
}
this.highlightBox.appendChild(inset);
}
if (aDetails.method == 'show') {
let padding = aDetails.padding;
let r = this._adjustBounds(aDetails.bounds, aBrowser);
let padding = aDetails.padding;
let r = this._adjustBounds(aDetails.bounds, aBrowser);
// First hide it to avoid flickering when changing the style.
this.highlightBox.style.display = 'none';
this.highlightBox.style.top = (r.top - padding) + 'px';
this.highlightBox.style.left = (r.left - padding) + 'px';
this.highlightBox.style.width = (r.width + padding*2) + 'px';
this.highlightBox.style.height = (r.height + padding*2) + 'px';
this.highlightBox.style.display = 'block';
} else if (aDetails.method == 'hide') {
this.highlightBox.style.display = 'none';
// First hide it to avoid flickering when changing the style.
this.highlightBox.style.display = 'none';
this.highlightBox.style.top = (r.top - padding) + 'px';
this.highlightBox.style.left = (r.left - padding) + 'px';
this.highlightBox.style.width = (r.width + padding*2) + 'px';
this.highlightBox.style.height = (r.height + padding*2) + 'px';
this.highlightBox.style.display = 'block';
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;
}
}
},

View File

@ -100,7 +100,12 @@ Presenter.prototype = {
/**
* 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 {
type: this.type,
details: {
method: 'show',
method: 'showBounds',
bounds: context.bounds,
padding: this.BORDER_PADDING
}
@ -139,7 +144,7 @@ VisualPresenter.prototype = {
this._currentAccessible = aContext.accessible;
if (!aContext.accessible)
return {type: this.type, details: {method: 'hide'}};
return {type: this.type, details: {method: 'hideBounds'}};
try {
aContext.accessible.scrollTo(
@ -147,7 +152,7 @@ VisualPresenter.prototype = {
return {
type: this.type,
details: {
method: 'show',
method: 'showBounds',
bounds: aContext.bounds,
padding: this.BORDER_PADDING
}
@ -165,9 +170,20 @@ VisualPresenter.prototype = {
tabStateChanged: function VisualPresenter_tabStateChanged(aDocObj,
aPageState) {
if (aPageState == 'newdoc')
return {type: this.type, details: {method: 'hide'}};
return {type: this.type, details: {method: 'hideBounds'}};
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,
aPageState) {
return this._appAnnounce(
UtteranceGenerator.genForTabStateChange(aDocObj, aPageState));
return this.announce(
UtteranceGenerator.genForTabStateChange(aDocObj, aPageState).join(' '));
},
textChanged: function AndroidPresenter_textChanged(aIsInserted, aStart,
@ -303,20 +319,18 @@ AndroidPresenter.prototype = {
},
editingModeChanged: function AndroidPresenter_editingModeChanged(aIsEditing) {
return this._appAnnounce(UtteranceGenerator.genForEditingMode(aIsEditing));
return this.announce(
UtteranceGenerator.genForEditingMode(aIsEditing).join(' '));
},
_appAnnounce: function _appAnnounce(aUtterance) {
if (!aUtterance.length)
return null;
announce: function AndroidPresenter_announce(aAnnouncement) {
return {
type: this.type,
details: [{
eventType: (Utils.AndroidSdkVersion >= 16) ?
this.ANDROID_ANNOUNCEMENT : this.ANDROID_VIEW_TEXT_CHANGED,
text: aUtterance,
addedCount: aUtterance.join(' ').length,
text: [aAnnouncement],
addedCount: aAnnouncement.length,
removedCount: 0,
fromIndex: 0
}]
@ -500,7 +514,6 @@ PresenterContext.prototype = {
}
};
this.Presentation = {
get presenters() {
delete this.presenters;
@ -550,5 +563,12 @@ this.Presentation = {
editingModeChanged: function Presentation_editingModeChanged(aIsEditing) {
return [p.editingModeChanged(aIsEditing)
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)];
}
};

View File

@ -97,6 +97,20 @@ this.UtteranceGenerator = {
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.
* @param {nsIAccessible} aAccessible accessible object of the tab's attached
@ -309,7 +323,7 @@ this.UtteranceGenerator = {
return stateUtterances;
},
_getListUtterance: function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : '';
let desc = [];