blob: b720b713497397acbe7b80e8dd336326ed21dcf7 [file] [log] [blame]
// 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/bind.h"
#include "base/check_op.h"
#include "base/lazy_instance.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/api/messaging/incognito_connectability_infobar_delegate.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/grit/generated_resources.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"
namespace extensions {
namespace {
IncognitoConnectability::ScopedAlertTracker::Mode g_alert_mode =
IncognitoConnectability::ScopedAlertTracker::INTERACTIVE;
int g_alert_count = 0;
} // 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) {
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,
base::OnceCallback<void(bool)> callback) {
GURL origin = url.GetOrigin();
if (origin.is_empty()) {
std::move(callback).Run(false);
return;
}
if (IsInMap(extension, origin, allowed_origins_)) {
std::move(callback).Run(true);
return;
}
if (IsInMap(extension, origin, disallowed_origins_)) {
std::move(callback).Run(false);
return;
}
PendingOrigin& pending_origin =
pending_origins_[make_pair(extension->id(), origin)];
InfoBarService* infobar_service =
InfoBarService::FromWebContents(web_contents);
TabContext& tab_context = pending_origin[infobar_service];
tab_context.callbacks.push_back(std::move(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_service,
l10n_util::GetStringFUTF16(template_id,
base::UTF8ToUTF16(origin.spec()),
base::UTF8ToUTF16(extension->name())),
base::BindOnce(&IncognitoConnectability::OnInteractiveResponse,
weak_factory_.GetWeakPtr(), extension->id(), origin,
infobar_service));
break;
}
// Testing code can override to always allow or deny.
case ScopedAlertTracker::ALWAYS_ALLOW:
case ScopedAlertTracker::ALWAYS_DENY:
OnInteractiveResponse(extension->id(), origin, infobar_service,
g_alert_mode);
break;
}
}
IncognitoConnectability::TabContext::TabContext() : infobar(nullptr) {
}
IncognitoConnectability::TabContext::~TabContext() = default;
void IncognitoConnectability::OnInteractiveResponse(
const std::string& extension_id,
const GURL& origin,
InfoBarService* infobar_service,
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;
}
PendingOriginMap::iterator origin_it =
pending_origins_.find(make_pair(extension_id, origin));
DCHECK(origin_it != pending_origins_.end());
PendingOrigin& pending_origin = origin_it->second;
DCHECK(base::Contains(pending_origin, infobar_service));
std::vector<base::OnceCallback<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_service];
callbacks.swap(tab_context.callbacks);
pending_origin.erase(infobar_service);
} else {
// We have a definitive answer for this extension and origin. Close all
// other infobars and answer all the callbacks.
for (auto& map_entry : pending_origin) {
InfoBarService* other_infobar_service = map_entry.first;
TabContext& other_tab_context = map_entry.second;
if (other_infobar_service != infobar_service) {
// 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_service->RemoveInfoBar(other_tab_context.infobar);
}
callbacks.insert(
callbacks.end(),
std::make_move_iterator(other_tab_context.callbacks.begin()),
std::make_move_iterator(other_tab_context.callbacks.end()));
}
pending_origins_.erase(origin_it);
}
DCHECK(!callbacks.empty());
for (auto& callback : callbacks) {
std::move(callback).Run(response == ScopedAlertTracker::ALWAYS_ALLOW);
}
}
bool IncognitoConnectability::IsInMap(const Extension* extension,
const GURL& origin,
const ExtensionToOriginsMap& map) {
DCHECK_EQ(origin, origin.GetOrigin());
auto it = map.find(extension->id());
return it != map.end() && it->second.count(origin) > 0;
}
static base::LazyInstance<
BrowserContextKeyedAPIFactory<IncognitoConnectability>>::DestructorAtExit
g_incognito_connectability_factory = LAZY_INSTANCE_INITIALIZER;
// static
BrowserContextKeyedAPIFactory<IncognitoConnectability>*
IncognitoConnectability::GetFactoryInstance() {
return g_incognito_connectability_factory.Pointer();
}
} // namespace extensions