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 "content/browser/accessibility/browser_accessibility_state_impl.h"
6
7#include "base/command_line.h"
8#include "base/metrics/histogram.h"
9#include "base/timer/timer.h"
10#include "content/browser/accessibility/accessibility_mode_helper.h"
11#include "content/browser/renderer_host/render_widget_host_impl.h"
12#include "content/public/browser/browser_thread.h"
13#include "content/public/browser/render_process_host.h"
14#include "content/public/browser/render_widget_host_iterator.h"
15#include "content/public/common/content_switches.h"
16#include "ui/gfx/sys_color_change_listener.h"
17
18#if defined(OS_WIN)
19#include "base/win/windows_version.h"
20#endif
21
22namespace content {
23
24// Update the accessibility histogram 45 seconds after initialization.
25static const int kAccessibilityHistogramDelaySecs = 45;
26
27// static
28BrowserAccessibilityState* BrowserAccessibilityState::GetInstance() {
29  return BrowserAccessibilityStateImpl::GetInstance();
30}
31
32// static
33BrowserAccessibilityStateImpl* BrowserAccessibilityStateImpl::GetInstance() {
34  return Singleton<BrowserAccessibilityStateImpl,
35                   LeakySingletonTraits<BrowserAccessibilityStateImpl> >::get();
36}
37
38BrowserAccessibilityStateImpl::BrowserAccessibilityStateImpl()
39    : BrowserAccessibilityState(),
40      accessibility_mode_(AccessibilityModeOff) {
41  ResetAccessibilityModeValue();
42#if defined(OS_WIN)
43  // On Windows, UpdateHistograms calls some system functions with unknown
44  // runtime, so call it on the file thread to ensure there's no jank.
45  // Everything in that method must be safe to call on another thread.
46  BrowserThread::ID update_histogram_thread = BrowserThread::FILE;
47#else
48  // On all other platforms, UpdateHistograms should be called on the main
49  // thread.
50  BrowserThread::ID update_histogram_thread = BrowserThread::UI;
51#endif
52
53  // We need to AddRef() the leaky singleton so that Bind doesn't
54  // delete it prematurely.
55  AddRef();
56  BrowserThread::PostDelayedTask(
57      update_histogram_thread, FROM_HERE,
58      base::Bind(&BrowserAccessibilityStateImpl::UpdateHistograms, this),
59      base::TimeDelta::FromSeconds(kAccessibilityHistogramDelaySecs));
60}
61
62BrowserAccessibilityStateImpl::~BrowserAccessibilityStateImpl() {
63}
64
65void BrowserAccessibilityStateImpl::OnScreenReaderDetected() {
66  if (CommandLine::ForCurrentProcess()->HasSwitch(
67          switches::kDisableRendererAccessibility)) {
68    return;
69  }
70  EnableAccessibility();
71}
72
73void BrowserAccessibilityStateImpl::EnableAccessibility() {
74  AddAccessibilityMode(AccessibilityModeComplete);
75}
76
77void BrowserAccessibilityStateImpl::DisableAccessibility() {
78  ResetAccessibilityMode();
79}
80
81void BrowserAccessibilityStateImpl::ResetAccessibilityModeValue() {
82  accessibility_mode_ = AccessibilityModeOff;
83#if defined(OS_WIN)
84  // On Windows 8, always enable accessibility for editable text controls
85  // so we can show the virtual keyboard when one is enabled.
86  if (base::win::GetVersion() >= base::win::VERSION_WIN8 &&
87      !CommandLine::ForCurrentProcess()->HasSwitch(
88          switches::kDisableRendererAccessibility)) {
89    accessibility_mode_ = AccessibilityModeEditableTextOnly;
90  }
91#endif  // defined(OS_WIN)
92
93  if (CommandLine::ForCurrentProcess()->HasSwitch(
94          switches::kForceRendererAccessibility)) {
95    accessibility_mode_ = AccessibilityModeComplete;
96  }
97}
98
99void BrowserAccessibilityStateImpl::ResetAccessibilityMode() {
100  ResetAccessibilityModeValue();
101
102  // Iterate over all RenderWidgetHosts, even swapped out ones in case
103  // they become active again.
104  scoped_ptr<RenderWidgetHostIterator> widgets(
105      RenderWidgetHostImpl::GetAllRenderWidgetHosts());
106  while (RenderWidgetHost* widget = widgets->GetNextHost()) {
107    // Ignore processes that don't have a connection, such as crashed tabs.
108    if (!widget->GetProcess()->HasConnection())
109      continue;
110    if (!widget->IsRenderView())
111      continue;
112
113    RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(widget);
114    rwhi->ResetAccessibilityMode();
115  }
116}
117
118bool BrowserAccessibilityStateImpl::IsAccessibleBrowser() {
119  return ((accessibility_mode_ & AccessibilityModeComplete) ==
120          AccessibilityModeComplete);
121}
122
123void BrowserAccessibilityStateImpl::AddHistogramCallback(
124    base::Closure callback) {
125  histogram_callbacks_.push_back(callback);
126}
127
128void BrowserAccessibilityStateImpl::UpdateHistogramsForTesting() {
129  UpdateHistograms();
130}
131
132void BrowserAccessibilityStateImpl::UpdateHistograms() {
133  UpdatePlatformSpecificHistograms();
134
135  for (size_t i = 0; i < histogram_callbacks_.size(); ++i)
136    histogram_callbacks_[i].Run();
137
138  UMA_HISTOGRAM_BOOLEAN("Accessibility.State", IsAccessibleBrowser());
139  UMA_HISTOGRAM_BOOLEAN("Accessibility.InvertedColors",
140                        gfx::IsInvertedColorScheme());
141  UMA_HISTOGRAM_BOOLEAN("Accessibility.ManuallyEnabled",
142                        CommandLine::ForCurrentProcess()->HasSwitch(
143                            switches::kForceRendererAccessibility));
144}
145
146#if !defined(OS_WIN)
147void BrowserAccessibilityStateImpl::UpdatePlatformSpecificHistograms() {
148}
149#endif
150
151void BrowserAccessibilityStateImpl::AddAccessibilityMode(
152    AccessibilityMode mode) {
153  if (CommandLine::ForCurrentProcess()->HasSwitch(
154          switches::kDisableRendererAccessibility)) {
155    return;
156  }
157
158  accessibility_mode_ =
159      content::AddAccessibilityModeTo(accessibility_mode_, mode);
160
161  AddOrRemoveFromRenderWidgets(mode, true);
162}
163
164void BrowserAccessibilityStateImpl::RemoveAccessibilityMode(
165    AccessibilityMode mode) {
166  if (CommandLine::ForCurrentProcess()->HasSwitch(
167          switches::kForceRendererAccessibility) &&
168      mode == AccessibilityModeComplete) {
169    return;
170  }
171
172  accessibility_mode_ =
173      content::RemoveAccessibilityModeFrom(accessibility_mode_, mode);
174
175  AddOrRemoveFromRenderWidgets(mode, false);
176}
177
178void BrowserAccessibilityStateImpl::AddOrRemoveFromRenderWidgets(
179    AccessibilityMode mode,
180    bool add) {
181  // Iterate over all RenderWidgetHosts, even swapped out ones in case
182  // they become active again.
183  scoped_ptr<RenderWidgetHostIterator> widgets(
184      RenderWidgetHostImpl::GetAllRenderWidgetHosts());
185  while (RenderWidgetHost* widget = widgets->GetNextHost()) {
186    // Ignore processes that don't have a connection, such as crashed tabs.
187    if (!widget->GetProcess()->HasConnection())
188      continue;
189    if (!widget->IsRenderView())
190      continue;
191
192    RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(widget);
193    if (add)
194      rwhi->AddAccessibilityMode(mode);
195    else
196      rwhi->RemoveAccessibilityMode(mode);
197  }
198}
199
200}  // namespace content
201