1// Copyright (c) 2012 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 "chrome/browser/accessibility/accessibility_extension_api.h"
6
7#include "base/json/json_writer.h"
8#include "base/strings/string_number_conversions.h"
9#include "base/values.h"
10#include "chrome/browser/accessibility/accessibility_extension_api_constants.h"
11#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
12#include "chrome/browser/extensions/extension_service.h"
13#include "chrome/browser/extensions/extension_tab_util.h"
14#include "chrome/browser/infobars/infobar_service.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/common/extensions/api/accessibility_private.h"
17#include "chrome/common/extensions/extension_constants.h"
18#include "components/infobars/core/confirm_infobar_delegate.h"
19#include "components/infobars/core/infobar.h"
20#include "content/public/browser/browser_accessibility_state.h"
21#include "extensions/browser/event_router.h"
22#include "extensions/browser/extension_host.h"
23#include "extensions/browser/extension_system.h"
24#include "extensions/browser/lazy_background_task_queue.h"
25#include "extensions/common/error_utils.h"
26#include "extensions/common/manifest_handlers/background_info.h"
27
28#if defined(OS_CHROMEOS)
29#include "chrome/browser/chromeos/ui/accessibility_focus_ring_controller.h"
30#endif
31
32namespace keys = extension_accessibility_api_constants;
33namespace accessibility_private = extensions::api::accessibility_private;
34
35// Returns the AccessibilityControlInfo serialized into a JSON string,
36// consisting of an array of a single object of type AccessibilityObject,
37// as defined in the accessibility extension api's json schema.
38scoped_ptr<base::ListValue> ControlInfoToEventArguments(
39    const AccessibilityEventInfo* info) {
40  base::DictionaryValue* dict = new base::DictionaryValue();
41  info->SerializeToDict(dict);
42
43  scoped_ptr<base::ListValue> args(new base::ListValue());
44  args->Append(dict);
45  return args.Pass();
46}
47
48ExtensionAccessibilityEventRouter*
49    ExtensionAccessibilityEventRouter::GetInstance() {
50  return Singleton<ExtensionAccessibilityEventRouter>::get();
51}
52
53ExtensionAccessibilityEventRouter::ExtensionAccessibilityEventRouter()
54    : enabled_(false) {
55}
56
57ExtensionAccessibilityEventRouter::~ExtensionAccessibilityEventRouter() {
58  control_event_callback_.Reset();
59}
60
61void ExtensionAccessibilityEventRouter::SetAccessibilityEnabled(bool enabled) {
62  enabled_ = enabled;
63}
64
65bool ExtensionAccessibilityEventRouter::IsAccessibilityEnabled() const {
66  return enabled_;
67}
68
69void ExtensionAccessibilityEventRouter::SetControlEventCallbackForTesting(
70    ControlEventCallback control_event_callback) {
71  DCHECK(control_event_callback_.is_null());
72  control_event_callback_ = control_event_callback;
73}
74
75void ExtensionAccessibilityEventRouter::ClearControlEventCallback() {
76  control_event_callback_.Reset();
77}
78
79void ExtensionAccessibilityEventRouter::HandleWindowEvent(
80    ui::AXEvent event,
81    const AccessibilityWindowInfo* info) {
82  if (!control_event_callback_.is_null())
83    control_event_callback_.Run(event, info);
84
85  if (event == ui::AX_EVENT_ALERT)
86    OnWindowOpened(info);
87}
88
89void ExtensionAccessibilityEventRouter::HandleMenuEvent(
90    ui::AXEvent event,
91    const AccessibilityMenuInfo* info) {
92  switch (event) {
93    case ui::AX_EVENT_MENU_START:
94    case ui::AX_EVENT_MENU_POPUP_START:
95      OnMenuOpened(info);
96      break;
97    case ui::AX_EVENT_MENU_END:
98    case ui::AX_EVENT_MENU_POPUP_END:
99      OnMenuClosed(info);
100      break;
101    case ui::AX_EVENT_FOCUS:
102      OnControlFocused(info);
103      break;
104    case ui::AX_EVENT_HOVER:
105      OnControlHover(info);
106      break;
107    default:
108      NOTREACHED();
109  }
110}
111
112void ExtensionAccessibilityEventRouter::HandleControlEvent(
113    ui::AXEvent event,
114    const AccessibilityControlInfo* info) {
115  if (!control_event_callback_.is_null())
116    control_event_callback_.Run(event, info);
117
118  switch (event) {
119    case ui::AX_EVENT_TEXT_CHANGED:
120    case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
121      OnTextChanged(info);
122      break;
123    case ui::AX_EVENT_VALUE_CHANGED:
124    case ui::AX_EVENT_ALERT:
125      OnControlAction(info);
126      break;
127    case ui::AX_EVENT_FOCUS:
128      OnControlFocused(info);
129      break;
130    case ui::AX_EVENT_HOVER:
131      OnControlHover(info);
132      break;
133    default:
134      NOTREACHED();
135  }
136}
137
138void ExtensionAccessibilityEventRouter::OnWindowOpened(
139    const AccessibilityWindowInfo* info) {
140  scoped_ptr<base::ListValue> args(ControlInfoToEventArguments(info));
141  DispatchEvent(info->profile(),
142                accessibility_private::OnWindowOpened::kEventName,
143                args.Pass());
144}
145
146void ExtensionAccessibilityEventRouter::OnControlFocused(
147    const AccessibilityControlInfo* info) {
148  last_focused_control_dict_.Clear();
149  info->SerializeToDict(&last_focused_control_dict_);
150  scoped_ptr<base::ListValue> args(ControlInfoToEventArguments(info));
151  DispatchEvent(info->profile(),
152                accessibility_private::OnControlFocused::kEventName,
153                args.Pass());
154}
155
156void ExtensionAccessibilityEventRouter::OnControlAction(
157    const AccessibilityControlInfo* info) {
158  scoped_ptr<base::ListValue> args(ControlInfoToEventArguments(info));
159  DispatchEvent(info->profile(),
160                accessibility_private::OnControlAction::kEventName,
161                args.Pass());
162}
163
164void ExtensionAccessibilityEventRouter::OnControlHover(
165    const AccessibilityControlInfo* info) {
166  scoped_ptr<base::ListValue> args(ControlInfoToEventArguments(info));
167  DispatchEvent(info->profile(),
168                accessibility_private::OnControlHover::kEventName,
169                args.Pass());
170}
171
172void ExtensionAccessibilityEventRouter::OnTextChanged(
173    const AccessibilityControlInfo* info) {
174  scoped_ptr<base::ListValue> args(ControlInfoToEventArguments(info));
175  DispatchEvent(info->profile(),
176                accessibility_private::OnTextChanged::kEventName,
177                args.Pass());
178}
179
180void ExtensionAccessibilityEventRouter::OnMenuOpened(
181    const AccessibilityMenuInfo* info) {
182  scoped_ptr<base::ListValue> args(ControlInfoToEventArguments(info));
183  DispatchEvent(info->profile(),
184                accessibility_private::OnMenuOpened::kEventName,
185                args.Pass());
186}
187
188void ExtensionAccessibilityEventRouter::OnMenuClosed(
189    const AccessibilityMenuInfo* info) {
190  scoped_ptr<base::ListValue> args(ControlInfoToEventArguments(info));
191  DispatchEvent(info->profile(),
192                accessibility_private::OnMenuClosed::kEventName,
193                args.Pass());
194}
195
196void ExtensionAccessibilityEventRouter::OnChromeVoxLoadStateChanged(
197    Profile* profile,
198    bool loading,
199    bool make_announcements) {
200  scoped_ptr<base::ListValue> event_args(new base::ListValue());
201  event_args->AppendBoolean(loading);
202  event_args->AppendBoolean(make_announcements);
203  ExtensionAccessibilityEventRouter::DispatchEventToChromeVox(
204      profile,
205      accessibility_private::OnChromeVoxLoadStateChanged::kEventName,
206      event_args.Pass());
207}
208
209// Static.
210void ExtensionAccessibilityEventRouter::DispatchEventToChromeVox(
211    Profile* profile,
212    const char* event_name,
213    scoped_ptr<base::ListValue> event_args) {
214  extensions::ExtensionSystem* system =
215      extensions::ExtensionSystem::Get(profile);
216  if (!system)
217    return;
218  scoped_ptr<extensions::Event> event(new extensions::Event(event_name,
219                                                            event_args.Pass()));
220  system->event_router()->DispatchEventWithLazyListener(
221      extension_misc::kChromeVoxExtensionId, event.Pass());
222}
223
224void ExtensionAccessibilityEventRouter::DispatchEvent(
225    Profile* profile,
226    const char* event_name,
227    scoped_ptr<base::ListValue> event_args) {
228  if (!enabled_ || !profile)
229    return;
230  extensions::EventRouter* event_router = extensions::EventRouter::Get(profile);
231  if (!event_router)
232    return;
233
234  scoped_ptr<extensions::Event> event(new extensions::Event(
235      event_name, event_args.Pass()));
236  event_router->BroadcastEvent(event.Pass());
237}
238
239bool AccessibilityPrivateSetAccessibilityEnabledFunction::RunSync() {
240  bool enabled;
241  EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &enabled));
242  ExtensionAccessibilityEventRouter::GetInstance()
243      ->SetAccessibilityEnabled(enabled);
244  return true;
245}
246
247bool AccessibilityPrivateSetNativeAccessibilityEnabledFunction::RunSync() {
248  bool enabled;
249  EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &enabled));
250  if (enabled) {
251    content::BrowserAccessibilityState::GetInstance()->
252        EnableAccessibility();
253  } else {
254    content::BrowserAccessibilityState::GetInstance()->
255        DisableAccessibility();
256  }
257  return true;
258}
259
260bool AccessibilityPrivateGetFocusedControlFunction::RunSync() {
261  // Get the serialized dict from the last focused control and return it.
262  // However, if the dict is empty, that means we haven't seen any focus
263  // events yet, so return null instead.
264  ExtensionAccessibilityEventRouter *accessibility_event_router =
265      ExtensionAccessibilityEventRouter::GetInstance();
266  base::DictionaryValue *last_focused_control_dict =
267      accessibility_event_router->last_focused_control_dict();
268  if (last_focused_control_dict->size()) {
269    SetResult(last_focused_control_dict->DeepCopyWithoutEmptyChildren());
270  } else {
271    SetResult(base::Value::CreateNullValue());
272  }
273  return true;
274}
275
276bool AccessibilityPrivateGetAlertsForTabFunction::RunSync() {
277  int tab_id;
278  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
279
280  TabStripModel* tab_strip = NULL;
281  content::WebContents* contents = NULL;
282  int tab_index = -1;
283  if (!extensions::ExtensionTabUtil::GetTabById(tab_id,
284                                                GetProfile(),
285                                                include_incognito(),
286                                                NULL,
287                                                &tab_strip,
288                                                &contents,
289                                                &tab_index)) {
290    error_ = extensions::ErrorUtils::FormatErrorMessage(
291        extensions::tabs_constants::kTabNotFoundError,
292        base::IntToString(tab_id));
293    return false;
294  }
295
296  base::ListValue* alerts_value = new base::ListValue;
297
298  InfoBarService* infobar_service = InfoBarService::FromWebContents(contents);
299  for (size_t i = 0; i < infobar_service->infobar_count(); ++i) {
300    // TODO(hashimoto): Make other kind of alerts available.  crosbug.com/24281
301    ConfirmInfoBarDelegate* confirm_infobar_delegate =
302        infobar_service->infobar_at(i)->delegate()->AsConfirmInfoBarDelegate();
303    if (confirm_infobar_delegate) {
304      base::DictionaryValue* alert_value = new base::DictionaryValue;
305      const base::string16 message_text =
306          confirm_infobar_delegate->GetMessageText();
307      alert_value->SetString(keys::kMessageKey, message_text);
308      alerts_value->Append(alert_value);
309    }
310  }
311
312  SetResult(alerts_value);
313  return true;
314}
315
316bool AccessibilityPrivateSetFocusRingFunction::RunSync() {
317#if defined(OS_CHROMEOS)
318  base::ListValue* rect_values = NULL;
319  EXTENSION_FUNCTION_VALIDATE(args_->GetList(0, &rect_values));
320
321  std::vector<gfx::Rect> rects;
322  for (size_t i = 0; i < rect_values->GetSize(); ++i) {
323    base::DictionaryValue* rect_value = NULL;
324    EXTENSION_FUNCTION_VALIDATE(rect_values->GetDictionary(i, &rect_value));
325    int left, top, width, height;
326    EXTENSION_FUNCTION_VALIDATE(rect_value->GetInteger(keys::kLeft, &left));
327    EXTENSION_FUNCTION_VALIDATE(rect_value->GetInteger(keys::kTop, &top));
328    EXTENSION_FUNCTION_VALIDATE(rect_value->GetInteger(keys::kWidth, &width));
329    EXTENSION_FUNCTION_VALIDATE(rect_value->GetInteger(keys::kHeight, &height));
330    rects.push_back(gfx::Rect(left, top, width, height));
331  }
332
333  chromeos::AccessibilityFocusRingController::GetInstance()->SetFocusRing(
334      rects);
335  return true;
336#endif  // defined(OS_CHROMEOS)
337
338  error_ = keys:: kErrorNotSupported;
339  return false;
340}
341