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.
5 #include "chrome/browser/extensions/api/messaging/incognito_connectability.h"
7 #include "base/lazy_instance.h"
8 #include "base/logging.h"
9 #include "base/strings/string16.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/infobars/infobar_service.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "components/infobars/core/confirm_infobar_delegate.h"
15 #include "components/infobars/core/infobar.h"
16 #include "content/public/browser/web_contents.h"
17 #include "extensions/common/extension.h"
18 #include "ui/base/l10n/l10n_util.h"
20 using infobars::InfoBar
;
21 using infobars::InfoBarManager
;
23 namespace extensions
{
27 IncognitoConnectability::ScopedAlertTracker::Mode g_alert_mode
=
28 IncognitoConnectability::ScopedAlertTracker::INTERACTIVE
;
29 int g_alert_count
= 0;
31 class IncognitoConnectabilityInfoBarDelegate
: public ConfirmInfoBarDelegate
{
33 typedef base::Callback
<void(
34 IncognitoConnectability::ScopedAlertTracker::Mode
)> InfoBarCallback
;
36 // Creates a confirmation infobar and delegate and adds the infobar to
38 static InfoBar
* Create(InfoBarManager
* infobar_manager
,
39 const base::string16
& message
,
40 const InfoBarCallback
& callback
);
42 // Marks the infobar as answered so that the callback is not executed when the
43 // delegate is destroyed.
44 void set_answered() { answered_
= true; }
47 IncognitoConnectabilityInfoBarDelegate(const base::string16
& message
,
48 const InfoBarCallback
& callback
);
49 ~IncognitoConnectabilityInfoBarDelegate() override
;
51 // ConfirmInfoBarDelegate:
52 Type
GetInfoBarType() const override
;
53 base::string16
GetMessageText() const override
;
54 base::string16
GetButtonLabel(InfoBarButton button
) const override
;
55 bool Accept() override
;
56 bool Cancel() override
;
58 base::string16 message_
;
60 InfoBarCallback callback_
;
64 InfoBar
* IncognitoConnectabilityInfoBarDelegate::Create(
65 InfoBarManager
* infobar_manager
,
66 const base::string16
& message
,
67 const IncognitoConnectabilityInfoBarDelegate::InfoBarCallback
& callback
) {
68 return infobar_manager
->AddInfoBar(infobar_manager
->CreateConfirmInfoBar(
69 scoped_ptr
<ConfirmInfoBarDelegate
>(
70 new IncognitoConnectabilityInfoBarDelegate(message
, callback
))));
73 IncognitoConnectabilityInfoBarDelegate::IncognitoConnectabilityInfoBarDelegate(
74 const base::string16
& message
,
75 const InfoBarCallback
& callback
)
76 : message_(message
), answered_(false), callback_(callback
) {
79 IncognitoConnectabilityInfoBarDelegate::
80 ~IncognitoConnectabilityInfoBarDelegate() {
82 // The infobar has closed without the user expressing an explicit
83 // preference. The current request should be denied but further requests
84 // should show an interactive prompt.
85 callback_
.Run(IncognitoConnectability::ScopedAlertTracker::INTERACTIVE
);
89 infobars::InfoBarDelegate::Type
90 IncognitoConnectabilityInfoBarDelegate::GetInfoBarType() const {
91 return PAGE_ACTION_TYPE
;
94 base::string16
IncognitoConnectabilityInfoBarDelegate::GetMessageText() const {
98 base::string16
IncognitoConnectabilityInfoBarDelegate::GetButtonLabel(
99 InfoBarButton button
) const {
100 return l10n_util::GetStringUTF16(
101 (button
== BUTTON_OK
) ? IDS_PERMISSION_ALLOW
: IDS_PERMISSION_DENY
);
104 bool IncognitoConnectabilityInfoBarDelegate::Accept() {
105 callback_
.Run(IncognitoConnectability::ScopedAlertTracker::ALWAYS_ALLOW
);
110 bool IncognitoConnectabilityInfoBarDelegate::Cancel() {
111 callback_
.Run(IncognitoConnectability::ScopedAlertTracker::ALWAYS_DENY
);
118 IncognitoConnectability::ScopedAlertTracker::ScopedAlertTracker(Mode mode
)
119 : last_checked_invocation_count_(g_alert_count
) {
120 DCHECK_EQ(INTERACTIVE
, g_alert_mode
);
121 DCHECK_NE(INTERACTIVE
, mode
);
125 IncognitoConnectability::ScopedAlertTracker::~ScopedAlertTracker() {
126 DCHECK_NE(INTERACTIVE
, g_alert_mode
);
127 g_alert_mode
= INTERACTIVE
;
130 int IncognitoConnectability::ScopedAlertTracker::GetAndResetAlertCount() {
131 int result
= g_alert_count
- last_checked_invocation_count_
;
132 last_checked_invocation_count_
= g_alert_count
;
136 IncognitoConnectability::IncognitoConnectability(
137 content::BrowserContext
* context
)
138 : weak_factory_(this) {
139 CHECK(context
->IsOffTheRecord());
142 IncognitoConnectability::~IncognitoConnectability() {
146 IncognitoConnectability
* IncognitoConnectability::Get(
147 content::BrowserContext
* context
) {
148 return BrowserContextKeyedAPIFactory
<IncognitoConnectability
>::Get(context
);
151 void IncognitoConnectability::Query(
152 const Extension
* extension
,
153 content::WebContents
* web_contents
,
155 const base::Callback
<void(bool)>& callback
) {
156 GURL origin
= url
.GetOrigin();
157 if (origin
.is_empty()) {
162 if (IsInMap(extension
, origin
, allowed_origins_
)) {
167 if (IsInMap(extension
, origin
, disallowed_origins_
)) {
172 PendingOrigin
& pending_origin
=
173 pending_origins_
[make_pair(extension
->id(), origin
)];
174 InfoBarManager
* infobar_manager
=
175 InfoBarService::FromWebContents(web_contents
);
176 TabContext
& tab_context
= pending_origin
[infobar_manager
];
177 tab_context
.callbacks
.push_back(callback
);
178 if (tab_context
.infobar
) {
179 // This tab is already displaying an infobar for this extension and origin.
183 // We need to ask the user.
186 switch (g_alert_mode
) {
187 // Production code should always be using INTERACTIVE.
188 case ScopedAlertTracker::INTERACTIVE
: {
191 ? IDS_EXTENSION_PROMPT_APP_CONNECT_FROM_INCOGNITO
192 : IDS_EXTENSION_PROMPT_EXTENSION_CONNECT_FROM_INCOGNITO
;
193 tab_context
.infobar
= IncognitoConnectabilityInfoBarDelegate::Create(
194 infobar_manager
, l10n_util::GetStringFUTF16(
195 template_id
, base::UTF8ToUTF16(origin
.spec()),
196 base::UTF8ToUTF16(extension
->name())),
197 base::Bind(&IncognitoConnectability::OnInteractiveResponse
,
198 weak_factory_
.GetWeakPtr(), extension
->id(), origin
,
203 // Testing code can override to always allow or deny.
204 case ScopedAlertTracker::ALWAYS_ALLOW
:
205 case ScopedAlertTracker::ALWAYS_DENY
:
206 OnInteractiveResponse(extension
->id(), origin
, infobar_manager
,
212 IncognitoConnectability::TabContext::TabContext() : infobar(nullptr) {
215 IncognitoConnectability::TabContext::~TabContext() {
218 void IncognitoConnectability::OnInteractiveResponse(
219 const std::string
& extension_id
,
221 InfoBarManager
* infobar_manager
,
222 ScopedAlertTracker::Mode response
) {
224 case ScopedAlertTracker::ALWAYS_ALLOW
:
225 allowed_origins_
[extension_id
].insert(origin
);
227 case ScopedAlertTracker::ALWAYS_DENY
:
228 disallowed_origins_
[extension_id
].insert(origin
);
231 // Otherwise the user has not expressed an explicit preference and so
232 // nothing should be permanently recorded.
236 DCHECK(ContainsKey(pending_origins_
, make_pair(extension_id
, origin
)));
237 PendingOrigin
& pending_origin
=
238 pending_origins_
[make_pair(extension_id
, origin
)];
239 DCHECK(ContainsKey(pending_origin
, infobar_manager
));
241 std::vector
<base::Callback
<void(bool)>> callbacks
;
242 if (response
== ScopedAlertTracker::INTERACTIVE
) {
243 // No definitive answer for this extension and origin. Execute only the
244 // callbacks associated with this tab.
245 TabContext
& tab_context
= pending_origin
[infobar_manager
];
246 callbacks
.swap(tab_context
.callbacks
);
247 pending_origin
.erase(infobar_manager
);
249 // We have a definitive answer for this extension and origin. Close all
250 // other infobars and answer all the callbacks.
251 for (const auto& map_entry
: pending_origin
) {
252 InfoBarManager
* other_infobar_manager
= map_entry
.first
;
253 const TabContext
& other_tab_context
= map_entry
.second
;
254 if (other_infobar_manager
!= infobar_manager
) {
255 // Disarm the delegate so that it doesn't think the infobar has been
257 IncognitoConnectabilityInfoBarDelegate
* delegate
=
258 static_cast<IncognitoConnectabilityInfoBarDelegate
*>(
259 other_tab_context
.infobar
->delegate());
260 delegate
->set_answered();
261 other_infobar_manager
->RemoveInfoBar(other_tab_context
.infobar
);
263 callbacks
.insert(callbacks
.end(), other_tab_context
.callbacks
.begin(),
264 other_tab_context
.callbacks
.end());
266 pending_origins_
.erase(make_pair(extension_id
, origin
));
269 DCHECK(!callbacks
.empty());
270 for (const auto& callback
: callbacks
) {
271 callback
.Run(response
== ScopedAlertTracker::ALWAYS_ALLOW
);
275 bool IncognitoConnectability::IsInMap(const Extension
* extension
,
277 const ExtensionToOriginsMap
& map
) {
278 DCHECK_EQ(origin
, origin
.GetOrigin());
279 ExtensionToOriginsMap::const_iterator it
= map
.find(extension
->id());
280 return it
!= map
.end() && it
->second
.count(origin
) > 0;
283 static base::LazyInstance
<
284 BrowserContextKeyedAPIFactory
<IncognitoConnectability
> > g_factory
=
285 LAZY_INSTANCE_INITIALIZER
;
288 BrowserContextKeyedAPIFactory
<IncognitoConnectability
>*
289 IncognitoConnectability::GetFactoryInstance() {
290 return g_factory
.Pointer();
293 } // namespace extensions