From b2992dd2283c3d0ac95f3293497dfaed0493f607 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 07 May 2014 11:34:28 -0400
Subject: [PATCH] Further accessibility improvements regarding keyboard navigation and document structure

---
 skins/larry/includes/mailtoolbar.html     |    8 
 program/steps/mail/compose.inc            |    5 
 skins/larry/templates/message.html        |   33 +++--
 skins/larry/templates/messagepart.html    |   14 +
 program/steps/mail/show.inc               |    7 +
 skins/larry/templates/messagepreview.html |   11 +
 skins/larry/templates/mail.html           |   22 ++-
 skins/classic/mail.css                    |    6 
 skins/larry/templates/messageerror.html   |   14 +-
 plugins/zipdownload/zipdownload.php       |    8 +
 program/steps/mail/func.inc               |   21 ++-
 skins/larry/styles.css                    |   10 +
 skins/larry/templates/login.html          |    4 
 program/js/app.js                         |   14 ++
 skins/larry/mail.css                      |   16 ++
 skins/larry/templates/compose.html        |   50 ++++---
 skins/larry/ui.js                         |   64 ++++++----
 17 files changed, 197 insertions(+), 110 deletions(-)

diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php
index 90a3144..edb8188 100644
--- a/plugins/zipdownload/zipdownload.php
+++ b/plugins/zipdownload/zipdownload.php
@@ -96,7 +96,10 @@
 
         $rcmail  = rcmail::get_instance();
         $menu    = array();
-        $ul_attr = $rcmail->config->get('skin') == 'classic' ? null : array('class' => 'toolbarmenu');
+        $ul_attr = array('role' => 'menu', 'aria-labelledby' => 'aria-label-zipdownloadmenu');
+        if ($rcmail->config->get('skin') != 'classic') {
+            $ul_attr['class'] = 'toolbarmenu';
+        }
 
         foreach (array('eml', 'mbox', 'maildir') as $type) {
             $menu[] = html::tag('li', null, $rcmail->output->button(array(
@@ -106,7 +109,8 @@
             )));
         }
 
-        $rcmail->output->add_footer(html::div(array('id' => 'zipdownload-menu', 'class' => 'popupmenu'),
+        $rcmail->output->add_footer(html::div(array('id' => 'zipdownload-menu', 'class' => 'popupmenu', 'aria-hidden' => 'true'),
+            html::tag('h2', array('class' => 'voice', 'id' => 'aria-label-zipdownloadmenu'), "Message Download Options Menu") .
             html::tag('ul', $ul_attr, implode('', $menu))));
     }
 
diff --git a/program/js/app.js b/program/js/app.js
index fecd5a0..11236d0 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -197,7 +197,7 @@
 
     // enable general commands
     this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref',
-      'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-save', true);
+      'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-close', 'menu-save', true);
 
     // set active task button
     this.set_button(this.task, 'sel');
@@ -711,7 +711,8 @@
         }
 
       case 'menu-save':
-        this.triggerEvent(command, {props:props, e:event});
+      case 'menu-close':
+        this.triggerEvent(command, {props:props, originalEvent:event});
         return false;
 
       case 'open':
@@ -2316,6 +2317,7 @@
       url._page = page;
 
     this.http_request('list', url, lock);
+    this.update_state({ _mbox: mbox, _page: (page && page > 1 ? page : null) });
   };
 
   // removes messages that doesn't exists from list selection array
@@ -3592,6 +3594,7 @@
       $('<a>').addClass('insertresponse active')
         .attr('href', '#')
         .attr('rel', key)
+        .attr('tabindex', '0')
         .html(this.quote_html(response.name))
         .appendTo(li)
         .mousedown(function(e){
@@ -6940,6 +6943,13 @@
     this.start_keepalive();
   };
 
+  // update browser location to remember current view
+  this.update_state = function(query)
+  {
+    if (window.history.replaceState)
+      window.history.replaceState({}, document.title, rcmail.url('', query));
+  };
+
   // send a http request to the server
   this.http_request = function(action, query, lock)
   {
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 2b717d6..b569385 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -977,7 +977,7 @@
         $OUTPUT->set_env('spellcheck_langs', join(',', $editor_lang_set));
     }
 
-    $out .= "\n".'<iframe name="savetarget" src="program/resources/blank.gif" style="width:0;height:0;border:none;visibility:hidden;"></iframe>';
+    $out .= "\n".'<iframe name="savetarget" src="program/resources/blank.gif" style="width:0;height:0;border:none;visibility:hidden;" aria-hidden="true"></iframe>';
 
     return $out;
 }
@@ -1864,9 +1864,10 @@
     foreach ($RCMAIL->get_compose_responses(true) as $response) {
         $key = $response['key'];
         $item = html::a(array(
-            'href '=> '#'.urlencode($response['name']),
+            'href' => '#'.urlencode($response['name']),
             'class' => rtrim('insertresponse ' . $attrib['itemclass']),
             'unselectable' => 'on',
+            'tabindex' => '0',
             'rel' => $key,
         ), rcube::Q($response['name']));
 
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index b9971ce..811e878 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -507,14 +507,19 @@
         $a_sort_cols = array('subject', 'date', 'from', 'to', 'fromto', 'size', 'cc');
 
     if (!empty($attrib['optionsmenuicon'])) {
-        $onclick = 'return ' . rcmail_output::JS_OBJECT_NAME . ".command('menu-open', 'messagelistmenu')";
-        if ($attrib['optionsmenuicon'] === true || $attrib['optionsmenuicon'] == 'true')
-            $list_menu = html::div(array('onclick' => $onclick, 'class' => 'listmenu',
-                'id' => 'listmenulink', 'title' => $RCMAIL->gettext('listoptions')));
-        else
-            $list_menu = html::a(array('href' => '#', 'onclick' => $onclick),
-                html::img(array('src' => $skin_path . $attrib['optionsmenuicon'],
-                    'id' => 'listmenulink', 'title' => $RCMAIL->gettext('listoptions'))));
+        $onclick = 'return ' . rcmail_output::JS_OBJECT_NAME . ".command('menu-open', 'messagelistmenu', null, event)";
+        $inner   = 'v';
+        if (is_string($attrib['optionsmenuicon']) && $attrib['optionsmenuicon'] != 'true') {
+            $inner = html::img(array('src' => $skin_path . $attrib['optionsmenuicon'], 'alt' => $RCMAIL->gettext('listoptions')));
+        }
+        $list_menu = html::a(array(
+            'href' => '#list-options',
+            'onclick' => $onclick,
+            'class' => 'listmenu',
+            'id' => 'listmenulink',
+            'title' => $RCMAIL->gettext('listoptions'),
+            'tabindex' => '0',
+        ), $inner);
     }
     else {
         $list_menu = '';
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index 9498d1d..7f9a23e 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -198,6 +198,7 @@
     if (sizeof($MESSAGE->attachments)) {
         foreach ($MESSAGE->attachments as $attach_prop) {
             $filename = rcmail_attachment_name($attach_prop, true);
+            $size = '';
 
             if ($PRINT_MODE) {
                 $size = $RCMAIL->message_part_size($attach_prop);
@@ -212,6 +213,10 @@
                     $title = '';
                 }
 
+                if ($attach_prop->size) {
+                    $size = ' ' . html::span('attachment-size', '(' . $RCMAIL->show_bytes($attach_prop->size) . ')');
+                }
+
                 $mimetype = rcmail_fix_mimetype($attach_prop->mimetype);
                 $class    = rcube_utils::file2class($mimetype, $filename);
                 $id       = 'attach' . $attach_prop->mime_id;
@@ -221,7 +226,7 @@
                         rcmail_output::JS_OBJECT_NAME, $attach_prop->mime_id),
                     'onmouseover' => $title ? '' : 'rcube_webmail.long_subject_title_ex(this, 0)',
                     'title'       => rcube::Q($title),
-                    ), rcube::Q($filename));
+                    ), rcube::Q($filename) . $size);
 
                 $ol .= html::tag('li', array('class' => $class, 'id' => $id), $link);
 
diff --git a/skins/classic/mail.css b/skins/classic/mail.css
index fc066e0..43a658b 100644
--- a/skins/classic/mail.css
+++ b/skins/classic/mail.css
@@ -814,7 +814,7 @@
 
 .messagelist tr td div.collapsed,
 .messagelist tr td div.expanded,
-.messagelist tr td.threads div.listmenu,
+.messagelist tr td.threads .listmenu,
 .messagelist tr td.attachment span.attachment,
 .messagelist tr td.attachment span.report,
 .messagelist tr td.priority span.priority,
@@ -954,10 +954,12 @@
   cursor: pointer;
 }
 
-.messagelist tr td.threads div.listmenu
+.messagelist tr td.threads .listmenu
 {
   background-position: 0 -238px;
   cursor: pointer;
+  overflow: hidden;
+  text-indent: -5000px;
 }
 
 .messagelist tbody tr td.subject
diff --git a/skins/larry/includes/mailtoolbar.html b/skins/larry/includes/mailtoolbar.html
index d73fa7d..ac93d47 100644
--- a/skins/larry/includes/mailtoolbar.html
+++ b/skins/larry/includes/mailtoolbar.html
@@ -18,7 +18,7 @@
 <roundcube:button name="markmenulink" id="markmessagemenulink" type="link" class="button markmessage" label="mark" title="markmessages" onclick="UI.toggle_popup('markmessagemenu',event);return false" aria-haspopup="true" aria-owns="markmessagemenu-menu" />
 <roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button more" label="more" title="moreactions" onclick="UI.toggle_popup('messagemenu',event);return false" aria-haspopup="true" aria-owns="messagemenu-menu" />
 
-<div id="forwardmenu" class="popupmenu">
+<div id="forwardmenu" class="popupmenu" aria-hidden="true">
 	<h3 id="aria-label-forwardmenu" class="voice">Forwarding options</h3>
 	<ul class="toolbarmenu" role="menu" aria-labelledby="aria-label-forwardmenu">
 		<li role="menuitem"><roundcube:button command="forward-inline" label="forwardinline" prop="sub" classAct="forwardlink active" class="forwardlink" /></li>
@@ -27,7 +27,7 @@
 	</ul>
 </div>
 
-<div id="replyallmenu" class="popupmenu">
+<div id="replyallmenu" class="popupmenu" aria-hidden="true">
 	<h3 id="aria-label-replyallmenu" class="voice">Reply-all options</h3>
 	<ul class="toolbarmenu" role="menu" aria-labelledby="aria-label-replyallmenu">
 		<li role="menuitem"><roundcube:button command="reply-all" label="replyall" prop="sub" class="replyalllink" classAct="replyalllink active" /></li>
@@ -36,7 +36,7 @@
 	</ul>
 </div>
 
-<div id="messagemenu" class="popupmenu">
+<div id="messagemenu" class="popupmenu" aria-hidden="true">
   <h3 id="aria-label-messagemenu" class="voice">More message toolbar actions</h3>
   <ul id="messagemenu-menu" class="toolbarmenu iconized" role="menu" aria-labelledby="aria-label-messagemenu">
 	<li role="menuitem"><roundcube:button command="print" label="printmessage" class="icon" classAct="icon active" innerclass="icon print" /></li>
@@ -50,7 +50,7 @@
   </ul>
 </div>
 
-<div id="markmessagemenu" class="popupmenu">
+<div id="markmessagemenu" class="popupmenu" aria-hidden="true">
   <h3 id="aria-label-markmessagemenu" class="voice">Mark selected messages as...</h3>
   <ul id="markmessagemenu-menu" class="toolbarmenu iconized" role="menu" aria-labelledby="aria-label-markmessagemenu">
 	<li role="menuitem"><roundcube:button command="mark" prop="read" label="markread" classAct="icon active" class="icon" innerclass="icon read" /></li>
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index c0bd3db..ff590c4 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -599,7 +599,7 @@
 
 .messagelist tr td div.collapsed,
 .messagelist tr td div.expanded,
-.messagelist tr td.threads div.listmenu,
+.messagelist tr td.threads .listmenu,
 .messagelist tr td.attachment span.attachment,
 .messagelist tr td.attachment span.report,
 .messagelist tr td.priority span.priority,
@@ -734,10 +734,20 @@
 	cursor: pointer;
 }
 
-.messagelist tr td.threads div.listmenu {
-	background-position: 0 -976px;
+.messagelist tr td.threads .listmenu {
+	background-position: 6px -972px;
 	cursor: pointer;
 	width: 26px;
+	height: 21px;
+	overflow: hidden;
+	text-indent: -5000px;
+	margin: -3px -5px -2px -6px;
+	padding: 3px  5px  2px  6px;
+}
+
+.messagelist tr td.threads .listmenu:focus {
+	background-color: rgba(73,180,210,0.7);
+	outline: none;
 }
 
 .messagelist thead tr td.subject,
diff --git a/skins/larry/styles.css b/skins/larry/styles.css
index 6c59b88..cbff87b 100644
--- a/skins/larry/styles.css
+++ b/skins/larry/styles.css
@@ -2678,6 +2678,7 @@
 	overflow: hidden;
 	text-overflow: ellipsis;
 	line-height: 20px;
+	outline: none;
 }
 
 .attachmentslist li a.drop {
@@ -2689,6 +2690,15 @@
 	right: 0;
 	top: 0;
 	padding: 0;
+	overflow: hidden;
+	text-indent: -5000px;
+	outline: none;
+}
+
+.attachmentslist li a:focus,
+.attachmentslist li a.drop:focus {
+	background-color: rgba(30,150,192, 0.5);
+	border-radius: 2px;
 }
 
 #compose-attachments ul li {
diff --git a/skins/larry/templates/compose.html b/skins/larry/templates/compose.html
index d033829..07d9357 100644
--- a/skins/larry/templates/compose.html
+++ b/skins/larry/templates/compose.html
@@ -13,9 +13,11 @@
 
 <div id="mainscreen">
 
+<h1 class="voice"><roundcube:object name="pagetitle" /></h1>
+
 <!-- toolbar -->
-<div id="messagetoolbar" class="fullwidth">
-<div id="mailtoolbar" class="toolbar">
+<h2 id="aria-label-toolbar" class="voice">Application toolbar</h2>
+<div id="messagetoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar">
 	<roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="cancel" condition="!env:extwin" />
 	<roundcube:button command="close" type="link" class="button close disabled" classAct="button close" classSel="button close pressed" label="cancel" condition="env:extwin" />
 	<span class="spacer"></span>
@@ -25,14 +27,13 @@
 	<roundcube:if condition="config:enable_spellcheck" />
 	<span class="dropbutton">
 		<roundcube:button command="spellcheck" type="link" class="button spellcheck disabled" classAct="button spellcheck" classSel="button spellcheck pressed" label="spellcheck" title="checkspelling" />
-		<span class="dropbuttontip" id="spellmenulink" onclick="UI.show_popup('spellmenu');return false"></span>
+		<a href="#languages" class="dropbuttontip" id="spellmenulink" onclick="UI.toggle_popup('spellmenu',event);return false" aria-haspopup="true"></a>
 	</span>
 	<roundcube:endif />
 	<roundcube:button name="addattachment" type="link" class="button attach" classAct="button attach" classSel="button attach pressed" label="attach" title="addattachment" onclick="UI.show_uploadform();return false" />
 	<roundcube:button command="insert-sig" type="link" class="button insertsig disabled" classAct="button insertsig" classSel="button insertsig pressed" label="signature" title="insertsignature" />
-	<a href="#responses" class="button responses" label="responses" title="<roundcube:label name='insertresponse' />" id="responsesmenulink" unselectable="on" onmousedown="return false" onclick="UI.show_popup('responsesmenu');return false"><roundcube:label name="responses" /></a>
+	<a href="#responses" class="button responses" label="responses" title="<roundcube:label name='insertresponse' />" id="responsesmenulink" unselectable="on" onmousedown="return false" onclick="UI.toggle_popup('responsesmenu',event);return false" aria-haspopup="true" aria-owns="textresponsesmenu"><roundcube:label name="responses" /></a>
 	<roundcube:container name="toolbar" id="compose-toolbar" />
-</div>
 </div>
 
 <div id="mainscreencontent">
@@ -40,10 +41,12 @@
 <div id="composeview-left">
 
 <!-- inline address book -->
-<div id="compose-contacts" class="uibox listbox">
-<h2 class="boxtitle"><roundcube:label name="contacts" /></h2>
-	<div id="composequicksearch">
+<div id="compose-contacts" class="uibox listbox" role="region" aria-labelledby="aria-label-composecontacts">
+<h2 id="aria-label-composecontacts" class="boxtitle"><roundcube:label name="contacts" /></h2>
+	<div id="composequicksearch" role="search" aria-labelledby="aria-label-composequicksearch">
+		<h3 id="aria-label-composequicksearch" class="voice">Contacts search form</h3>
 		<div class="searchbox">
+			<label for="contactsearchbox" class="voice">Contact search input</label>
 			<roundcube:object name="searchform" id="contactsearchbox" />
 			<a id="searchmenulink" class="iconbutton searchoptions"> </a>
 			<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
@@ -66,12 +69,14 @@
 
 </div>
 
-<div id="composeview-right">
+<div id="composeview-right" role="main">
 
 <roundcube:form name="form" method="post" id="compose-content" class="uibox">
 
 <!-- message headers -->
-<div id="composeheaders">
+<div id="composeheaders" role="region" aria-labelledby="aria-label-composeheaders">
+<h2 id="aria-label-composeheaders" class="voice">Message headers</h2>
+
 <a href="#options" id="composeoptionstoggle" class="moreheaderstoggle"><span class="iconlink" title="<roundcube:label name='options' />"></span></a>
 
 <table class="headers-table compose-headers">
@@ -80,7 +85,7 @@
 		<td class="title"><label for="_from"><roundcube:label name="from" /></label></td>
 		<td class="editfield">
 			<roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" />
-			<a href="#identities" onclick="return rcmail.command('identities')" class="iconlink edit"><roundcube:label name="editidents" /></a>
+			<a href="#identities" onclick="return rcmail.command('identities')" class="iconlink edit" tabindex="0"><roundcube:label name="editidents" /></a>
 		</td>
 	</tr><tr>
 		<td class="title top"><label for="_to"><roundcube:label name="to" /></label></td>
@@ -129,7 +134,8 @@
 </div>
 
 <!-- (collapsable) message options -->
-<div id="composeoptions">
+<div id="composeoptions" role="region" aria-labelledby="aria-label-composeoptions">
+	<h2 id="aria-label-composeoptions" class="voice">Composition options</h2>
 	<roundcube:if condition="!in_array('htmleditor', (array)config:dont_override)" />
 	<span class="composeoption">
 		<label><roundcube:label name="editortype" />
@@ -163,7 +169,8 @@
 	<div id="composebodycontainer">
 		<roundcube:object name="composeBody" id="composebody" form="form" cols="70" rows="20" tabindex="9" />
 	</div>
-	<div id="compose-attachments" class="rightcol">
+	<div id="compose-attachments" class="rightcol" role="region">
+		<h2 id="aria-label-composeoptions" class="voice"><roundcube:label name="attachments" /></h2>
 		<div style="text-align:center; margin-bottom:20px">
 			<roundcube:button name="addattachment" type="input" class="button" classSel="button pressed" label="addattachment" onclick="UI.show_uploadform();return false" />
 		</div>
@@ -187,7 +194,8 @@
 
 </div><!-- end mainscreen -->
 
-<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='addattachment' />">
+<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='addattachment' />" aria-hidden="true">
+	<h2 id="aria-label-uploaddialog" class="voice">Attachment upload form</h2>
 	<roundcube:object name="composeAttachmentForm" id="uploadform" buttons="no" />
 	<div class="formbuttons">
 		<roundcube:button command="send-attachment" type="input" class="button mainaction" label="upload" />
@@ -195,15 +203,15 @@
 	</div>
 </div>
 
-<div id="spellmenu" class="popupmenu"></div>
+<div id="spellmenu" class="popupmenu" aria-hidden="true"></div>
 
-<div id="responsesmenu" class="popupmenu">
-    <ul class="toolbarmenu" id="textresponsesmenu">
-		<li class="separator" id=""><label><roundcube:label name="insertresponse" /></label></li>
+<div id="responsesmenu" class="popupmenu" aria-hidden="true">
+	<ul class="toolbarmenu" id="textresponsesmenu" role="menu">
+		<li role="separator" class="separator" id=""><label><roundcube:label name="insertresponse" /></label></li>
 		<roundcube:object name="responseslist" id="responseslist" tagname="ul" itemclass="active" />
-		<li class="separator"><label><roundcube:label name="manageresponses" /></label></li>
-		<li><roundcube:button command="save-response" type="link" label="savenewresponse" classAct="active" unselectable="on" /></li>
-		<li><roundcube:button command="responses" type="link" label="editresponses" classAct="active" /></li>
+		<li role="separator" class="separator"><label><roundcube:label name="manageresponses" /></label></li>
+		<li role="menuitem"><roundcube:button command="save-response" type="link" label="savenewresponse" classAct="active" unselectable="on" /></li>
+		<li role="menuitem"><roundcube:button command="responses" type="link" label="editresponses" classAct="active" /></li>
 	</ul>
 </div>
 
diff --git a/skins/larry/templates/login.html b/skins/larry/templates/login.html
index b14d196..557b029 100644
--- a/skins/larry/templates/login.html
+++ b/skins/larry/templates/login.html
@@ -7,6 +7,8 @@
 </head>
 <body>
 
+<h1 class="voice"><roundcube:object name="productname" /> <roundcube:label name="login" /></h1>
+
 <div id="login-form">
 <div class="box-inner" role="main">
 <roundcube:object name="logo" src="/images/roundcube_logo.png" id="logo" />
@@ -25,7 +27,7 @@
 </div>
 
 <div id="bottomline" role="contentinfo">
-	<roundcube:var name="config:product_name"> <roundcube:object name="version" condition="config:display_version" />
+	<roundcube:object name="productname" /> <roundcube:object name="version" condition="config:display_version" />
 	<roundcube:if condition="config:support_url" />
 		&nbsp;&#9679;&nbsp; <a href="<roundcube:var name='config:support_url' />" target="_blank" class="support-link"><roundcube:label name="support" /></a>
 	<roundcube:endif />
diff --git a/skins/larry/templates/mail.html b/skins/larry/templates/mail.html
index 07020f6..b41e4b9 100644
--- a/skins/larry/templates/mail.html
+++ b/skins/larry/templates/mail.html
@@ -92,12 +92,13 @@
 
 <!-- messagelist -->
 <div id="messagelistcontainer" class="boxlistcontent">
-<h2 id="aria-label-messagelist" class="voice">Messages list</h2>
+<h2 id="aria-label-messagelist" class="voice">Email Messages Listing</h2>
 <roundcube:object name="messages"
 	id="messagelist"
 	class="records-table messagelist sortheader fixedheader"
 	optionsmenuIcon="true"
-    role="grid"
+	summary="Email Messages Listing"
+	role="grid"
 	aria-labelledby="aria-label-messagelist" />
 </div>
 
@@ -136,7 +137,7 @@
 
 <div id="mailpreviewframe" class="iframebox" role="complementary" aria-labelledby="aria-label-mailpreviewframe">
 <h2 id="aria-label-mailpreviewframe" class="voice">Message preview</h2>
-<roundcube:object name="messagecontentframe" id="messagecontframe" style="width:100%; height:100%" frameborder="0" src="/watermark.html" />
+<roundcube:object name="messagecontentframe" id="messagecontframe" style="width:100%; height:100%" frameborder="0" src="/watermark.html" title="Message preview" />
 </div>
 
 </div><!-- end mailview-bottom -->
@@ -147,14 +148,14 @@
 
 </div><!-- end mainscreen -->
 
-<div id="dragmessagemenu" class="popupmenu">
+<div id="dragmessagemenu" class="popupmenu" aria-hidden="true">
 	<ul class="toolbarmenu" role="menu">
 		<li role="menuitem"><roundcube:button command="move" onclick="return rcmail.drag_menu_action('move')" label="move" classAct="active" /></li>
 		<li role="menuitem"><roundcube:button command="copy" onclick="return rcmail.drag_menu_action('copy')" label="copy" classAct="active" /></li>
 	</ul>
 </div>
 
-<div id="mailboxmenu" class="popupmenu">
+<div id="mailboxmenu" class="popupmenu" aria-hidden="true">
 	<h3 id="aria-label-mailboxmenu" class="voice">Folder actions menu</h3>
 	<ul id="mailboxmenu-menu" class="toolbarmenu" id="mailboxoptionsmenu" role="menu" aria-labelledby="aria-label-mailboxmenu">
 		<li role="menuitem"><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li>
@@ -165,7 +166,7 @@
 	</ul>
 </div>
 
-<div id="listselectmenu" class="popupmenu dropdown">
+<div id="listselectmenu" class="popupmenu dropdown" aria-hidden="true">
 	<h3 id="aria-label-listselectmenu" class="voice">List selection menu</h3>
 	<ul id="listselectmenu-menu" class="toolbarmenu iconized" role="menu" aria-labelledby="aria-label-listselectmenu">
 		<li role="menuitem"><roundcube:button command="select-all" type="link" label="all" class="icon" classAct="icon active" innerclass="icon mail" /></li>
@@ -177,7 +178,7 @@
 	</ul>
 </div>
 
-<div id="threadselectmenu" class="popupmenu dropdown">
+<div id="threadselectmenu" class="popupmenu dropdown" aria-hidden="true">
 	<h3 id="aria-label-threadselectmenu" class="voice">Threads listing menu</h3>
 	<ul id="threadselectmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-threadselectmenu">
 		<li role="menuitem"><roundcube:button command="expand-all" type="link" label="expand-all" class="icon" classAct="icon active" innerclass="icon conversation" /></li>
@@ -186,7 +187,7 @@
 	</ul>
 </div>
 
-<div id="listoptions" class="propform popupdialog" role="dialog" aria-labelledby="aria-label-listoptions">
+<div id="listoptions" class="propform popupdialog" role="dialog" aria-labelledby="aria-label-listoptions" aria-hidden="true">
 <h2 id="aria-label-listoptions" class="voice">Message list display and sorting options</h2>
 <roundcube:if condition="!in_array('list_cols', (array)config:dont_override)" />
 	<fieldset class="floating">
@@ -236,11 +237,12 @@
 	<br style="clear:both" />
 	<div class="formbuttons">
 		<roundcube:button command="menu-save" id="listmenusave" type="input" class="button mainaction" label="save" />
-		<roundcube:button command="menu-open" id="listmenucancel" type="input" class="button" label="cancel" />
+		<roundcube:button command="menu-close" id="listmenucancel" type="input" class="button" label="cancel" />
 	</div>
 </div>
 
-<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='importmessages' />">
+<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='importmessages' />" aria-hidden="true">
+	<h2 id="aria-label-uploaddialog" class="voice">Message import dialog</h2>
 	<roundcube:object name="messageimportform" id="uploadform" buttons="no" />
 	<div class="formbuttons">
 		<roundcube:button command="import-messages" type="input" class="button mainaction" label="upload" />
diff --git a/skins/larry/templates/message.html b/skins/larry/templates/message.html
index 37ee71f..c7df4d8 100644
--- a/skins/larry/templates/message.html
+++ b/skins/larry/templates/message.html
@@ -10,8 +10,11 @@
 
 <div id="mainscreen">
 
+<h1 class="voice"><roundcube:object name="messageHeaders" valueOf="subject" /></h1>
+
 <!-- toolbar -->
-<div id="messagetoolbar" class="toolbar fullwidth">
+<h2 id="aria-label-toolbar" class="voice">Application toolbar</h2>
+<div id="messagetoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar">
 <roundcube:if condition="!env:extwin" />
 	<roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="back" />
 <roundcube:endif />
@@ -25,7 +28,8 @@
 <div id="mailview-left">
 
 <!-- folders list -->
-<div id="mailboxcontainer" class="uibox listbox">
+<div id="mailboxcontainer" class="uibox listbox" role="navigation" aria-labelledby="aria-label-folderlist">
+<h2 id="aria-label-folderlist" class="voice">Email folder selection</h2>
 <div class="scroller">
 <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist listing" folder_filter="mail" unreadwrap="%s" />
 </div>
@@ -33,18 +37,19 @@
 
 </div>
 
-<div id="mailview-right" class="uibox">
+<div id="mailview-right" class="uibox" role="main">
 <roundcube:else />
 <roundcube:object name="mailboxlist" folder_filter="mail" type="js" />
 
-<div id="mailview-right" class="offset fullwidth uibox">
+<div id="mailview-right" class="offset fullwidth uibox" role="main">
 <roundcube:endif />
 
 <div id="messageheader">
 <span class="moreheaderstoggle"></span>
 
 <!-- record navigation -->
-<div id="countcontrols" class="pagenav">
+<div id="countcontrols" class="pagenav" role="navigation" aria-labelledby="aria-label-countcontrols">
+	<h2 id="aria-label-countcontrols" class="voice">Message navigation</h2>
 	<roundcube:object name="messageCountDisplay" class="countdisplay" />
 	<roundcube:button command="previousmessage" type="link" class="button prevpage disabled" classAct="button prevpage" classSel="button prevpage pressed" innerClass="inner" title="previousmessage" content="&amp;lt;" />
 	<roundcube:button command="nextmessage" type="link" class="button nextpage disabled" classAct="button nextpage" classSel="button nextpage pressed" innerClass="inner" title="nextmessage" content="&amp;gt;" />
@@ -64,7 +69,7 @@
 </div>
 <roundcube:endif />
 
-<h2 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h2>
+<h2 class="subject"><span class="voice"><roundcube:label name="subject" />: </span><roundcube:object name="messageHeaders" valueOf="subject" /></h2>
 <div class="message-headers">
 <roundcube:object name="messageHeaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject" max="20" />
 </div>
@@ -73,11 +78,13 @@
 <div id="contactphoto"><roundcube:object name="contactphoto" /></div>
 </div>
 
-<div id="messagecontent" role="main">
-<div class="rightcol">
+<div id="messagecontent">
+<div class="rightcol" role="region" aria-labelledby="aria-label-messageattachments">
+<h2 id="aria-label-messageattachments" class="voice"><roundcube:label name="attachments" /></h2>
 <roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" />
 </div>
-<div class="leftcol">
+<div class="leftcol" role="region" aria-labelledby="aria-label-messagebody">
+<h2 id="aria-label-messagebody" class="voice">Message Body</h2>
 <roundcube:object name="messageObjects" id="message-objects" />
 <roundcube:object name="messageBody" id="messagebody" headertableclass="message-partheaders headers-table" />
 </div>
@@ -91,10 +98,10 @@
 
 </div><!-- end mainscreen -->
 
-<div id="attachmentmenu" class="popupmenu">
-	<ul class="toolbarmenu">
-		<li><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="icon" classAct="icon active" innerclass="icon extwin" /></li>
-		<li><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="icon" classAct="icon active" innerclass="icon download" /></li>
+<div id="attachmentmenu" class="popupmenu" aria-hidden="true">
+	<ul class="toolbarmenu" role="menu">
+		<li role="menuitem"><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="icon" classAct="icon active" innerclass="icon extwin" /></li>
+		<li role="menuitem"><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="icon" classAct="icon active" innerclass="icon download" /></li>
 		<roundcube:container name="attachmentmenu" id="attachmentmenu" />
 	</ul>
 </div>
diff --git a/skins/larry/templates/messageerror.html b/skins/larry/templates/messageerror.html
index d509ce8..ffe3a87 100644
--- a/skins/larry/templates/messageerror.html
+++ b/skins/larry/templates/messageerror.html
@@ -16,11 +16,12 @@
 
 <div id="mainscreen">
 
+<h1 class="voice"><roundcube:label name="messageopenerror" /></h1>
+
 <!-- toolbar -->
-<div id="messagetoolbar" class="fullwidth">
-	<div id="mailtoolbar" class="toolbar">
-		<roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="back" />
-	</div>
+<h2 id="aria-label-toolbar" class="voice">Application toolbar</h2>
+<div id="messagetoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar">
+	<roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="back" />
 </div>
 
 <div id="mainscreencontent">
@@ -28,7 +29,8 @@
 <div id="mailview-left">
 
 <!-- folders list -->
-<div id="mailboxcontainer" class="uibox listbox">
+<div id="mailboxcontainer" class="uibox listbox" role="navigation" aria-labelledby="aria-label-folderlist">
+<h2 id="aria-label-folderlist" class="voice">Email folder selection</h2>
 <div class="scroller">
 	<roundcube:object name="mailboxlist" id="mailboxlist" class="treelist listing" folder_filter="mail" unreadwrap="%s" />
 </div>
@@ -36,7 +38,7 @@
 
 </div>
 
-<div id="mailview-right" class="offset uibox">
+<div id="mailview-right" class="uibox">
 
 <div id="messagecontent" class="watermark"></div>
 
diff --git a/skins/larry/templates/messagepart.html b/skins/larry/templates/messagepart.html
index 3b878c9..c76d8a3 100644
--- a/skins/larry/templates/messagepart.html
+++ b/skins/larry/templates/messagepart.html
@@ -10,7 +10,10 @@
 
 <div id="mainscreen">
 
-<div id="messagetoolbar" class="toolbar fullwidth">
+<h1 class="voice"><roundcube:label name="attachment" />: <roundcube:var name="env:filename" /></h1>
+
+<h2 id="aria-label-toolbar" class="voice">Application toolbar</h2>
+<div id="messagetoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar">
 	<roundcube:button command="download" type="link" class="button download disabled" classAct="button download" classSel="button download pressed" label="download" />
 	<roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="print" />
 	<roundcube:container name="toolbar" id="messagetoolbar" />
@@ -18,16 +21,17 @@
 
 <div id="mainscreencontent">
 
-<div id="messagepartheader" class="uibox listbox">
-	<h2 class="boxtitle"><roundcube:label name="properties" /></h2>
+<div id="messagepartheader" class="uibox listbox" role="contentinfo" aria-labelledby="aria-label-contentinfo">
+	<h2 class="boxtitle" id="aria-label-contentinfo"><roundcube:label name="properties" /></h2>
 	<div class="scroller">
 		<roundcube:object name="messagePartControls" class="listing" />
 	</div>
 </div>
 
-<div id="messagepartcontainer" class="uibox">
+<div id="messagepartcontainer" class="uibox" role="main" aria-labelledby="aria-label-messagepart">
+	<h2 id="aria-label-messagepart" class="voice">Attachment preview</h2>
 	<div class="iframebox">
-	<roundcube:object name="messagePartFrame" id="messagepartframe" frameborder="0" />
+	<roundcube:object name="messagePartFrame" id="messagepartframe" frameborder="0" title="Attachment preview" />
 	</div>
 </div>
 
diff --git a/skins/larry/templates/messagepreview.html b/skins/larry/templates/messagepreview.html
index e2be099..0acc0d0 100644
--- a/skins/larry/templates/messagepreview.html
+++ b/skins/larry/templates/messagepreview.html
@@ -9,7 +9,8 @@
 <div id="messageheader" class="previewheader">
 
 <!-- record navigation -->
-<div id="countcontrols">
+<div id="countcontrols" role="toolbar" aria-labelledby="aria-label-messagetoolbar">
+<h2 id="aria-label-messagetoolbar" class="voice">Message actions</h2>
 <roundcube:if condition="env:optional_format=='text'" />
 	<span class="buttongroup">
 		<roundcube:button command="change-format" prop="html" type="link" class="button first changeformat html selected" innerClass="icon" title="changeformathtml" content="HTML" /><roundcube:button command="change-format" prop="text" type="link" class="button last changeformat text" classSel="button changeformat text pressed" innerClass="icon" title="changeformattext" content="Text" />
@@ -30,7 +31,7 @@
 	<roundcube:button command="extwin" type="link" class="button extwin" classSel="button extwin pressed" innerClass="icon" title="openinextwin" content="[]" />
 </div>
 
-<h3 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h3>
+<h3 class="subject"><span class="voice"><roundcube:label name="subject" />: </span><roundcube:object name="messageHeaders" valueOf="subject" /></h3>
 
 <a href="#details" id="previewheaderstoggle" class="moreheaderstoggle"><span class="iconlink" title="<roundcube:label name='togglemoreheaders' />"></span></a>
 <div id="contactphoto"><roundcube:object name="contactphoto" /></div>
@@ -54,10 +55,12 @@
 </div>
 
 <div id="messagepreview" role="main">
-<div class="rightcol">
+<div class="rightcol" role="region" aria-labelledby="aria-label-messageattachments">
+<h2 id="aria-label-messageattachments" class="voice"><roundcube:label name="attachments" /></h2>
 <roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" />
 </div>
-<div class="leftcol">
+<div class="leftcol" role="region" aria-labelledby="aria-label-messagebody">
+<h2 id="aria-label-messagebody" class="voice">Message Body</h2>
 <roundcube:object name="messageObjects" id="message-objects" />
 <roundcube:object name="messageBody" id="messagebody" headertableclass="message-partheaders headers-table" />
 </div>
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index 8476d91..922bb21 100644
--- a/skins/larry/ui.js
+++ b/skins/larry/ui.js
@@ -141,8 +141,9 @@
 
     /***  mail task  ***/
     if (rcmail.env.task == 'mail') {
-      rcmail.addEventListener('menu-open', menu_open)
-        .addEventListener('menu-save', menu_save)
+      rcmail.addEventListener('menu-open', menu_toggle)
+        .addEventListener('menu-close', menu_toggle)
+        .addEventListener('menu-save', save_listoptions)
         .addEventListener('responseafterlist', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list', true) })
         .addEventListener('responseaftersearch', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list', true) });
 
@@ -161,8 +162,12 @@
         // add menu link for each attachment
         $('#attachment-list > li').each(function() {
           $(this).append($('<a class="drop" tabindex="0" aria-haspopup="true">Show options</a>')
-              .click(function(e) { attachmentmenu(this, e); return false; })
-              .keypress(function(e){ if (rcube_event.get_keycode(e) == 13) attachmentmenu(this, e); return false; })
+              .bind('click keypress', function(e) {
+                  if (e.type != 'keypress' || rcube_event.get_keycode(e) == 13) {
+                      attachmentmenu(this, e);
+                      return false;
+                  }
+              })
           );
         });
 
@@ -819,17 +824,12 @@
 
   /**** popup callbacks ****/
 
-  function menu_open(p)
+  function menu_toggle(p)
   {
     if (p && p.props && p.props.menu == 'attachmentmenu')
-      show_popupmenu('attachmentmenu', true, rcube_event.is_keyboard(p.e));
+      show_popupmenu('attachmentmenu', undefined, rcube_event.is_keyboard(p.originalEvent));
     else
-      show_listoptions();
-  }
-
-  function menu_save(prop)
-  {
-    save_listoptions();
+      show_listoptions(p);
   }
 
   function searchmenu(show)
@@ -884,20 +884,24 @@
 
   function spellmenu(show)
   {
-    var link, li,
+    var k, link, li,
       lang = rcmail.spellcheck_lang(),
       menu = popups.spellmenu,
       ul = $('ul', menu);
 
     if (!ul.length) {
-      ul = $('<ul class="toolbarmenu selectable">');
+      ul = $('<ul class="toolbarmenu selectable" role="menu">');
 
-      for (i in rcmail.env.spell_langs) {
-        li = $('<li>');
-        link = $('<a href="#"></a>').text(rcmail.env.spell_langs[i])
-          .addClass('active').data('lang', i)
-          .click(function() {
-            rcmail.spellcheck_lang_set($(this).data('lang'));
+      for (k in rcmail.env.spell_langs) {
+        li = $('<li role="menuitem">');
+        link = $('<a href="#'+k+'" tabindex="0"></a>').text(rcmail.env.spell_langs[k])
+          .addClass('active').data('lang', k)
+          .bind('click keypress', function(e) {
+              if (e.type != 'keypress' || rcube_event.get_keycode(e) == 13) {
+                  rcmail.spellcheck_lang_set($(this).data('lang'));
+                  show_popupmenu('spellmenu', false, rcube_event.is_keyboard(e))
+                  return false;
+              }
           });
 
         link.appendTo(li);
@@ -911,9 +915,9 @@
     $('li', ul).each(function() {
       var el = $('a', this);
       if (el.data('lang') == lang)
-        el.addClass('selected');
+        el.addClass('selected').attr('aria-selected', 'true');
       else if (el.hasClass('selected'))
-        el.removeClass('selected');
+        el.removeClass('selected').removeAttr('aria-selected');
     });
   }
 
@@ -921,13 +925,13 @@
   /**
    *
    */
-  function show_listoptions()
+  function show_listoptions(p)
   {
     var $dialog = $('#listoptions');
 
     // close the dialog
     if ($dialog.is(':visible')) {
-      $dialog.dialog('close');
+      $dialog.dialog('close', p.originalEvent);
       return;
     }
 
@@ -946,8 +950,13 @@
       resizable: false,
       closeOnEscape: true,
       title: null,
-      close: function() {
+      open: function(e) {
+        setTimeout(function(){ $dialog.find('a, input:not(:disabled)').not('[aria-disabled=true]').first().focus(); }, 100);
+      },
+      close: function(e) {
         $dialog.dialog('destroy').hide();
+        if (e.originalEvent && rcube_event.is_keyboard(e.originalEvent))
+          $('#listmenulink').focus();
       },
       minWidth: 500,
       width: $dialog.width()+25
@@ -958,10 +967,13 @@
   /**
    *
    */
-  function save_listoptions()
+  function save_listoptions(p)
   {
     $('#listoptions').dialog('close');
 
+    if (rcube_event.is_keyboard(p.originalEvent))
+      $('#listmenulink').focus();
+
     var sort = $('input[name="sort_col"]:checked').val(),
       ord = $('input[name="sort_ord"]:checked').val(),
       cols = $('input[name="list_col[]"]:checked')

--
Gitblit v1.9.1