diff options
author | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-23 21:07:33 +0000 |
---|---|---|
committer | dmazzoni@chromium.org <dmazzoni@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-23 21:07:33 +0000 |
commit | fc1926184af3507865502a92b5102e12680b7c21 (patch) | |
tree | 129b46aec9b7028f900a7e8e4d3634708a597259 /content/browser | |
parent | 6131fb5ed032194875383f386b682cdc38338809 (diff) | |
download | chromium_src-fc1926184af3507865502a92b5102e12680b7c21.zip chromium_src-fc1926184af3507865502a92b5102e12680b7c21.tar.gz chromium_src-fc1926184af3507865502a92b5102e12680b7c21.tar.bz2 |
Move browser accessibility code from chrome to content.
BUG=85932
TEST=none
Review URL: http://codereview.chromium.org/7233022
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@90265 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser')
19 files changed, 4459 insertions, 1 deletions
diff --git a/content/browser/accessibility/OWNERS b/content/browser/accessibility/OWNERS new file mode 100644 index 0000000..ab8afa4 --- /dev/null +++ b/content/browser/accessibility/OWNERS @@ -0,0 +1,3 @@ +ctguil@chromium.org +dmazzoni@chromium.org +dtseng@chromium.org diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc new file mode 100644 index 0000000..784bae0 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility.cc @@ -0,0 +1,204 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/accessibility/browser_accessibility.h" + +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +// There's no OS-specific implementation of BrowserAccessibilityManager +// on Unix, so just instantiate the base class. +// static +BrowserAccessibility* BrowserAccessibility::Create() { + return new BrowserAccessibility(); +} +#endif + +BrowserAccessibility::BrowserAccessibility() + : manager_(NULL), + parent_(NULL), + child_id_(0), + index_in_parent_(0), + renderer_id_(0), + ref_count_(1), + role_(0), + state_(0), + instance_active_(false) { +} + +BrowserAccessibility::~BrowserAccessibility() { +} + +void BrowserAccessibility::DetachTree( + std::vector<BrowserAccessibility*>* nodes) { + nodes->push_back(this); + for (size_t i = 0; i < children_.size(); i++) + children_[i]->DetachTree(nodes); + children_.clear(); + parent_ = NULL; +} + +void BrowserAccessibility::Initialize( + BrowserAccessibilityManager* manager, + BrowserAccessibility* parent, + int32 child_id, + int32 index_in_parent, + const webkit_glue::WebAccessibility& src) { + manager_ = manager; + parent_ = parent; + child_id_ = child_id; + index_in_parent_ = index_in_parent; + + renderer_id_ = src.id; + name_ = src.name; + value_ = src.value; + attributes_ = src.attributes; + html_attributes_ = src.html_attributes; + location_ = src.location; + role_ = src.role; + state_ = src.state; + indirect_child_ids_ = src.indirect_child_ids; + line_breaks_ = src.line_breaks; + + Initialize(); +} + +void BrowserAccessibility::Initialize() { + instance_active_ = true; +} + +void BrowserAccessibility::AddChild(BrowserAccessibility* child) { + children_.push_back(child); +} + +void BrowserAccessibility::UpdateParent(BrowserAccessibility* parent, + int index_in_parent) { + parent_ = parent; + index_in_parent_ = index_in_parent; +} + +bool BrowserAccessibility::IsDescendantOf( + BrowserAccessibility* ancestor) { + if (this == ancestor) { + return true; + } else if (parent_) { + return parent_->IsDescendantOf(ancestor); + } + + return false; +} + +BrowserAccessibility* BrowserAccessibility::GetChild(uint32 child_index) { + DCHECK(child_index < children_.size()); + return children_[child_index]; +} + +BrowserAccessibility* BrowserAccessibility::GetPreviousSibling() { + if (parent_ && index_in_parent_ > 0) + return parent_->children_[index_in_parent_ - 1]; + + return NULL; +} + +BrowserAccessibility* BrowserAccessibility::GetNextSibling() { + if (parent_ && + index_in_parent_ >= 0 && + index_in_parent_ < static_cast<int>(parent_->children_.size() - 1)) { + return parent_->children_[index_in_parent_ + 1]; + } + + return NULL; +} + +gfx::Rect BrowserAccessibility::GetBoundsRect() { + gfx::Rect bounds = location_; + + // Adjust the bounds by the top left corner of the containing view's bounds + // in screen coordinates. + gfx::Point top_left = manager_->GetViewBounds().origin(); + bounds.Offset(top_left); + + // Adjust top left position by the root document's scroll offset. + BrowserAccessibility* root = manager_->GetRoot(); + int scroll_x = 0; + int scroll_y = 0; + root->GetAttributeAsInt( + WebAccessibility::ATTR_DOC_SCROLLX, &scroll_x); + root->GetAttributeAsInt( + WebAccessibility::ATTR_DOC_SCROLLY, &scroll_y); + bounds.Offset(-scroll_x, -scroll_y); + + return bounds; +} + +BrowserAccessibility* BrowserAccessibility::BrowserAccessibilityForPoint( + const gfx::Point& point) { + // Walk the children recursively looking for the BrowserAccessibility that + // most tightly encloses the specified point. + for (int i = children_.size() - 1; i >= 0; --i) { + BrowserAccessibility* child = children_[i]; + if (child->GetBoundsRect().Contains(point)) + return child->BrowserAccessibilityForPoint(point); + } + return this; +} + +void BrowserAccessibility::InternalAddReference() { + ref_count_++; +} + +void BrowserAccessibility::InternalReleaseReference(bool recursive) { + DCHECK_GT(ref_count_, 0); + + if (recursive || ref_count_ == 1) { + for (std::vector<BrowserAccessibility*>::iterator iter = children_.begin(); + iter != children_.end(); + ++iter) { + (*iter)->InternalReleaseReference(true); + } + } + + ref_count_--; + if (ref_count_ == 0) { + instance_active_ = false; + children_.clear(); + manager_->Remove(child_id_, renderer_id_); + NativeReleaseReference(); + } +} + +void BrowserAccessibility::NativeReleaseReference() { + delete this; +} + +bool BrowserAccessibility::HasAttribute( + WebAccessibility::Attribute attribute) { + return (attributes_.find(attribute) != attributes_.end()); +} + +bool BrowserAccessibility::GetAttribute( + WebAccessibility::Attribute attribute, string16* value) { + std::map<int32, string16>::iterator iter = attributes_.find(attribute); + if (iter != attributes_.end()) { + *value = iter->second; + return true; + } + + return false; +} + +bool BrowserAccessibility::GetAttributeAsInt( + WebAccessibility::Attribute attribute, int* value_int) { + string16 value_str; + + if (!GetAttribute(attribute, &value_str)) + return false; + + if (!base::StringToInt(value_str, value_int)) + return false; + + return true; +} diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h new file mode 100644 index 0000000..dbd3e13 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility.h @@ -0,0 +1,227 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_ +#pragma once + +#include <map> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "build/build_config.h" +#include "webkit/glue/webaccessibility.h" + +class BrowserAccessibilityManager; +#if defined(OS_MACOSX) && __OBJC__ +@class BrowserAccessibilityCocoa; +#elif defined(OS_WIN) +class BrowserAccessibilityWin; +#endif + +using webkit_glue::WebAccessibility; + +//////////////////////////////////////////////////////////////////////////////// +// +// BrowserAccessibility +// +// Class implementing the cross platform interface for the Browser-Renderer +// communication of accessibility information, providing accessibility +// to be used by screen readers and other assistive technology (AT). +// +// An implementation for each platform handles platform specific accessibility +// APIs. +// +//////////////////////////////////////////////////////////////////////////////// +class BrowserAccessibility { + public: + // Creates a platform specific BrowserAccessibility. Ownership passes to the + // caller. + static BrowserAccessibility* Create(); + + virtual ~BrowserAccessibility(); + + // Detach all descendants of this subtree and push all of the node pointers, + // including this node, onto the end of |nodes|. + virtual void DetachTree(std::vector<BrowserAccessibility*>* nodes); + + // Perform platform specific initialization. This can be called multiple times + // during the lifetime of this instance after the members of this base object + // have been reset with new values from the renderer process. + virtual void Initialize(); + + // Initialize this object, reading attributes from |src|. Does not + // recurse into children of |src| and build the whole subtree. + void Initialize(BrowserAccessibilityManager* manager, + BrowserAccessibility* parent, + int32 child_id, + int32 index_in_parent, + const WebAccessibility& src); + + // Add a child of this object. + void AddChild(BrowserAccessibility* child); + + // Update the parent and index in parent if this node has been moved. + void UpdateParent(BrowserAccessibility* parent, int index_in_parent); + + // Return true if this object is equal to or a descendant of |ancestor|. + bool IsDescendantOf(BrowserAccessibility* ancestor); + + // Returns the parent of this object, or NULL if it's the root. + BrowserAccessibility* parent() { return parent_; } + + // Returns the number of children of this object. + uint32 child_count() const { return children_.size(); } + + // Return a pointer to the child with the given index. + BrowserAccessibility* GetChild(uint32 child_index); + + // Return the previous sibling of this object, or NULL if it's the first + // child of its parent. + BrowserAccessibility* GetPreviousSibling(); + + // Return the next sibling of this object, or NULL if it's the last child + // of its parent. + BrowserAccessibility* GetNextSibling(); + + // Returns the bounds of this object in screen coordinates. + gfx::Rect GetBoundsRect(); + + // Returns the deepest descendant that contains the specified point. + BrowserAccessibility* BrowserAccessibilityForPoint(const gfx::Point& point); + + // + // Reference counting + // + // Each object has an internal reference count and many platform + // implementations may also use native reference counting. + // + // The internal reference counting is used because sometimes + // multiple references to the same object exist temporarily during + // an update. When the internal reference count reaches zero, + // NativeReleaseReference is called. + // + // Native reference counting is used on some platforms because the + // operating system may hold onto a reference to a BrowserAccessibility + // object even after we're through with it. On these platforms, when + // the internal reference count reaches zero, instance_active is set + // to zero, and all queries on this object should return failure. + // The object isn't actually deleted until the operating system releases + // all of its references. + // + + // Increment this node's internal reference count. + virtual void InternalAddReference(); + + // Decrement this node's internal reference count. If the reference count + // reaches zero, call NativeReleaseReference(). + virtual void InternalReleaseReference(bool recursive); + + // Subclasses should override this to support platform reference counting. + virtual void NativeAddReference() { } + + // Subclasses should override this to support platform reference counting. + virtual void NativeReleaseReference(); + + // + // Accessors + // + + const std::map<int32, string16>& attributes() const { return attributes_; } + int32 child_id() const { return child_id_; } + const std::vector<BrowserAccessibility*>& children() const { + return children_; + } + const std::vector<std::pair<string16, string16> >& html_attributes() const { + return html_attributes_; + } + int32 index_in_parent() const { return index_in_parent_; } + const std::vector<int32>& indirect_child_ids() const { + return indirect_child_ids_; + } + const std::vector<int32>& line_breaks() const { + return line_breaks_; + } + gfx::Rect location() const { return location_; } + BrowserAccessibilityManager* manager() const { return manager_; } + const string16& name() const { return name_; } + int32 renderer_id() const { return renderer_id_; } + int32 role() const { return role_; } + const string16& role_name() const { return role_name_; } + int32 state() const { return state_; } + const string16& value() const { return value_; } + bool instance_active() const { return instance_active_; } + int32 ref_count() const { return ref_count_; } + +#if defined(OS_MACOSX) && __OBJC__ + BrowserAccessibilityCocoa* toBrowserAccessibilityCocoa(); +#elif defined(OS_WIN) + BrowserAccessibilityWin* toBrowserAccessibilityWin(); +#endif + + // BrowserAccessibilityCocoa needs access to these methods. + // Return true if this attribute is in the attributes map. + bool HasAttribute(WebAccessibility::Attribute attribute); + + // Retrieve the string value of an attribute from the attribute map and + // returns true if found. + bool GetAttribute(WebAccessibility::Attribute attribute, string16* value); + + // Retrieve the value of an attribute from the attribute map and + // if found and nonempty, try to convert it to an integer. + // Returns true only if both the attribute was found and it was successfully + // converted to an integer. + bool GetAttributeAsInt( + WebAccessibility::Attribute attribute, int* value_int); + + protected: + BrowserAccessibility(); + + // The manager of this tree of accessibility objects; needed for + // global operations like focus tracking. + BrowserAccessibilityManager* manager_; + + // The parent of this object, may be NULL if we're the root object. + BrowserAccessibility* parent_; + + // The ID of this object; globally unique within the browser process. + int32 child_id_; + + // The index of this within its parent object. + int32 index_in_parent_; + + // The ID of this object in the renderer process. + int32 renderer_id_; + + // The children of this object. + std::vector<BrowserAccessibility*> children_; + + // The number of internal references to this object. + int32 ref_count_; + + // Accessibility metadata from the renderer + string16 name_; + string16 value_; + std::map<int32, string16> attributes_; + std::vector<std::pair<string16, string16> > html_attributes_; + int32 role_; + int32 state_; + string16 role_name_; + gfx::Rect location_; + std::vector<int32> indirect_child_ids_; + std::vector<int32> line_breaks_; + + // BrowserAccessibility objects are reference-counted on some platforms. + // When we're done with this object and it's removed from our accessibility + // tree, a client may still be holding onto a pointer to this object, so + // we mark it as inactive so that calls to any of this object's methods + // immediately return failure. + bool instance_active_; + + private: + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibility); +}; + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_H_ diff --git a/content/browser/accessibility/browser_accessibility_cocoa.h b/content/browser/accessibility/browser_accessibility_cocoa.h new file mode 100644 index 0000000..ab4044f --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_cocoa.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_COCOA_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_COCOA_H_ +#pragma once + +#import <Cocoa/Cocoa.h> + +#import "base/memory/scoped_nsobject.h" +#import "content/browser/accessibility/browser_accessibility_delegate_mac.h" +#include "content/browser/accessibility/browser_accessibility.h" + +// BrowserAccessibilityCocoa is a cocoa wrapper around the BrowserAccessibility +// object. The renderer converts webkit's accessibility tree into a +// WebAccessibility tree and passes it to the browser process over IPC. +// This class converts it into a format Cocoa can query. +// Inheriting from NSView rather than NSObject as clients cannot add +// observers to pure NSObject derived classes. +@interface BrowserAccessibilityCocoa : NSView { + @private + BrowserAccessibility* browserAccessibility_; + scoped_nsobject<NSMutableArray> children_; + id<BrowserAccessibilityDelegateCocoa> delegate_; +} + +// This creates a cocoa browser accessibility object around +// the cross platform BrowserAccessibility object. The delegate is +// used to communicate with the host renderer. None of these +// parameters can be null. +- (id)initWithObject:(BrowserAccessibility*)accessibility + delegate:(id<BrowserAccessibilityDelegateCocoa>)delegate; + +// Invalidate children for a non-ignored ancestor (including self). +- (void)childrenChanged; + +// Children is an array of BrowserAccessibility objects, representing +// the accessibility children of this object. +@property(nonatomic, readonly) NSArray* children; +@property(nonatomic, readonly) NSArray* columns; +@property(nonatomic, readonly) NSString* description; +@property(nonatomic, readonly) NSNumber* enabled; +@property(nonatomic, readonly) NSNumber* focused; +@property(nonatomic, readonly) NSString* help; +// isIgnored returns whether or not the accessibility object +// should be ignored by the accessibility hierarchy. +@property(nonatomic, readonly, getter=isIgnored) BOOL ignored; +// The origin of this object in the page's document. +// This is relative to webkit's top-left origin, not Cocoa's +// bottom-left origin. +@property(nonatomic, readonly) NSPoint origin; +@property(nonatomic, readonly) NSNumber* numberOfCharacters; +@property(nonatomic, readonly) id parent; +@property(nonatomic, readonly) NSValue* position; +// A string indicating the role of this object as far as accessibility +// is concerned. +@property(nonatomic, readonly) NSString* role; +@property(nonatomic, readonly) NSString* roleDescription; +@property(nonatomic, readonly) NSArray* rows; +// The size of this object. +@property(nonatomic, readonly) NSValue* size; +// A string indicating the subrole of this object as far as accessibility +// is concerned. +@property(nonatomic, readonly) NSString* subrole; +// The tabs owned by a tablist. +@property(nonatomic, readonly) NSArray* tabs; +@property(nonatomic, readonly) NSString* title; +@property(nonatomic, readonly) NSString* url; +@property(nonatomic, readonly) NSString* value; +@property(nonatomic, readonly) NSValue* visibleCharacterRange; +@property(nonatomic, readonly) NSNumber* visited; +@property(nonatomic, readonly) id window; +@end + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_COCOA_H_ diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm new file mode 100644 index 0000000..1920493 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_cocoa.mm @@ -0,0 +1,822 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <execinfo.h> + +#import "content/browser/accessibility/browser_accessibility_cocoa.h" + +#include <map> + +#include "base/string16.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "grit/webkit_strings.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h" +#include "ui/base/l10n/l10n_util_mac.h" + +namespace { + +// Returns an autoreleased copy of the WebAccessibility's attribute. +NSString* NSStringForWebAccessibilityAttribute( + const std::map<int32, string16>& attributes, + WebAccessibility::Attribute attribute) { + std::map<int32, string16>::const_iterator iter = + attributes.find(attribute); + NSString* returnValue = @""; + if (iter != attributes.end()) { + returnValue = base::SysUTF16ToNSString(iter->second); + } + return returnValue; +} + +struct MapEntry { + WebAccessibility::Role webKitValue; + NSString* nativeValue; +}; + +struct AttributeToMethodNameEntry { + NSString* attribute; + NSString* methodName; +}; + +static const MapEntry roles[] = { + { WebAccessibility::ROLE_NONE, NSAccessibilityUnknownRole }, + { WebAccessibility::ROLE_ALERT, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_ALERT_DIALOG, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_ANNOTATION, NSAccessibilityUnknownRole }, + { WebAccessibility::ROLE_APPLICATION, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_ARTICLE, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_BROWSER, NSAccessibilityBrowserRole }, + { WebAccessibility::ROLE_BUSY_INDICATOR, NSAccessibilityBusyIndicatorRole }, + { WebAccessibility::ROLE_BUTTON, NSAccessibilityButtonRole }, + { WebAccessibility::ROLE_CELL, @"AXCell" }, + { WebAccessibility::ROLE_CHECKBOX, NSAccessibilityCheckBoxRole }, + { WebAccessibility::ROLE_COLOR_WELL, NSAccessibilityColorWellRole }, + { WebAccessibility::ROLE_COLUMN, NSAccessibilityColumnRole }, + { WebAccessibility::ROLE_COLUMN_HEADER, @"AXCell" }, + { WebAccessibility::ROLE_DEFINITION_LIST_DEFINITION, + NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_DEFINITION_LIST_TERM, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_DIALOG, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_DIRECTORY, NSAccessibilityListRole }, + { WebAccessibility::ROLE_DISCLOSURE_TRIANGLE, + NSAccessibilityDisclosureTriangleRole }, + { WebAccessibility::ROLE_DOCUMENT, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_DRAWER, NSAccessibilityDrawerRole }, + { WebAccessibility::ROLE_EDITABLE_TEXT, NSAccessibilityTextFieldRole }, + { WebAccessibility::ROLE_GRID, NSAccessibilityGridRole }, + { WebAccessibility::ROLE_GROUP, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_GROW_AREA, NSAccessibilityGrowAreaRole }, + { WebAccessibility::ROLE_HEADING, @"AXHeading" }, + { WebAccessibility::ROLE_HELP_TAG, NSAccessibilityHelpTagRole }, + { WebAccessibility::ROLE_IGNORED, NSAccessibilityUnknownRole }, + { WebAccessibility::ROLE_IMAGE, NSAccessibilityImageRole }, + { WebAccessibility::ROLE_IMAGE_MAP, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_IMAGE_MAP_LINK, NSAccessibilityLinkRole }, + { WebAccessibility::ROLE_INCREMENTOR, NSAccessibilityIncrementorRole }, + { WebAccessibility::ROLE_LANDMARK_APPLICATION, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LANDMARK_BANNER, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LANDMARK_COMPLEMENTARY, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LANDMARK_CONTENTINFO, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LANDMARK_MAIN, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LANDMARK_NAVIGATION, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LANDMARK_SEARCH, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LINK, NSAccessibilityLinkRole }, + { WebAccessibility::ROLE_LIST, NSAccessibilityListRole }, + { WebAccessibility::ROLE_LIST_ITEM, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LIST_MARKER, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LISTBOX, NSAccessibilityListRole }, + { WebAccessibility::ROLE_LISTBOX_OPTION, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_LOG, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_MARQUEE, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_MATH, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_MATTE, NSAccessibilityMatteRole }, + { WebAccessibility::ROLE_MENU, NSAccessibilityMenuRole }, + { WebAccessibility::ROLE_MENU_ITEM, NSAccessibilityMenuItemRole }, + { WebAccessibility::ROLE_MENU_BUTTON, NSAccessibilityButtonRole }, + { WebAccessibility::ROLE_MENU_LIST_OPTION, NSAccessibilityMenuItemRole }, + { WebAccessibility::ROLE_MENU_LIST_POPUP, NSAccessibilityUnknownRole }, + { WebAccessibility::ROLE_NOTE, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_OUTLINE, NSAccessibilityOutlineRole }, + { WebAccessibility::ROLE_POPUP_BUTTON, NSAccessibilityPopUpButtonRole }, + { WebAccessibility::ROLE_PROGRESS_INDICATOR, + NSAccessibilityProgressIndicatorRole }, + { WebAccessibility::ROLE_RADIO_BUTTON, NSAccessibilityRadioButtonRole }, + { WebAccessibility::ROLE_RADIO_GROUP, NSAccessibilityRadioGroupRole }, + { WebAccessibility::ROLE_REGION, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_ROW, NSAccessibilityRowRole }, + { WebAccessibility::ROLE_ROW_HEADER, @"AXCell" }, + { WebAccessibility::ROLE_RULER, NSAccessibilityRulerRole }, + { WebAccessibility::ROLE_RULER_MARKER, NSAccessibilityRulerMarkerRole }, + { WebAccessibility::ROLE_SCROLLAREA, NSAccessibilityScrollAreaRole }, + { WebAccessibility::ROLE_SCROLLBAR, NSAccessibilityScrollBarRole }, + { WebAccessibility::ROLE_SHEET, NSAccessibilitySheetRole }, + { WebAccessibility::ROLE_SLIDER, NSAccessibilitySliderRole }, + { WebAccessibility::ROLE_SLIDER_THUMB, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_SPLITTER, NSAccessibilitySplitterRole }, + { WebAccessibility::ROLE_SPLIT_GROUP, NSAccessibilitySplitGroupRole }, + { WebAccessibility::ROLE_STATIC_TEXT, NSAccessibilityStaticTextRole }, + { WebAccessibility::ROLE_STATUS, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_SYSTEM_WIDE, NSAccessibilityUnknownRole }, + { WebAccessibility::ROLE_TAB, NSAccessibilityRadioButtonRole }, + { WebAccessibility::ROLE_TAB_LIST, NSAccessibilityTabGroupRole }, + { WebAccessibility::ROLE_TAB_PANEL, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_TABLE, NSAccessibilityTableRole }, + { WebAccessibility::ROLE_TABLE_HEADER_CONTAINER, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_TAB_GROUP, NSAccessibilityTabGroupRole }, + { WebAccessibility::ROLE_TEXTAREA, NSAccessibilityTextAreaRole }, + { WebAccessibility::ROLE_TEXT_FIELD, NSAccessibilityTextFieldRole }, + { WebAccessibility::ROLE_TIMER, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_TOOLBAR, NSAccessibilityToolbarRole }, + { WebAccessibility::ROLE_TOOLTIP, NSAccessibilityGroupRole }, + { WebAccessibility::ROLE_TREE, NSAccessibilityOutlineRole }, + { WebAccessibility::ROLE_TREE_GRID, NSAccessibilityTableRole }, + { WebAccessibility::ROLE_TREE_ITEM, NSAccessibilityRowRole }, + { WebAccessibility::ROLE_VALUE_INDICATOR, NSAccessibilityValueIndicatorRole }, + { WebAccessibility::ROLE_WEBCORE_LINK, NSAccessibilityLinkRole }, + { WebAccessibility::ROLE_WEB_AREA, @"AXWebArea" }, + { WebAccessibility::ROLE_WINDOW, NSAccessibilityUnknownRole }, +}; + +static const MapEntry subroles[] = { + { WebAccessibility::ROLE_ALERT, @"AXApplicationAlert" }, + { WebAccessibility::ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog" }, + { WebAccessibility::ROLE_ARTICLE, @"AXDocumentArticle" }, + { WebAccessibility::ROLE_DEFINITION_LIST_DEFINITION, @"AXDefinition" }, + { WebAccessibility::ROLE_DEFINITION_LIST_TERM, @"AXTerm" }, + { WebAccessibility::ROLE_DIALOG, @"AXApplicationDialog" }, + { WebAccessibility::ROLE_DOCUMENT, @"AXDocument" }, + { WebAccessibility::ROLE_LANDMARK_APPLICATION, @"AXLandmarkApplication" }, + { WebAccessibility::ROLE_LANDMARK_BANNER, @"AXLandmarkBanner" }, + { WebAccessibility::ROLE_LANDMARK_COMPLEMENTARY, @"AXLandmarkComplementary" }, + { WebAccessibility::ROLE_LANDMARK_CONTENTINFO, @"AXLandmarkContentInfo" }, + { WebAccessibility::ROLE_LANDMARK_MAIN, @"AXLandmarkMain" }, + { WebAccessibility::ROLE_LANDMARK_NAVIGATION, @"AXLandmarkNavigation" }, + { WebAccessibility::ROLE_LANDMARK_SEARCH, @"AXLandmarkSearch" }, + { WebAccessibility::ROLE_LOG, @"AXApplicationLog" }, + { WebAccessibility::ROLE_MARQUEE, @"AXApplicationMarquee" }, + { WebAccessibility::ROLE_MATH, @"AXDocumentMath" }, + { WebAccessibility::ROLE_NOTE, @"AXDocumentNote" }, + { WebAccessibility::ROLE_REGION, @"AXDocumentRegion" }, + { WebAccessibility::ROLE_STATUS, @"AXApplicationStatus" }, + { WebAccessibility::ROLE_TAB_PANEL, @"AXTabPanel" }, + { WebAccessibility::ROLE_TIMER, @"AXApplicationTimer" }, + { WebAccessibility::ROLE_TOOLTIP, @"AXUserInterfaceTooltip" }, + { WebAccessibility::ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole }, +}; + +static const AttributeToMethodNameEntry attributeToMethodNameContainer[] = { + { NSAccessibilityChildrenAttribute, @"children" }, + { NSAccessibilityColumnsAttribute, @"columns" }, + { NSAccessibilityDescriptionAttribute, @"description" }, + { NSAccessibilityEnabledAttribute, @"enabled" }, + { NSAccessibilityFocusedAttribute, @"focused" }, + { NSAccessibilityHelpAttribute, @"help" }, + { NSAccessibilityNumberOfCharactersAttribute, @"numberOfCharacters" }, + { NSAccessibilityParentAttribute, @"parent" }, + { NSAccessibilityPositionAttribute, @"position" }, + { NSAccessibilityRoleAttribute, @"role" }, + { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" }, + { NSAccessibilityRowsAttribute, @"rows" }, + { NSAccessibilitySizeAttribute, @"size" }, + { NSAccessibilitySubroleAttribute, @"subrole" }, + { NSAccessibilityTabsAttribute, @"tabs" }, + { NSAccessibilityTitleAttribute, @"title" }, + { NSAccessibilityTopLevelUIElementAttribute, @"window" }, + { NSAccessibilityURLAttribute, @"url" }, + { NSAccessibilityValueAttribute, @"value" }, + { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" }, + { NSAccessibilityWindowAttribute, @"window" }, + { @"AXVisited", @"visited" }, +}; + +// GetState checks the bitmask used in webaccessibility.h to check +// if the given state was set on the accessibility object. +bool GetState(BrowserAccessibility* accessibility, int state) { + return ((accessibility->state() >> state) & 1); +} + +// A mapping of webkit roles to native roles. +std::map<WebAccessibility::Role, NSString*> webAccessibilityToNativeRole; +// A mapping of webkit roles to native subroles. +std::map<WebAccessibility::Role, NSString*> webAccessibilityToNativeSubrole; +// A mapping from an accessibility attribute to its method name. +NSDictionary* attributeToMethodNameMap = nil; + +} // namespace + +@implementation BrowserAccessibilityCocoa + ++ (void)initialize { + const size_t numRoles = sizeof(roles) / sizeof(roles[0]); + for (size_t i = 0; i < numRoles; ++i) { + webAccessibilityToNativeRole[roles[i].webKitValue] = roles[i].nativeValue; + } + + const size_t numSubroles = sizeof(subroles) / sizeof(subroles[0]); + for (size_t i = 0; i < numSubroles; ++i) { + webAccessibilityToNativeSubrole[subroles[i].webKitValue] = + subroles[i].nativeValue; + } + + NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; + const size_t numAttributes = sizeof(attributeToMethodNameContainer) / + sizeof(attributeToMethodNameContainer[0]); + for (size_t i = 0; i < numAttributes; ++i) { + [dict setObject:attributeToMethodNameContainer[i].methodName + forKey:attributeToMethodNameContainer[i].attribute]; + } + attributeToMethodNameMap = dict; + dict = nil; +} + +- (id)initWithObject:(BrowserAccessibility*)accessibility + delegate:(id<BrowserAccessibilityDelegateCocoa>)delegate { + if ((self = [super init])) { + browserAccessibility_ = accessibility; + delegate_ = delegate; + } + return self; +} + +// Deletes our associated BrowserAccessibilityMac. +- (void)dealloc { + if (browserAccessibility_) { + delete browserAccessibility_; + browserAccessibility_ = NULL; + } + + [super dealloc]; +} + +// Returns an array of BrowserAccessibilityCocoa objects, representing the +// accessibility children of this object. +- (NSArray*)children { + if (!children_.get()) { + children_.reset([[NSMutableArray alloc] + initWithCapacity:browserAccessibility_->child_count()] ); + for (uint32 index = 0; + index < browserAccessibility_->child_count(); + ++index) { + BrowserAccessibilityCocoa* child = + browserAccessibility_->GetChild(index)->toBrowserAccessibilityCocoa(); + if ([child isIgnored]) + [children_ addObjectsFromArray:[child children]]; + else + [children_ addObject:child]; + } + + // Also, add indirect children (if any). + for (uint32 i = 0; + i < browserAccessibility_->indirect_child_ids().size(); + ++i) { + int32 child_id = browserAccessibility_->indirect_child_ids()[i]; + BrowserAccessibilityCocoa* child = + browserAccessibility_->manager()->GetFromRendererID(child_id)-> + toBrowserAccessibilityCocoa(); + [children_ addObject:child]; + } + } + return children_; +} + +- (void)childrenChanged { + if (![self isIgnored]) { + children_.reset(); + } else { + [browserAccessibility_->parent()->toBrowserAccessibilityCocoa() + childrenChanged]; + } +} + +- (NSArray*)columns { + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + for (BrowserAccessibilityCocoa* child in [self children]) { + if ([[child role] isEqualToString:NSAccessibilityColumnRole]) + [ret addObject:child]; + } + return ret; +} + +- (NSString*)description { + return NSStringForWebAccessibilityAttribute( + browserAccessibility_->attributes(), + WebAccessibility::ATTR_DESCRIPTION); +} + +- (NSNumber*)enabled { + return [NSNumber numberWithBool: + !GetState(browserAccessibility_, WebAccessibility::STATE_UNAVAILABLE)]; +} + +- (NSNumber*)focused { + NSNumber* ret = [NSNumber numberWithBool: + GetState(browserAccessibility_, WebAccessibility::STATE_FOCUSED)]; + return ret; +} + +- (NSString*)help { + return NSStringForWebAccessibilityAttribute( + browserAccessibility_->attributes(), + WebAccessibility::ATTR_HELP); +} + +// Returns whether or not this node should be ignored in the +// accessibility tree. +- (BOOL)isIgnored { + return [[self role] isEqualToString:NSAccessibilityUnknownRole]; +} + +- (NSNumber*)loaded { + return [NSNumber numberWithBool:YES]; +} + +// The origin of this accessibility object in the page's document. +// This is relative to webkit's top-left origin, not Cocoa's +// bottom-left origin. +- (NSPoint)origin { + return NSMakePoint(browserAccessibility_->location().x(), + browserAccessibility_->location().y()); +} + +- (NSNumber*)numberOfCharacters { + return [NSNumber numberWithInt:browserAccessibility_->value().length()]; +} + +- (id)parent { + // A nil parent means we're the root. + if (browserAccessibility_->parent()) { + return NSAccessibilityUnignoredAncestor( + browserAccessibility_->parent()->toBrowserAccessibilityCocoa()); + } else { + // Hook back up to RenderWidgetHostViewCocoa. + return browserAccessibility_->manager()->GetParentView(); + } +} + +- (NSValue*)position { + return [NSValue valueWithPoint:[delegate_ accessibilityPointInScreen:self]]; +} + +// Returns a string indicating the role of this object. +- (NSString*)role { + WebAccessibility::Role browserAccessibilityRole = + static_cast<WebAccessibility::Role>( browserAccessibility_->role()); + + // Roles that we only determine at runtime. + if (browserAccessibilityRole == WebAccessibility::ROLE_TEXT_FIELD && + GetState(browserAccessibility_, WebAccessibility::STATE_PROTECTED)) { + return @"AXSecureTextField"; + } + + std::map<WebAccessibility::Role, NSString*>::iterator it = + webAccessibilityToNativeRole.find(browserAccessibilityRole); + + if (it != webAccessibilityToNativeRole.end()) + return it->second; + else + return NSAccessibilityUnknownRole; +} + +// Returns a string indicating the role description of this object. +- (NSString*)roleDescription { + NSString* role = [self role]; + // The following descriptions are specific to webkit. + if ([role isEqualToString:@"AXWebArea"]) + return l10n_util::GetNSString(IDS_AX_ROLE_WEB_AREA); + + if ([role isEqualToString:@"NSAccessibilityLinkRole"]) + return l10n_util::GetNSString(IDS_AX_ROLE_LINK); + + if ([role isEqualToString:@"AXHeading"]) + return l10n_util::GetNSString(IDS_AX_ROLE_HEADING); + + if ([role isEqualToString:NSAccessibilityGroupRole] || + [role isEqualToString:NSAccessibilityRadioButtonRole]) { + const std::vector<std::pair<string16, string16> >& htmlAttributes = + browserAccessibility_->html_attributes(); + WebAccessibility::Role browserAccessibilityRole = + static_cast<WebAccessibility::Role>(browserAccessibility_->role()); + + if ((browserAccessibilityRole != WebAccessibility::ROLE_GROUP && + browserAccessibilityRole != WebAccessibility::ROLE_LIST_ITEM) || + browserAccessibilityRole == WebAccessibility::ROLE_TAB) { + for (size_t i = 0; i < htmlAttributes.size(); ++i) { + const std::pair<string16, string16>& htmlAttribute = htmlAttributes[i]; + if (htmlAttribute.first == ASCIIToUTF16("role")) { + // TODO(dtseng): This is not localized; see crbug/84814. + return base::SysUTF16ToNSString(htmlAttribute.second); + } + } + } + } + + return NSAccessibilityRoleDescription(role, nil); +} + +- (NSArray*)rows { + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + for (BrowserAccessibilityCocoa* child in [self children]) { + if ([[child role] isEqualToString:NSAccessibilityRowRole]) + [ret addObject:child]; + } + + return ret; +} + +// Returns the size of this object. +- (NSValue*)size { + return [NSValue valueWithSize:NSMakeSize( + browserAccessibility_->location().width(), + browserAccessibility_->location().height())]; +} + +// Returns a subrole based upon the role. +- (NSString*) subrole { + // TODO: support password field -> NSAccessibilitySecureTextFieldSubrole + // TODO: support attachments + // TODO: support lists -> NSAccessibilityContentListSubrole || + // NSAccessibilityDefinitionListSubrole + + WebAccessibility::Role browserAccessibilityRole = + static_cast<WebAccessibility::Role>( browserAccessibility_->role()); + + std::map<WebAccessibility::Role, NSString*>::iterator it = + webAccessibilityToNativeSubrole.find(browserAccessibilityRole); + + if (it != webAccessibilityToNativeSubrole.end()) + return it->second; + else + return nil; +} + +// Returns all tabs in this subtree. +- (NSArray*)tabs { + NSMutableArray* tabSubtree = [[[NSMutableArray alloc] init] autorelease]; + + if (browserAccessibility_->role() == WebAccessibility::ROLE_TAB) + [tabSubtree addObject:self]; + + for (uint i=0; i < [[self children] count]; ++i) { + NSArray* tabChildren = [[[self children] objectAtIndex:i] tabs]; + if ([tabChildren count] > 0) + [tabSubtree addObjectsFromArray:tabChildren]; + } + + return tabSubtree; +} + +- (NSString*)title { + return base::SysUTF16ToNSString(browserAccessibility_->name()); +} + +- (NSString*)url { + WebAccessibility::Attribute urlAttribute = + [[self role] isEqualToString:@"AXWebArea"] ? + WebAccessibility::ATTR_DOC_URL : + WebAccessibility::ATTR_URL; + return NSStringForWebAccessibilityAttribute( + browserAccessibility_->attributes(), + urlAttribute); +} + +- (id)value { + // WebCore uses an attachmentView to get the below behavior. + // We do not have any native views backing this object, so need + // to approximate Cocoa ax behavior best as we can. + NSString* role = [self role]; + if ([role isEqualToString:@"AXHeading"]) { + NSString* headingLevel = + NSStringForWebAccessibilityAttribute( + browserAccessibility_->attributes(), + WebAccessibility::ATTR_HTML_TAG); + if ([headingLevel length] >= 2) { + return [NSNumber numberWithInt: + [[headingLevel substringFromIndex:1] intValue]]; + } + } else if ([role isEqualToString:NSAccessibilityButtonRole]) { + // AXValue does not make sense for pure buttons. + return @""; + } else if ([role isEqualToString:NSAccessibilityCheckBoxRole] || + [role isEqualToString:NSAccessibilityRadioButtonRole]) { + return [NSNumber numberWithInt:GetState( + browserAccessibility_, WebAccessibility::STATE_CHECKED) ? 1 : 0]; + } + return base::SysUTF16ToNSString(browserAccessibility_->value()); +} + +- (NSValue*)visibleCharacterRange { + return [NSValue valueWithRange: + NSMakeRange(0, browserAccessibility_->value().length())]; +} + +- (NSNumber*)visited { + return [NSNumber numberWithBool: + GetState(browserAccessibility_, WebAccessibility::STATE_TRAVERSED)]; +} + +- (id)window { + return [delegate_ window]; +} + +// Returns the accessibility value for the given attribute. If the value isn't +// supported this will return nil. +- (id)accessibilityAttributeValue:(NSString*)attribute { + SEL selector = + NSSelectorFromString([attributeToMethodNameMap objectForKey:attribute]); + if (selector) + return [self performSelector:selector]; + + // TODO(dtseng): refactor remaining attributes. + int selStart, selEnd; + if (browserAccessibility_->GetAttributeAsInt( + WebAccessibility::ATTR_TEXT_SEL_START, &selStart) && + browserAccessibility_-> + GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_END, &selEnd)) { + if (selStart > selEnd) + std::swap(selStart, selEnd); + int selLength = selEnd - selStart; + if ([attribute isEqualToString: + NSAccessibilityInsertionPointLineNumberAttribute]) { + const std::vector<int32>& line_breaks = + browserAccessibility_->line_breaks(); + for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) { + if (line_breaks[i] > selStart) + return [NSNumber numberWithInt:i]; + } + return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())]; + } + if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) { + return base::SysUTF16ToNSString(browserAccessibility_->value().substr( + selStart, selLength)); + } + if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { + return [NSValue valueWithRange:NSMakeRange(selStart, selLength)]; + } + } + return nil; +} + +// Returns the accessibility value for the given attribute and parameter. If the +// value isn't supported this will return nil. +- (id)accessibilityAttributeValue:(NSString*)attribute + forParameter:(id)parameter { + const std::vector<int32>& line_breaks = browserAccessibility_->line_breaks(); + int len = static_cast<int>(browserAccessibility_->value().size()); + + if ([attribute isEqualToString: + NSAccessibilityStringForRangeParameterizedAttribute]) { + NSRange range = [(NSValue*)parameter rangeValue]; + return base::SysUTF16ToNSString( + browserAccessibility_->value().substr(range.location, range.length)); + } + + if ([attribute isEqualToString: + NSAccessibilityLineForIndexParameterizedAttribute]) { + int index = [(NSNumber*)parameter intValue]; + for (int i = 0; i < static_cast<int>(line_breaks.size()); ++i) { + if (line_breaks[i] > index) + return [NSNumber numberWithInt:i]; + } + return [NSNumber numberWithInt:static_cast<int>(line_breaks.size())]; + } + + if ([attribute isEqualToString: + NSAccessibilityRangeForLineParameterizedAttribute]) { + int line_index = [(NSNumber*)parameter intValue]; + int line_count = static_cast<int>(line_breaks.size()) + 1; + if (line_index < 0 || line_index >= line_count) + return nil; + int start = line_index > 0 ? line_breaks[line_index - 1] : 0; + int end = line_index < line_count - 1 ? line_breaks[line_index] : len; + return [NSValue valueWithRange: + NSMakeRange(start, end - start)]; + } + + // TODO(dtseng): support the following attributes. + if ([attribute isEqualTo: + NSAccessibilityRangeForPositionParameterizedAttribute] || + [attribute isEqualTo: + NSAccessibilityRangeForIndexParameterizedAttribute] || + [attribute isEqualTo: + NSAccessibilityBoundsForRangeParameterizedAttribute] || + [attribute isEqualTo:NSAccessibilityRTFForRangeParameterizedAttribute] || + [attribute isEqualTo: + NSAccessibilityStyleRangeForIndexParameterizedAttribute]) { + return nil; + } + return nil; +} + +// Returns an array of parameterized attributes names that this object will +// respond to. +- (NSArray*)accessibilityParameterizedAttributeNames { + if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] || + [[self role] isEqualToString:NSAccessibilityTextAreaRole]) { + return [NSArray arrayWithObjects: + NSAccessibilityLineForIndexParameterizedAttribute, + NSAccessibilityRangeForLineParameterizedAttribute, + NSAccessibilityStringForRangeParameterizedAttribute, + NSAccessibilityRangeForPositionParameterizedAttribute, + NSAccessibilityRangeForIndexParameterizedAttribute, + NSAccessibilityBoundsForRangeParameterizedAttribute, + NSAccessibilityRTFForRangeParameterizedAttribute, + NSAccessibilityAttributedStringForRangeParameterizedAttribute, + NSAccessibilityStyleRangeForIndexParameterizedAttribute, + nil]; + } + return nil; +} + +// Returns an array of action names that this object will respond to. +- (NSArray*)accessibilityActionNames { + NSMutableArray* ret = + [NSMutableArray arrayWithObject:NSAccessibilityShowMenuAction]; + NSString* role = [self role]; + // TODO(dtseng): this should only get set when there's a default action. + if (![role isEqualToString:NSAccessibilityStaticTextRole] && + ![role isEqualToString:NSAccessibilityTextAreaRole] && + ![role isEqualToString:NSAccessibilityTextFieldRole]) { + [ret addObject:NSAccessibilityPressAction]; + } + + return ret; +} + +// Returns a sub-array of values for the given attribute value, starting at +// index, with up to maxCount items. If the given index is out of bounds, +// or there are no values for the given attribute, it will return nil. +// This method is used for querying subsets of values, without having to +// return a large set of data, such as elements with a large number of +// children. +- (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute + index:(NSUInteger)index + maxCount:(NSUInteger)maxCount { + NSArray* fullArray = [self accessibilityAttributeValue:attribute]; + if (!fullArray) + return nil; + NSUInteger arrayCount = [fullArray count]; + if (index >= arrayCount) + return nil; + NSRange subRange; + if ((index + maxCount) > arrayCount) { + subRange = NSMakeRange(index, arrayCount - index); + } else { + subRange = NSMakeRange(index, maxCount); + } + return [fullArray subarrayWithRange:subRange]; +} + +// Returns the count of the specified accessibility array attribute. +- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute { + NSArray* fullArray = [self accessibilityAttributeValue:attribute]; + return [fullArray count]; +} + +// Returns the list of accessibility attributes that this object supports. +- (NSArray*)accessibilityAttributeNames { + // General attributes. + NSMutableArray* ret = [NSMutableArray arrayWithObjects: + NSAccessibilityChildrenAttribute, + NSAccessibilityDescriptionAttribute, + NSAccessibilityEnabledAttribute, + NSAccessibilityFocusedAttribute, + NSAccessibilityHelpAttribute, + NSAccessibilityParentAttribute, + NSAccessibilityPositionAttribute, + NSAccessibilityRoleAttribute, + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilitySizeAttribute, + NSAccessibilitySubroleAttribute, + NSAccessibilityTitleAttribute, + NSAccessibilityTopLevelUIElementAttribute, + NSAccessibilityValueAttribute, + NSAccessibilityWindowAttribute, + NSAccessibilityURLAttribute, + @"AXVisited", + nil]; + + // Specific role attributes. + NSString* role = [self role]; + if ([role isEqualToString:NSAccessibilityTableRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityColumnsAttribute, + NSAccessibilityRowsAttribute, + nil]]; + } else if ([role isEqualToString:@"AXWebArea"]) { + [ret addObject:@"AXLoaded"]; + } else if ([role isEqualToString:NSAccessibilityTextFieldRole] || + [role isEqualToString:NSAccessibilityTextAreaRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityInsertionPointLineNumberAttribute, + NSAccessibilityNumberOfCharactersAttribute, + NSAccessibilitySelectedTextAttribute, + NSAccessibilitySelectedTextRangeAttribute, + NSAccessibilityVisibleCharacterRangeAttribute, + nil]]; + } else if ([role isEqualToString:NSAccessibilityTabGroupRole]) { + [ret addObject:NSAccessibilityTabsAttribute]; + } + + return ret; +} + +// Returns the index of the child in this objects array of children. +- (NSUInteger)accessibilityGetIndexOf:(id)child { + NSUInteger index = 0; + for (BrowserAccessibilityCocoa* childToCheck in [self children]) { + if ([child isEqual:childToCheck]) + return index; + ++index; + } + return NSNotFound; +} + +// Returns whether or not the specified attribute can be set by the +// accessibility API via |accessibilitySetValue:forAttribute:|. +- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute { + if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) + return GetState(browserAccessibility_, WebAccessibility::STATE_FOCUSABLE); + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) + return !GetState(browserAccessibility_, WebAccessibility::STATE_READONLY); + return NO; +} + +// Returns whether or not this object should be ignored in the accessibilty +// tree. +- (BOOL)accessibilityIsIgnored { + return [self isIgnored]; +} + +// Performs the given accessibilty action on the webkit accessibility object +// that backs this object. +- (void)accessibilityPerformAction:(NSString*)action { + // TODO(feldstein): Support more actions. + if ([action isEqualToString:NSAccessibilityPressAction]) + [delegate_ doDefaultAction:browserAccessibility_->renderer_id()]; + else if ([action isEqualToString:NSAccessibilityShowMenuAction]) + [delegate_ performShowMenuAction:self]; +} + +// Returns the description of the given action. +- (NSString*)accessibilityActionDescription:(NSString*)action { + return NSAccessibilityActionDescription(action); +} + +// Sets an override value for a specific accessibility attribute. +// This class does not support this. +- (BOOL)accessibilitySetOverrideValue:(id)value + forAttribute:(NSString*)attribute { + return NO; +} + +// Sets the value for an accessibility attribute via the accessibility API. +- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute { + if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { + NSNumber* focusedNumber = value; + BOOL focused = [focusedNumber intValue]; + [delegate_ setAccessibilityFocus:focused + accessibilityId:browserAccessibility_->renderer_id()]; + } +} + +// Returns the deepest accessibility child that should not be ignored. +// It is assumed that the hit test has been narrowed down to this object +// or one of its children, so this will never return nil. +- (id)accessibilityHitTest:(NSPoint)point { + BrowserAccessibilityCocoa* hit = self; + for (BrowserAccessibilityCocoa* child in [self children]) { + NSPoint origin = [child origin]; + NSSize size = [[child size] sizeValue]; + NSRect rect; + rect.origin = origin; + rect.size = size; + if (NSPointInRect(point, rect)) { + hit = child; + id childResult = [child accessibilityHitTest:point]; + if (![childResult accessibilityIsIgnored]) { + hit = childResult; + break; + } + } + } + return NSAccessibilityUnignoredAncestor(hit); +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[BrowserAccessibilityCocoa class]]) + return NO; + return ([self hash] == [object hash]); +} + +- (NSUInteger)hash { + // Potentially called during dealloc. + if (!browserAccessibility_) + return [super hash]; + return browserAccessibility_->renderer_id(); +} + +@end + diff --git a/content/browser/accessibility/browser_accessibility_delegate_mac.h b/content/browser/accessibility/browser_accessibility_delegate_mac.h new file mode 100644 index 0000000..680ab87 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_delegate_mac.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_DELEGATE_MAC_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_DELEGATE_MAC_H_ +#pragma once + +@class BrowserAccessibilityCocoa; +@class NSWindow; + +// This protocol is used by the BrowserAccessibility objects to pass messages +// to, or otherwise communicate with, their underlying WebAccessibility +// objects over the IPC boundary. +@protocol BrowserAccessibilityDelegateCocoa +- (NSPoint)accessibilityPointInScreen:(BrowserAccessibilityCocoa*)accessibility; +- (void)doDefaultAction:(int32)accessibilityObjectId; +- (void)performShowMenuAction:(BrowserAccessibilityCocoa*)accessibility; +- (void)setAccessibilityFocus:(BOOL)focus + accessibilityId:(int32)accessibilityObjectId; +- (NSWindow*)window; +@end + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_DELEGATE_MAC_H_ diff --git a/content/browser/accessibility/browser_accessibility_mac.h b/content/browser/accessibility/browser_accessibility_mac.h new file mode 100644 index 0000000..ee6cfca --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_mac.h @@ -0,0 +1,46 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MAC_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MAC_H_ +#pragma once + +#include <map> +#include <utility> +#include <vector> + +#include "base/memory/scoped_nsobject.h" +#include "content/browser/accessibility/browser_accessibility.h" + +@class BrowserAccessibilityCocoa; + +class BrowserAccessibilityMac : public BrowserAccessibility { + public: + // Implementation of BrowserAccessibility. + virtual void Initialize(); + virtual void NativeReleaseReference(); + + // Overrides from BrowserAccessibility. + virtual void DetachTree(std::vector<BrowserAccessibility*>* nodes); + + // The BrowserAccessibilityCocoa associated with us. + BrowserAccessibilityCocoa* native_view() const { + return browser_accessibility_cocoa_; + } + + private: + // This gives BrowserAccessibility::Create access to the class constructor. + friend class BrowserAccessibility; + + BrowserAccessibilityMac(); + + // Allows access to the BrowserAccessibilityCocoa which wraps this. + // BrowserAccessibility. + // We own this object until our manager calls ReleaseReference; + // thereafter, the cocoa object owns us. + BrowserAccessibilityCocoa* browser_accessibility_cocoa_; + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityMac); +}; + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MAC_H_ diff --git a/content/browser/accessibility/browser_accessibility_mac.mm b/content/browser/accessibility/browser_accessibility_mac.mm new file mode 100644 index 0000000..145294d --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_mac.mm @@ -0,0 +1,59 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import <Cocoa/Cocoa.h> + +#import "content/browser/accessibility/browser_accessibility_mac.h" + +#import "content/browser/accessibility/browser_accessibility_cocoa.h" +#import "content/browser/accessibility/browser_accessibility_delegate_mac.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" + + +// Static. +BrowserAccessibility* BrowserAccessibility::Create() { + return new BrowserAccessibilityMac(); +} + +BrowserAccessibilityMac::BrowserAccessibilityMac() + : browser_accessibility_cocoa_(NULL) { +} + +void BrowserAccessibilityMac::Initialize() { + BrowserAccessibility::Initialize(); + + if (browser_accessibility_cocoa_) + return; + + // We take ownership of the cocoa obj here. + browser_accessibility_cocoa_ = [[BrowserAccessibilityCocoa alloc] + initWithObject:this + delegate: + (id<BrowserAccessibilityDelegateCocoa>)manager_->GetParentView()]; +} + +void BrowserAccessibilityMac::NativeReleaseReference() { + if (browser_accessibility_cocoa_) { + BrowserAccessibilityCocoa* temp = browser_accessibility_cocoa_; + browser_accessibility_cocoa_ = nil; + // Relinquish ownership of the cocoa obj. + [temp release]; + // At this point, other processes may have a reference to + // the cocoa object. When the retain count hits zero, it will + // destroy us in dealloc. + // For that reason, do *not* make any more calls here after + // as we might have been deleted. + } +} + +void BrowserAccessibilityMac::DetachTree( + std::vector<BrowserAccessibility*>* nodes) { + [browser_accessibility_cocoa_ childrenChanged]; + BrowserAccessibility::DetachTree(nodes); +} + +BrowserAccessibilityCocoa* BrowserAccessibility::toBrowserAccessibilityCocoa() { + return static_cast<BrowserAccessibilityMac*>(this)-> + native_view(); +} diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc new file mode 100644 index 0000000..a99c782 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_manager.cc @@ -0,0 +1,373 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/accessibility/browser_accessibility_manager.h" + +#include "base/logging.h" +#include "content/browser/accessibility/browser_accessibility.h" +#include "content/common/view_messages.h" + +using webkit_glue::WebAccessibility; + +BrowserAccessibility* BrowserAccessibilityFactory::Create() { + return BrowserAccessibility::Create(); +} + +// Start child IDs at -1 and decrement each time, because clients use +// child IDs of 1, 2, 3, ... to access the children of an object by +// index, so we use negative IDs to clearly distinguish between indices +// and unique IDs. +// static +int32 BrowserAccessibilityManager::next_child_id_ = -1; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +// There's no OS-specific implementation of BrowserAccessibilityManager +// on Unix, so just instantiate the base class. +// static +BrowserAccessibilityManager* BrowserAccessibilityManager::Create( + gfx::NativeView parent_view, + const WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) { + return new BrowserAccessibilityManager( + parent_view, src, delegate, factory); +} +#endif + +BrowserAccessibilityManager::BrowserAccessibilityManager( + gfx::NativeView parent_view, + const WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) + : parent_view_(parent_view), + delegate_(delegate), + factory_(factory), + focus_(NULL) { + root_ = CreateAccessibilityTree(NULL, src, 0); + if (!focus_) + SetFocus(root_, false); +} + +// static +int32 BrowserAccessibilityManager::GetNextChildID() { + // Get the next child ID, and wrap around when we get near the end + // of a 32-bit integer range. It's okay to wrap around; we just want + // to avoid it as long as possible because clients may cache the ID of + // an object for a while to determine if they've seen it before. + next_child_id_--; + if (next_child_id_ == -2000000000) + next_child_id_ = -1; + + return next_child_id_; +} + +BrowserAccessibilityManager::~BrowserAccessibilityManager() { + // Clients could still hold references to some nodes of the tree, so + // calling InternalReleaseReference will make sure that as many nodes + // as possible are released now, and remaining nodes are marked as + // inactive so that calls to any methods on them will fail gracefully. + focus_->InternalReleaseReference(false); + root_->InternalReleaseReference(true); +} + +BrowserAccessibility* BrowserAccessibilityManager::GetRoot() { + return root_; +} + +BrowserAccessibility* BrowserAccessibilityManager::GetFromChildID( + int32 child_id) { + base::hash_map<int32, BrowserAccessibility*>::iterator iter = + child_id_map_.find(child_id); + if (iter != child_id_map_.end()) { + return iter->second; + } else { + return NULL; + } +} + +BrowserAccessibility* BrowserAccessibilityManager::GetFromRendererID( + int32 renderer_id) { + base::hash_map<int32, int32>::iterator iter = + renderer_id_to_child_id_map_.find(renderer_id); + if (iter == renderer_id_to_child_id_map_.end()) + return NULL; + + int32 child_id = iter->second; + return GetFromChildID(child_id); +} + +void BrowserAccessibilityManager::Remove(int32 child_id, int32 renderer_id) { + child_id_map_.erase(child_id); + renderer_id_to_child_id_map_.erase(renderer_id); +} + +void BrowserAccessibilityManager::OnAccessibilityNotifications( + const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) { + for (uint32 index = 0; index < params.size(); index++) { + const ViewHostMsg_AccessibilityNotification_Params& param = params[index]; + + switch (param.notification_type) { + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_CHECK_STATE_CHANGED: + OnAccessibilityObjectStateChange(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_CHILDREN_CHANGED: + OnAccessibilityObjectChildrenChange(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_FOCUS_CHANGED: + OnAccessibilityObjectFocusChange(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_LOAD_COMPLETE: + OnAccessibilityObjectLoadComplete(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_VALUE_CHANGED: + OnAccessibilityObjectValueChange(param.acc_obj); + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED: + OnAccessibilityObjectTextChange(param.acc_obj); + break; + default: + DCHECK(0); + break; + } + } +} + +void BrowserAccessibilityManager::OnAccessibilityObjectStateChange( + const WebAccessibility& acc_obj) { + BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false); + if (!new_browser_acc) + return; + + NotifyAccessibilityEvent( + ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_CHECK_STATE_CHANGED, + new_browser_acc); +} + +void BrowserAccessibilityManager::OnAccessibilityObjectChildrenChange( + const WebAccessibility& acc_obj) { + BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, true); + if (!new_browser_acc) + return; + + NotifyAccessibilityEvent( + ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_CHILDREN_CHANGED, + new_browser_acc); +} + +void BrowserAccessibilityManager::OnAccessibilityObjectFocusChange( + const WebAccessibility& acc_obj) { + BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false); + if (!new_browser_acc) + return; + + SetFocus(new_browser_acc, false); + if (delegate_ && delegate_->HasFocus()) { + GotFocus(); + } else if (!delegate_) { + // Mac currently does not have a BrowserAccessibilityDelegate. + NotifyAccessibilityEvent( + ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_FOCUS_CHANGED, + focus_); + } +} + +void BrowserAccessibilityManager::OnAccessibilityObjectLoadComplete( + const WebAccessibility& acc_obj) { + SetFocus(NULL, false); + root_->InternalReleaseReference(true); + + root_ = CreateAccessibilityTree(NULL, acc_obj, 0); + if (!focus_) + SetFocus(root_, false); + + NotifyAccessibilityEvent( + ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_LOAD_COMPLETE, + root_); + if (delegate_ && delegate_->HasFocus()) + GotFocus(); +} + +void BrowserAccessibilityManager::OnAccessibilityObjectValueChange( + const WebAccessibility& acc_obj) { + BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false); + if (!new_browser_acc) + return; + + NotifyAccessibilityEvent( + ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_VALUE_CHANGED, + new_browser_acc); +} + +void BrowserAccessibilityManager::OnAccessibilityObjectTextChange( + const WebAccessibility& acc_obj) { + BrowserAccessibility* new_browser_acc = UpdateNode(acc_obj, false); + if (!new_browser_acc) + return; + + NotifyAccessibilityEvent( + ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED, + new_browser_acc); +} + +void BrowserAccessibilityManager::GotFocus() { + // TODO(ctguil): Remove when tree update logic handles focus changes. + if (!focus_) + return; + + NotifyAccessibilityEvent( + ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_FOCUS_CHANGED, + focus_); +} + +gfx::NativeView BrowserAccessibilityManager::GetParentView() { + return parent_view_; +} + +BrowserAccessibility* BrowserAccessibilityManager::GetFocus( + BrowserAccessibility* root) { + if (focus_ && (!root || focus_->IsDescendantOf(root))) + return focus_; + + return NULL; +} + +void BrowserAccessibilityManager::SetFocus( + BrowserAccessibility* node, bool notify) { + if (focus_) + focus_->InternalReleaseReference(false); + focus_ = node; + if (focus_) + focus_->InternalAddReference(); + + if (notify && node && delegate_) + delegate_->SetAccessibilityFocus(node->renderer_id()); +} + +void BrowserAccessibilityManager::DoDefaultAction( + const BrowserAccessibility& node) { + if (delegate_) + delegate_->AccessibilityDoDefaultAction(node.renderer_id()); +} + +gfx::Rect BrowserAccessibilityManager::GetViewBounds() { + if (delegate_) + return delegate_->GetViewBounds(); + return gfx::Rect(); +} + +BrowserAccessibility* BrowserAccessibilityManager::UpdateNode( + const WebAccessibility& src, + bool include_children) { + base::hash_map<int32, int32>::iterator iter = + renderer_id_to_child_id_map_.find(src.id); + if (iter == renderer_id_to_child_id_map_.end()) + return NULL; + + int32 child_id = iter->second; + BrowserAccessibility* current = GetFromChildID(child_id); + if (!current) + return NULL; + + if (!include_children) { + DCHECK_EQ(0U, src.children.size()); + current->Initialize( + this, + current->parent(), + current->child_id(), + current->index_in_parent(), + src); + return current; + } + + BrowserAccessibility* current_parent = current->parent(); + int current_index_in_parent = current->index_in_parent(); + + // Detach all of the nodes in the old tree and get a single flat vector + // of all node pointers. + std::vector<BrowserAccessibility*> old_tree_nodes; + current->DetachTree(&old_tree_nodes); + + // Build a new tree, reusing old nodes if possible. Each node that's + // reused will have its reference count incremented by one. + current = + CreateAccessibilityTree(current_parent, src, current_index_in_parent); + + // Decrement the reference count of all nodes in the old tree, which will + // delete any nodes no longer needed. + for (int i = 0; i < static_cast<int>(old_tree_nodes.size()); i++) + old_tree_nodes[i]->InternalReleaseReference(false); + + DCHECK(focus_); + if (!focus_->instance_active()) + SetFocus(root_, false); + + return current; +} + +BrowserAccessibility* BrowserAccessibilityManager::CreateAccessibilityTree( + BrowserAccessibility* parent, + const WebAccessibility& src, + int index_in_parent) { + BrowserAccessibility* instance = NULL; + int32 child_id = 0; + base::hash_map<int32, int32>::iterator iter = + renderer_id_to_child_id_map_.find(src.id); + + // If a BrowserAccessibility instance for this ID already exists, add a + // new reference to it and retrieve its children vector. + if (iter != renderer_id_to_child_id_map_.end()) { + child_id = iter->second; + instance = GetFromChildID(child_id); + } + + // If the node has changed roles, don't reuse a BrowserAccessibility + // object, that could confuse a screen reader. + if (instance && instance->role() != src.role) + instance = NULL; + + // If we're reusing a node, it should already be detached from a parent + // and any children. If not, that means we have a serious bug somewhere, + // like the same child is reachable from two places in the same tree. + if (instance && (instance->parent() != NULL || instance->child_count() > 0)) { + NOTREACHED(); + instance = NULL; + } + + if (instance) { + // If we're reusing a node, update its parent and increment its + // reference count. + instance->UpdateParent(parent, index_in_parent); + instance->InternalAddReference(); + } else { + // Otherwise, create a new instance. + instance = factory_->Create(); + child_id = GetNextChildID(); + } + + instance->Initialize(this, parent, child_id, index_in_parent, src); + child_id_map_[child_id] = instance; + renderer_id_to_child_id_map_[src.id] = child_id; + if ((src.state >> WebAccessibility::STATE_FOCUSED) & 1) + SetFocus(instance, false); + for (int i = 0; i < static_cast<int>(src.children.size()); ++i) { + BrowserAccessibility* child = CreateAccessibilityTree( + instance, src.children[i], i); + instance->AddChild(child); + } + + return instance; +} diff --git a/content/browser/accessibility/browser_accessibility_manager.h b/content/browser/accessibility/browser_accessibility_manager.h new file mode 100644 index 0000000..bbb7d77 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_manager.h @@ -0,0 +1,177 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_ +#pragma once + +#include <vector> + +#include "base/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" +#include "ui/gfx/native_widget_types.h" +#include "webkit/glue/webaccessibility.h" + +class BrowserAccessibility; +#if defined(OS_WIN) +class BrowserAccessibilityManagerWin; +#endif + +struct ViewHostMsg_AccessibilityNotification_Params; + +using webkit_glue::WebAccessibility; + +// Class that can perform actions on behalf of the BrowserAccessibilityManager. +class BrowserAccessibilityDelegate { + public: + virtual ~BrowserAccessibilityDelegate() {} + virtual void SetAccessibilityFocus(int acc_obj_id) = 0; + virtual void AccessibilityDoDefaultAction(int acc_obj_id) = 0; + virtual bool HasFocus() = 0; + virtual gfx::Rect GetViewBounds() const = 0; +}; + +class BrowserAccessibilityFactory { + public: + virtual ~BrowserAccessibilityFactory() {} + + // Create an instance of BrowserAccessibility and return a new + // reference to it. + virtual BrowserAccessibility* Create(); +}; + +// Manages a tree of BrowserAccessibility objects. +class BrowserAccessibilityManager { + public: + // Creates the platform specific BrowserAccessibilityManager. Ownership passes + // to the caller. + static BrowserAccessibilityManager* Create( + gfx::NativeView parent_view, + const WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory()); + + virtual ~BrowserAccessibilityManager(); + + // Type is a ViewHostMsg_AccessibilityNotification_Params::NotificationType. + // We pass it as int so that we don't include the render message declaration + // header here. + virtual void NotifyAccessibilityEvent( + int type, + BrowserAccessibility* node) { } + + // Returns the next unique child id. + static int32 GetNextChildID(); + + // Return a pointer to the root of the tree, does not make a new reference. + BrowserAccessibility* GetRoot(); + + // Removes the BrowserAccessibility child_id and renderer_id from the manager. + void Remove(int32 child_id, int32 renderer_id); + + // Return a pointer to the object corresponding to the given child_id, + // does not make a new reference. + BrowserAccessibility* GetFromChildID(int32 child_id); + + // Return a pointer to the object corresponding to the given renderer_id, + // does not make a new reference. + BrowserAccessibility* GetFromRendererID(int32 renderer_id); + + // Called to notify the accessibility manager that its associated native + // view got focused. + void GotFocus(); + + // Update the focused node to |node|, which may be null. + // If |notify| is true, send a message to the renderer to set focus + // to this node. + void SetFocus(BrowserAccessibility* node, bool notify); + + // Tell the renderer to do the default action for this node. + void DoDefaultAction(const BrowserAccessibility& node); + + // Retrieve the bounds of the parent View in screen coordinates. + gfx::Rect GetViewBounds(); + + // Called when the renderer process has notified us of about tree changes. + // Send a notification to MSAA clients of the change. + void OnAccessibilityNotifications( + const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params); + + gfx::NativeView GetParentView(); + +#if defined(OS_WIN) + BrowserAccessibilityManagerWin* toBrowserAccessibilityManagerWin(); +#endif + + // Return the object that has focus, if it's a descandant of the + // given root (inclusive). Does not make a new reference. + BrowserAccessibility* GetFocus(BrowserAccessibility* root); + + protected: + BrowserAccessibilityManager( + gfx::NativeView parent_view, + const WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory); + + private: + void OnAccessibilityObjectStateChange( + const WebAccessibility& acc_obj); + void OnAccessibilityObjectChildrenChange( + const WebAccessibility& acc_obj); + void OnAccessibilityObjectFocusChange( + const WebAccessibility& acc_obj); + void OnAccessibilityObjectLoadComplete( + const WebAccessibility& acc_obj); + void OnAccessibilityObjectValueChange( + const WebAccessibility& acc_obj); + void OnAccessibilityObjectTextChange( + const WebAccessibility& acc_obj); + + // Update an accessibility node with an updated WebAccessibility node + // received from the renderer process. When |include_children| is true + // the node's children will also be updated, otherwise only the node + // itself is updated. Returns the updated node or NULL if no node was + // updated. + BrowserAccessibility* UpdateNode( + const WebAccessibility& src, + bool include_children); + + // Recursively build a tree of BrowserAccessibility objects from + // the WebAccessibility tree received from the renderer process. + BrowserAccessibility* CreateAccessibilityTree( + BrowserAccessibility* parent, + const WebAccessibility& src, + int index_in_parent); + + protected: + // The next unique id for a BrowserAccessibility instance. + static int32 next_child_id_; + + // The parent view. + gfx::NativeView parent_view_; + + // The object that can perform actions on our behalf. + BrowserAccessibilityDelegate* delegate_; + + // Factory to create BrowserAccessibility objects (for dependency injection). + scoped_ptr<BrowserAccessibilityFactory> factory_; + + // The root of the tree of IAccessible objects and the element that + // currently has focus, if any. + BrowserAccessibility* root_; + BrowserAccessibility* focus_; + + // A mapping from the IDs of objects in the renderer, to the child IDs + // we use internally here. + base::hash_map<int32, int32> renderer_id_to_child_id_map_; + + // A mapping from child IDs to BrowserAccessibility objects. + base::hash_map<int32, BrowserAccessibility*> child_id_map_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManager); +}; + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_H_ diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.h b/content/browser/accessibility/browser_accessibility_manager_mac.h new file mode 100644 index 0000000..c7c5089 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_manager_mac.h @@ -0,0 +1,31 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_MAC_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_MAC_H_ +#pragma once + +#import <Cocoa/Cocoa.h> + +#include "content/browser/accessibility/browser_accessibility_manager.h" + +class BrowserAccessibilityManagerMac : public BrowserAccessibilityManager { + public: + // Implementation of BrowserAccessibilityManager. + virtual void NotifyAccessibilityEvent(int type, BrowserAccessibility* node); + + private: + // This gives BrowserAccessibilityManager::Create access to the class + // constructor. + friend class BrowserAccessibilityManager; + + BrowserAccessibilityManagerMac(gfx::NativeView parent_view, + const webkit_glue::WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory); + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerMac); +}; + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_MAC_H_ diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.mm b/content/browser/accessibility/browser_accessibility_manager_mac.mm new file mode 100644 index 0000000..b5618fe3 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_manager_mac.mm @@ -0,0 +1,63 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/accessibility/browser_accessibility_manager_mac.h" + +#import "base/logging.h" +#import "content/browser/accessibility/browser_accessibility_cocoa.h" +#include "content/common/view_messages.h" + +// static +BrowserAccessibilityManager* BrowserAccessibilityManager::Create( + gfx::NativeView parent_view, + const WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) { + return new BrowserAccessibilityManagerMac( + parent_view, src, delegate, factory); +} + +BrowserAccessibilityManagerMac::BrowserAccessibilityManagerMac( + gfx::NativeView parent_window, + const webkit_glue::WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) + : BrowserAccessibilityManager(parent_window, src, delegate, factory) { +} + +void BrowserAccessibilityManagerMac::NotifyAccessibilityEvent( + int type, + BrowserAccessibility* node) { + // Refer to AXObjectCache.mm (webkit). + NSString* event_id = @""; + switch (type) { + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_CHECK_STATE_CHANGED: + // Does not exist on Mac. + return; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_CHILDREN_CHANGED: + // TODO(dtseng): no clear equivalent on Mac. + return; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_FOCUS_CHANGED: + event_id = NSAccessibilityFocusedUIElementChangedNotification; + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_LOAD_COMPLETE: + event_id = @"AXLoadComplete"; + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_VALUE_CHANGED: + event_id = NSAccessibilityValueChangedNotification; + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED: + event_id = NSAccessibilitySelectedTextChangedNotification; + break; + } + BrowserAccessibilityCocoa* native_node = node->toBrowserAccessibilityCocoa(); + DCHECK(native_node); + NSAccessibilityPostNotification(native_node, event_id); +} diff --git a/content/browser/accessibility/browser_accessibility_manager_win.cc b/content/browser/accessibility/browser_accessibility_manager_win.cc new file mode 100644 index 0000000..7b992e7 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_manager_win.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/accessibility/browser_accessibility_manager_win.h" + +#include "content/browser/accessibility/browser_accessibility_win.h" +#include "content/common/view_messages.h" + +using webkit_glue::WebAccessibility; + +// static +BrowserAccessibilityManager* BrowserAccessibilityManager::Create( + gfx::NativeView parent_view, + const WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) { + return new BrowserAccessibilityManagerWin( + parent_view, + src, + delegate, + factory); +} + +BrowserAccessibilityManagerWin* +BrowserAccessibilityManager::toBrowserAccessibilityManagerWin() { + return static_cast<BrowserAccessibilityManagerWin*>(this); +} + +BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin( + HWND parent_view, + const WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory) + : BrowserAccessibilityManager(parent_view, src, delegate, factory) { + // Allow NULL parent_view for unit testing. + if (parent_view == NULL) { + window_iaccessible_ = NULL; + return; + } + + HRESULT hr = ::CreateStdAccessibleObject( + parent_view, OBJID_WINDOW, IID_IAccessible, + reinterpret_cast<void **>(&window_iaccessible_)); + DCHECK(SUCCEEDED(hr)); +} + +BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() { +} + +IAccessible* BrowserAccessibilityManagerWin::GetParentWindowIAccessible() { + return window_iaccessible_; +} + +void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent( + int type, + BrowserAccessibility* node) { + LONG event_id = EVENT_MIN; + switch (type) { + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_CHECK_STATE_CHANGED: + event_id = EVENT_OBJECT_STATECHANGE; + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_CHILDREN_CHANGED: + event_id = EVENT_OBJECT_REORDER; + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_FOCUS_CHANGED: + event_id = EVENT_OBJECT_FOCUS; + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_LOAD_COMPLETE: + event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE; + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_VALUE_CHANGED: + event_id = EVENT_OBJECT_VALUECHANGE; + break; + case ViewHostMsg_AccessibilityNotification_Type:: + NOTIFICATION_TYPE_SELECTED_TEXT_CHANGED: + event_id = IA2_EVENT_TEXT_CARET_MOVED; + break; + default: + NOTREACHED(); + break; + } + + NotifyWinEvent(event_id, GetParentView(), OBJID_CLIENT, node->child_id()); +} diff --git a/content/browser/accessibility/browser_accessibility_manager_win.h b/content/browser/accessibility/browser_accessibility_manager_win.h new file mode 100644 index 0000000..04ea53f --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_manager_win.h @@ -0,0 +1,48 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_ +#pragma once + +#include <oleacc.h> + +#include "base/win/scoped_comptr.h" +#include "content/browser/accessibility/browser_accessibility_manager.h" +#include "webkit/glue/webaccessibility.h" + +class BrowserAccessibilityWin; +struct ViewHostMsg_AccessibilityNotification_Params; + +using webkit_glue::WebAccessibility; + +// Manages a tree of BrowserAccessibilityWin objects. +class BrowserAccessibilityManagerWin : public BrowserAccessibilityManager { + public: + virtual ~BrowserAccessibilityManagerWin(); + + // Get a the default IAccessible for the parent window, does not make a + // new reference. + IAccessible* GetParentWindowIAccessible(); + + // BrowserAccessibilityManager methods + virtual void NotifyAccessibilityEvent(int type, BrowserAccessibility* node); + + private: + BrowserAccessibilityManagerWin( + HWND parent_window, + const WebAccessibility& src, + BrowserAccessibilityDelegate* delegate, + BrowserAccessibilityFactory* factory); + + // A default IAccessible instance for the parent window. + base::win::ScopedComPtr<IAccessible> window_iaccessible_; + + // Give BrowserAccessibilityManager::Create access to our constructor. + friend class BrowserAccessibilityManager; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerWin); +}; + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_WIN_H_ diff --git a/content/browser/accessibility/browser_accessibility_state.cc b/content/browser/accessibility/browser_accessibility_state.cc new file mode 100644 index 0000000..45b3a3b --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_state.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/accessibility/browser_accessibility_state.h" + +#include "base/memory/singleton.h" + +BrowserAccessibilityState::BrowserAccessibilityState() + : screen_reader_active_(false) { +} + +BrowserAccessibilityState::~BrowserAccessibilityState() { +} + +// static +BrowserAccessibilityState* BrowserAccessibilityState::GetInstance() { + return Singleton<BrowserAccessibilityState>::get(); +} + +void BrowserAccessibilityState::OnScreenReaderDetected() { + screen_reader_active_ = true; +} + +bool BrowserAccessibilityState::IsAccessibleBrowser() { + return screen_reader_active_; +} diff --git a/content/browser/accessibility/browser_accessibility_state.h b/content/browser/accessibility/browser_accessibility_state.h new file mode 100644 index 0000000..82cb8a3 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_state.h @@ -0,0 +1,53 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_H_ +#pragma once + +#include "base/basictypes.h" + +template <typename T> struct DefaultSingletonTraits; + +// The BrowserAccessibilityState class is used to determine if Chrome should be +// customized for users with assistive technology, such as screen readers. We +// modify the behavior of certain user interfaces to provide a better experience +// for screen reader users. The way we detect a screen reader program is +// different for each platform. +// +// Screen Reader Detection +// (1) On windows many screen reader detection mechinisms will give false +// positives like relying on the SPI_GETSCREENREADER system parameter. In Chrome +// we attempt to dynamically detect a MSAA client screen reader by calling +// NotifiyWinEvent in NativeWidgetWin with a custom ID and wait to see if the ID +// is requested by a subsequent call to WM_GETOBJECT. +// (2) On mac we detect dynamically if VoiceOver is running. We rely upon the +// undocumented accessibility attribute @"AXEnhancedUserInterface" which is set +// when VoiceOver is launched and unset when VoiceOver is closed. This is an +// improvement over reading defaults preference values (which has no callback +// mechanism). +class BrowserAccessibilityState { + public: + // Returns the singleton instance. + static BrowserAccessibilityState* GetInstance(); + + ~BrowserAccessibilityState(); + + // Called when screen reader client is detected. + void OnScreenReaderDetected(); + + // Returns true if the browser should be customized for accessibility. + bool IsAccessibleBrowser(); + + private: + BrowserAccessibilityState(); + friend struct DefaultSingletonTraits<BrowserAccessibilityState>; + + // Set to true when a screen reader client is detected. + bool screen_reader_active_; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityState); +}; + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_STATE_H_ diff --git a/content/browser/accessibility/browser_accessibility_win.cc b/content/browser/accessibility/browser_accessibility_win.cc new file mode 100644 index 0000000..8cd1566 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_win.cc @@ -0,0 +1,1634 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/accessibility/browser_accessibility_win.h" + +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_manager_win.h" +#include "net/base/escape.h" + +using webkit_glue::WebAccessibility; + +// The GUID for the ISimpleDOM service is not defined in the IDL files. +// This is taken directly from the Mozilla sources +// (accessible/src/msaa/nsAccessNodeWrap.cpp) and it's also documented at: +// http://developer.mozilla.org/en/Accessibility/AT-APIs/ImplementationFeatures/MSAA + +const GUID GUID_ISimpleDOM = { + 0x0c539790, 0x12e4, 0x11cf, + 0xb6, 0x61, 0x00, 0xaa, 0x00, 0x4c, 0xd6, 0xd8}; + +// static +BrowserAccessibility* BrowserAccessibility::Create() { + CComObject<BrowserAccessibilityWin>* instance; + HRESULT hr = CComObject<BrowserAccessibilityWin>::CreateInstance(&instance); + DCHECK(SUCCEEDED(hr)); + return instance->NewReference(); +} + +BrowserAccessibilityWin* BrowserAccessibility::toBrowserAccessibilityWin() { + return static_cast<BrowserAccessibilityWin*>(this); +} + +BrowserAccessibilityWin::BrowserAccessibilityWin() + : ia_role_(0), + ia_state_(0), + ia2_role_(0), + ia2_state_(0) { +} + +BrowserAccessibilityWin::~BrowserAccessibilityWin() { +} + +// +// IAccessible methods. +// +// Conventions: +// * Always test for instance_active_ first and return E_FAIL if it's false. +// * Always check for invalid arguments first, even if they're unused. +// * Return S_FALSE if the only output is a string argument and it's empty. +// + +HRESULT BrowserAccessibilityWin::accDoDefaultAction(VARIANT var_id) { + if (!instance_active_) + return E_FAIL; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + manager_->DoDefaultAction(*target); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::accHitTest(LONG x_left, + LONG y_top, + VARIANT* child) { + if (!instance_active_) + return E_FAIL; + + if (!child) + return E_INVALIDARG; + + gfx::Point point(x_left, y_top); + if (!GetBoundsRect().Contains(point)) { + // Return S_FALSE and VT_EMPTY when the outside the object's boundaries. + child->vt = VT_EMPTY; + return S_FALSE; + } + + BrowserAccessibility* result = BrowserAccessibilityForPoint(point); + if (result == this) { + // Point is within this object. + child->vt = VT_I4; + child->lVal = CHILDID_SELF; + } else { + child->vt = VT_DISPATCH; + child->pdispVal = result->toBrowserAccessibilityWin()->NewReference(); + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::accLocation(LONG* x_left, LONG* y_top, + LONG* width, LONG* height, + VARIANT var_id) { + if (!instance_active_) + return E_FAIL; + + if (!x_left || !y_top || !width || !height) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + gfx::Rect bounds = target->GetBoundsRect(); + *x_left = bounds.x(); + *y_top = bounds.y(); + *width = bounds.width(); + *height = bounds.height(); + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::accNavigate( + LONG nav_dir, VARIANT start, VARIANT* end) { + BrowserAccessibilityWin* target = GetTargetFromChildID(start); + if (!target) + return E_INVALIDARG; + + if ((nav_dir == NAVDIR_LASTCHILD || nav_dir == NAVDIR_FIRSTCHILD) && + start.lVal != CHILDID_SELF) { + // MSAA states that navigating to first/last child can only be from self. + return E_INVALIDARG; + } + + BrowserAccessibility* result = NULL; + switch (nav_dir) { + case NAVDIR_DOWN: + case NAVDIR_UP: + case NAVDIR_LEFT: + case NAVDIR_RIGHT: + // These directions are not implemented, matching Mozilla and IE. + return E_NOTIMPL; + case NAVDIR_FIRSTCHILD: + if (!target->children_.empty()) + result = target->children_.front(); + break; + case NAVDIR_LASTCHILD: + if (!target->children_.empty()) + result = target->children_.back(); + break; + case NAVDIR_NEXT: + result = target->GetNextSibling(); + break; + case NAVDIR_PREVIOUS: + result = target->GetPreviousSibling(); + break; + } + + if (!result) { + end->vt = VT_EMPTY; + return S_FALSE; + } + + end->vt = VT_DISPATCH; + end->pdispVal = result->toBrowserAccessibilityWin()->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accChild(VARIANT var_child, + IDispatch** disp_child) { + if (!instance_active_) + return E_FAIL; + + if (!disp_child) + return E_INVALIDARG; + + *disp_child = NULL; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_child); + if (!target) + return E_INVALIDARG; + + (*disp_child) = target->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accChildCount(LONG* child_count) { + if (!instance_active_) + return E_FAIL; + + if (!child_count) + return E_INVALIDARG; + + *child_count = children_.size(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accDefaultAction(VARIANT var_id, + BSTR* def_action) { + if (!instance_active_) + return E_FAIL; + + if (!def_action) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetAttributeAsBstr( + WebAccessibility::ATTR_SHORTCUT, def_action); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accDescription(VARIANT var_id, + BSTR* desc) { + if (!instance_active_) + return E_FAIL; + + if (!desc) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetAttributeAsBstr(WebAccessibility::ATTR_DESCRIPTION, desc); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accFocus(VARIANT* focus_child) { + if (!instance_active_) + return E_FAIL; + + if (!focus_child) + return E_INVALIDARG; + + BrowserAccessibilityWin* focus = static_cast<BrowserAccessibilityWin*>( + manager_->GetFocus(this)); + if (focus == this) { + focus_child->vt = VT_I4; + focus_child->lVal = CHILDID_SELF; + } else if (focus == NULL) { + focus_child->vt = VT_EMPTY; + } else { + focus_child->vt = VT_DISPATCH; + focus_child->pdispVal = focus->NewReference(); + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accHelp(VARIANT var_id, BSTR* help) { + if (!instance_active_) + return E_FAIL; + + if (!help) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetAttributeAsBstr(WebAccessibility::ATTR_HELP, help); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accKeyboardShortcut(VARIANT var_id, + BSTR* acc_key) { + if (!instance_active_) + return E_FAIL; + + if (!acc_key) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + return target->GetAttributeAsBstr(WebAccessibility::ATTR_SHORTCUT, acc_key); +} + +STDMETHODIMP BrowserAccessibilityWin::get_accName(VARIANT var_id, BSTR* name) { + if (!instance_active_) + return E_FAIL; + + if (!name) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + if (target->name_.empty()) + return S_FALSE; + + *name = SysAllocString(target->name_.c_str()); + + DCHECK(*name); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accParent(IDispatch** disp_parent) { + if (!instance_active_) + return E_FAIL; + + if (!disp_parent) + return E_INVALIDARG; + + IAccessible* parent = parent_->toBrowserAccessibilityWin(); + if (parent == NULL) { + // This happens if we're the root of the tree; + // return the IAccessible for the window. + parent = manager_->toBrowserAccessibilityManagerWin()-> + GetParentWindowIAccessible(); + } + + parent->AddRef(); + *disp_parent = parent; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accRole( + VARIANT var_id, VARIANT* role) { + if (!instance_active_) + return E_FAIL; + + if (!role) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + if (!target->role_name_.empty()) { + role->vt = VT_BSTR; + role->bstrVal = SysAllocString(target->role_name_.c_str()); + } else { + role->vt = VT_I4; + role->lVal = target->ia_role_; + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accState(VARIANT var_id, + VARIANT* state) { + if (!instance_active_) + return E_FAIL; + + if (!state) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + state->vt = VT_I4; + state->lVal = target->ia_state_; + if (manager_->GetFocus(NULL) == this) + state->lVal |= STATE_SYSTEM_FOCUSED; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accValue( + VARIANT var_id, BSTR* value) { + if (!instance_active_) + return E_FAIL; + + if (!value) + return E_INVALIDARG; + + BrowserAccessibilityWin* target = GetTargetFromChildID(var_id); + if (!target) + return E_INVALIDARG; + + *value = SysAllocString(target->value_.c_str()); + + DCHECK(*value); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accHelpTopic( + BSTR* help_file, VARIANT var_id, LONG* topic_id) { + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_accSelection(VARIANT* selected) { + if (!instance_active_) + return E_FAIL; + + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::accSelect( + LONG flags_sel, VARIANT var_id) { + if (!instance_active_) + return E_FAIL; + + if (flags_sel & SELFLAG_TAKEFOCUS) { + manager_->SetFocus(this, true); + return S_OK; + } + + return S_FALSE; +} + +// +// IAccessible2 methods. +// + +STDMETHODIMP BrowserAccessibilityWin::role(LONG* role) { + if (!instance_active_) + return E_FAIL; + + if (!role) + return E_INVALIDARG; + + *role = ia2_role_; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_attributes(BSTR* attributes) { + if (!instance_active_) + return E_FAIL; + + if (!attributes) + return E_INVALIDARG; + + // Follow Firefox's convention, which is to return a set of key-value pairs + // separated by semicolons, with a colon between the key and the value. + string16 str; + for (unsigned int i = 0; i < html_attributes_.size(); i++) { + if (i != 0) + str += L';'; + str += Escape(html_attributes_[i].first); + str += L':'; + str += Escape(html_attributes_[i].second); + } + + if (str.empty()) + return S_FALSE; + + *attributes = SysAllocString(str.c_str()); + DCHECK(*attributes); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_states(AccessibleStates* states) { + if (!instance_active_) + return E_FAIL; + + if (!states) + return E_INVALIDARG; + + *states = ia2_state_; + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_uniqueID(LONG* unique_id) { + if (!instance_active_) + return E_FAIL; + + if (!unique_id) + return E_INVALIDARG; + + *unique_id = child_id_; + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_windowHandle(HWND* window_handle) { + if (!instance_active_) + return E_FAIL; + + if (!window_handle) + return E_INVALIDARG; + + *window_handle = manager_->GetParentView(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_indexInParent(LONG* index_in_parent) { + if (!instance_active_) + return E_FAIL; + + if (!index_in_parent) + return E_INVALIDARG; + + *index_in_parent = index_in_parent_; + return S_OK; +} + +// +// IAccessibleImage methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_description(BSTR* desc) { + if (!instance_active_) + return E_FAIL; + + if (!desc) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DESCRIPTION, desc); +} + +STDMETHODIMP BrowserAccessibilityWin::get_imagePosition( + enum IA2CoordinateType coordinate_type, LONG* x, LONG* y) { + if (!instance_active_) + return E_FAIL; + + if (!x || !y) + return E_INVALIDARG; + + if (coordinate_type == IA2_COORDTYPE_SCREEN_RELATIVE) { + HWND parent_hwnd = manager_->GetParentView(); + POINT top_left = {0, 0}; + ::ClientToScreen(parent_hwnd, &top_left); + *x = location_.x() + top_left.x; + *y = location_.y() + top_left.y; + } else if (coordinate_type == IA2_COORDTYPE_PARENT_RELATIVE) { + *x = location_.x(); + *y = location_.y(); + if (parent_) { + *x -= parent_->location().x(); + *y -= parent_->location().y(); + } + } else { + return E_INVALIDARG; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_imageSize(LONG* height, LONG* width) { + if (!instance_active_) + return E_FAIL; + + if (!height || !width) + return E_INVALIDARG; + + *height = location_.height(); + *width = location_.width(); + return S_OK; +} + +// +// IAccessibleText methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_nCharacters(LONG* n_characters) { + if (!instance_active_) + return E_FAIL; + + if (!n_characters) + return E_INVALIDARG; + + if (role_ == WebAccessibility::ROLE_TEXT_FIELD || + role_ == WebAccessibility::ROLE_TEXTAREA) { + *n_characters = value_.length(); + } else { + *n_characters = name_.length(); + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_caretOffset(LONG* offset) { + if (!instance_active_) + return E_FAIL; + + if (!offset) + return E_INVALIDARG; + + if (role_ == WebAccessibility::ROLE_TEXT_FIELD || + role_ == WebAccessibility::ROLE_TEXTAREA) { + int sel_start = 0; + if (GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_START, &sel_start)) { + *offset = sel_start; + } else { + *offset = 0; + } + } else { + *offset = 0; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_nSelections(LONG* n_selections) { + if (!instance_active_) + return E_FAIL; + + if (!n_selections) + return E_INVALIDARG; + + if (role_ == WebAccessibility::ROLE_TEXT_FIELD || + role_ == WebAccessibility::ROLE_TEXTAREA) { + int sel_start = 0; + int sel_end = 0; + if (GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_START, &sel_start) && + GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_END, &sel_end) && + sel_start != sel_end) { + *n_selections = 1; + } else { + *n_selections = 0; + } + } else { + *n_selections = 0; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_selection(LONG selection_index, + LONG* start_offset, + LONG* end_offset) { + if (!instance_active_) + return E_FAIL; + + if (!start_offset || !end_offset || selection_index != 0) + return E_INVALIDARG; + + if (role_ == WebAccessibility::ROLE_TEXT_FIELD || + role_ == WebAccessibility::ROLE_TEXTAREA) { + int sel_start = 0; + int sel_end = 0; + if (GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_START, &sel_start) && + GetAttributeAsInt(WebAccessibility::ATTR_TEXT_SEL_END, &sel_end)) { + *start_offset = sel_start; + *end_offset = sel_end; + } else { + *start_offset = 0; + *end_offset = 0; + } + } else { + *start_offset = 0; + *end_offset = 0; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_text( + LONG start_offset, LONG end_offset, BSTR* text) { + if (!instance_active_) + return E_FAIL; + + if (!text) + return E_INVALIDARG; + + const string16& text_str = TextForIAccessibleText(); + + // Handle special text offsets. + HandleSpecialTextOffset(text_str, &start_offset); + HandleSpecialTextOffset(text_str, &end_offset); + + // The spec allows the arguments to be reversed. + if (start_offset > end_offset) { + LONG tmp = start_offset; + start_offset = end_offset; + end_offset = tmp; + } + + // The spec does not allow the start or end offsets to be out or range; + // we must return an error if so. + LONG len = text_str.length(); + if (start_offset < 0) + return E_INVALIDARG; + if (end_offset > len) + return E_INVALIDARG; + + string16 substr = text_str.substr(start_offset, end_offset - start_offset); + if (substr.empty()) + return S_FALSE; + + *text = SysAllocString(substr.c_str()); + DCHECK(*text); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_textAtOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text) { + if (!instance_active_) + return E_FAIL; + + if (!start_offset || !end_offset || !text) + return E_INVALIDARG; + + // The IAccessible2 spec says we don't have to implement the "sentence" + // boundary type, we can just let the screenreader handle it. + if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { + *start_offset = 0; + *end_offset = 0; + *text = NULL; + return S_FALSE; + } + + const string16& text_str = TextForIAccessibleText(); + + *start_offset = FindBoundary(text_str, boundary_type, offset, -1); + *end_offset = FindBoundary(text_str, boundary_type, offset, 1); + return get_text(*start_offset, *end_offset, text); +} + +STDMETHODIMP BrowserAccessibilityWin::get_textBeforeOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text) { + if (!instance_active_) + return E_FAIL; + + if (!start_offset || !end_offset || !text) + return E_INVALIDARG; + + // The IAccessible2 spec says we don't have to implement the "sentence" + // boundary type, we can just let the screenreader handle it. + if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { + *start_offset = 0; + *end_offset = 0; + *text = NULL; + return S_FALSE; + } + + const string16& text_str = TextForIAccessibleText(); + + *start_offset = FindBoundary(text_str, boundary_type, offset, -1); + *end_offset = offset; + return get_text(*start_offset, *end_offset, text); +} + +STDMETHODIMP BrowserAccessibilityWin::get_textAfterOffset( + LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text) { + if (!instance_active_) + return E_FAIL; + + if (!start_offset || !end_offset || !text) + return E_INVALIDARG; + + // The IAccessible2 spec says we don't have to implement the "sentence" + // boundary type, we can just let the screenreader handle it. + if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { + *start_offset = 0; + *end_offset = 0; + *text = NULL; + return S_FALSE; + } + + const string16& text_str = TextForIAccessibleText(); + + *start_offset = offset; + *end_offset = FindBoundary(text_str, boundary_type, offset, 1); + return get_text(*start_offset, *end_offset, text); +} + +// +// ISimpleDOMDocument methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_URL(BSTR* url) { + if (!instance_active_) + return E_FAIL; + + if (!url) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DOC_URL, url); +} + +STDMETHODIMP BrowserAccessibilityWin::get_title(BSTR* title) { + if (!instance_active_) + return E_FAIL; + + if (!title) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DOC_TITLE, title); +} + +STDMETHODIMP BrowserAccessibilityWin::get_mimeType(BSTR* mime_type) { + if (!instance_active_) + return E_FAIL; + + if (!mime_type) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DOC_MIMETYPE, mime_type); +} + +STDMETHODIMP BrowserAccessibilityWin::get_docType(BSTR* doc_type) { + if (!instance_active_) + return E_FAIL; + + if (!doc_type) + return E_INVALIDARG; + + return GetAttributeAsBstr(WebAccessibility::ATTR_DOC_DOCTYPE, doc_type); +} + +// +// ISimpleDOMNode methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_nodeInfo( + BSTR* node_name, + short* name_space_id, + BSTR* node_value, + unsigned int* num_children, + unsigned int* unique_id, + unsigned short* node_type) { + if (!instance_active_) + return E_FAIL; + + if (!node_name || !name_space_id || !node_value || !num_children || + !unique_id || !node_type) { + return E_INVALIDARG; + } + + string16 tag; + if (GetAttribute(WebAccessibility::ATTR_HTML_TAG, &tag)) + *node_name = SysAllocString(tag.c_str()); + else + *node_name = NULL; + + *name_space_id = 0; + *node_value = SysAllocString(value_.c_str()); + *num_children = children_.size(); + *unique_id = child_id_; + + if (ia_role_ == ROLE_SYSTEM_DOCUMENT) { + *node_type = NODETYPE_DOCUMENT; + } else if (ia_role_ == ROLE_SYSTEM_TEXT && + ((ia2_state_ & IA2_STATE_EDITABLE) == 0)) { + *node_type = NODETYPE_TEXT; + } else { + *node_type = NODETYPE_ELEMENT; + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_attributes( + unsigned short max_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values, + unsigned short* num_attribs) { + if (!instance_active_) + return E_FAIL; + + if (!attrib_names || !name_space_id || !attrib_values || !num_attribs) + return E_INVALIDARG; + + *num_attribs = max_attribs; + if (*num_attribs > html_attributes_.size()) + *num_attribs = html_attributes_.size(); + + for (unsigned short i = 0; i < *num_attribs; ++i) { + attrib_names[i] = SysAllocString(html_attributes_[i].first.c_str()); + name_space_id[i] = 0; + attrib_values[i] = SysAllocString(html_attributes_[i].second.c_str()); + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_attributesForNames( + unsigned short num_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values) { + if (!instance_active_) + return E_FAIL; + + if (!attrib_names || !name_space_id || !attrib_values) + return E_INVALIDARG; + + for (unsigned short i = 0; i < num_attribs; ++i) { + name_space_id[i] = 0; + bool found = false; + string16 name = (LPCWSTR)attrib_names[i]; + for (unsigned int j = 0; j < html_attributes_.size(); ++j) { + if (html_attributes_[j].first == name) { + attrib_values[i] = SysAllocString(html_attributes_[j].second.c_str()); + found = true; + break; + } + } + if (!found) { + attrib_values[i] = NULL; + } + } + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_computedStyle( + unsigned short max_style_properties, + boolean use_alternate_view, + BSTR *style_properties, + BSTR *style_values, + unsigned short *num_style_properties) { + if (!instance_active_) + return E_FAIL; + + if (!style_properties || !style_values) + return E_INVALIDARG; + + // We only cache a single style property for now: DISPLAY + + if (max_style_properties == 0 || + !HasAttribute(WebAccessibility::ATTR_DISPLAY)) { + *num_style_properties = 0; + return S_OK; + } + + string16 display; + GetAttribute(WebAccessibility::ATTR_DISPLAY, &display); + *num_style_properties = 1; + style_properties[0] = SysAllocString(L"display"); + style_values[0] = SysAllocString(display.c_str()); + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_computedStyleForProperties( + unsigned short num_style_properties, + boolean use_alternate_view, + BSTR* style_properties, + BSTR* style_values) { + if (!instance_active_) + return E_FAIL; + + if (!style_properties || !style_values) + return E_INVALIDARG; + + // We only cache a single style property for now: DISPLAY + + for (unsigned short i = 0; i < num_style_properties; i++) { + string16 name = (LPCWSTR)style_properties[i]; + StringToLowerASCII(&name); + if (name == L"display") { + string16 display; + GetAttribute(WebAccessibility::ATTR_DISPLAY, &display); + style_values[i] = SysAllocString(display.c_str()); + } else { + style_values[i] = NULL; + } + } + + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::scrollTo(boolean placeTopLeft) { + return E_NOTIMPL; +} + +STDMETHODIMP BrowserAccessibilityWin::get_parentNode(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + *node = parent_->toBrowserAccessibilityWin()->NewReference(); + return S_OK; +} + +STDMETHODIMP BrowserAccessibilityWin::get_firstChild(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (children_.size()) { + *node = children_[0]->toBrowserAccessibilityWin()->NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +STDMETHODIMP BrowserAccessibilityWin::get_lastChild(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (children_.size()) { + *node = children_[children_.size() - 1]->toBrowserAccessibilityWin()-> + NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +STDMETHODIMP BrowserAccessibilityWin::get_previousSibling( + ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (parent_ && index_in_parent_ > 0) { + *node = parent_->children()[index_in_parent_ - 1]-> + toBrowserAccessibilityWin()->NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +STDMETHODIMP BrowserAccessibilityWin::get_nextSibling(ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (parent_ && + index_in_parent_ >= 0 && + index_in_parent_ < static_cast<int>(parent_->children().size()) - 1) { + *node = parent_->children()[index_in_parent_ + 1]-> + toBrowserAccessibilityWin()->NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +STDMETHODIMP BrowserAccessibilityWin::get_childAt( + unsigned int child_index, + ISimpleDOMNode** node) { + if (!instance_active_) + return E_FAIL; + + if (!node) + return E_INVALIDARG; + + if (child_index < children_.size()) { + *node = children_[child_index]->toBrowserAccessibilityWin()->NewReference(); + return S_OK; + } else { + *node = NULL; + return S_FALSE; + } +} + +// +// ISimpleDOMText methods. +// + +STDMETHODIMP BrowserAccessibilityWin::get_domText(BSTR* dom_text) { + if (!instance_active_) + return E_FAIL; + + if (!dom_text) + return E_INVALIDARG; + + if (name_.empty()) + return S_FALSE; + + *dom_text = SysAllocString(name_.c_str()); + DCHECK(*dom_text); + return S_OK; +} + +// +// IServiceProvider methods. +// + +STDMETHODIMP BrowserAccessibilityWin::QueryService( + REFGUID guidService, REFIID riid, void** object) { + if (!instance_active_) + return E_FAIL; + + if (guidService == IID_IAccessible || + guidService == IID_IAccessible2 || + guidService == IID_IAccessibleImage || + guidService == IID_IAccessibleText || + guidService == IID_ISimpleDOMDocument || + guidService == IID_ISimpleDOMNode || + guidService == IID_ISimpleDOMText || + guidService == GUID_ISimpleDOM) { + return QueryInterface(riid, object); + } + + *object = NULL; + return E_FAIL; +} + +// +// CComObjectRootEx methods. +// + +HRESULT WINAPI BrowserAccessibilityWin::InternalQueryInterface( + void* this_ptr, + const _ATL_INTMAP_ENTRY* entries, + REFIID iid, + void** object) { + if (iid == IID_IAccessibleText) { + if (ia_role_ != ROLE_SYSTEM_LINK && ia_role_ != ROLE_SYSTEM_TEXT) { + *object = NULL; + return E_NOINTERFACE; + } + } else if (iid == IID_IAccessibleImage) { + if (ia_role_ != ROLE_SYSTEM_GRAPHIC) { + *object = NULL; + return E_NOINTERFACE; + } + } else if (iid == IID_ISimpleDOMDocument) { + if (ia_role_ != ROLE_SYSTEM_DOCUMENT) { + *object = NULL; + return E_NOINTERFACE; + } + } + + return CComObjectRootBase::InternalQueryInterface( + this_ptr, entries, iid, object); +} + +// +// Private methods. +// + +// Initialize this object and mark it as active. +void BrowserAccessibilityWin::Initialize() { + BrowserAccessibility::Initialize(); + + InitRoleAndState(); + + // Expose headings levels to NVDA with the "level" object attribute. + if (role_ == WebAccessibility::ROLE_HEADING && role_name_.size() == 2 && + IsAsciiDigit(role_name_[1])) { + html_attributes_.push_back(std::make_pair(L"level", role_name_.substr(1))); + } + + // Expose the "display" object attribute. + string16 display; + if (GetAttribute(WebAccessibility::ATTR_DISPLAY, &display)) + html_attributes_.push_back(std::make_pair(L"display", display)); + + // If this is static text, put the text in the name rather than the value. + if (role_ == WebAccessibility::ROLE_STATIC_TEXT && name_.empty()) + name_.swap(value_); + + // If this object doesn't have a name but it does have a description, + // use the description as its name - because some screen readers only + // announce the name. + if (name_.empty() && HasAttribute(WebAccessibility::ATTR_DESCRIPTION)) + GetAttribute(WebAccessibility::ATTR_DESCRIPTION, &name_); + + // If this doesn't have a value and is linked then set its value to the url + // attribute. This allows screen readers to read an empty link's destination. + if (value_.empty() && (ia_state_ & STATE_SYSTEM_LINKED) && + HasAttribute(WebAccessibility::ATTR_URL)) { + GetAttribute(WebAccessibility::ATTR_URL, &value_); + } +} + +void BrowserAccessibilityWin::NativeAddReference() { + AddRef(); +} + +void BrowserAccessibilityWin::NativeReleaseReference() { + Release(); +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::NewReference() { + AddRef(); + return this; +} + +BrowserAccessibilityWin* BrowserAccessibilityWin::GetTargetFromChildID( + const VARIANT& var_id) { + if (var_id.vt != VT_I4) + return NULL; + + LONG child_id = var_id.lVal; + if (child_id == CHILDID_SELF) + return this; + + if (child_id >= 1 && child_id <= static_cast<LONG>(children_.size())) + return children_[child_id - 1]->toBrowserAccessibilityWin(); + + return manager_->GetFromChildID(child_id)->toBrowserAccessibilityWin(); +} + +HRESULT BrowserAccessibilityWin::GetAttributeAsBstr( + WebAccessibility::Attribute attribute, BSTR* value_bstr) { + string16 str; + + if (!GetAttribute(attribute, &str)) + return S_FALSE; + + if (str.empty()) + return S_FALSE; + + *value_bstr = SysAllocString(str.c_str()); + DCHECK(*value_bstr); + + return S_OK; +} + +string16 BrowserAccessibilityWin::Escape(const string16& str) { + return EscapeQueryParamValueUTF8(str, false); +} + +const string16& BrowserAccessibilityWin::TextForIAccessibleText() { + if (role_ == WebAccessibility::ROLE_TEXT_FIELD || + role_ == WebAccessibility::ROLE_TEXTAREA) { + return value_; + } else { + return name_; + } +} + +void BrowserAccessibilityWin::HandleSpecialTextOffset( + const string16& text, LONG* offset) { + if (*offset == IA2_TEXT_OFFSET_LENGTH) { + *offset = static_cast<LONG>(text.size()); + } else if (*offset == IA2_TEXT_OFFSET_CARET) { + get_caretOffset(offset); + } +} + +LONG BrowserAccessibilityWin::FindBoundary( + const string16& text, + IA2TextBoundaryType boundary, + LONG start_offset, + LONG direction) { + LONG text_size = static_cast<LONG>(text.size()); + DCHECK((start_offset >= 0 && start_offset <= text_size) || + start_offset == IA2_TEXT_OFFSET_LENGTH || + start_offset == IA2_TEXT_OFFSET_CARET); + DCHECK(direction == 1 || direction == -1); + + HandleSpecialTextOffset(text, &start_offset); + + if (boundary == IA2_TEXT_BOUNDARY_CHAR) { + if (direction == 1 && start_offset < text_size) + return start_offset + 1; + else + return start_offset; + } else if (boundary == IA2_TEXT_BOUNDARY_LINE) { + if (direction == 1) { + for (int j = 0; j < static_cast<int>(line_breaks_.size()); j++) { + if (line_breaks_[j] > start_offset) + return line_breaks_[j]; + } + return text_size; + } else { + for (int j = static_cast<int>(line_breaks_.size()) - 1; j >= 0; j--) { + if (line_breaks_[j] <= start_offset) + return line_breaks_[j]; + } + return 0; + } + } + + LONG result = start_offset; + for (;;) { + LONG pos; + if (direction == 1) { + if (result >= text_size) + return text_size; + pos = result; + } else { + if (result <= 0) + return 0; + pos = result - 1; + } + + switch (boundary) { + case IA2_TEXT_BOUNDARY_CHAR: + case IA2_TEXT_BOUNDARY_LINE: + NOTREACHED(); // These are handled above. + break; + case IA2_TEXT_BOUNDARY_WORD: + if (IsWhitespace(text[pos])) + return result; + break; + case IA2_TEXT_BOUNDARY_PARAGRAPH: + if (text[pos] == '\n') + return result; + case IA2_TEXT_BOUNDARY_SENTENCE: + // Note that we don't actually have to implement sentence support; + // currently IAccessibleText functions return S_FALSE so that + // screenreaders will handle it on their own. + if ((text[pos] == '.' || text[pos] == '!' || text[pos] == '?') && + (pos == text_size - 1 || IsWhitespace(text[pos + 1]))) { + return result; + } + case IA2_TEXT_BOUNDARY_ALL: + default: + break; + } + + if (direction > 0) { + result++; + } else if (direction < 0) { + result--; + } else { + NOTREACHED(); + return result; + } + } +} + +void BrowserAccessibilityWin::InitRoleAndState() { + ia_state_ = 0; + ia2_state_ = IA2_STATE_OPAQUE; + + if ((state_ >> WebAccessibility::STATE_CHECKED) & 1) + ia_state_ |= STATE_SYSTEM_CHECKED; + if ((state_ >> WebAccessibility::STATE_COLLAPSED) & 1) + ia_state_|= STATE_SYSTEM_COLLAPSED; + if ((state_ >> WebAccessibility::STATE_EXPANDED) & 1) + ia_state_|= STATE_SYSTEM_EXPANDED; + if ((state_ >> WebAccessibility::STATE_FOCUSABLE) & 1) + ia_state_|= STATE_SYSTEM_FOCUSABLE; + if ((state_ >> WebAccessibility::STATE_HASPOPUP) & 1) + ia_state_|= STATE_SYSTEM_HASPOPUP; + if ((state_ >> WebAccessibility::STATE_HOTTRACKED) & 1) + ia_state_|= STATE_SYSTEM_HOTTRACKED; + if ((state_ >> WebAccessibility::STATE_INDETERMINATE) & 1) + ia_state_|= STATE_SYSTEM_INDETERMINATE; + if ((state_ >> WebAccessibility::STATE_INVISIBLE) & 1) + ia_state_|= STATE_SYSTEM_INVISIBLE; + if ((state_ >> WebAccessibility::STATE_LINKED) & 1) + ia_state_|= STATE_SYSTEM_LINKED; + if ((state_ >> WebAccessibility::STATE_MULTISELECTABLE) & 1) + ia_state_|= STATE_SYSTEM_MULTISELECTABLE; + // TODO(ctguil): Support STATE_SYSTEM_EXTSELECTABLE/accSelect. + if ((state_ >> WebAccessibility::STATE_OFFSCREEN) & 1) + ia_state_|= STATE_SYSTEM_OFFSCREEN; + if ((state_ >> WebAccessibility::STATE_PRESSED) & 1) + ia_state_|= STATE_SYSTEM_PRESSED; + if ((state_ >> WebAccessibility::STATE_PROTECTED) & 1) + ia_state_|= STATE_SYSTEM_PROTECTED; + if ((state_ >> WebAccessibility::STATE_SELECTABLE) & 1) + ia_state_|= STATE_SYSTEM_SELECTABLE; + if ((state_ >> WebAccessibility::STATE_SELECTED) & 1) + ia_state_|= STATE_SYSTEM_SELECTED; + if ((state_ >> WebAccessibility::STATE_READONLY) & 1) + ia_state_|= STATE_SYSTEM_READONLY; + if ((state_ >> WebAccessibility::STATE_TRAVERSED) & 1) + ia_state_|= STATE_SYSTEM_TRAVERSED; + if ((state_ >> WebAccessibility::STATE_BUSY) & 1) + ia_state_|= STATE_SYSTEM_BUSY; + if ((state_ >> WebAccessibility::STATE_UNAVAILABLE) & 1) + ia_state_|= STATE_SYSTEM_UNAVAILABLE; + + string16 html_tag; + GetAttribute(WebAccessibility::ATTR_HTML_TAG, &html_tag); + ia_role_ = 0; + ia2_role_ = 0; + switch (role_) { + case WebAccessibility::ROLE_ALERT: + case WebAccessibility::ROLE_ALERT_DIALOG: + ia_role_ = ROLE_SYSTEM_ALERT; + break; + case WebAccessibility::ROLE_APPLICATION: + ia_role_ = ROLE_SYSTEM_APPLICATION; + break; + case WebAccessibility::ROLE_ARTICLE: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_BUSY_INDICATOR: + ia_role_ = ROLE_SYSTEM_ANIMATION; + break; + case WebAccessibility::ROLE_BUTTON: + ia_role_ = ROLE_SYSTEM_PUSHBUTTON; + break; + case WebAccessibility::ROLE_CELL: + ia_role_ = ROLE_SYSTEM_CELL; + break; + case WebAccessibility::ROLE_CHECKBOX: + ia_role_ = ROLE_SYSTEM_CHECKBUTTON; + break; + case WebAccessibility::ROLE_COLOR_WELL: + ia_role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_COLOR_CHOOSER; + break; + case WebAccessibility::ROLE_COLUMN: + ia_role_ = ROLE_SYSTEM_COLUMN; + break; + case WebAccessibility::ROLE_COLUMN_HEADER: + ia_role_ = ROLE_SYSTEM_COLUMNHEADER; + break; + case WebAccessibility::ROLE_COMBO_BOX: + ia_role_ = ROLE_SYSTEM_COMBOBOX; + break; + case WebAccessibility::ROLE_DEFINITION_LIST_DEFINITION: + role_name_ = html_tag; + ia2_role_ = IA2_ROLE_PARAGRAPH; + break; + case WebAccessibility::ROLE_DEFINITION_LIST_TERM: + ia_role_ = ROLE_SYSTEM_LISTITEM; + break; + case WebAccessibility::ROLE_DIALOG: + ia_role_ = ROLE_SYSTEM_DIALOG; + break; + case WebAccessibility::ROLE_DISCLOSURE_TRIANGLE: + ia_role_ = ROLE_SYSTEM_OUTLINEBUTTON; + break; + case WebAccessibility::ROLE_DOCUMENT: + case WebAccessibility::ROLE_WEB_AREA: + ia_role_ = ROLE_SYSTEM_DOCUMENT; + ia_state_|= STATE_SYSTEM_READONLY; + ia_state_|= STATE_SYSTEM_FOCUSABLE; + break; + case WebAccessibility::ROLE_EDITABLE_TEXT: + ia_role_ = ROLE_SYSTEM_TEXT; + ia2_state_ |= IA2_STATE_SINGLE_LINE; + ia2_state_ |= IA2_STATE_EDITABLE; + break; + case WebAccessibility::ROLE_GRID: + ia_role_ = ROLE_SYSTEM_TABLE; + break; + case WebAccessibility::ROLE_GROUP: + if (html_tag == L"li") { + ia_role_ = ROLE_SYSTEM_LISTITEM; + } else { + if (html_tag.empty()) + role_name_ = L"div"; + else + role_name_ = html_tag; + ia2_role_ = IA2_ROLE_SECTION; + } + break; + case WebAccessibility::ROLE_GROW_AREA: + ia_role_ = ROLE_SYSTEM_GRIP; + break; + case WebAccessibility::ROLE_HEADING: + role_name_ = html_tag; + ia2_role_ = IA2_ROLE_HEADING; + break; + case WebAccessibility::ROLE_IMAGE: + ia_role_ = ROLE_SYSTEM_GRAPHIC; + break; + case WebAccessibility::ROLE_IMAGE_MAP: + role_name_ = html_tag; + ia2_role_ = IA2_ROLE_IMAGE_MAP; + break; + case WebAccessibility::ROLE_IMAGE_MAP_LINK: + ia_role_ = ROLE_SYSTEM_LINK; + ia_state_|= STATE_SYSTEM_LINKED; + break; + case WebAccessibility::ROLE_LANDMARK_APPLICATION: + case WebAccessibility::ROLE_LANDMARK_BANNER: + case WebAccessibility::ROLE_LANDMARK_COMPLEMENTARY: + case WebAccessibility::ROLE_LANDMARK_CONTENTINFO: + case WebAccessibility::ROLE_LANDMARK_MAIN: + case WebAccessibility::ROLE_LANDMARK_NAVIGATION: + case WebAccessibility::ROLE_LANDMARK_SEARCH: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_LINK: + case WebAccessibility::ROLE_WEBCORE_LINK: + ia_role_ = ROLE_SYSTEM_LINK; + ia_state_|= STATE_SYSTEM_LINKED; + break; + case WebAccessibility::ROLE_LIST: + ia_role_ = ROLE_SYSTEM_LIST; + break; + case WebAccessibility::ROLE_LISTBOX: + ia_role_ = ROLE_SYSTEM_LIST; + break; + case WebAccessibility::ROLE_LISTBOX_OPTION: + case WebAccessibility::ROLE_LIST_ITEM: + case WebAccessibility::ROLE_LIST_MARKER: + ia_role_ = ROLE_SYSTEM_LISTITEM; + break; + case WebAccessibility::ROLE_MATH: + ia_role_ = ROLE_SYSTEM_EQUATION; + break; + case WebAccessibility::ROLE_MENU: + case WebAccessibility::ROLE_MENU_BUTTON: + ia_role_ = ROLE_SYSTEM_MENUPOPUP; + break; + case WebAccessibility::ROLE_MENU_BAR: + ia_role_ = ROLE_SYSTEM_MENUBAR; + break; + case WebAccessibility::ROLE_MENU_ITEM: + case WebAccessibility::ROLE_MENU_LIST_OPTION: + ia_role_ = ROLE_SYSTEM_MENUITEM; + break; + case WebAccessibility::ROLE_MENU_LIST_POPUP: + ia_role_ = ROLE_SYSTEM_MENUPOPUP; + break; + case WebAccessibility::ROLE_NOTE: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_NOTE; + break; + case WebAccessibility::ROLE_OUTLINE: + ia_role_ = ROLE_SYSTEM_OUTLINE; + break; + case WebAccessibility::ROLE_POPUP_BUTTON: + ia_role_ = ROLE_SYSTEM_COMBOBOX; + break; + case WebAccessibility::ROLE_PROGRESS_INDICATOR: + ia_role_ = ROLE_SYSTEM_PROGRESSBAR; + break; + case WebAccessibility::ROLE_RADIO_BUTTON: + ia_role_ = ROLE_SYSTEM_RADIOBUTTON; + break; + case WebAccessibility::ROLE_RADIO_GROUP: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_REGION: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_ROW: + ia_role_ = ROLE_SYSTEM_ROW; + break; + case WebAccessibility::ROLE_ROW_HEADER: + ia_role_ = ROLE_SYSTEM_ROWHEADER; + break; + case WebAccessibility::ROLE_RULER: + ia_role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_RULER; + break; + case WebAccessibility::ROLE_SCROLLAREA: + ia_role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_SCROLL_PANE; + break; + case WebAccessibility::ROLE_SCROLLBAR: + ia_role_ = ROLE_SYSTEM_SCROLLBAR; + break; + case WebAccessibility::ROLE_SLIDER: + ia_role_ = ROLE_SYSTEM_SLIDER; + break; + case WebAccessibility::ROLE_SPLIT_GROUP: + ia_role_ = ROLE_SYSTEM_CLIENT; + ia2_role_ = IA2_ROLE_SPLIT_PANE; + break; + case WebAccessibility::ROLE_ANNOTATION: + case WebAccessibility::ROLE_STATIC_TEXT: + ia_role_ = ROLE_SYSTEM_TEXT; + break; + case WebAccessibility::ROLE_STATUS: + ia_role_ = ROLE_SYSTEM_STATUSBAR; + break; + case WebAccessibility::ROLE_SPLITTER: + ia_role_ = ROLE_SYSTEM_SEPARATOR; + break; + case WebAccessibility::ROLE_TAB: + ia_role_ = ROLE_SYSTEM_PAGETAB; + break; + case WebAccessibility::ROLE_TABLE: + ia_role_ = ROLE_SYSTEM_TABLE; + break; + case WebAccessibility::ROLE_TABLE_HEADER_CONTAINER: + ia_role_ = ROLE_SYSTEM_GROUPING; + ia2_role_ = IA2_ROLE_SECTION; + break; + case WebAccessibility::ROLE_TAB_GROUP: + case WebAccessibility::ROLE_TAB_LIST: + case WebAccessibility::ROLE_TAB_PANEL: + ia_role_ = ROLE_SYSTEM_PAGETABLIST; + break; + case WebAccessibility::ROLE_TEXTAREA: + ia_role_ = ROLE_SYSTEM_TEXT; + ia2_state_ |= IA2_STATE_MULTI_LINE; + ia2_state_ |= IA2_STATE_EDITABLE; + ia2_state_ |= IA2_STATE_SELECTABLE_TEXT; + break; + case WebAccessibility::ROLE_TEXT_FIELD: + ia_role_ = ROLE_SYSTEM_TEXT; + ia2_state_ |= IA2_STATE_SINGLE_LINE; + ia2_state_ |= IA2_STATE_EDITABLE; + ia2_state_ |= IA2_STATE_SELECTABLE_TEXT; + break; + case WebAccessibility::ROLE_TIMER: + ia_role_ = ROLE_SYSTEM_CLOCK; + break; + case WebAccessibility::ROLE_TOOLBAR: + ia_role_ = ROLE_SYSTEM_TOOLBAR; + break; + case WebAccessibility::ROLE_TOOLTIP: + ia_role_ = ROLE_SYSTEM_TOOLTIP; + break; + case WebAccessibility::ROLE_TREE: + ia_role_ = ROLE_SYSTEM_OUTLINE; + break; + case WebAccessibility::ROLE_TREE_GRID: + ia_role_ = ROLE_SYSTEM_OUTLINE; + break; + case WebAccessibility::ROLE_TREE_ITEM: + ia_role_ = ROLE_SYSTEM_OUTLINEITEM; + break; + case WebAccessibility::ROLE_WINDOW: + ia_role_ = ROLE_SYSTEM_WINDOW; + break; + + // TODO(dmazzoni): figure out the proper MSAA role for all of these. + case WebAccessibility::ROLE_BROWSER: + case WebAccessibility::ROLE_DIRECTORY: + case WebAccessibility::ROLE_DRAWER: + case WebAccessibility::ROLE_HELP_TAG: + case WebAccessibility::ROLE_IGNORED: + case WebAccessibility::ROLE_INCREMENTOR: + case WebAccessibility::ROLE_LOG: + case WebAccessibility::ROLE_MARQUEE: + case WebAccessibility::ROLE_MATTE: + case WebAccessibility::ROLE_RULER_MARKER: + case WebAccessibility::ROLE_SHEET: + case WebAccessibility::ROLE_SLIDER_THUMB: + case WebAccessibility::ROLE_SYSTEM_WIDE: + case WebAccessibility::ROLE_VALUE_INDICATOR: + default: + ia_role_ = ROLE_SYSTEM_CLIENT; + break; + } + + // The role should always be set. + DCHECK(!role_name_.empty() || ia_role_); + + // If we didn't explicitly set the IAccessible2 role, make it the same + // as the MSAA role. + if (!ia2_role_) + ia2_role_ = ia_role_; +} diff --git a/content/browser/accessibility/browser_accessibility_win.h b/content/browser/accessibility/browser_accessibility_win.h new file mode 100644 index 0000000..fb8e014 --- /dev/null +++ b/content/browser/accessibility/browser_accessibility_win.h @@ -0,0 +1,499 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_ +#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_ +#pragma once + +#include <atlbase.h> +#include <atlcom.h> +#include <oleacc.h> + +#include <vector> + +#include "content/browser/accessibility/browser_accessibility.h" +#include "ia2_api_all.h" // Generated +#include "ISimpleDOMDocument.h" // Generated +#include "ISimpleDOMNode.h" // Generated +#include "ISimpleDOMText.h" // Generated +#include "webkit/glue/webaccessibility.h" + +class BrowserAccessibilityManagerWin; + +using webkit_glue::WebAccessibility; + +//////////////////////////////////////////////////////////////////////////////// +// +// BrowserAccessibilityWin +// +// Class implementing the windows accessible interface for the Browser-Renderer +// communication of accessibility information, providing accessibility +// to be used by screen readers and other assistive technology (AT). +// +//////////////////////////////////////////////////////////////////////////////// +class BrowserAccessibilityWin + : public BrowserAccessibility, + public CComObjectRootEx<CComMultiThreadModel>, + public IDispatchImpl<IAccessible2, &IID_IAccessible2, + &LIBID_IAccessible2Lib>, + public IAccessibleImage, + public IAccessibleText, + public IServiceProvider, + public ISimpleDOMDocument, + public ISimpleDOMNode, + public ISimpleDOMText { + public: + BEGIN_COM_MAP(BrowserAccessibilityWin) + COM_INTERFACE_ENTRY2(IDispatch, IAccessible2) + COM_INTERFACE_ENTRY2(IAccessible, IAccessible2) + COM_INTERFACE_ENTRY(IAccessible2) + COM_INTERFACE_ENTRY(IAccessibleImage) + COM_INTERFACE_ENTRY(IAccessibleText) + COM_INTERFACE_ENTRY(IServiceProvider) + COM_INTERFACE_ENTRY(ISimpleDOMDocument) + COM_INTERFACE_ENTRY(ISimpleDOMNode) + COM_INTERFACE_ENTRY(ISimpleDOMText) + END_COM_MAP() + + BrowserAccessibilityWin(); + + virtual ~BrowserAccessibilityWin(); + + // + // BrowserAccessibility methods. + // + virtual void Initialize(); + virtual void NativeAddReference(); + virtual void NativeReleaseReference(); + + // + // IAccessible methods. + // + + // Performs the default action on a given object. + STDMETHODIMP accDoDefaultAction(VARIANT var_id); + + // Retrieves the child element or child object at a given point on the screen. + STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child); + + // Retrieves the specified object's current screen location. + STDMETHODIMP accLocation(LONG* x_left, + LONG* y_top, + LONG* width, + LONG* height, + VARIANT var_id); + + // Traverses to another UI element and retrieves the object. + STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end); + + // Retrieves an IDispatch interface pointer for the specified child. + STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child); + + // Retrieves the number of accessible children. + STDMETHODIMP get_accChildCount(LONG* child_count); + + // Retrieves a string that describes the object's default action. + STDMETHODIMP get_accDefaultAction(VARIANT var_id, BSTR* default_action); + + // Retrieves the object's description. + STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc); + + // Retrieves the object that has the keyboard focus. + STDMETHODIMP get_accFocus(VARIANT* focus_child); + + // Retrieves the help information associated with the object. + STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help); + + // Retrieves the specified object's shortcut. + STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id, BSTR* access_key); + + // Retrieves the name of the specified object. + STDMETHODIMP get_accName(VARIANT var_id, BSTR* name); + + // Retrieves the IDispatch interface of the object's parent. + STDMETHODIMP get_accParent(IDispatch** disp_parent); + + // Retrieves information describing the role of the specified object. + STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role); + + // Retrieves the current state of the specified object. + STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state); + + // Returns the value associated with the object. + STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value); + + // Make an object take focus or extend the selection. + STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id); + + STDMETHODIMP get_accHelpTopic(BSTR* help_file, + VARIANT var_id, + LONG* topic_id); + + STDMETHODIMP get_accSelection(VARIANT* selected); + + // Deprecated methods, not implemented. + STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name) { + return E_NOTIMPL; + } + STDMETHODIMP put_accValue(VARIANT var_id, BSTR put_val) { + return E_NOTIMPL; + } + + // + // IAccessible2 methods. + // + + // Returns role from a longer list of possible roles. + STDMETHODIMP role(LONG* role); + + // Returns the state bitmask from a larger set of possible states. + STDMETHODIMP get_states(AccessibleStates* states); + + // Returns the attributes specific to this IAccessible2 object, + // such as a cell's formula. + STDMETHODIMP get_attributes(BSTR* attributes); + + // Get the unique ID of this object so that the client knows if it's + // been encountered previously. + STDMETHODIMP get_uniqueID(LONG* unique_id); + + // Get the window handle of the enclosing window. + STDMETHODIMP get_windowHandle(HWND* window_handle); + + // Get this object's index in its parent object. + STDMETHODIMP get_indexInParent(LONG* index_in_parent); + + // IAccessible2 methods not implemented. + STDMETHODIMP get_extendedRole(BSTR* extended_role) { + return E_NOTIMPL; + } + STDMETHODIMP get_nRelations(LONG* n_relations) { + return E_NOTIMPL; + } + STDMETHODIMP get_relation(LONG relation_index, + IAccessibleRelation** relation) { + return E_NOTIMPL; + } + STDMETHODIMP get_relations(LONG max_relations, + IAccessibleRelation** relations, + LONG* n_relations) { + return E_NOTIMPL; + } + STDMETHODIMP scrollTo(enum IA2ScrollType scroll_type) { + return E_NOTIMPL; + } + STDMETHODIMP scrollToPoint(enum IA2CoordinateType coordinate_type, + LONG x, + LONG y) { + return E_NOTIMPL; + } + STDMETHODIMP get_groupPosition(LONG* group_level, + LONG* similar_items_in_group, + LONG* position_in_group) { + return E_NOTIMPL; + } + STDMETHODIMP get_localizedExtendedRole(BSTR* localized_extended_role) { + return E_NOTIMPL; + } + STDMETHODIMP get_nExtendedStates(LONG* n_extended_states) { + return E_NOTIMPL; + } + STDMETHODIMP get_extendedStates(LONG max_extended_states, + BSTR** extended_states, + LONG* n_extended_states) { + return E_NOTIMPL; + } + STDMETHODIMP get_localizedExtendedStates(LONG max_localized_extended_states, + BSTR** localized_extended_states, + LONG* n_localized_extended_states) { + return E_NOTIMPL; + } + STDMETHODIMP get_locale(IA2Locale* locale) { + return E_NOTIMPL; + } + + // + // IAccessibleImage methods. + // + + STDMETHODIMP get_description(BSTR* description); + + STDMETHODIMP get_imagePosition(enum IA2CoordinateType coordinate_type, + LONG* x, LONG* y); + + STDMETHODIMP get_imageSize(LONG* height, LONG* width); + + // + // IAccessibleText methods. + // + + STDMETHODIMP get_nCharacters(LONG* n_characters); + + STDMETHODIMP get_caretOffset(LONG* offset); + + STDMETHODIMP get_nSelections(LONG* n_selections); + + STDMETHODIMP get_selection(LONG selection_index, + LONG* start_offset, + LONG* end_offset); + + STDMETHODIMP get_text(LONG start_offset, LONG end_offset, BSTR* text); + + STDMETHODIMP get_textAtOffset(LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text); + + STDMETHODIMP get_textBeforeOffset(LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text); + + STDMETHODIMP get_textAfterOffset(LONG offset, + enum IA2TextBoundaryType boundary_type, + LONG* start_offset, LONG* end_offset, + BSTR* text); + + // IAccessibleText methods not implemented. + STDMETHODIMP addSelection(LONG start_offset, LONG end_offset) { + return E_NOTIMPL; + } + STDMETHODIMP get_attributes(LONG offset, LONG* start_offset, LONG* end_offset, + BSTR* text_attributes) { + return E_NOTIMPL; + } + STDMETHODIMP get_characterExtents(LONG offset, + enum IA2CoordinateType coord_type, + LONG* x, LONG* y, + LONG* width, LONG* height) { + return E_NOTIMPL; + } + STDMETHODIMP get_offsetAtPoint(LONG x, LONG y, + enum IA2CoordinateType coord_type, + LONG* offset) { + return E_NOTIMPL; + } + STDMETHODIMP removeSelection(LONG selection_index) { + return E_NOTIMPL; + } + STDMETHODIMP setCaretOffset(LONG offset) { + return E_NOTIMPL; + } + STDMETHODIMP setSelection(LONG selection_index, + LONG start_offset, + LONG end_offset) { + return E_NOTIMPL; + } + STDMETHODIMP scrollSubstringTo(LONG start_index, + LONG end_index, + enum IA2ScrollType scroll_type) { + return E_NOTIMPL; + } + STDMETHODIMP scrollSubstringToPoint(LONG start_index, LONG end_index, + enum IA2CoordinateType coordinate_type, + LONG x, LONG y) { + return E_NOTIMPL; + } + STDMETHODIMP get_newText(IA2TextSegment* new_text) { + return E_NOTIMPL; + } + STDMETHODIMP get_oldText(IA2TextSegment* old_text) { + return E_NOTIMPL; + } + + // + // ISimpleDOMDocument methods. + // + + STDMETHODIMP get_URL(BSTR* url); + + STDMETHODIMP get_title(BSTR* title); + + STDMETHODIMP get_mimeType(BSTR* mime_type); + + STDMETHODIMP get_docType(BSTR* doc_type); + + STDMETHODIMP get_nameSpaceURIForID( + short name_space_id, BSTR *name_space_uri) { + return E_NOTIMPL; + } + STDMETHODIMP put_alternateViewMediaTypes( + BSTR *comma_separated_media_types) { + return E_NOTIMPL; + } + + // + // ISimpleDOMNode methods. + // + + STDMETHODIMP get_nodeInfo( + BSTR* node_name, + short* name_space_id, + BSTR* node_value, + unsigned int* num_children, + unsigned int* unique_id, + unsigned short* node_type); + + STDMETHODIMP get_attributes( + unsigned short max_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values, + unsigned short* num_attribs); + + STDMETHODIMP get_attributesForNames( + unsigned short num_attribs, + BSTR* attrib_names, + short* name_space_id, + BSTR* attrib_values); + + STDMETHODIMP get_computedStyle( + unsigned short max_style_properties, + boolean use_alternate_view, + BSTR *style_properties, + BSTR *style_values, + unsigned short *num_style_properties); + + STDMETHODIMP get_computedStyleForProperties( + unsigned short num_style_properties, + boolean use_alternate_view, + BSTR* style_properties, + BSTR* style_values); + + STDMETHODIMP scrollTo(boolean placeTopLeft); + + STDMETHODIMP get_parentNode(ISimpleDOMNode** node); + + STDMETHODIMP get_firstChild(ISimpleDOMNode** node); + + STDMETHODIMP get_lastChild(ISimpleDOMNode** node); + + STDMETHODIMP get_previousSibling(ISimpleDOMNode** node); + + STDMETHODIMP get_nextSibling(ISimpleDOMNode** node); + + STDMETHODIMP get_childAt( + unsigned int child_index, + ISimpleDOMNode** node); + + STDMETHODIMP get_innerHTML(BSTR* innerHTML) { + return E_NOTIMPL; + } + + STDMETHODIMP get_localInterface(void** local_interface) { + return E_NOTIMPL; + } + + STDMETHODIMP get_language(BSTR* language) { + return E_NOTIMPL; + } + + // + // ISimpleDOMText methods. + // + + STDMETHODIMP get_domText(BSTR* dom_text); + + STDMETHODIMP get_clippedSubstringBounds( + unsigned int start_index, + unsigned int end_index, + int* x, + int* y, + int* width, + int* height) { + return E_NOTIMPL; + } + + STDMETHODIMP get_unclippedSubstringBounds( + unsigned int start_index, + unsigned int end_index, + int* x, + int* y, + int* width, + int* height) { + return E_NOTIMPL; + } + + STDMETHODIMP scrollToSubstring( + unsigned int start_index, + unsigned int end_index) { + return E_NOTIMPL; + } + + STDMETHODIMP get_fontFamily(BSTR *font_family) { + return E_NOTIMPL; + } + + // + // IServiceProvider methods. + // + + STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void** object); + + // + // CComObjectRootEx methods. + // + + HRESULT WINAPI InternalQueryInterface(void* this_ptr, + const _ATL_INTMAP_ENTRY* entries, + REFIID iid, + void** object); + + private: + // Add one to the reference count and return the same object. Always + // use this method when returning a BrowserAccessibilityWin object as + // an output parameter to a COM interface, never use it otherwise. + BrowserAccessibilityWin* NewReference(); + + // Many MSAA methods take a var_id parameter indicating that the operation + // should be performed on a particular child ID, rather than this object. + // This method tries to figure out the target object from |var_id| and + // returns a pointer to the target object if it exists, otherwise NULL. + // Does not return a new reference. + BrowserAccessibilityWin* GetTargetFromChildID(const VARIANT& var_id); + + // Initialize the role and state metadata from the role enum and state + // bitmasks defined in webkit/glue/webaccessibility.h. + void InitRoleAndState(); + + // Retrieve the string value of an attribute from the attribute map and + // if found and nonempty, allocate a new BSTR (with SysAllocString) + // and return S_OK. If not found or empty, return S_FALSE. + HRESULT GetAttributeAsBstr( + WebAccessibility::Attribute attribute, BSTR* value_bstr); + + // Escape a string like it would be escaped for a URL or HTML form. + string16 Escape(const string16& str); + + // Get the text of this node for the purposes of IAccessibleText - it may + // be the name, it may be the value, etc. depending on the role. + const string16& TextForIAccessibleText(); + + // If offset is a member of IA2TextSpecialOffsets this function updates the + // value of offset and returns, otherwise offset remains unchanged. + void HandleSpecialTextOffset(const string16& text, LONG* offset); + + // Search forwards (direction == 1) or backwards (direction == -1) from + // the given offset until the given IAccessible2 boundary (like word, + // sentence) is found, and return its offset. + LONG FindBoundary(const string16& text, + IA2TextBoundaryType boundary, + LONG start_offset, + LONG direction); + + // IAccessible role and state. + int32 ia_role_; + int32 ia_state_; + + // IAccessible2 role and state. + int32 ia2_role_; + int32 ia2_state_; + + // Give BrowserAccessibility::Create access to our constructor. + friend class BrowserAccessibility; + + DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityWin); +}; + +#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_WIN_H_ diff --git a/content/browser/renderer_host/render_widget_host.cc b/content/browser/renderer_host/render_widget_host.cc index 1f98484..b962f6e 100644 --- a/content/browser/renderer_host/render_widget_host.cc +++ b/content/browser/renderer_host/render_widget_host.cc @@ -11,6 +11,7 @@ #include "base/message_loop.h" #include "base/metrics/histogram.h" #include "base/utf_string_conversions.h" +#include "content/browser/accessibility/browser_accessibility_state.h" #include "content/browser/gpu/gpu_process_host.h" #include "content/browser/renderer_host/backing_store.h" #include "content/browser/renderer_host/backing_store_manager.h" @@ -90,7 +91,8 @@ RenderWidgetHost::RenderWidgetHost(RenderProcessHost* process, process_->WidgetRestored(); if (CommandLine::ForCurrentProcess()->HasSwitch( - switches::kForceRendererAccessibility)) { + switches::kForceRendererAccessibility) || + BrowserAccessibilityState::GetInstance()->IsAccessibleBrowser()) { EnableRendererAccessibility(); } } |