| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/messaging/incognito_connectability.h" |
| |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/infobars/infobar_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/infobars/core/confirm_infobar_delegate.h" |
| #include "components/infobars/core/infobar.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/common/extension.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| using infobars::InfoBar; |
| using infobars::InfoBarManager; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| IncognitoConnectability::ScopedAlertTracker::Mode g_alert_mode = |
| IncognitoConnectability::ScopedAlertTracker::INTERACTIVE; |
| int g_alert_count = 0; |
| |
| class IncognitoConnectabilityInfoBarDelegate : public ConfirmInfoBarDelegate { |
| public: |
| typedef base::Callback<void( |
| IncognitoConnectability::ScopedAlertTracker::Mode)> InfoBarCallback; |
| |
| // Creates a confirmation infobar and delegate and adds the infobar to |
| // |infobar_service|. |
| static InfoBar* Create(InfoBarManager* infobar_manager, |
| const base::string16& message, |
| const InfoBarCallback& callback); |
| |
| // Marks the infobar as answered so that the callback is not executed when the |
| // delegate is destroyed. |
| void set_answered() { answered_ = true; } |
| |
| private: |
| IncognitoConnectabilityInfoBarDelegate(const base::string16& message, |
| const InfoBarCallback& callback); |
| ~IncognitoConnectabilityInfoBarDelegate() override; |
| |
| // ConfirmInfoBarDelegate: |
| Type GetInfoBarType() const override; |
| base::string16 GetMessageText() const override; |
| base::string16 GetButtonLabel(InfoBarButton button) const override; |
| bool Accept() override; |
| bool Cancel() override; |
| |
| base::string16 message_; |
| bool answered_; |
| InfoBarCallback callback_; |
| }; |
| |
| // static |
| InfoBar* IncognitoConnectabilityInfoBarDelegate::Create( |
| InfoBarManager* infobar_manager, |
| const base::string16& message, |
| const IncognitoConnectabilityInfoBarDelegate::InfoBarCallback& callback) { |
| return infobar_manager->AddInfoBar(infobar_manager->CreateConfirmInfoBar( |
| scoped_ptr<ConfirmInfoBarDelegate>( |
| new IncognitoConnectabilityInfoBarDelegate(message, callback)))); |
| } |
| |
| IncognitoConnectabilityInfoBarDelegate::IncognitoConnectabilityInfoBarDelegate( |
| const base::string16& message, |
| const InfoBarCallback& callback) |
| : message_(message), answered_(false), callback_(callback) { |
| } |
| |
| IncognitoConnectabilityInfoBarDelegate:: |
| ~IncognitoConnectabilityInfoBarDelegate() { |
| if (!answered_) { |
| // The infobar has closed without the user expressing an explicit |
| // preference. The current request should be denied but further requests |
| // should show an interactive prompt. |
| callback_.Run(IncognitoConnectability::ScopedAlertTracker::INTERACTIVE); |
| } |
| } |
| |
| infobars::InfoBarDelegate::Type |
| IncognitoConnectabilityInfoBarDelegate::GetInfoBarType() const { |
| return PAGE_ACTION_TYPE; |
| } |
| |
| base::string16 IncognitoConnectabilityInfoBarDelegate::GetMessageText() const { |
| return message_; |
| } |
| |
| base::string16 IncognitoConnectabilityInfoBarDelegate::GetButtonLabel( |
| InfoBarButton button) const { |
| return l10n_util::GetStringUTF16( |
| (button == BUTTON_OK) ? IDS_PERMISSION_ALLOW : IDS_PERMISSION_DENY); |
| } |
| |
| bool IncognitoConnectabilityInfoBarDelegate::Accept() { |
| callback_.Run(IncognitoConnectability::ScopedAlertTracker::ALWAYS_ALLOW); |
| answered_ = true; |
| return true; |
| } |
| |
| bool IncognitoConnectabilityInfoBarDelegate::Cancel() { |
| callback_.Run(IncognitoConnectability::ScopedAlertTracker::ALWAYS_DENY); |
| answered_ = true; |
| return true; |
| } |
| |
| } // namespace |
| |
| IncognitoConnectability::ScopedAlertTracker::ScopedAlertTracker(Mode mode) |
| : last_checked_invocation_count_(g_alert_count) { |
| DCHECK_EQ(INTERACTIVE, g_alert_mode); |
| DCHECK_NE(INTERACTIVE, mode); |
| g_alert_mode = mode; |
| } |
| |
| IncognitoConnectability::ScopedAlertTracker::~ScopedAlertTracker() { |
| DCHECK_NE(INTERACTIVE, g_alert_mode); |
| g_alert_mode = INTERACTIVE; |
| } |
| |
| int IncognitoConnectability::ScopedAlertTracker::GetAndResetAlertCount() { |
| int result = g_alert_count - last_checked_invocation_count_; |
| last_checked_invocation_count_ = g_alert_count; |
| return result; |
| } |
| |
| IncognitoConnectability::IncognitoConnectability( |
| content::BrowserContext* context) |
| : weak_factory_(this) { |
| CHECK(context->IsOffTheRecord()); |
| } |
| |
| IncognitoConnectability::~IncognitoConnectability() { |
| } |
| |
| // static |
| IncognitoConnectability* IncognitoConnectability::Get( |
| content::BrowserContext* context) { |
| return BrowserContextKeyedAPIFactory<IncognitoConnectability>::Get(context); |
| } |
| |
| void IncognitoConnectability::Query( |
| const Extension* extension, |
| content::WebContents* web_contents, |
| const GURL& url, |
| const base::Callback<void(bool)>& callback) { |
| GURL origin = url.GetOrigin(); |
| if (origin.is_empty()) { |
| callback.Run(false); |
| return; |
| } |
| |
| if (IsInMap(extension, origin, allowed_origins_)) { |
| callback.Run(true); |
| return; |
| } |
| |
| if (IsInMap(extension, origin, disallowed_origins_)) { |
| callback.Run(false); |
| return; |
| } |
| |
| PendingOrigin& pending_origin = |
| pending_origins_[make_pair(extension->id(), origin)]; |
| InfoBarManager* infobar_manager = |
| InfoBarService::FromWebContents(web_contents); |
| TabContext& tab_context = pending_origin[infobar_manager]; |
| tab_context.callbacks.push_back(callback); |
| if (tab_context.infobar) { |
| // This tab is already displaying an infobar for this extension and origin. |
| return; |
| } |
| |
| // We need to ask the user. |
| ++g_alert_count; |
| |
| switch (g_alert_mode) { |
| // Production code should always be using INTERACTIVE. |
| case ScopedAlertTracker::INTERACTIVE: { |
| int template_id = |
| extension->is_app() |
| ? IDS_EXTENSION_PROMPT_APP_CONNECT_FROM_INCOGNITO |
| : IDS_EXTENSION_PROMPT_EXTENSION_CONNECT_FROM_INCOGNITO; |
| tab_context.infobar = IncognitoConnectabilityInfoBarDelegate::Create( |
| infobar_manager, l10n_util::GetStringFUTF16( |
| template_id, base::UTF8ToUTF16(origin.spec()), |
| base::UTF8ToUTF16(extension->name())), |
| base::Bind(&IncognitoConnectability::OnInteractiveResponse, |
| weak_factory_.GetWeakPtr(), extension->id(), origin, |
| infobar_manager)); |
| break; |
| } |
| |
| // Testing code can override to always allow or deny. |
| case ScopedAlertTracker::ALWAYS_ALLOW: |
| case ScopedAlertTracker::ALWAYS_DENY: |
| OnInteractiveResponse(extension->id(), origin, infobar_manager, |
| g_alert_mode); |
| break; |
| } |
| } |
| |
| IncognitoConnectability::TabContext::TabContext() : infobar(nullptr) { |
| } |
| |
| IncognitoConnectability::TabContext::~TabContext() { |
| } |
| |
| void IncognitoConnectability::OnInteractiveResponse( |
| const std::string& extension_id, |
| const GURL& origin, |
| InfoBarManager* infobar_manager, |
| ScopedAlertTracker::Mode response) { |
| switch (response) { |
| case ScopedAlertTracker::ALWAYS_ALLOW: |
| allowed_origins_[extension_id].insert(origin); |
| break; |
| case ScopedAlertTracker::ALWAYS_DENY: |
| disallowed_origins_[extension_id].insert(origin); |
| break; |
| default: |
| // Otherwise the user has not expressed an explicit preference and so |
| // nothing should be permanently recorded. |
| break; |
| } |
| |
| DCHECK(ContainsKey(pending_origins_, make_pair(extension_id, origin))); |
| PendingOrigin& pending_origin = |
| pending_origins_[make_pair(extension_id, origin)]; |
| DCHECK(ContainsKey(pending_origin, infobar_manager)); |
| |
| std::vector<base::Callback<void(bool)>> callbacks; |
| if (response == ScopedAlertTracker::INTERACTIVE) { |
| // No definitive answer for this extension and origin. Execute only the |
| // callbacks associated with this tab. |
| TabContext& tab_context = pending_origin[infobar_manager]; |
| callbacks.swap(tab_context.callbacks); |
| pending_origin.erase(infobar_manager); |
| } else { |
| // We have a definitive answer for this extension and origin. Close all |
| // other infobars and answer all the callbacks. |
| for (const auto& map_entry : pending_origin) { |
| InfoBarManager* other_infobar_manager = map_entry.first; |
| const TabContext& other_tab_context = map_entry.second; |
| if (other_infobar_manager != infobar_manager) { |
| // Disarm the delegate so that it doesn't think the infobar has been |
| // dismissed. |
| IncognitoConnectabilityInfoBarDelegate* delegate = |
| static_cast<IncognitoConnectabilityInfoBarDelegate*>( |
| other_tab_context.infobar->delegate()); |
| delegate->set_answered(); |
| other_infobar_manager->RemoveInfoBar(other_tab_context.infobar); |
| } |
| callbacks.insert(callbacks.end(), other_tab_context.callbacks.begin(), |
| other_tab_context.callbacks.end()); |
| } |
| pending_origins_.erase(make_pair(extension_id, origin)); |
| } |
| |
| DCHECK(!callbacks.empty()); |
| for (const auto& callback : callbacks) { |
| callback.Run(response == ScopedAlertTracker::ALWAYS_ALLOW); |
| } |
| } |
| |
| bool IncognitoConnectability::IsInMap(const Extension* extension, |
| const GURL& origin, |
| const ExtensionToOriginsMap& map) { |
| DCHECK_EQ(origin, origin.GetOrigin()); |
| ExtensionToOriginsMap::const_iterator it = map.find(extension->id()); |
| return it != map.end() && it->second.count(origin) > 0; |
| } |
| |
| static base::LazyInstance< |
| BrowserContextKeyedAPIFactory<IncognitoConnectability> > g_factory = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<IncognitoConnectability>* |
| IncognitoConnectability::GetFactoryInstance() { |
| return g_factory.Pointer(); |
| } |
| |
| } // namespace extensions |