blob: a2955efa4b5cc3586ce488965056c65673048af5 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/extensions/extensions_request_access_button.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <string>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/string_util.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/extensions/extensions_container.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
#include "chrome/browser/ui/views/extensions/extensions_dialogs_utils.h"
#include "chrome/browser/ui/views/extensions/extensions_request_access_hover_card_coordinator.h"
#include "chrome/grit/generated_resources.h"
#include "components/feature_engagement/public/event_constants.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/view_class_properties.h"
namespace {
// TODO(crbug.com/1452171): Same as permission's ChipController. Pull out to a
// shared location.
constexpr auto kConfirmationDisplayDuration = base::Seconds(4);
std::vector<const extensions::Extension*> GetExtensions(
Profile* profile,
std::vector<extensions::ExtensionId>& extension_ids) {
const extensions::ExtensionSet& enabled_extensions =
extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
std::vector<const extensions::Extension*> extensions;
for (auto extension_id : extension_ids) {
extensions.push_back(enabled_extensions.GetByID(extension_id));
}
return extensions;
}
} // namespace
ExtensionsRequestAccessButton::ExtensionsRequestAccessButton(
Browser* browser,
ExtensionsContainer* extensions_container)
: ToolbarButton(
base::BindRepeating(&ExtensionsRequestAccessButton::OnButtonPressed,
base::Unretained(this))),
browser_(browser),
extensions_container_(extensions_container),
hover_card_coordinator_(
std::make_unique<ExtensionsRequestAccessHoverCardCoordinator>()) {
// Set button for IPH.
SetProperty(views::kElementIdentifierKey,
kExtensionsRequestAccessButtonElementId);
}
ExtensionsRequestAccessButton::~ExtensionsRequestAccessButton() = default;
void ExtensionsRequestAccessButton::Update(
std::vector<extensions::ExtensionId>& extension_ids) {
CHECK(!IsShowingConfirmation());
extension_ids_ = extension_ids;
SetVisible(!extension_ids_.empty());
if (extension_ids_.empty()) {
return;
}
// Show IPH when button is visible.
const int extensions_size = extension_ids.size();
browser_->window()->MaybeShowFeaturePromo(
feature_engagement::kIPHExtensionsRequestAccessButtonFeature,
/*close_callback=*/base::DoNothing(), /*body_params=*/extensions_size,
/*title_params=*/extensions_size);
// TODO(crbug.com/1239772): Set the label and background color without borders
// separately to match the mocks. For now, using SetHighlight to display that
// adds a border and highlight color in addition to the label.
absl::optional<SkColor> color;
SetHighlight(
l10n_util::GetStringFUTF16Int(IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON,
static_cast<int>(extension_ids_.size())),
color);
SetEnabled(true);
}
// TODO(crbug.com/1390952): Remove hover card once
// kExtensionsMenuAccessControlWithPermittedSites is rolled out. We are keeping
// it for now since we may bring the hover card back.
void ExtensionsRequestAccessButton::MaybeShowHoverCard() {
if (hover_card_coordinator_->IsShowing() ||
!GetWidget()->IsMouseEventsEnabled()) {
return;
}
hover_card_coordinator_->ShowBubble(GetActiveWebContents(), this,
extensions_container_, extension_ids_);
}
void ExtensionsRequestAccessButton::ResetConfirmation() {
SetVisible(false);
confirmation_origin_ = absl::nullopt;
collapse_timer_.AbandonAndStop();
}
bool ExtensionsRequestAccessButton::IsShowingConfirmation() const {
if (!confirmation_origin_.has_value()) {
return false;
}
CHECK(GetVisible());
return confirmation_origin_.has_value();
}
bool ExtensionsRequestAccessButton::IsShowingConfirmationFor(
const url::Origin& origin) const {
if (!confirmation_origin_.has_value()) {
return false;
}
CHECK(GetVisible());
return confirmation_origin_ == origin;
}
std::u16string ExtensionsRequestAccessButton::GetTooltipText(
const gfx::Point& p) const {
std::vector<std::u16string> tooltip_parts;
tooltip_parts.push_back(l10n_util::GetStringFUTF16(
IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_TOOLTIP_MULTIPLE_EXTENSIONS,
GetCurrentHost(GetActiveWebContents())));
for (const auto& extension_id : extension_ids_) {
ToolbarActionViewController* action =
extensions_container_->GetActionForId(extension_id);
tooltip_parts.push_back(action->GetActionName());
}
return base::JoinString(tooltip_parts, u"\n");
}
bool ExtensionsRequestAccessButton::ShouldShowInkdropAfterIphInteraction() {
return false;
}
void ExtensionsRequestAccessButton::OnButtonPressed() {
// Record IPH usage.
browser_->window()->NotifyFeatureEngagementEvent(
feature_engagement::events::kExtensionsRequestAccessButtonClicked);
content::WebContents* web_contents = GetActiveWebContents();
extensions::ExtensionActionRunner* action_runner =
extensions::ExtensionActionRunner::GetForWebContents(web_contents);
if (!action_runner) {
return;
}
// Make sure we set this before granting tab permissions, since that will
// trigger an update to the request access button for each extension that is
// granted access.
confirmation_origin_ =
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
// Grant tab permission to all extensions.
DCHECK_GT(extension_ids_.size(), 0u);
std::vector<const extensions::Extension*> extensions_to_run =
GetExtensions(browser_->profile(), extension_ids_);
base::RecordAction(base::UserMetricsAction(
"Extensions.Toolbar.ExtensionsActivatedFromRequestAccessButton"));
UMA_HISTOGRAM_COUNTS_100(
"Extensions.Toolbar.ExtensionsActivatedFromRequestAccessButton",
extension_ids_.size());
action_runner->GrantTabPermissions(extensions_to_run);
// Show confirmation message, and disable the button, for a specific duration.
absl::optional<SkColor> color;
SetHighlight(l10n_util::GetStringUTF16(
IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT),
color);
SetEnabled(false);
base::TimeDelta collapse_duration = remove_confirmation_for_testing_
? base::Seconds(0)
: kConfirmationDisplayDuration;
// base::Unretained() below is safe because this view is tied to the lifetime
// of `extensions_container_`.
collapse_timer_.Start(
FROM_HERE, collapse_duration,
base::BindOnce(&ExtensionsContainer::CollapseConfirmation,
base::Unretained(extensions_container_)));
}
content::WebContents* ExtensionsRequestAccessButton::GetActiveWebContents()
const {
return browser_->tab_strip_model()->GetActiveWebContents();
}