| // Copyright 2015 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/ui/chrome_bubble_manager.h" |
| |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "components/bubble/bubble_controller.h" |
| #include "components/bubble/bubble_delegate.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_details.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace { |
| |
| // Add any new enum before BUBBLE_TYPE_MAX and update BubbleType in |
| // tools/metrics/histograms/histograms.xml. |
| // Do not re-number these because they are used for collecting metrics. |
| // Related bubbles can be grouped together every 10 values. |
| enum BubbleType { |
| // Special bubble values: |
| BUBBLE_TYPE_UNKNOWN = 0, // Used for unrecognized bubble names. |
| BUBBLE_TYPE_MOCK = 1, // Used for testing. |
| |
| // Extension-related bubbles: |
| BUBBLE_TYPE_EXTENSION_INSTALLED = 10, // Displayed after installing. |
| |
| // Translation-related bubbles: |
| BUBBLE_TYPE_TRANSLATE = 20, // Displays a request to translate a page. |
| |
| // Permissions-related bubbles: |
| BUBBLE_TYPE_PERMISSION = 30, // Displays a permission request to the user. |
| BUBBLE_TYPE_CHOOSER_PERMISSION = 31, // For chooser permissions. |
| |
| // Upper boundary for metrics. |
| BUBBLE_TYPE_MAX, |
| }; |
| |
| // TODO(juncai): Since LogBubbleCloseReason function adds metrics for each |
| // close type, we can use only enum, and it may not be necessary to keep the |
| // bubble name. |
| // Convert from bubble name to ID. The bubble ID will allow collecting the |
| // close reason for each bubble type. |
| static int GetBubbleId(BubbleReference bubble) { |
| BubbleType bubble_type = BUBBLE_TYPE_UNKNOWN; |
| |
| // Translate from bubble name to enum. |
| if (bubble->GetName().compare("MockBubble") == 0) |
| bubble_type = BUBBLE_TYPE_MOCK; |
| else if (bubble->GetName().compare("ExtensionInstalled") == 0) |
| bubble_type = BUBBLE_TYPE_EXTENSION_INSTALLED; |
| else if (bubble->GetName().compare("TranslateBubble") == 0) |
| bubble_type = BUBBLE_TYPE_TRANSLATE; |
| else if (bubble->GetName().compare("PermissionBubble") == 0) |
| bubble_type = BUBBLE_TYPE_PERMISSION; |
| else if (bubble->GetName().compare("ChooserBubble") == 0) |
| bubble_type = BUBBLE_TYPE_CHOOSER_PERMISSION; |
| |
| DCHECK_NE(bubble_type, BUBBLE_TYPE_UNKNOWN); |
| DCHECK_NE(bubble_type, BUBBLE_TYPE_MAX); |
| |
| return bubble_type; |
| } |
| |
| // Log the reason for closing this bubble. |
| // Each reason is its own metric. Each histogram call MUST have a runtime |
| // constant value passed in for the title. |
| static void LogBubbleCloseReason(BubbleReference bubble, |
| BubbleCloseReason reason) { |
| int bubble_id = GetBubbleId(bubble); |
| switch (reason) { |
| case BUBBLE_CLOSE_FORCED: |
| base::UmaHistogramSparse("Bubbles.Close.Forced", bubble_id); |
| return; |
| case BUBBLE_CLOSE_FOCUS_LOST: |
| base::UmaHistogramSparse("Bubbles.Close.FocusLost", bubble_id); |
| return; |
| case BUBBLE_CLOSE_TABSWITCHED: |
| base::UmaHistogramSparse("Bubbles.Close.TabSwitched", bubble_id); |
| return; |
| case BUBBLE_CLOSE_TABDETACHED: |
| base::UmaHistogramSparse("Bubbles.Close.TabDetached", bubble_id); |
| return; |
| case BUBBLE_CLOSE_USER_DISMISSED: |
| base::UmaHistogramSparse("Bubbles.Close.UserDismissed", bubble_id); |
| return; |
| case BUBBLE_CLOSE_NAVIGATED: |
| base::UmaHistogramSparse("Bubbles.Close.Navigated", bubble_id); |
| return; |
| case BUBBLE_CLOSE_FULLSCREEN_TOGGLED: |
| base::UmaHistogramSparse("Bubbles.Close.FullscreenToggled", bubble_id); |
| return; |
| case BUBBLE_CLOSE_ACCEPTED: |
| base::UmaHistogramSparse("Bubbles.Close.Accepted", bubble_id); |
| return; |
| case BUBBLE_CLOSE_CANCELED: |
| base::UmaHistogramSparse("Bubbles.Close.Canceled", bubble_id); |
| return; |
| case BUBBLE_CLOSE_FRAME_DESTROYED: |
| base::UmaHistogramSparse("Bubbles.Close.FrameDestroyed", bubble_id); |
| return; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| } // namespace |
| |
| ChromeBubbleManager::ChromeBubbleManager(TabStripModel* tab_strip_model) |
| : tab_strip_model_(tab_strip_model) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(tab_strip_model_); |
| tab_strip_model_->AddObserver(this); |
| AddBubbleManagerObserver(&chrome_bubble_metrics_); |
| } |
| |
| ChromeBubbleManager::~ChromeBubbleManager() { |
| tab_strip_model_->RemoveObserver(this); |
| |
| // Finalize requests before removing the BubbleManagerObserver so it can |
| // collect metrics when closing any open bubbles. |
| FinalizePendingRequests(); |
| RemoveBubbleManagerObserver(&chrome_bubble_metrics_); |
| } |
| |
| void ChromeBubbleManager::OnTabStripModelChanged( |
| TabStripModel* tab_strip_model, |
| const TabStripModelChange& change, |
| const TabStripSelectionChange& selection) { |
| if (change.type() == TabStripModelChange::kRemoved) { |
| CloseAllBubbles(BUBBLE_CLOSE_TABDETACHED); |
| // Any bubble that didn't close should update its anchor position. |
| UpdateAllBubbleAnchors(); |
| } |
| |
| if (tab_strip_model->empty() || !selection.active_tab_changed()) |
| return; |
| |
| if (selection.old_contents) |
| CloseAllBubbles(BUBBLE_CLOSE_TABSWITCHED); |
| |
| if (selection.new_contents) |
| Observe(selection.new_contents); |
| } |
| |
| void ChromeBubbleManager::FrameDeleted( |
| content::RenderFrameHost* render_frame_host) { |
| // When a frame is destroyed, bubbles spawned by that frame should default to |
| // being closed, so that they can't traverse any references they hold to the |
| // destroyed frame. |
| CloseBubblesOwnedBy(render_frame_host); |
| } |
| |
| void ChromeBubbleManager::DidToggleFullscreenModeForTab( |
| bool entered_fullscreen, |
| bool will_cause_resize) { |
| CloseAllBubbles(BUBBLE_CLOSE_FULLSCREEN_TOGGLED); |
| // Any bubble that didn't close should update its anchor position. |
| UpdateAllBubbleAnchors(); |
| } |
| |
| void ChromeBubbleManager::NavigationEntryCommitted( |
| const content::LoadCommittedDetails& load_details) { |
| if (!load_details.is_same_document) |
| CloseAllBubbles(BUBBLE_CLOSE_NAVIGATED); |
| } |
| |
| void ChromeBubbleManager::ChromeBubbleMetrics::OnBubbleNeverShown( |
| BubbleReference bubble) { |
| base::UmaHistogramSparse("Bubbles.NeverShown", GetBubbleId(bubble)); |
| } |
| |
| void ChromeBubbleManager::ChromeBubbleMetrics::OnBubbleClosed( |
| BubbleReference bubble, |
| BubbleCloseReason reason) { |
| // Log the amount of time the bubble was visible. |
| base::TimeDelta visible_time = bubble->GetVisibleTime(); |
| UMA_HISTOGRAM_LONG_TIMES("Bubbles.DisplayTime.All", visible_time); |
| |
| LogBubbleCloseReason(bubble, reason); |
| } |
| |
| void ChromeBubbleManager::ChromeBubbleMetrics::OnBubbleShown( |
| BubbleReference bubble) {} |