1 // Copyright 2019 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_labels_service.h"
6
7 #include "base/metrics/histogram_functions.h"
8 #include "base/no_destructor.h"
9 #include "build/build_config.h"
10 #include "chrome/browser/accessibility/accessibility_state_utils.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
13 #include "chrome/common/channel_info.h"
14 #include "chrome/common/pref_names.h"
15 #include "components/pref_registry/pref_registry_syncable.h"
16 #include "components/prefs/pref_service.h"
17 #include "components/sync_preferences/pref_service_syncable.h"
18 #include "components/version_info/channel.h"
19 #include "content/public/browser/browser_accessibility_state.h"
20 #include "content/public/common/content_features.h"
21 #include "google_apis/google_api_keys.h"
22 #include "services/data_decoder/public/cpp/data_decoder.h"
23 #include "services/image_annotation/image_annotation_service.h"
24 #include "ui/accessibility/ax_action_data.h"
25 #include "ui/accessibility/ax_enums.mojom.h"
26
27 #if !defined(OS_ANDROID)
28 #include "chrome/browser/ui/browser.h"
29 #include "chrome/browser/ui/browser_finder.h"
30 #include "chrome/browser/ui/browser_list.h"
31 #endif
32
33 namespace {
34
35 // Returns the Chrome Google API key for the channel of this build.
APIKeyForChannel()36 std::string APIKeyForChannel() {
37 if (chrome::GetChannel() == version_info::Channel::STABLE)
38 return google_apis::GetAPIKey();
39 return google_apis::GetNonStableAPIKey();
40 }
41
42 AccessibilityLabelsService::ImageAnnotatorBinder&
GetImageAnnotatorBinderOverride()43 GetImageAnnotatorBinderOverride() {
44 static base::NoDestructor<AccessibilityLabelsService::ImageAnnotatorBinder>
45 binder;
46 return *binder;
47 }
48
49 class ImageAnnotatorClient : public image_annotation::Annotator::Client {
50 public:
51 ImageAnnotatorClient() = default;
52 ~ImageAnnotatorClient() override = default;
53
54 // image_annotation::Annotator::Client implementation:
BindJsonParser(mojo::PendingReceiver<data_decoder::mojom::JsonParser> receiver)55 void BindJsonParser(mojo::PendingReceiver<data_decoder::mojom::JsonParser>
56 receiver) override {
57 data_decoder_.GetService()->BindJsonParser(std::move(receiver));
58 }
59
60 private:
61 data_decoder::DataDecoder data_decoder_;
62
63 DISALLOW_COPY_AND_ASSIGN(ImageAnnotatorClient);
64 };
65
66 } // namespace
67
~AccessibilityLabelsService()68 AccessibilityLabelsService::~AccessibilityLabelsService() {}
69
70 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)71 void AccessibilityLabelsService::RegisterProfilePrefs(
72 user_prefs::PrefRegistrySyncable* registry) {
73 registry->RegisterBooleanPref(
74 prefs::kAccessibilityImageLabelsEnabled, false,
75 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
76 registry->RegisterBooleanPref(
77 prefs::kAccessibilityImageLabelsOptInAccepted, false,
78 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
79 }
80
81 // static
InitOffTheRecordPrefs(Profile * off_the_record_profile)82 void AccessibilityLabelsService::InitOffTheRecordPrefs(
83 Profile* off_the_record_profile) {
84 DCHECK(off_the_record_profile->IsOffTheRecord());
85 off_the_record_profile->GetPrefs()->SetBoolean(
86 prefs::kAccessibilityImageLabelsEnabled, false);
87 off_the_record_profile->GetPrefs()->SetBoolean(
88 prefs::kAccessibilityImageLabelsOptInAccepted, false);
89 }
90
Init()91 void AccessibilityLabelsService::Init() {
92 // Hidden behind a feature flag.
93 if (!base::FeatureList::IsEnabled(features::kExperimentalAccessibilityLabels))
94 return;
95
96 pref_change_registrar_.Init(profile_->GetPrefs());
97 pref_change_registrar_.Add(
98 prefs::kAccessibilityImageLabelsEnabled,
99 base::BindRepeating(
100 &AccessibilityLabelsService::OnImageLabelsEnabledChanged,
101 weak_factory_.GetWeakPtr()));
102
103 // Log whether the feature is enabled after startup. This must be run on the
104 // UI thread because it accesses prefs.
105 content::BrowserAccessibilityState::GetInstance()
106 ->AddUIThreadHistogramCallback(base::BindRepeating(
107 &AccessibilityLabelsService::UpdateAccessibilityLabelsHistograms,
108 weak_factory_.GetWeakPtr()));
109 }
110
AccessibilityLabelsService(Profile * profile)111 AccessibilityLabelsService::AccessibilityLabelsService(Profile* profile)
112 : profile_(profile) {}
113
GetAXMode()114 ui::AXMode AccessibilityLabelsService::GetAXMode() {
115 ui::AXMode ax_mode =
116 content::BrowserAccessibilityState::GetInstance()->GetAccessibilityMode();
117
118 // Hidden behind a feature flag.
119 if (base::FeatureList::IsEnabled(
120 features::kExperimentalAccessibilityLabels)) {
121 bool enabled = profile_->GetPrefs()->GetBoolean(
122 prefs::kAccessibilityImageLabelsEnabled);
123 ax_mode.set_mode(ui::AXMode::kLabelImages, enabled);
124 }
125
126 return ax_mode;
127 }
128
EnableLabelsServiceOnce()129 void AccessibilityLabelsService::EnableLabelsServiceOnce() {
130 if (!accessibility_state_utils::IsScreenReaderEnabled()) {
131 return;
132 }
133
134 // TODO(crbug.com/905419): Implement for Android, which does not support
135 // BrowserList::GetInstance.
136 #if !defined(OS_ANDROID)
137 Browser* browser = chrome::FindLastActiveWithProfile(profile_);
138 if (!browser)
139 return;
140 auto* web_contents = browser->tab_strip_model()->GetActiveWebContents();
141 if (!web_contents)
142 return;
143 // Fire an AXAction on the active tab to enable this feature once only.
144 ui::AXActionData action_data;
145 action_data.action = ax::mojom::Action::kAnnotatePageImages;
146 for (content::RenderFrameHost* frame : web_contents->GetAllFrames()) {
147 if (frame->IsRenderFrameLive())
148 frame->AccessibilityPerformAction(action_data);
149 }
150 #endif
151 }
152
BindImageAnnotator(mojo::PendingReceiver<image_annotation::mojom::Annotator> receiver)153 void AccessibilityLabelsService::BindImageAnnotator(
154 mojo::PendingReceiver<image_annotation::mojom::Annotator> receiver) {
155 if (!remote_service_) {
156 auto service_receiver = remote_service_.BindNewPipeAndPassReceiver();
157 auto& binder = GetImageAnnotatorBinderOverride();
158 if (binder) {
159 binder.Run(std::move(service_receiver));
160 } else {
161 service_ = std::make_unique<image_annotation::ImageAnnotationService>(
162 std::move(service_receiver), APIKeyForChannel(),
163 profile_->GetURLLoaderFactory(),
164 std::make_unique<ImageAnnotatorClient>());
165 }
166 }
167
168 remote_service_->BindAnnotator(std::move(receiver));
169 }
170
OverrideImageAnnotatorBinderForTesting(ImageAnnotatorBinder binder)171 void AccessibilityLabelsService::OverrideImageAnnotatorBinderForTesting(
172 ImageAnnotatorBinder binder) {
173 GetImageAnnotatorBinderOverride() = std::move(binder);
174 }
175
OnImageLabelsEnabledChanged()176 void AccessibilityLabelsService::OnImageLabelsEnabledChanged() {
177 // TODO(dmazzoni) Implement for Android, which doesn't support
178 // AllTabContentses(). crbug.com/905419
179 #if !defined(OS_ANDROID)
180 bool enabled = profile_->GetPrefs()->GetBoolean(
181 prefs::kAccessibilityImageLabelsEnabled) &&
182 accessibility_state_utils::IsScreenReaderEnabled();
183
184 for (auto* web_contents : AllTabContentses()) {
185 if (web_contents->GetBrowserContext() != profile_)
186 continue;
187
188 ui::AXMode ax_mode = web_contents->GetAccessibilityMode();
189 ax_mode.set_mode(ui::AXMode::kLabelImages, enabled);
190 web_contents->SetAccessibilityMode(ax_mode);
191 }
192 #endif
193 }
194
UpdateAccessibilityLabelsHistograms()195 void AccessibilityLabelsService::UpdateAccessibilityLabelsHistograms() {
196 if (!profile_ || !profile_->GetPrefs())
197 return;
198
199 base::UmaHistogramBoolean("Accessibility.ImageLabels",
200 profile_->GetPrefs()->GetBoolean(
201 prefs::kAccessibilityImageLabelsEnabled));
202 }
203