1 // Copyright 2013 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/extensions/api/messaging/incognito_connectability.h"
6
7 #include "base/bind.h"
8 #include "base/lazy_instance.h"
9 #include "base/logging.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.h"
13 #include "chrome/browser/infobars/infobar_service.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "components/infobars/core/infobar.h"
17 #include "content/public/browser/web_contents.h"
18 #include "extensions/common/extension.h"
19 #include "ui/base/l10n/l10n_util.h"
20
21 namespace extensions {
22
23 namespace {
24
25 IncognitoConnectability::ScopedAlertTracker::Mode g_alert_mode =
26 IncognitoConnectability::ScopedAlertTracker::INTERACTIVE;
27 int g_alert_count = 0;
28
29 } // namespace
30
ScopedAlertTracker(Mode mode)31 IncognitoConnectability::ScopedAlertTracker::ScopedAlertTracker(Mode mode)
32 : last_checked_invocation_count_(g_alert_count) {
33 DCHECK_EQ(INTERACTIVE, g_alert_mode);
34 DCHECK_NE(INTERACTIVE, mode);
35 g_alert_mode = mode;
36 }
37
~ScopedAlertTracker()38 IncognitoConnectability::ScopedAlertTracker::~ScopedAlertTracker() {
39 DCHECK_NE(INTERACTIVE, g_alert_mode);
40 g_alert_mode = INTERACTIVE;
41 }
42
GetAndResetAlertCount()43 int IncognitoConnectability::ScopedAlertTracker::GetAndResetAlertCount() {
44 int result = g_alert_count - last_checked_invocation_count_;
45 last_checked_invocation_count_ = g_alert_count;
46 return result;
47 }
48
IncognitoConnectability(content::BrowserContext * context)49 IncognitoConnectability::IncognitoConnectability(
50 content::BrowserContext* context) {
51 CHECK(context->IsOffTheRecord());
52 }
53
~IncognitoConnectability()54 IncognitoConnectability::~IncognitoConnectability() {
55 }
56
57 // static
Get(content::BrowserContext * context)58 IncognitoConnectability* IncognitoConnectability::Get(
59 content::BrowserContext* context) {
60 return BrowserContextKeyedAPIFactory<IncognitoConnectability>::Get(context);
61 }
62
Query(const Extension * extension,content::WebContents * web_contents,const GURL & url,const base::Callback<void (bool)> & callback)63 void IncognitoConnectability::Query(
64 const Extension* extension,
65 content::WebContents* web_contents,
66 const GURL& url,
67 const base::Callback<void(bool)>& callback) {
68 GURL origin = url.GetOrigin();
69 if (origin.is_empty()) {
70 callback.Run(false);
71 return;
72 }
73
74 if (IsInMap(extension, origin, allowed_origins_)) {
75 callback.Run(true);
76 return;
77 }
78
79 if (IsInMap(extension, origin, disallowed_origins_)) {
80 callback.Run(false);
81 return;
82 }
83
84 PendingOrigin& pending_origin =
85 pending_origins_[make_pair(extension->id(), origin)];
86 InfoBarService* infobar_service =
87 InfoBarService::FromWebContents(web_contents);
88 TabContext& tab_context = pending_origin[infobar_service];
89 tab_context.callbacks.push_back(callback);
90 if (tab_context.infobar) {
91 // This tab is already displaying an infobar for this extension and origin.
92 return;
93 }
94
95 // We need to ask the user.
96 ++g_alert_count;
97
98 switch (g_alert_mode) {
99 // Production code should always be using INTERACTIVE.
100 case ScopedAlertTracker::INTERACTIVE: {
101 int template_id =
102 extension->is_app()
103 ? IDS_EXTENSION_PROMPT_APP_CONNECT_FROM_INCOGNITO
104 : IDS_EXTENSION_PROMPT_EXTENSION_CONNECT_FROM_INCOGNITO;
105 tab_context.infobar = IncognitoConnectabilityInfoBarDelegate::Create(
106 infobar_service,
107 l10n_util::GetStringFUTF16(template_id,
108 base::UTF8ToUTF16(origin.spec()),
109 base::UTF8ToUTF16(extension->name())),
110 base::Bind(&IncognitoConnectability::OnInteractiveResponse,
111 weak_factory_.GetWeakPtr(), extension->id(), origin,
112 infobar_service));
113 break;
114 }
115
116 // Testing code can override to always allow or deny.
117 case ScopedAlertTracker::ALWAYS_ALLOW:
118 case ScopedAlertTracker::ALWAYS_DENY:
119 OnInteractiveResponse(extension->id(), origin, infobar_service,
120 g_alert_mode);
121 break;
122 }
123 }
124
TabContext()125 IncognitoConnectability::TabContext::TabContext() : infobar(nullptr) {
126 }
127
128 IncognitoConnectability::TabContext::TabContext(const TabContext& other) =
129 default;
130
~TabContext()131 IncognitoConnectability::TabContext::~TabContext() {
132 }
133
OnInteractiveResponse(const std::string & extension_id,const GURL & origin,InfoBarService * infobar_service,ScopedAlertTracker::Mode response)134 void IncognitoConnectability::OnInteractiveResponse(
135 const std::string& extension_id,
136 const GURL& origin,
137 InfoBarService* infobar_service,
138 ScopedAlertTracker::Mode response) {
139 switch (response) {
140 case ScopedAlertTracker::ALWAYS_ALLOW:
141 allowed_origins_[extension_id].insert(origin);
142 break;
143 case ScopedAlertTracker::ALWAYS_DENY:
144 disallowed_origins_[extension_id].insert(origin);
145 break;
146 default:
147 // Otherwise the user has not expressed an explicit preference and so
148 // nothing should be permanently recorded.
149 break;
150 }
151
152 DCHECK(base::Contains(pending_origins_, make_pair(extension_id, origin)));
153 PendingOrigin& pending_origin =
154 pending_origins_[make_pair(extension_id, origin)];
155 DCHECK(base::Contains(pending_origin, infobar_service));
156
157 std::vector<base::Callback<void(bool)>> callbacks;
158 if (response == ScopedAlertTracker::INTERACTIVE) {
159 // No definitive answer for this extension and origin. Execute only the
160 // callbacks associated with this tab.
161 TabContext& tab_context = pending_origin[infobar_service];
162 callbacks.swap(tab_context.callbacks);
163 pending_origin.erase(infobar_service);
164 } else {
165 // We have a definitive answer for this extension and origin. Close all
166 // other infobars and answer all the callbacks.
167 for (const auto& map_entry : pending_origin) {
168 InfoBarService* other_infobar_service = map_entry.first;
169 const TabContext& other_tab_context = map_entry.second;
170 if (other_infobar_service != infobar_service) {
171 // Disarm the delegate so that it doesn't think the infobar has been
172 // dismissed.
173 IncognitoConnectabilityInfoBarDelegate* delegate =
174 static_cast<IncognitoConnectabilityInfoBarDelegate*>(
175 other_tab_context.infobar->delegate());
176 delegate->set_answered();
177 other_infobar_service->RemoveInfoBar(other_tab_context.infobar);
178 }
179 callbacks.insert(callbacks.end(), other_tab_context.callbacks.begin(),
180 other_tab_context.callbacks.end());
181 }
182 pending_origins_.erase(make_pair(extension_id, origin));
183 }
184
185 DCHECK(!callbacks.empty());
186 for (const auto& callback : callbacks) {
187 callback.Run(response == ScopedAlertTracker::ALWAYS_ALLOW);
188 }
189 }
190
IsInMap(const Extension * extension,const GURL & origin,const ExtensionToOriginsMap & map)191 bool IncognitoConnectability::IsInMap(const Extension* extension,
192 const GURL& origin,
193 const ExtensionToOriginsMap& map) {
194 DCHECK_EQ(origin, origin.GetOrigin());
195 auto it = map.find(extension->id());
196 return it != map.end() && it->second.count(origin) > 0;
197 }
198
199 static base::LazyInstance<
200 BrowserContextKeyedAPIFactory<IncognitoConnectability>>::DestructorAtExit
201 g_incognito_connectability_factory = LAZY_INSTANCE_INITIALIZER;
202
203 // static
204 BrowserContextKeyedAPIFactory<IncognitoConnectability>*
GetFactoryInstance()205 IncognitoConnectability::GetFactoryInstance() {
206 return g_incognito_connectability_factory.Pointer();
207 }
208
209 } // namespace extensions
210