1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/shell/renderer/test_runner/accessibility_controller.h"
6
7#include "gin/handle.h"
8#include "gin/object_template_builder.h"
9#include "gin/wrappable.h"
10#include "third_party/WebKit/public/web/WebElement.h"
11#include "third_party/WebKit/public/web/WebFrame.h"
12#include "third_party/WebKit/public/web/WebKit.h"
13#include "third_party/WebKit/public/web/WebSettings.h"
14#include "third_party/WebKit/public/web/WebView.h"
15
16namespace content {
17
18class AccessibilityControllerBindings
19    : public gin::Wrappable<AccessibilityControllerBindings> {
20 public:
21  static gin::WrapperInfo kWrapperInfo;
22
23  static void Install(base::WeakPtr<AccessibilityController> controller,
24                      blink::WebFrame* frame);
25
26 private:
27  explicit AccessibilityControllerBindings(
28      base::WeakPtr<AccessibilityController> controller);
29  virtual ~AccessibilityControllerBindings();
30
31  // gin::Wrappable:
32  virtual gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
33      v8::Isolate* isolate) OVERRIDE;
34
35  void LogAccessibilityEvents();
36  void SetNotificationListener(v8::Handle<v8::Function> callback);
37  void UnsetNotificationListener();
38  v8::Handle<v8::Object> FocusedElement();
39  v8::Handle<v8::Object> RootElement();
40  v8::Handle<v8::Object> AccessibleElementById(const std::string& id);
41
42  base::WeakPtr<AccessibilityController> controller_;
43
44  DISALLOW_COPY_AND_ASSIGN(AccessibilityControllerBindings);
45};
46
47gin::WrapperInfo AccessibilityControllerBindings::kWrapperInfo = {
48    gin::kEmbedderNativeGin};
49
50// static
51void AccessibilityControllerBindings::Install(
52    base::WeakPtr<AccessibilityController> controller,
53    blink::WebFrame* frame) {
54  v8::Isolate* isolate = blink::mainThreadIsolate();
55  v8::HandleScope handle_scope(isolate);
56  v8::Handle<v8::Context> context = frame->mainWorldScriptContext();
57  if (context.IsEmpty())
58    return;
59
60  v8::Context::Scope context_scope(context);
61
62  gin::Handle<AccessibilityControllerBindings> bindings =
63      gin::CreateHandle(isolate,
64                        new AccessibilityControllerBindings(controller));
65  if (bindings.IsEmpty())
66    return;
67  v8::Handle<v8::Object> global = context->Global();
68  global->Set(gin::StringToV8(isolate, "accessibilityController"),
69              bindings.ToV8());
70}
71
72AccessibilityControllerBindings::AccessibilityControllerBindings(
73    base::WeakPtr<AccessibilityController> controller)
74  : controller_(controller) {
75}
76
77AccessibilityControllerBindings::~AccessibilityControllerBindings() {
78}
79
80gin::ObjectTemplateBuilder
81AccessibilityControllerBindings::GetObjectTemplateBuilder(
82    v8::Isolate* isolate) {
83  return gin::Wrappable<AccessibilityControllerBindings>::
84      GetObjectTemplateBuilder(isolate)
85      .SetMethod("logAccessibilityEvents",
86                 &AccessibilityControllerBindings::LogAccessibilityEvents)
87      .SetMethod("setNotificationListener",
88                 &AccessibilityControllerBindings::SetNotificationListener)
89      .SetMethod("unsetNotificationListener",
90                 &AccessibilityControllerBindings::UnsetNotificationListener)
91      .SetProperty("focusedElement",
92                   &AccessibilityControllerBindings::FocusedElement)
93      .SetProperty("rootElement",
94                   &AccessibilityControllerBindings::RootElement)
95      .SetMethod("accessibleElementById",
96                 &AccessibilityControllerBindings::AccessibleElementById)
97      // TODO(hajimehoshi): These are for backward compatibility. Remove them.
98      .SetMethod("addNotificationListener",
99                 &AccessibilityControllerBindings::SetNotificationListener)
100      .SetMethod("removeNotificationListener",
101                 &AccessibilityControllerBindings::UnsetNotificationListener);
102}
103
104void AccessibilityControllerBindings::LogAccessibilityEvents() {
105  if (controller_)
106    controller_->LogAccessibilityEvents();
107}
108
109void AccessibilityControllerBindings::SetNotificationListener(
110    v8::Handle<v8::Function> callback) {
111  if (controller_)
112    controller_->SetNotificationListener(callback);
113}
114
115void AccessibilityControllerBindings::UnsetNotificationListener() {
116  if (controller_)
117    controller_->UnsetNotificationListener();
118}
119
120v8::Handle<v8::Object> AccessibilityControllerBindings::FocusedElement() {
121  return controller_ ? controller_->FocusedElement() : v8::Handle<v8::Object>();
122}
123
124v8::Handle<v8::Object> AccessibilityControllerBindings::RootElement() {
125  return controller_ ? controller_->RootElement() : v8::Handle<v8::Object>();
126}
127
128v8::Handle<v8::Object> AccessibilityControllerBindings::AccessibleElementById(
129    const std::string& id) {
130  return controller_ ? controller_->AccessibleElementById(id)
131                     : v8::Handle<v8::Object>();
132}
133
134AccessibilityController::AccessibilityController()
135    : log_accessibility_events_(false),
136      weak_factory_(this) {
137}
138
139AccessibilityController::~AccessibilityController() {}
140
141void AccessibilityController::Reset() {
142  root_element_ = blink::WebAXObject();
143  focused_element_ = blink::WebAXObject();
144  elements_.Clear();
145  notification_callback_.Reset();
146  log_accessibility_events_ = false;
147}
148
149void AccessibilityController::Install(blink::WebFrame* frame) {
150  frame->view()->settings()->setAccessibilityEnabled(true);
151  frame->view()->settings()->setInlineTextBoxAccessibilityEnabled(true);
152
153  AccessibilityControllerBindings::Install(weak_factory_.GetWeakPtr(), frame);
154}
155
156void AccessibilityController::SetFocusedElement(
157    const blink::WebAXObject& focused_element) {
158  focused_element_ = focused_element;
159}
160
161bool AccessibilityController::ShouldLogAccessibilityEvents() {
162  return log_accessibility_events_;
163}
164
165void AccessibilityController::NotificationReceived(
166    const blink::WebAXObject& target, const std::string& notification_name) {
167  v8::Isolate* isolate = blink::mainThreadIsolate();
168  v8::HandleScope handle_scope(isolate);
169
170  blink::WebFrame* frame = web_view_->mainFrame();
171  if (!frame)
172    return;
173
174  v8::Handle<v8::Context> context = frame->mainWorldScriptContext();
175  if (context.IsEmpty())
176    return;
177
178  v8::Context::Scope context_scope(context);
179
180  // Call notification listeners on the element.
181  v8::Handle<v8::Object> element_handle = elements_.GetOrCreate(target);
182  if (element_handle.IsEmpty())
183    return;
184
185  WebAXObjectProxy* element;
186  bool result = gin::ConvertFromV8(isolate, element_handle, &element);
187  DCHECK(result);
188  element->NotificationReceived(frame, notification_name);
189
190  if (notification_callback_.IsEmpty())
191    return;
192
193  // Call global notification listeners.
194  v8::Handle<v8::Value> argv[] = {
195    element_handle,
196    v8::String::NewFromUtf8(isolate, notification_name.data(),
197                            v8::String::kNormalString,
198                            notification_name.size()),
199  };
200  frame->callFunctionEvenIfScriptDisabled(
201      v8::Local<v8::Function>::New(isolate, notification_callback_),
202      context->Global(),
203      arraysize(argv),
204      argv);
205}
206
207void AccessibilityController::SetDelegate(WebTestDelegate* delegate) {
208  delegate_ = delegate;
209}
210
211void AccessibilityController::SetWebView(blink::WebView* web_view) {
212  web_view_ = web_view;
213}
214
215void AccessibilityController::LogAccessibilityEvents() {
216  log_accessibility_events_ = true;
217}
218
219void AccessibilityController::SetNotificationListener(
220    v8::Handle<v8::Function> callback) {
221  v8::Isolate* isolate = blink::mainThreadIsolate();
222  notification_callback_.Reset(isolate, callback);
223}
224
225void AccessibilityController::UnsetNotificationListener() {
226  notification_callback_.Reset();
227}
228
229v8::Handle<v8::Object> AccessibilityController::FocusedElement() {
230  if (focused_element_.isNull())
231    focused_element_ = web_view_->accessibilityObject();
232  return elements_.GetOrCreate(focused_element_);
233}
234
235v8::Handle<v8::Object> AccessibilityController::RootElement() {
236  if (root_element_.isNull())
237    root_element_ = web_view_->accessibilityObject();
238  return elements_.CreateRoot(root_element_);
239}
240
241v8::Handle<v8::Object>
242AccessibilityController::AccessibleElementById(const std::string& id) {
243  if (root_element_.isNull())
244    root_element_ = web_view_->accessibilityObject();
245
246  if (!root_element_.updateBackingStoreAndCheckValidity())
247    return v8::Handle<v8::Object>();
248
249  return FindAccessibleElementByIdRecursive(
250      root_element_, blink::WebString::fromUTF8(id.c_str()));
251}
252
253v8::Handle<v8::Object>
254AccessibilityController::FindAccessibleElementByIdRecursive(
255    const blink::WebAXObject& obj, const blink::WebString& id) {
256  if (obj.isNull() || obj.isDetached())
257    return v8::Handle<v8::Object>();
258
259  blink::WebNode node = obj.node();
260  if (!node.isNull() && node.isElementNode()) {
261    blink::WebElement element = node.to<blink::WebElement>();
262    element.getAttribute("id");
263    if (element.getAttribute("id") == id)
264      return elements_.GetOrCreate(obj);
265  }
266
267  unsigned childCount = obj.childCount();
268  for (unsigned i = 0; i < childCount; i++) {
269    v8::Handle<v8::Object> result =
270        FindAccessibleElementByIdRecursive(obj.childAt(i), id);
271    if (*result)
272      return result;
273  }
274
275  return v8::Handle<v8::Object>();
276}
277
278}  // namespace content
279