Home | History | Annotate | Download | only in glue
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "webkit/glue/webaccessibility.h"
      6 
      7 #include <set>
      8 
      9 #include "base/string_number_conversions.h"
     10 #include "base/string_util.h"
     11 #include "base/utf_string_conversions.h"
     12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityCache.h"
     13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityObject.h"
     14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityRole.h"
     15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAttribute.h"
     16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
     17 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocumentType.h"
     18 #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h"
     19 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFormControlElement.h"
     20 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
     21 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h"
     22 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNamedNodeMap.h"
     23 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h"
     24 #include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h"
     25 #include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h"
     26 #include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
     27 
     28 using WebKit::WebAccessibilityCache;
     29 using WebKit::WebAccessibilityRole;
     30 using WebKit::WebAccessibilityObject;
     31 
     32 namespace webkit_glue {
     33 
     34 // Provides a conversion between the WebKit::WebAccessibilityRole and a role
     35 // supported on the Browser side. Listed alphabetically by the
     36 // WebAccessibilityRole (except for default role).
     37 WebAccessibility::Role ConvertRole(WebKit::WebAccessibilityRole role) {
     38   switch (role) {
     39     case WebKit::WebAccessibilityRoleAnnotation:
     40       return WebAccessibility::ROLE_ANNOTATION;
     41     case WebKit::WebAccessibilityRoleApplication:
     42       return WebAccessibility::ROLE_APPLICATION;
     43     case WebKit::WebAccessibilityRoleApplicationAlert:
     44       return WebAccessibility::ROLE_ALERT;
     45     case WebKit::WebAccessibilityRoleApplicationAlertDialog:
     46       return WebAccessibility::ROLE_ALERT_DIALOG;
     47     case WebKit::WebAccessibilityRoleApplicationDialog:
     48       return WebAccessibility::ROLE_DIALOG;
     49     case WebKit::WebAccessibilityRoleApplicationLog:
     50       return WebAccessibility::ROLE_LOG;
     51     case WebKit::WebAccessibilityRoleApplicationMarquee:
     52       return WebAccessibility::ROLE_MARQUEE;
     53     case WebKit::WebAccessibilityRoleApplicationStatus:
     54       return WebAccessibility::ROLE_STATUS;
     55     case WebKit::WebAccessibilityRoleApplicationTimer:
     56       return WebAccessibility::ROLE_TIMER;
     57     case WebKit::WebAccessibilityRoleBrowser:
     58       return WebAccessibility::ROLE_BROWSER;
     59     case WebKit::WebAccessibilityRoleBusyIndicator:
     60       return WebAccessibility::ROLE_BUSY_INDICATOR;
     61     case WebKit::WebAccessibilityRoleButton:
     62       return WebAccessibility::ROLE_BUTTON;
     63     case WebKit::WebAccessibilityRoleCell:
     64       return WebAccessibility::ROLE_CELL;
     65     case WebKit::WebAccessibilityRoleCheckBox:
     66       return WebAccessibility::ROLE_CHECKBOX;
     67     case WebKit::WebAccessibilityRoleColorWell:
     68       return WebAccessibility::ROLE_COLOR_WELL;
     69     case WebKit::WebAccessibilityRoleColumn:
     70       return WebAccessibility::ROLE_COLUMN;
     71     case WebKit::WebAccessibilityRoleColumnHeader:
     72       return WebAccessibility::ROLE_COLUMN_HEADER;
     73     case WebKit::WebAccessibilityRoleComboBox:
     74       return WebAccessibility::ROLE_COMBO_BOX;
     75     case WebKit::WebAccessibilityRoleDefinitionListDefinition:
     76       return WebAccessibility::ROLE_DEFINITION_LIST_DEFINITION;
     77     case WebKit::WebAccessibilityRoleDefinitionListTerm:
     78       return WebAccessibility::ROLE_DEFINITION_LIST_TERM;
     79     case WebKit::WebAccessibilityRoleDirectory:
     80       return WebAccessibility::ROLE_DIRECTORY;
     81     case WebKit::WebAccessibilityRoleDisclosureTriangle:
     82       return WebAccessibility::ROLE_DISCLOSURE_TRIANGLE;
     83     case WebKit::WebAccessibilityRoleDocument:
     84       return WebAccessibility::ROLE_DOCUMENT;
     85     case WebKit::WebAccessibilityRoleDocumentArticle:
     86       return WebAccessibility::ROLE_ARTICLE;
     87     case WebKit::WebAccessibilityRoleDocumentMath:
     88       return WebAccessibility::ROLE_MATH;
     89     case WebKit::WebAccessibilityRoleDocumentNote:
     90       return WebAccessibility::ROLE_NOTE;
     91     case WebKit::WebAccessibilityRoleDocumentRegion:
     92       return WebAccessibility::ROLE_REGION;
     93     case WebKit::WebAccessibilityRoleDrawer:
     94       return WebAccessibility::ROLE_DRAWER;
     95     case WebKit::WebAccessibilityRoleEditableText:
     96       return WebAccessibility::ROLE_EDITABLE_TEXT;
     97     case WebKit::WebAccessibilityRoleGrid:
     98       return WebAccessibility::ROLE_GRID;
     99     case WebKit::WebAccessibilityRoleGroup:
    100       return WebAccessibility::ROLE_GROUP;
    101     case WebKit::WebAccessibilityRoleGrowArea:
    102       return WebAccessibility::ROLE_GROW_AREA;
    103     case WebKit::WebAccessibilityRoleHeading:
    104       return WebAccessibility::ROLE_HEADING;
    105     case WebKit::WebAccessibilityRoleHelpTag:
    106       return WebAccessibility::ROLE_HELP_TAG;
    107     case WebKit::WebAccessibilityRoleIgnored:
    108       return WebAccessibility::ROLE_IGNORED;
    109     case WebKit::WebAccessibilityRoleImage:
    110       return WebAccessibility::ROLE_IMAGE;
    111     case WebKit::WebAccessibilityRoleImageMap:
    112       return WebAccessibility::ROLE_IMAGE_MAP;
    113     case WebKit::WebAccessibilityRoleImageMapLink:
    114       return WebAccessibility::ROLE_IMAGE_MAP_LINK;
    115     case WebKit::WebAccessibilityRoleIncrementor:
    116       return WebAccessibility::ROLE_INCREMENTOR;
    117     case WebKit::WebAccessibilityRoleLandmarkApplication:
    118       return WebAccessibility::ROLE_LANDMARK_APPLICATION;
    119     case WebKit::WebAccessibilityRoleLandmarkBanner:
    120       return WebAccessibility::ROLE_LANDMARK_BANNER;
    121     case WebKit::WebAccessibilityRoleLandmarkComplementary:
    122       return WebAccessibility::ROLE_LANDMARK_COMPLEMENTARY;
    123     case WebKit::WebAccessibilityRoleLandmarkContentInfo:
    124       return WebAccessibility::ROLE_LANDMARK_CONTENTINFO;
    125     case WebKit::WebAccessibilityRoleLandmarkMain:
    126       return WebAccessibility::ROLE_LANDMARK_MAIN;
    127     case WebKit::WebAccessibilityRoleLandmarkNavigation:
    128       return WebAccessibility::ROLE_LANDMARK_NAVIGATION;
    129     case WebKit::WebAccessibilityRoleLandmarkSearch:
    130       return WebAccessibility::ROLE_LANDMARK_SEARCH;
    131     case WebKit::WebAccessibilityRoleLink:
    132       return WebAccessibility::ROLE_LINK;
    133     case WebKit::WebAccessibilityRoleList:
    134       return WebAccessibility::ROLE_LIST;
    135     case WebKit::WebAccessibilityRoleListBox:
    136       return WebAccessibility::ROLE_LISTBOX;
    137     case WebKit::WebAccessibilityRoleListBoxOption:
    138       return WebAccessibility::ROLE_LISTBOX_OPTION;
    139     case WebKit::WebAccessibilityRoleListItem:
    140       return WebAccessibility::ROLE_LIST_ITEM;
    141     case WebKit::WebAccessibilityRoleListMarker:
    142       return WebAccessibility::ROLE_LIST_MARKER;
    143     case WebKit::WebAccessibilityRoleMatte:
    144       return WebAccessibility::ROLE_MATTE;
    145     case WebKit::WebAccessibilityRoleMenu:
    146       return WebAccessibility::ROLE_MENU;
    147     case WebKit::WebAccessibilityRoleMenuBar:
    148       return WebAccessibility::ROLE_MENU_BAR;
    149     case WebKit::WebAccessibilityRoleMenuButton:
    150       return WebAccessibility::ROLE_MENU_BUTTON;
    151     case WebKit::WebAccessibilityRoleMenuItem:
    152       return WebAccessibility::ROLE_MENU_ITEM;
    153     case WebKit::WebAccessibilityRoleMenuListOption:
    154       return WebAccessibility::ROLE_MENU_LIST_OPTION;
    155     case WebKit::WebAccessibilityRoleMenuListPopup:
    156       return WebAccessibility::ROLE_MENU_LIST_POPUP;
    157     case WebKit::WebAccessibilityRoleOutline:
    158       return WebAccessibility::ROLE_OUTLINE;
    159     case WebKit::WebAccessibilityRolePopUpButton:
    160       return WebAccessibility::ROLE_POPUP_BUTTON;
    161     case WebKit::WebAccessibilityRoleProgressIndicator:
    162       return WebAccessibility::ROLE_PROGRESS_INDICATOR;
    163     case WebKit::WebAccessibilityRoleRadioButton:
    164       return WebAccessibility::ROLE_RADIO_BUTTON;
    165     case WebKit::WebAccessibilityRoleRadioGroup:
    166       return WebAccessibility::ROLE_RADIO_GROUP;
    167     case WebKit::WebAccessibilityRoleRow:
    168       return WebAccessibility::ROLE_ROW;
    169     case WebKit::WebAccessibilityRoleRowHeader:
    170       return WebAccessibility::ROLE_ROW_HEADER;
    171     case WebKit::WebAccessibilityRoleRuler:
    172       return WebAccessibility::ROLE_RULER;
    173     case WebKit::WebAccessibilityRoleRulerMarker:
    174       return WebAccessibility::ROLE_RULER_MARKER;
    175     case WebKit::WebAccessibilityRoleScrollArea:
    176       return WebAccessibility::ROLE_SCROLLAREA;
    177     case WebKit::WebAccessibilityRoleScrollBar:
    178       return WebAccessibility::ROLE_SCROLLBAR;
    179     case WebKit::WebAccessibilityRoleSheet:
    180       return WebAccessibility::ROLE_SHEET;
    181     case WebKit::WebAccessibilityRoleSlider:
    182       return WebAccessibility::ROLE_SLIDER;
    183     case WebKit::WebAccessibilityRoleSliderThumb:
    184       return WebAccessibility::ROLE_SLIDER_THUMB;
    185     case WebKit::WebAccessibilityRoleSplitGroup:
    186       return WebAccessibility::ROLE_SPLIT_GROUP;
    187     case WebKit::WebAccessibilityRoleSplitter:
    188       return WebAccessibility::ROLE_SPLITTER;
    189     case WebKit::WebAccessibilityRoleStaticText:
    190       return WebAccessibility::ROLE_STATIC_TEXT;
    191     case WebKit::WebAccessibilityRoleSystemWide:
    192       return WebAccessibility::ROLE_SYSTEM_WIDE;
    193     case WebKit::WebAccessibilityRoleTab:
    194       return WebAccessibility::ROLE_TAB;
    195     case WebKit::WebAccessibilityRoleTabGroup:
    196       return WebAccessibility::ROLE_TAB_GROUP;
    197     case WebKit::WebAccessibilityRoleTabList:
    198       return WebAccessibility::ROLE_TAB_LIST;
    199     case WebKit::WebAccessibilityRoleTabPanel:
    200       return WebAccessibility::ROLE_TAB_PANEL;
    201     case WebKit::WebAccessibilityRoleTable:
    202       return WebAccessibility::ROLE_TABLE;
    203     case WebKit::WebAccessibilityRoleTableHeaderContainer:
    204       return WebAccessibility::ROLE_TABLE_HEADER_CONTAINER;
    205     case WebKit::WebAccessibilityRoleTextArea:
    206       return WebAccessibility::ROLE_TEXTAREA;
    207     case WebKit::WebAccessibilityRoleTextField:
    208       return WebAccessibility::ROLE_TEXT_FIELD;
    209     case WebKit::WebAccessibilityRoleToolbar:
    210       return WebAccessibility::ROLE_TOOLBAR;
    211     case WebKit::WebAccessibilityRoleTreeGrid:
    212       return WebAccessibility::ROLE_TREE_GRID;
    213     case WebKit::WebAccessibilityRoleTreeItemRole:
    214       return WebAccessibility::ROLE_TREE_ITEM;
    215     case WebKit::WebAccessibilityRoleTreeRole:
    216       return WebAccessibility::ROLE_TREE;
    217     case WebKit::WebAccessibilityRoleUserInterfaceTooltip:
    218       return WebAccessibility::ROLE_TOOLTIP;
    219     case WebKit::WebAccessibilityRoleValueIndicator:
    220       return WebAccessibility::ROLE_VALUE_INDICATOR;
    221     case WebKit::WebAccessibilityRoleWebArea:
    222       return WebAccessibility::ROLE_WEB_AREA;
    223     case WebKit::WebAccessibilityRoleWebCoreLink:
    224       return WebAccessibility::ROLE_WEBCORE_LINK;
    225     case WebKit::WebAccessibilityRoleWindow:
    226       return WebAccessibility::ROLE_WINDOW;
    227 
    228     default:
    229       return WebAccessibility::ROLE_UNKNOWN;
    230   }
    231 }
    232 
    233 uint32 ConvertState(const WebAccessibilityObject& o) {
    234   uint32 state = 0;
    235   if (o.isChecked())
    236     state |= (1 << WebAccessibility::STATE_CHECKED);
    237 
    238   if (o.isCollapsed())
    239     state |= (1 << WebAccessibility::STATE_COLLAPSED);
    240 
    241   if (o.canSetFocusAttribute())
    242     state |= (1 << WebAccessibility::STATE_FOCUSABLE);
    243 
    244   if (o.isFocused())
    245     state |= (1 << WebAccessibility::STATE_FOCUSED);
    246 
    247   if (o.roleValue() == WebKit::WebAccessibilityRolePopUpButton) {
    248     state |= (1 << WebAccessibility::STATE_HASPOPUP);
    249 
    250     if (!o.isCollapsed())
    251       state |= (1 << WebAccessibility::STATE_EXPANDED);
    252   }
    253 
    254   if (o.isHovered())
    255     state |= (1 << WebAccessibility::STATE_HOTTRACKED);
    256 
    257   if (o.isIndeterminate())
    258     state |= (1 << WebAccessibility::STATE_INDETERMINATE);
    259 
    260   if (!o.isVisible())
    261     state |= (1 << WebAccessibility::STATE_INVISIBLE);
    262 
    263   if (o.isLinked())
    264     state |= (1 << WebAccessibility::STATE_LINKED);
    265 
    266   if (o.isMultiSelectable())
    267     state |= (1 << WebAccessibility::STATE_MULTISELECTABLE);
    268 
    269   if (o.isOffScreen())
    270     state |= (1 << WebAccessibility::STATE_OFFSCREEN);
    271 
    272   if (o.isPressed())
    273     state |= (1 << WebAccessibility::STATE_PRESSED);
    274 
    275   if (o.isPasswordField())
    276     state |= (1 << WebAccessibility::STATE_PROTECTED);
    277 
    278   if (o.isReadOnly())
    279     state |= (1 << WebAccessibility::STATE_READONLY);
    280 
    281   if (o.canSetSelectedAttribute())
    282     state |= (1 << WebAccessibility::STATE_SELECTABLE);
    283 
    284   if (o.isSelected())
    285     state |= (1 << WebAccessibility::STATE_SELECTED);
    286 
    287   if (o.isVisited())
    288     state |= (1 << WebAccessibility::STATE_TRAVERSED);
    289 
    290   if (!o.isEnabled())
    291     state |= (1 << WebAccessibility::STATE_UNAVAILABLE);
    292 
    293   return state;
    294 }
    295 
    296 WebAccessibility::WebAccessibility()
    297     : id(-1),
    298       role(ROLE_NONE),
    299       state(-1) {
    300 }
    301 
    302 WebAccessibility::WebAccessibility(const WebKit::WebAccessibilityObject& src,
    303                                    WebKit::WebAccessibilityCache* cache,
    304                                    bool include_children) {
    305   Init(src, cache, include_children);
    306 }
    307 
    308 WebAccessibility::~WebAccessibility() {
    309 }
    310 
    311 void WebAccessibility::Init(const WebKit::WebAccessibilityObject& src,
    312                             WebKit::WebAccessibilityCache* cache,
    313                             bool include_children) {
    314   name = src.title();
    315   value = src.stringValue();
    316   role = ConvertRole(src.roleValue());
    317   state = ConvertState(src);
    318   location = src.boundingBoxRect();
    319 
    320   if (src.actionVerb().length())
    321     attributes[ATTR_ACTION] = src.actionVerb();
    322   if (src.accessibilityDescription().length())
    323     attributes[ATTR_DESCRIPTION] = src.accessibilityDescription();
    324   if (src.helpText().length())
    325     attributes[ATTR_HELP] = src.helpText();
    326   if (src.keyboardShortcut().length())
    327     attributes[ATTR_SHORTCUT] = src.keyboardShortcut();
    328   if (src.hasComputedStyle())
    329     attributes[ATTR_DISPLAY] = src.computedStyleDisplay();
    330   if (!src.url().isEmpty())
    331     attributes[ATTR_URL] = src.url().spec().utf16();
    332 
    333   WebKit::WebNode node = src.node();
    334   bool is_iframe = false;
    335 
    336   if (!node.isNull() && node.isElementNode()) {
    337     WebKit::WebElement element = node.to<WebKit::WebElement>();
    338     is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME"));
    339 
    340     // TODO(ctguil): The tagName in WebKit is lower cased but
    341     // HTMLElement::nodeName calls localNameUpper. Consider adding
    342     // a WebElement method that returns the original lower cased tagName.
    343     attributes[ATTR_HTML_TAG] = StringToLowerASCII(string16(element.tagName()));
    344     for (unsigned i = 0; i < element.attributes().length(); i++) {
    345       html_attributes.push_back(
    346           std::pair<string16, string16>(
    347               element.attributes().attributeItem(i).localName(),
    348               element.attributes().attributeItem(i).value()));
    349     }
    350 
    351     if (element.isFormControlElement()) {
    352       WebKit::WebFormControlElement form_element =
    353           element.to<WebKit::WebFormControlElement>();
    354       if (form_element.formControlType() == ASCIIToUTF16("text")) {
    355         WebKit::WebInputElement input_element =
    356             form_element.to<WebKit::WebInputElement>();
    357         attributes[ATTR_TEXT_SEL_START] = base::IntToString16(
    358             input_element.selectionStart());
    359         attributes[ATTR_TEXT_SEL_END] = base::IntToString16(
    360             input_element.selectionEnd());
    361       }
    362     }
    363   }
    364 
    365   if (role == WebAccessibility::ROLE_DOCUMENT ||
    366       role == WebAccessibility::ROLE_WEB_AREA) {
    367     const WebKit::WebDocument& document = src.document();
    368     if (name.empty())
    369       name = document.title();
    370     attributes[ATTR_DOC_TITLE] = document.title();
    371     attributes[ATTR_DOC_URL] = document.frame()->url().spec().utf16();
    372     if (document.isXHTMLDocument())
    373       attributes[ATTR_DOC_MIMETYPE] = WebKit::WebString("text/xhtml");
    374     else
    375       attributes[ATTR_DOC_MIMETYPE] = WebKit::WebString("text/html");
    376 
    377     const WebKit::WebDocumentType& doctype = document.doctype();
    378     if (!doctype.isNull())
    379       attributes[ATTR_DOC_DOCTYPE] = doctype.name();
    380 
    381     const gfx::Size& scroll_offset = document.frame()->scrollOffset();
    382     attributes[ATTR_DOC_SCROLLX] = base::IntToString16(scroll_offset.width());
    383     attributes[ATTR_DOC_SCROLLY] = base::IntToString16(scroll_offset.height());
    384   }
    385 
    386   // Add the source object to the cache and store its id.
    387   id = cache->addOrGetId(src);
    388 
    389   if (include_children) {
    390     // Recursively create children.
    391     int child_count = src.childCount();
    392     std::set<int32> child_ids;
    393     for (int i = 0; i < child_count; i++) {
    394       WebAccessibilityObject child = src.childAt(i);
    395       int32 child_id = cache->addOrGetId(child);
    396 
    397       // The child may be invalid due to issues in webkit accessibility code.
    398       // Don't add children that are invalid thus preventing a crash.
    399       // https://bugs.webkit.org/show_bug.cgi?id=44149
    400       // TODO(ctguil): We may want to remove this check as webkit stabilizes.
    401       if (!child.isValid())
    402         continue;
    403 
    404       // Children may duplicated in the webkit accessibility tree. Only add a
    405       // child once for the web accessibility tree.
    406       // https://bugs.webkit.org/show_bug.cgi?id=58930
    407       if (child_ids.find(child_id) != child_ids.end())
    408         continue;
    409       child_ids.insert(child_id);
    410 
    411       // Some nodes appear in the tree in more than one place: for example,
    412       // a cell in a table appears as a child of both a row and a column.
    413       // Only recursively add child nodes that have this node as its
    414       // unignored parent. For child nodes that are actually parented to
    415       // somethinng else, store only the ID.
    416       //
    417       // As an exception, also add children of an iframe element.
    418       // https://bugs.webkit.org/show_bug.cgi?id=57066
    419       if (is_iframe || IsParentUnignoredOf(src, child)) {
    420         children.push_back(WebAccessibility(child, cache, include_children));
    421       } else {
    422         indirect_child_ids.push_back(child_id);
    423       }
    424     }
    425   }
    426 }
    427 
    428 bool WebAccessibility::IsParentUnignoredOf(
    429     const WebKit::WebAccessibilityObject& ancestor,
    430     const WebKit::WebAccessibilityObject& child) {
    431   WebKit::WebAccessibilityObject parent = child.parentObject();
    432   while (!parent.isNull() && parent.accessibilityIsIgnored())
    433     parent = parent.parentObject();
    434   return parent.equals(ancestor);
    435 }
    436 
    437 }  // namespace webkit_glue
    438