chromium/components/subresource_filter/content/browser/ads_blocked_message_delegate.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/subresource_filter/content/browser/ads_blocked_message_delegate.h"

#include <utility>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/not_fatal_until.h"
#include "base/strings/utf_string_conversions.h"
#include "components/messages/android/message_dispatcher_bridge.h"
#include "components/resources/android/theme_resources.h"
#include "components/strings/grit/components_strings.h"
#include "components/subresource_filter/android/ads_blocked_dialog.h"
#include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
#include "components/subresource_filter/core/browser/subresource_filter_constants.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"

namespace subresource_filter {

AdsBlockedMessageDelegate::~AdsBlockedMessageDelegate() {
  // Do not use message_ after this.
  DismissMessage(messages::DismissReason::UNKNOWN);
}

void AdsBlockedMessageDelegate::OnWebContentsFocused(
    content::RenderWidgetHost* render_widget_host) {
  if (reprompt_required_) {
    // This will be true only if the user has been redirected to
    // a new tab by clicking on 'Learn more' on the dialog.
    // Upon returning to the original tab from the redirected tab,
    // the dialog will be restored.
    reprompt_required_ = false;
    ShowDialog(/*should_post_dialog=*/true);
  }
}

void AdsBlockedMessageDelegate::ShowMessage() {
  if (message_ || ads_blocked_dialog_) {
    // There is already an active ads blocked message or dialog.
    return;
  }

  // Unretained is safe because |this| will always outlive |message_| which owns
  // the callback.
  auto message = std::make_unique<messages::MessageWrapper>(
      messages::MessageIdentifier::ADS_BLOCKED,
      base::BindOnce(&AdsBlockedMessageDelegate::HandleMessageOkClicked,
                     base::Unretained(this)),
      base::BindOnce(&AdsBlockedMessageDelegate::HandleMessageDismissed,
                     base::Unretained(this)));

  message->SetTitle(
      l10n_util::GetStringUTF16(IDS_BLOCKED_ADS_MESSAGE_PRIMARY_TEXT));
  message->SetPrimaryButtonText(l10n_util::GetStringUTF16(IDS_OK));
  messages::MessageDispatcherBridge* message_dispatcher_bridge =
      messages::MessageDispatcherBridge::Get();
  message->SetIconResourceId(message_dispatcher_bridge->MapToJavaDrawableId(
      IDR_ANDROID_INFOBAR_BLOCKED_POPUPS));

  message->SetSecondaryIconResourceId(
      message_dispatcher_bridge->MapToJavaDrawableId(IDR_ANDROID_SETTINGS));
  message->SetSecondaryActionCallback(base::BindRepeating(
      &AdsBlockedMessageDelegate::HandleMessageManageClicked,
      base::Unretained(this)));

  // TODO(crbug.com/40774444): On rare occasions, such as the moment when
  // activity is being recreated or destroyed, ads blocked message will not be
  // displayed.
  message_dispatcher_bridge->EnqueueMessage(
      message.get(), web_contents(), messages::MessageScopeType::NAVIGATION,
      messages::MessagePriority::kNormal);

  message_ = std::move(message);
}

void AdsBlockedMessageDelegate::DismissMessage(
    messages::DismissReason dismiss_reason) {
  if (message_) {
    messages::MessageDispatcherBridge::Get()->DismissMessage(message_.get(),
                                                             dismiss_reason);
  }
}

void AdsBlockedMessageDelegate::DismissMessageForTesting(
    messages::DismissReason dismiss_reason) {
  HandleMessageDismissed(dismiss_reason);
}

AdsBlockedMessageDelegate::AdsBlockedMessageDelegate(
    content::WebContents* web_contents)
    : AdsBlockedMessageDelegate(web_contents,
                                base::BindRepeating(AdsBlockedDialog::Create)) {
}

AdsBlockedMessageDelegate::AdsBlockedMessageDelegate(
    content::WebContents* web_contents,
    AdsBlockedDialogFactory ads_blocked_dialog_factory)
    : content::WebContentsUserData<AdsBlockedMessageDelegate>(*web_contents),
      content::WebContentsObserver(web_contents),
      ads_blocked_dialog_factory_(std::move(ads_blocked_dialog_factory)) {}

void AdsBlockedMessageDelegate::HandleMessageOkClicked() {}

void AdsBlockedMessageDelegate::HandleMessageManageClicked() {
  DismissMessage(messages::DismissReason::SECONDARY_ACTION);
  ShowDialog(/*should_post_dialog=*/false);
  subresource_filter::ContentSubresourceFilterThrottleManager::LogAction(
      subresource_filter::SubresourceFilterAction::kDetailsShown);
}

void AdsBlockedMessageDelegate::HandleMessageDismissed(
    messages::DismissReason dismiss_reason) {
  CHECK(message_, base::NotFatalUntil::M129);
  message_.reset();
}

void AdsBlockedMessageDelegate::HandleDialogAllowAdsClicked() {
  subresource_filter::ContentSubresourceFilterThrottleManager::FromPage(
      web_contents()->GetPrimaryPage())
      ->OnReloadRequested();
}

void AdsBlockedMessageDelegate::HandleDialogLearnMoreClicked() {
  reprompt_required_ = true;
  subresource_filter::ContentSubresourceFilterThrottleManager::LogAction(
      subresource_filter::SubresourceFilterAction::kClickedLearnMore);
  web_contents()->OpenURL(
      content::OpenURLParams(GURL(subresource_filter::kLearnMoreLink),
                             content::Referrer(),
                             WindowOpenDisposition::NEW_FOREGROUND_TAB,
                             ui::PAGE_TRANSITION_LINK, false),
      /*navigation_handle_callback=*/{});
}

void AdsBlockedMessageDelegate::HandleDialogDismissed() {
  if (reprompt_required_) {
    // When the dialog has been dismissed due to the user clicking on
    // 'Learn more', do not clean up the dialog instance as the dialog
    // will be restored when the user navigates back to the original tab.
    return;
  }
  CHECK(ads_blocked_dialog_, base::NotFatalUntil::M129);
  ads_blocked_dialog_.reset();
}

void AdsBlockedMessageDelegate::ShowDialog(bool should_post_dialog) {
  CHECK(!reprompt_required_, base::NotFatalUntil::M129);
  // Binding with base::Unretained(this) is safe here because
  // AdsBlockedMessageDelegate owns ads_blocked_dialog_. Callbacks won't be
  // called after the AdsBlockedMessageDelegate object is destroyed.
  ads_blocked_dialog_ = ads_blocked_dialog_factory_.Run(
      web_contents(),
      base::BindOnce(&AdsBlockedMessageDelegate::HandleDialogAllowAdsClicked,
                     base::Unretained(this)),
      base::BindOnce(&AdsBlockedMessageDelegate::HandleDialogLearnMoreClicked,
                     base::Unretained(this)),
      base::BindOnce(&AdsBlockedMessageDelegate::HandleDialogDismissed,
                     base::Unretained(this)));

  // Ads blocked dialog factory method can return nullptr when web_contents()
  // is not attached to a window. See crbug.com/1049090 for details.
  if (!ads_blocked_dialog_)
    return;
  ads_blocked_dialog_->Show(should_post_dialog);
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(AdsBlockedMessageDelegate);

}  // namespace subresource_filter