| // 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/permissions/chip_controller.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include "base/check.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ui/page_info/page_info_dialog.h" |
| #include "chrome/browser/ui/views/content_setting_bubble_contents.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/location_bar/location_bar_view.h" |
| #include "chrome/browser/ui/views/omnibox/omnibox_view_views.h" |
| #include "chrome/browser/ui/views/page_info/page_info_bubble_view.h" |
| #include "chrome/browser/ui/views/permissions/permission_prompt_bubble_view_factory.h" |
| #include "chrome/browser/ui/views/permissions/permission_prompt_chip_model.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/permissions/features.h" |
| #include "components/permissions/permission_prompt.h" |
| #include "components/permissions/permission_request_manager.h" |
| #include "components/permissions/permission_util.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/visibility.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/controls/button/button_controller.h" |
| #include "ui/views/widget/widget.h" |
| |
| constexpr auto kExpandDuration = base::Milliseconds(350); |
| constexpr auto kAnimateToFitDuration = base::Milliseconds(200); |
| constexpr auto kPromptCollapseDuration = base::Milliseconds(250); |
| constexpr auto kConfirmationCollapseDuration = base::Milliseconds(75); |
| constexpr auto kConfirmationDisplayDuration = base::Seconds(4); |
| constexpr auto kDelayBeforeCollapsingChip = base::Seconds(12); |
| |
| // Abusive origins do not support expand animation, hence the dismiss timer |
| // should be longer. |
| constexpr auto kDelayBeforeCollapsingChipForAbusiveOrigins = base::Seconds(18); |
| constexpr auto kPermissionChipAutoDismissDelay = base::Seconds(6); |
| |
| class BubbleButtonController : public views::ButtonController { |
| public: |
| BubbleButtonController( |
| views::Button* button, |
| BubbleOwnerDelegate* bubble_owner, |
| std::unique_ptr<views::ButtonControllerDelegate> delegate) |
| : views::ButtonController(button, std::move(delegate)), |
| bubble_owner_(bubble_owner) {} |
| |
| // TODO(crbug.com/1270699): Add keyboard support. |
| void OnMouseEntered(const ui::MouseEvent& event) override { |
| if (bubble_owner_->IsBubbleShowing() || bubble_owner_->IsAnimating()) { |
| return; |
| } |
| bubble_owner_->RestartTimersOnMouseHover(); |
| } |
| |
| private: |
| raw_ptr<BubbleOwnerDelegate, DanglingUntriaged> bubble_owner_ = nullptr; |
| }; |
| |
| ChipController::ChipController(Browser* browser, OmniboxChipButton* chip_view) |
| : chip_(chip_view), browser_(browser) { |
| chip_->SetVisible(false); |
| } |
| |
| ChipController::~ChipController() { |
| views::Widget* current = GetBubbleWidget(); |
| if (current) { |
| current->RemoveObserver(this); |
| current->Close(); |
| } |
| if (active_chip_permission_request_manager_.has_value()) { |
| active_chip_permission_request_manager_.value()->RemoveObserver(this); |
| } |
| observation_.Reset(); |
| } |
| |
| void ChipController::OnPermissionRequestManagerDestructed() { |
| ResetPermissionPromptChip(); |
| if (active_chip_permission_request_manager_.has_value()) { |
| active_chip_permission_request_manager_.value()->RemoveObserver(this); |
| active_chip_permission_request_manager_.reset(); |
| } |
| } |
| |
| void ChipController::OnNavigation( |
| content::NavigationHandle* navigation_handle) { |
| // TODO(crbug.com/1416493): Refactor this so that this observer method is only |
| // called when a non-same-document navigation starts in the primary main |
| // frame. |
| if (!navigation_handle->IsInPrimaryMainFrame() || |
| navigation_handle->IsSameDocument()) { |
| return; |
| } |
| |
| ResetPermissionPromptChip(); |
| } |
| |
| void ChipController::OnTabVisibilityChanged(content::Visibility visibility) { |
| if (visibility == content::Visibility::HIDDEN) { |
| ResetPermissionPromptChip(); |
| } |
| } |
| |
| void ChipController::OnRequestsFinalized() { |
| if (!is_confirmation_showing_) { |
| ResetPermissionPromptChip(); |
| } |
| } |
| |
| void ChipController::OnRequestDecided( |
| permissions::PermissionAction permission_action) { |
| DCHECK(permission_prompt_model_); |
| RemoveBubbleObserverAndResetTimersAndChipCallbacks(); |
| permission_prompt_model_->UpdateWithUserDecision(permission_action); |
| |
| if (!GetLocationBarView()->IsDrawn() || |
| GetLocationBarView()->GetWidget()->IsFullscreen()) { |
| // If the location bar isn't drawn or during fullscreen, the chip can't be |
| // shown anywhere. |
| ResetPermissionPromptChip(); |
| } else if (base::FeatureList::IsEnabled( |
| permissions::features::kConfirmationChip)) { |
| HandleConfirmation(permission_action); |
| } else { |
| HideChip(); |
| } |
| } |
| |
| bool ChipController::IsBubbleShowing() { |
| return chip_ != nullptr && (GetBubbleWidget() != nullptr); |
| } |
| |
| bool ChipController::IsAnimating() const { |
| return chip_->is_animating(); |
| } |
| |
| void ChipController::RestartTimersOnMouseHover() { |
| ResetTimers(); |
| if (!permission_prompt_model_ || IsBubbleShowing() || IsAnimating()) { |
| return; |
| } |
| |
| if (is_confirmation_showing_) { |
| collapse_timer_.Start(FROM_HERE, kConfirmationDisplayDuration, this, |
| &ChipController::CollapseConfirmation); |
| } else if (chip_->is_fully_collapsed()) { |
| StartDismissTimer(); |
| } else { |
| StartCollapseTimer(); |
| } |
| } |
| |
| void ChipController::OnWidgetDestroying(views::Widget* widget) { |
| DCHECK_EQ(GetBubbleWidget(), widget); |
| ResetTimers(); |
| |
| disallowed_custom_cursors_scope_.RunAndReset(); |
| widget->RemoveObserver(this); |
| |
| observation_.Reset(); |
| |
| if (widget->closed_reason() == views::Widget::ClosedReason::kEscKeyPressed || |
| widget->closed_reason() == |
| views::Widget::ClosedReason::kCloseButtonClicked) { |
| OnPromptBubbleDismissed(); |
| } else { |
| CollapsePrompt(/*allow_restart=*/false); |
| } |
| } |
| |
| void ChipController::OnWidgetActivationChanged(views::Widget* widget, |
| bool active) { |
| // This logic prevents clickjacking. See https://crbug.com/1160485 |
| auto* prompt_bubble_widget = GetBubbleWidget(); |
| if (active && !parent_was_visible_when_activation_changed_) { |
| // If the widget is active and the primary window wasn't active the last |
| // time activation changed, we know that the window just came to the |
| // foreground and trigger input protection. |
| GetPromptBubbleView()->AsDialogDelegate()->TriggerInputProtection( |
| /*force_early*/ true); |
| } |
| parent_was_visible_when_activation_changed_ = |
| prompt_bubble_widget->GetPrimaryWindowWidget()->IsVisible(); |
| } |
| |
| bool ChipController::ShouldWaitForConfirmationToComplete() { |
| return is_confirmation_showing_ && collapse_timer_.IsRunning(); |
| } |
| |
| void ChipController::InitializePermissionPrompt( |
| content::WebContents* web_contents, |
| base::WeakPtr<permissions::PermissionPrompt::Delegate> delegate, |
| base::OnceCallback<void()> callback) { |
| DCHECK(delegate); |
| if (ShouldWaitForConfirmationToComplete()) { |
| delay_prompt_timer_.Start( |
| FROM_HERE, collapse_timer_.GetCurrentDelay(), |
| base::BindOnce(&ChipController::InitializePermissionPrompt, |
| weak_factory_.GetWeakPtr(), |
| base::UnsafeDanglingUntriaged(web_contents), delegate, |
| std::move(callback))); |
| return; |
| } |
| |
| if (delegate.WasInvalidated()) { |
| return; |
| } |
| |
| ResetPermissionPromptChip(); |
| |
| // Here we just initialize the controller with the current request. We might |
| // not yet want to display the chip, for example when a prompt bubble without |
| // a request chip is shown --> only once a confirmation should be displayed, |
| // the chip should become visible. |
| chip_->SetVisible(false); |
| permission_prompt_model_ = |
| std::make_unique<PermissionPromptChipModel>(delegate.get()); |
| |
| if (active_chip_permission_request_manager_.has_value()) { |
| active_chip_permission_request_manager_.value()->RemoveObserver(this); |
| } |
| |
| active_chip_permission_request_manager_ = |
| permissions::PermissionRequestManager::FromWebContents(web_contents); |
| active_chip_permission_request_manager_.value()->AddObserver(this); |
| observation_.Observe(chip_); |
| std::move(callback).Run(); |
| } |
| |
| void ChipController::ShowPermissionPrompt( |
| content::WebContents* web_contents, |
| base::WeakPtr<permissions::PermissionPrompt::Delegate> delegate) { |
| DCHECK(delegate); |
| if (ShouldWaitForConfirmationToComplete()) { |
| delay_prompt_timer_.Start( |
| FROM_HERE, collapse_timer_.GetCurrentDelay(), |
| base::BindOnce(&ChipController::ShowPermissionPrompt, |
| weak_factory_.GetWeakPtr(), |
| base::UnsafeDanglingUntriaged(web_contents), delegate)); |
| return; |
| } |
| |
| if (delegate.WasInvalidated()) { |
| return; |
| } |
| |
| InitializePermissionPrompt(web_contents, delegate, base::DoNothing()); |
| |
| // HaTS surveys may be triggered while a quiet chip is displayed. If that |
| // happens, the quiet chip should not collapse anymore, because otherwise a |
| // user answering a survey would no longer be able to click on the chip. To |
| // enable the PRM to handle this case, we pass a callback to stop the timers. |
| if (delegate->ReasonForUsingQuietUi().has_value()) { |
| delegate->SetHatsShownCallback(base::BindOnce(&ChipController::ResetTimers, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| request_chip_shown_time_ = base::TimeTicks::Now(); |
| |
| AnnouncePermissionRequestForAccessibility( |
| permission_prompt_model_->GetAccessibilityChipText()); |
| chip_->SetVisible(true); |
| |
| SyncChipWithModel(); |
| |
| chip_->SetButtonController(std::make_unique<BubbleButtonController>( |
| chip_.get(), this, |
| std::make_unique<views::Button::DefaultButtonControllerDelegate>( |
| chip_.get()))); |
| chip_->SetCallback(base::BindRepeating( |
| &ChipController::OnRequestChipButtonPressed, weak_factory_.GetWeakPtr())); |
| chip_->ResetAnimation(); |
| ObservePromptBubble(); |
| |
| if (permission_prompt_model_->IsExpandAnimationAllowed()) { |
| AnimateExpand(); |
| } else { |
| StartDismissTimer(); |
| } |
| } |
| |
| void ChipController::RemoveBubbleObserverAndResetTimersAndChipCallbacks() { |
| views::Widget* const bubble_widget = GetBubbleWidget(); |
| if (bubble_widget) { |
| disallowed_custom_cursors_scope_.RunAndReset(); |
| bubble_widget->RemoveObserver(this); |
| bubble_widget->Close(); |
| } |
| |
| // Reset button click callback |
| chip_->SetCallback(base::RepeatingCallback<void()>(base::DoNothing())); |
| |
| ResetTimers(); |
| } |
| |
| void ChipController::ResetPermissionPromptChip() { |
| RemoveBubbleObserverAndResetTimersAndChipCallbacks(); |
| observation_.Reset(); |
| if (permission_prompt_model_) { |
| // permission_request_manager_ is empty if the PermissionRequestManager |
| // instance has destructed, which triggers the observer method |
| // OnPermissionRequestManagerDestructed() implemented by this controller. |
| if (active_chip_permission_request_manager_.has_value()) { |
| active_chip_permission_request_manager_.value()->RemoveObserver(this); |
| |
| // When the user starts typing into the location bar we need to inform the |
| // PermissionRequestManager to update the PermissionPrompt reference it |
| // is holding. The typical update is for it to destruct the |
| // PermissionPrompt instance and not to hold any PermissionPrompt instance |
| // during the edit time. |
| if (GetLocationBarView()->IsEditingOrEmpty() && |
| (active_chip_permission_request_manager_.value() |
| ->IsRequestInProgress() && |
| (active_chip_permission_request_manager_.value() |
| ->web_contents() |
| ->GetVisibleURL() != GURL(chrome::kChromeUINewTabURL)))) { |
| active_chip_permission_request_manager_.value()->RecreateView(); |
| } |
| active_chip_permission_request_manager_.reset(); |
| } |
| permission_prompt_model_.reset(); |
| } |
| |
| HideChip(); |
| is_confirmation_showing_ = false; |
| } |
| |
| void ChipController::ShowPageInfoDialog() { |
| content::WebContents* contents = GetLocationBarView()->GetWebContents(); |
| if (!contents) |
| return; |
| |
| content::NavigationEntry* entry = contents->GetController().GetVisibleEntry(); |
| if (entry->IsInitialEntry()) |
| return; |
| |
| // prevent chip from collapsing while prompt bubble is open |
| ResetTimers(); |
| |
| auto initialized_callback = |
| GetPageInfoDialogCreatedCallbackForTesting() |
| ? std::move(GetPageInfoDialogCreatedCallbackForTesting()) |
| : base::DoNothing(); |
| |
| views::BubbleDialogDelegateView* bubble = |
| PageInfoBubbleView::CreatePageInfoBubble( |
| chip_, gfx::Rect(), chip_->GetWidget()->GetNativeWindow(), contents, |
| entry->GetVirtualURL(), std::move(initialized_callback), |
| base::BindOnce(&ChipController::OnPageInfoBubbleClosed, |
| weak_factory_.GetWeakPtr())); |
| bubble->GetWidget()->Show(); |
| bubble_tracker_.SetView(bubble); |
| permissions::PermissionUmaUtil::RecordPageInfoDialogAccessType( |
| permissions::PageInfoDialogAccessType::CONFIRMATION_CHIP_CLICK); |
| } |
| |
| void ChipController::OnPageInfoBubbleClosed( |
| views::Widget::ClosedReason closed_reason, |
| bool reload_prompt) { |
| GetLocationBarView()->ResetConfirmationChipShownTime(); |
| HideChip(); |
| } |
| |
| void ChipController::CollapseConfirmation() { |
| chip_->AnimateCollapse(kConfirmationCollapseDuration); |
| is_confirmation_showing_ = false; |
| is_waiting_for_confirmation_collapse = true; |
| GetLocationBarView()->ResetConfirmationChipShownTime(); |
| } |
| |
| bool ChipController::should_expand_for_testing() { |
| CHECK_IS_TEST(); |
| return permission_prompt_model_->ShouldExpand(); |
| } |
| |
| void ChipController::AnimateExpand() { |
| chip_->ResetAnimation(); |
| chip_->AnimateExpand(kExpandDuration); |
| chip_->SetVisible(true); |
| } |
| |
| void ChipController::HandleConfirmation( |
| permissions::PermissionAction user_decision) { |
| SyncChipWithModel(); |
| if (user_decision != permissions::PermissionAction::IGNORED && |
| user_decision != permissions::PermissionAction::DISMISSED && |
| active_chip_permission_request_manager_.has_value() && |
| !active_chip_permission_request_manager_.value() |
| ->has_pending_requests() && |
| permission_prompt_model_->CanDisplayConfirmation()) { |
| is_confirmation_showing_ = true; |
| |
| if (chip_->GetVisible()) { |
| chip_->AnimateToFit(kAnimateToFitDuration); |
| } else { |
| // No request chip was shown, always expand independently of what contents |
| // are stored in the previous chip (which is not visible before the |
| // SetVisible call). |
| chip_->SetVisible(true); |
| chip_->AnimateExpand(kExpandDuration); |
| } |
| |
| chip_->SetCallback(base::BindRepeating(&ChipController::ShowPageInfoDialog, |
| weak_factory_.GetWeakPtr())); |
| AnnouncePermissionRequestForAccessibility( |
| permission_prompt_model_->GetAccessibilityChipText()); |
| collapse_timer_.Start(FROM_HERE, kConfirmationDisplayDuration, this, |
| &ChipController::CollapseConfirmation); |
| } else { |
| ResetPermissionPromptChip(); |
| } |
| } |
| |
| void ChipController::AnnouncePermissionRequestForAccessibility( |
| const std::u16string& text) { |
| #if BUILDFLAG(IS_MAC) |
| chip_->GetViewAccessibility().OverrideName(text); |
| chip_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true); |
| #else |
| |
| chip_->GetViewAccessibility().AnnounceText(text); |
| #endif |
| } |
| |
| void ChipController::CollapsePrompt(bool allow_restart) { |
| if (allow_restart && chip_->IsMouseHovered()) { |
| StartCollapseTimer(); |
| } else { |
| permission_prompt_model_->UpdateAutoCollapsePromptChipState(true); |
| SyncChipWithModel(); |
| |
| if (!chip_->is_fully_collapsed()) |
| chip_->AnimateCollapse(kPromptCollapseDuration); |
| |
| StartDismissTimer(); |
| } |
| } |
| |
| void ChipController::OnExpandAnimationEnded() { |
| if (is_confirmation_showing_ || IsBubbleShowing() || |
| !IsPermissionPromptChipVisible() || !permission_prompt_model_) { |
| return; |
| } |
| |
| if (permission_prompt_model_->ShouldBubbleStartOpen()) { |
| OpenPermissionPromptBubble(); |
| } else { |
| StartCollapseTimer(); |
| } |
| } |
| |
| void ChipController::OnCollapseAnimationEnded() { |
| if (is_waiting_for_confirmation_collapse) { |
| HideChip(); |
| is_waiting_for_confirmation_collapse = false; |
| } |
| } |
| |
| void ChipController::HideChip() { |
| if (!chip_->GetVisible()) |
| return; |
| |
| chip_->SetVisible(false); |
| // When the chip visibility changed from visible -> hidden, the locationbar |
| // layout should be updated. |
| GetLocationBarView()->InvalidateLayout(); |
| } |
| |
| void ChipController::OpenPermissionPromptBubble() { |
| DCHECK(!IsBubbleShowing()); |
| if (!permission_prompt_model_ || |
| !permission_prompt_model_->GetDelegate().has_value()) { |
| return; |
| } |
| |
| disallowed_custom_cursors_scope_ = permission_prompt_model_->GetDelegate() |
| .value() |
| ->GetAssociatedWebContents() |
| ->CreateDisallowCustomCursorScope(); |
| |
| // prevent chip from collapsing while prompt bubble is open |
| ResetTimers(); |
| |
| if (permission_prompt_model_->GetPromptStyle() == |
| PermissionPromptStyle::kChip) { |
| // Loud prompt bubble |
| raw_ptr<PermissionPromptBubbleBaseView> prompt_bubble = |
| CreatePermissionPromptBubbleView( |
| browser_, |
| permission_prompt_model_->GetDelegate().value()->GetWeakPtr(), |
| request_chip_shown_time_, PermissionPromptStyle::kChip); |
| bubble_tracker_.SetView(prompt_bubble); |
| prompt_bubble->Show(); |
| } else if (permission_prompt_model_->GetPromptStyle() == |
| PermissionPromptStyle::kQuietChip) { |
| // Quiet prompt bubble |
| LocationBarView* lbv = GetLocationBarView(); |
| content::WebContents* web_contents = lbv->GetContentSettingWebContents(); |
| |
| if (web_contents) { |
| std::unique_ptr<ContentSettingQuietRequestBubbleModel> |
| content_setting_bubble_model = |
| std::make_unique<ContentSettingQuietRequestBubbleModel>( |
| lbv->GetContentSettingBubbleModelDelegate(), web_contents); |
| ContentSettingBubbleContents* quiet_request_bubble = |
| new ContentSettingBubbleContents( |
| std::move(content_setting_bubble_model), web_contents, lbv, |
| views::BubbleBorder::TOP_LEFT); |
| quiet_request_bubble->set_close_on_deactivate(false); |
| views::Widget* bubble_widget = |
| views::BubbleDialogDelegateView::CreateBubble(quiet_request_bubble); |
| quiet_request_bubble->set_close_on_deactivate(false); |
| bubble_tracker_.SetView(quiet_request_bubble); |
| bubble_widget->Show(); |
| } |
| } |
| |
| // It is possible that a Chip got reset while the permission prompt bubble was |
| // displayed. |
| if (permission_prompt_model_ && IsBubbleShowing()) { |
| ObservePromptBubble(); |
| permission_prompt_model_->GetDelegate().value()->SetPromptShown(); |
| } |
| } |
| |
| void ChipController::ClosePermissionPromptBubbleWithReason( |
| views::Widget::ClosedReason reason) { |
| DCHECK(IsBubbleShowing()); |
| GetBubbleWidget()->CloseWithReason(reason); |
| } |
| |
| void ChipController::RecordRequestChipButtonPressed(const char* recordKey) { |
| base::UmaHistogramMediumTimes( |
| recordKey, base::TimeTicks::Now() - request_chip_shown_time_); |
| } |
| |
| void ChipController::ObservePromptBubble() { |
| views::Widget* prompt_bubble_widget = GetBubbleWidget(); |
| if (prompt_bubble_widget) { |
| parent_was_visible_when_activation_changed_ = |
| prompt_bubble_widget->GetPrimaryWindowWidget()->IsVisible(); |
| prompt_bubble_widget->AddObserver(this); |
| } |
| } |
| |
| void ChipController::OnPromptBubbleDismissed() { |
| DCHECK(permission_prompt_model_); |
| if (!permission_prompt_model_) |
| return; |
| |
| if (permission_prompt_model_->GetDelegate().has_value()) { |
| permission_prompt_model_->GetDelegate().value()->SetDismissOnTabClose(); |
| // If the permission prompt bubble is closed, we count it as "Dismissed", |
| // hence it should record the time when the bubble is closed. |
| permission_prompt_model_->GetDelegate().value()->SetDecisionTime(); |
| // If a permission popup bubble is closed/dismissed, a permission request |
| // should be dismissed as well. |
| permission_prompt_model_->GetDelegate().value()->Dismiss(); |
| } |
| } |
| |
| void ChipController::OnPromptExpired() { |
| AnnouncePermissionRequestForAccessibility(l10n_util::GetStringUTF16( |
| IDS_PERMISSIONS_EXPIRED_SCREENREADER_ANNOUNCEMENT)); |
| if (active_chip_permission_request_manager_.has_value()) { |
| active_chip_permission_request_manager_.value()->RemoveObserver(this); |
| active_chip_permission_request_manager_.reset(); |
| } |
| |
| if (permission_prompt_model_ && |
| permission_prompt_model_->GetDelegate().has_value()) { |
| permission_prompt_model_->GetDelegate().value()->Ignore(); |
| } |
| |
| ResetPermissionPromptChip(); |
| } |
| |
| void ChipController::OnRequestChipButtonPressed() { |
| if (permission_prompt_model_ && |
| (!IsBubbleShowing() || |
| permission_prompt_model_->ShouldBubbleStartOpen())) { |
| // Only record if its the first interaction. |
| if (permission_prompt_model_->GetPromptStyle() == |
| PermissionPromptStyle::kChip) { |
| RecordRequestChipButtonPressed("Permissions.Chip.TimeToInteraction"); |
| } else if (permission_prompt_model_->GetPromptStyle() == |
| PermissionPromptStyle::kQuietChip) { |
| RecordRequestChipButtonPressed("Permissions.QuietChip.TimeToInteraction"); |
| } |
| } |
| |
| if (IsBubbleShowing()) { |
| // A mouse click on chip while a permission prompt is open should dismiss |
| // the prompt and collapse the chip |
| ClosePermissionPromptBubbleWithReason( |
| views::Widget::ClosedReason::kCloseButtonClicked); |
| } else { |
| OpenPermissionPromptBubble(); |
| } |
| } |
| |
| void ChipController::OnChipVisibilityChanged(bool is_visible) { |
| auto* prompt_bubble = GetBubbleWidget(); |
| if (!chip_->GetVisible() && prompt_bubble) { |
| // In case if the prompt bubble isn't closed on focus loss, manually close |
| // it when chip is hidden. |
| prompt_bubble->Close(); |
| } |
| } |
| |
| void ChipController::SyncChipWithModel() { |
| chip_->SetChipIcon(permission_prompt_model_->GetIcon()); |
| chip_->SetText(permission_prompt_model_->GetChipText()); |
| chip_->SetTheme(permission_prompt_model_->GetChipTheme()); |
| } |
| |
| void ChipController::StartCollapseTimer() { |
| collapse_timer_.Start(FROM_HERE, kDelayBeforeCollapsingChip, |
| base::BindOnce(&ChipController::CollapsePrompt, |
| weak_factory_.GetWeakPtr(), |
| /*allow_restart=*/true)); |
| } |
| |
| void ChipController::StartDismissTimer() { |
| if (!permission_prompt_model_) |
| return; |
| |
| if (permission_prompt_model_->ShouldExpand()) { |
| dismiss_timer_.Start(FROM_HERE, kPermissionChipAutoDismissDelay, this, |
| &ChipController::OnPromptExpired); |
| } else { |
| dismiss_timer_.Start(FROM_HERE, kDelayBeforeCollapsingChipForAbusiveOrigins, |
| this, &ChipController::OnPromptExpired); |
| } |
| } |
| |
| void ChipController::ResetTimers() { |
| collapse_timer_.AbandonAndStop(); |
| dismiss_timer_.AbandonAndStop(); |
| delay_prompt_timer_.AbandonAndStop(); |
| } |
| |
| LocationBarView* ChipController::GetLocationBarView() { |
| BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_); |
| return browser_view ? browser_view->GetLocationBarView() : nullptr; |
| } |
| |
| views::Widget* ChipController::GetBubbleWidget() { |
| // We can't call GetPromptBubbleView() here, because the bubble_tracker may |
| // hold objects that aren't of typ `PermissionPromptBubbleBaseView`. |
| return bubble_tracker_.view() ? bubble_tracker_.view()->GetWidget() : nullptr; |
| } |
| |
| PermissionPromptBubbleBaseView* ChipController::GetPromptBubbleView() { |
| // The tracked bubble view is a `PermissionPromptBubbleBaseView` only when |
| // `kChip` is used. |
| CHECK_EQ(permission_prompt_model_->GetPromptStyle(), |
| PermissionPromptStyle::kChip); |
| auto* view = bubble_tracker_.view(); |
| return view ? static_cast<PermissionPromptBubbleBaseView*>(view) : nullptr; |
| } |