chromium/ios/chrome/browser/web/model/blocked_popup_tab_helper.mm

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

#import "ios/chrome/browser/web/model/blocked_popup_tab_helper.h"

#import <UIKit/UIKit.h>

#import <memory>
#import <utility>

#import "base/apple/bundle_locations.h"
#import "base/apple/foundation_util.h"
#import "base/format_macros.h"
#import "base/memory/raw_ptr.h"
#import "base/strings/stringprintf.h"
#import "base/strings/utf_string_conversions.h"
#import "components/content_settings/core/browser/host_content_settings_map.h"
#import "components/infobars/core/confirm_infobar_delegate.h"
#import "components/infobars/core/infobar.h"
#import "ios/chrome/browser/content_settings/model/host_content_settings_map_factory.h"
#import "ios/chrome/browser/infobars/model/confirm_infobar_metrics_recorder.h"
#import "ios/chrome/browser/infobars/model/infobar_ios.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/navigation/referrer.h"
#import "net/base/apple/url_conversions.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/models/image_model.h"
#import "ui/gfx/image/image.h"

namespace {
// The infobar to display when a popup is blocked.

// The size of the symbol image.
const CGFloat kSymbolImagePointSize = 18.;

// The name if the popup symbol.
NSString* const kPopupBadgeMinusSymbol = @"popup_badge_minus";

class BlockPopupInfoBarDelegate : public ConfirmInfoBarDelegate {
 public:
  BlockPopupInfoBarDelegate(
      ChromeBrowserState* browser_state,
      web::WebState* web_state,
      const std::vector<BlockedPopupTabHelper::Popup>& popups)
      : browser_state_(browser_state), web_state_(web_state), popups_(popups) {
    delegate_creation_time_ = [NSDate timeIntervalSinceReferenceDate];
  }

  ~BlockPopupInfoBarDelegate() override {}

  InfoBarIdentifier GetIdentifier() const override {
    return POPUP_BLOCKED_INFOBAR_DELEGATE_MOBILE;
  }

  ui::ImageModel GetIcon() const override {
    if (icon_.IsEmpty()) {
      // This symbol is not created using CustomSymbolWithPointSize() because
      // "ios/chrome/browser/shared/ui/symbols/symbols.h" cannot be imported
      // here.
      UIImageSymbolConfiguration* configuration = [UIImageSymbolConfiguration
          configurationWithPointSize:kSymbolImagePointSize
                              weight:UIImageSymbolWeightMedium
                               scale:UIImageSymbolScaleMedium];
      UIImage* image = [UIImage imageNamed:kPopupBadgeMinusSymbol
                                  inBundle:nil
                         withConfiguration:configuration];
      icon_ = gfx::Image(image);
    }
    return ui::ImageModel::FromImage(icon_);
  }

  std::u16string GetMessageText() const override {
    return l10n_util::GetStringFUTF16(
        IDS_IOS_POPUPS_BLOCKED_MOBILE,
        base::UTF8ToUTF16(base::StringPrintf("%" PRIuS, popups_.size())));
  }

  std::u16string GetButtonLabel(InfoBarButton button) const override {
    DCHECK(button == BUTTON_OK);
    return l10n_util::GetStringUTF16(IDS_IOS_POPUPS_ALWAYS_SHOW_MOBILE);
  }

  bool Accept() override {
    NSTimeInterval duration =
        [NSDate timeIntervalSinceReferenceDate] - delegate_creation_time_;
    [ConfirmInfobarMetricsRecorder
        recordConfirmAcceptTime:duration
          forInfobarConfirmType:InfobarConfirmType::
                                    kInfobarConfirmTypeBlockPopups];
    [ConfirmInfobarMetricsRecorder
        recordConfirmInfobarEvent:MobileMessagesConfirmInfobarEvents::Accepted
            forInfobarConfirmType:InfobarConfirmType::
                                      kInfobarConfirmTypeBlockPopups];
    scoped_refptr<HostContentSettingsMap> host_content_map_settings(
        ios::HostContentSettingsMapFactory::GetForBrowserState(browser_state_));
    for (auto& popup : popups_) {
      web::WebState::OpenURLParams params(
          popup.popup_url, popup.referrer, WindowOpenDisposition::NEW_POPUP,
          ui::PAGE_TRANSITION_LINK, true /* is_renderer_initiated */);
      web_state_->OpenURL(params);
      host_content_map_settings->SetContentSettingCustomScope(
          ContentSettingsPattern::FromURL(popup.referrer.url),
          ContentSettingsPattern::Wildcard(), ContentSettingsType::POPUPS,
          CONTENT_SETTING_ALLOW);
    }
    return true;
  }

  void InfoBarDismissed() override {
    [ConfirmInfobarMetricsRecorder
        recordConfirmInfobarEvent:MobileMessagesConfirmInfobarEvents::Dismissed
            forInfobarConfirmType:InfobarConfirmType::
                                      kInfobarConfirmTypeBlockPopups];
  }

  int GetButtons() const override { return BUTTON_OK; }

 private:
  raw_ptr<ChromeBrowserState> browser_state_;
  raw_ptr<web::WebState> web_state_;
  // The popups to open.
  std::vector<BlockedPopupTabHelper::Popup> popups_;
  // The icon to display.
  mutable gfx::Image icon_;
  // TimeInterval when the delegate was created.
  NSTimeInterval delegate_creation_time_;
};
}  // namespace

BlockedPopupTabHelper::BlockedPopupTabHelper(web::WebState* web_state)
    : web_state_(web_state), infobar_(nullptr) {}

BlockedPopupTabHelper::~BlockedPopupTabHelper() = default;

bool BlockedPopupTabHelper::ShouldBlockPopup(const GURL& source_url) {
  HostContentSettingsMap* settings_map =
      ios::HostContentSettingsMapFactory::GetForBrowserState(GetBrowserState());
  ContentSetting setting = settings_map->GetContentSetting(
      source_url, source_url, ContentSettingsType::POPUPS);
  return setting != CONTENT_SETTING_ALLOW;
}

void BlockedPopupTabHelper::HandlePopup(const GURL& popup_url,
                                        const web::Referrer& referrer) {
  DCHECK(ShouldBlockPopup(referrer.url));
  popups_.push_back(Popup(popup_url, referrer));
  ShowInfoBar();
}

void BlockedPopupTabHelper::OnInfoBarRemoved(infobars::InfoBar* infobar,
                                             bool animate) {
  if (infobar == infobar_) {
    infobar_ = nullptr;
    popups_.clear();
    scoped_observation_.Reset();
  }
}

void BlockedPopupTabHelper::OnManagerShuttingDown(
    infobars::InfoBarManager* infobar_manager) {
  DCHECK(scoped_observation_.IsObservingSource(infobar_manager));
  scoped_observation_.Reset();
}

void BlockedPopupTabHelper::ShowInfoBar() {
  infobars::InfoBarManager* infobar_manager =
      InfoBarManagerImpl::FromWebState(web_state_);
  if (!popups_.size() || !infobar_manager)
    return;

  RegisterAsInfoBarManagerObserverIfNeeded(infobar_manager);

  std::unique_ptr<BlockPopupInfoBarDelegate> delegate(
      std::make_unique<BlockPopupInfoBarDelegate>(GetBrowserState(), web_state_,
                                                  popups_));

  std::unique_ptr<infobars::InfoBar> infobar = std::make_unique<InfoBarIOS>(
      InfobarType::kInfobarTypeConfirm, std::move(delegate));

  if (infobar_) {
    infobar_ = infobar_manager->ReplaceInfoBar(infobar_, std::move(infobar));
  } else {
    infobar_ = infobar_manager->AddInfoBar(std::move(infobar));
  }
  [ConfirmInfobarMetricsRecorder
      recordConfirmInfobarEvent:MobileMessagesConfirmInfobarEvents::Presented
          forInfobarConfirmType:InfobarConfirmType::
                                    kInfobarConfirmTypeBlockPopups];
}

ChromeBrowserState* BlockedPopupTabHelper::GetBrowserState() const {
  return ChromeBrowserState::FromBrowserState(web_state_->GetBrowserState());
}

void BlockedPopupTabHelper::RegisterAsInfoBarManagerObserverIfNeeded(
    infobars::InfoBarManager* infobar_manager) {
  DCHECK(infobar_manager);

  if (scoped_observation_.IsObservingSource(infobar_manager)) {
    return;
  }

  // Verify that this object is never observing more than one InfoBarManager.
  DCHECK(!scoped_observation_.IsObserving());
  scoped_observation_.Observe(infobar_manager);
}

WEB_STATE_USER_DATA_KEY_IMPL(BlockedPopupTabHelper)