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