blob: 7454eb61e51b811471845345535c76cf2cbe226c [file] [log] [blame]
// 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::TabDetachedAt(content::WebContents* contents,
int index) {
CloseAllBubbles(BUBBLE_CLOSE_TABDETACHED);
// Any bubble that didn't close should update its anchor position.
UpdateAllBubbleAnchors();
}
void ChromeBubbleManager::TabDeactivated(content::WebContents* contents) {
CloseAllBubbles(BUBBLE_CLOSE_TABSWITCHED);
}
void ChromeBubbleManager::ActiveTabChanged(content::WebContents* old_contents,
content::WebContents* new_contents,
int index,
int reason) {
Observe(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);
}