blob: 3b21f6645c62e113ac1c7ccb70e353c97dc49a5b [file] [log] [blame]
// Copyright 2013 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/extensions/activity_log/uma_policy.h"
#include "base/metrics/histogram.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/extensions/activity_log/activity_action_constants.h"
#include "chrome/browser/sessions/session_id.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/extensions/dom_action_types.h"
#include "content/public/browser/web_contents.h"
namespace {
// For convenience.
const int kNoStatus = extensions::UmaPolicy::NONE;
const int kContentScript = 1 << extensions::UmaPolicy::CONTENT_SCRIPT;
const int kReadDom = 1 << extensions::UmaPolicy::READ_DOM;
const int kModifiedDom = 1 << extensions::UmaPolicy::MODIFIED_DOM;
const int kDomMethod = 1 << extensions::UmaPolicy::DOM_METHOD;
const int kDocumentWrite = 1 << extensions::UmaPolicy::DOCUMENT_WRITE;
const int kInnerHtml = 1 << extensions::UmaPolicy::INNER_HTML;
const int kCreatedScript = 1 << extensions::UmaPolicy::CREATED_SCRIPT;
const int kCreatedIframe = 1 << extensions::UmaPolicy::CREATED_IFRAME;
const int kCreatedDiv = 1 << extensions::UmaPolicy::CREATED_DIV;
const int kCreatedLink = 1 << extensions::UmaPolicy::CREATED_LINK;
const int kCreatedInput = 1 << extensions::UmaPolicy::CREATED_INPUT;
const int kCreatedEmbed = 1 << extensions::UmaPolicy::CREATED_EMBED;
const int kCreatedObject = 1 << extensions::UmaPolicy::CREATED_OBJECT;
} // namespace
namespace extensions {
// Class constants, also used in testing. --------------------------------------
const char UmaPolicy::kNumberOfTabs[] = "num_tabs";
const size_t UmaPolicy::kMaxTabsTracked = 50;
// Setup and shutdown. ---------------------------------------------------------
UmaPolicy::UmaPolicy(Profile* profile)
: ActivityLogPolicy(profile), profile_(profile) {
DCHECK(!profile->IsOffTheRecord());
BrowserList::AddObserver(this);
}
UmaPolicy::~UmaPolicy() {
BrowserList::RemoveObserver(this);
}
// Unlike the other policies, UmaPolicy can commit suicide directly because it
// doesn't have a dependency on a database.
void UmaPolicy::Close() {
delete this;
}
// Process actions. ------------------------------------------------------------
void UmaPolicy::ProcessAction(scoped_refptr<Action> action) {
if (!action->page_url().is_valid() && !action->arg_url().is_valid())
return;
if (action->page_incognito() || action->arg_incognito())
return;
std::string url;
int status = MatchActionToStatus(action);
if (action->page_url().is_valid()) {
url = CleanURL(action->page_url());
} else if (status & kContentScript) {
// This is for the tabs.executeScript case.
url = CleanURL(action->arg_url());
}
if (url.empty())
return;
SiteMap::iterator site_lookup = url_status_.find(url);
if (site_lookup != url_status_.end())
site_lookup->second[action->extension_id()] |= status;
}
int UmaPolicy::MatchActionToStatus(scoped_refptr<Action> action) {
if (action->action_type() == Action::ACTION_CONTENT_SCRIPT) {
return kContentScript;
} else if (action->action_type() == Action::ACTION_API_CALL &&
action->api_name() == "tabs.executeScript") {
return kContentScript;
} else if (action->action_type() != Action::ACTION_DOM_ACCESS) {
return kNoStatus;
}
int dom_verb;
if (!action->other() ||
!action->other()->GetIntegerWithoutPathExpansion(
activity_log_constants::kActionDomVerb, &dom_verb)) {
return kNoStatus;
}
int ret_bit = kNoStatus;
DomActionType::Type dom_type = static_cast<DomActionType::Type>(dom_verb);
if (dom_type == DomActionType::GETTER)
return kReadDom;
if (dom_type == DomActionType::SETTER) {
ret_bit |= kModifiedDom;
} else if (dom_type == DomActionType::METHOD) {
ret_bit |= kDomMethod;
} else {
return kNoStatus;
}
if (action->api_name() == "HTMLDocument.write" ||
action->api_name() == "HTMLDocument.writeln") {
ret_bit |= kDocumentWrite;
} else if (action->api_name() == "Element.innerHTML") {
ret_bit |= kInnerHtml;
} else if (action->api_name() == "Document.createElement") {
std::string arg;
action->args()->GetString(0, &arg);
if (arg == "script") {
ret_bit |= kCreatedScript;
} else if (arg == "iframe") {
ret_bit |= kCreatedIframe;
} else if (arg == "div") {
ret_bit |= kCreatedDiv;
} else if (arg == "a") {
ret_bit |= kCreatedLink;
} else if (arg == "input") {
ret_bit |= kCreatedInput;
} else if (arg == "embed") {
ret_bit |= kCreatedEmbed;
} else if (arg == "object") {
ret_bit |= kCreatedObject;
}
}
return ret_bit;
}
void UmaPolicy::HistogramOnClose(const std::string& url) {
// Let's try to avoid histogramming useless URLs.
if (url == "about:blank" || url.empty() || url == "chrome://newtab/")
return;
int statuses[MAX_STATUS-1];
std::memset(statuses, 0, sizeof(statuses));
SiteMap::iterator site_lookup = url_status_.find(url);
ExtensionMap exts = site_lookup->second;
ExtensionMap::iterator ext_iter;
for (ext_iter = exts.begin(); ext_iter != exts.end(); ++ext_iter) {
if (ext_iter->first == kNumberOfTabs)
continue;
for (int i = NONE + 1; i < MAX_STATUS; ++i) {
if (ext_iter->second & (1 << i))
statuses[i-1]++;
}
}
std::string prefix = "ExtensionActivity.";
if (GURL(url).host() != "www.google.com") {
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CONTENT_SCRIPT),
statuses[CONTENT_SCRIPT - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(READ_DOM),
statuses[READ_DOM - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(MODIFIED_DOM),
statuses[MODIFIED_DOM - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(DOM_METHOD),
statuses[DOM_METHOD - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(DOCUMENT_WRITE),
statuses[DOCUMENT_WRITE - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(INNER_HTML),
statuses[INNER_HTML - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_SCRIPT),
statuses[CREATED_SCRIPT - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_IFRAME),
statuses[CREATED_IFRAME - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_DIV),
statuses[CREATED_DIV - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_LINK),
statuses[CREATED_LINK - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_INPUT),
statuses[CREATED_INPUT - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_EMBED),
statuses[CREATED_EMBED - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_OBJECT),
statuses[CREATED_OBJECT - 1]);
} else {
prefix += "Google.";
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CONTENT_SCRIPT),
statuses[CONTENT_SCRIPT - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(READ_DOM),
statuses[READ_DOM - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(MODIFIED_DOM),
statuses[MODIFIED_DOM - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(DOM_METHOD),
statuses[DOM_METHOD - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(DOCUMENT_WRITE),
statuses[DOCUMENT_WRITE - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(INNER_HTML),
statuses[INNER_HTML - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_SCRIPT),
statuses[CREATED_SCRIPT - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_IFRAME),
statuses[CREATED_IFRAME - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_DIV),
statuses[CREATED_DIV - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_LINK),
statuses[CREATED_LINK - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_INPUT),
statuses[CREATED_INPUT - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_EMBED),
statuses[CREATED_EMBED - 1]);
UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_OBJECT),
statuses[CREATED_OBJECT - 1]);
}
}
// Handle tab tracking. --------------------------------------------------------
void UmaPolicy::OnBrowserAdded(Browser* browser) {
if (!profile_->IsSameProfile(browser->profile()))
return;
browser->tab_strip_model()->AddObserver(this);
}
void UmaPolicy::OnBrowserRemoved(Browser* browser) {
if (!profile_->IsSameProfile(browser->profile()))
return;
browser->tab_strip_model()->RemoveObserver(this);
}
// Use the value from SessionID::IdForTab, *not* |index|. |index| will be
// duplicated across tabs in a session, whereas IdForTab uniquely identifies
// each tab.
void UmaPolicy::TabChangedAt(content::WebContents* contents,
int index,
TabChangeType change_type) {
if (change_type != TabStripModelObserver::LOADING_ONLY)
return;
if (!contents)
return;
std::string url = CleanURL(contents->GetLastCommittedURL());
int32 tab_id = SessionID::IdForTab(contents);
std::map<int32, std::string>::iterator tab_it = tab_list_.find(tab_id);
// Ignore tabs that haven't changed status.
if (tab_it != tab_list_.end() && tab_it->second == url)
return;
// Is this an existing tab whose URL has changed.
if (tab_it != tab_list_.end()) {
CleanupClosedPage(tab_it->second);
tab_list_.erase(tab_id);
}
// Check that tab_list_ isn't over the kMaxTabsTracked budget.
if (tab_list_.size() >= kMaxTabsTracked)
return;
// Set up the new entries.
tab_list_[tab_id] = url;
SetupOpenedPage(url);
}
// Use the value from SessionID::IdForTab, *not* |index|. |index| will be
// duplicated across tabs in a session, whereas IdForTab uniquely identifies
// each tab.
void UmaPolicy::TabClosingAt(TabStripModel* tab_strip_model,
content::WebContents* contents,
int index) {
if (!contents)
return;
std::string url = CleanURL(contents->GetLastCommittedURL());
int32 tab_id = SessionID::IdForTab(contents);
std::map<int, std::string>::iterator tab_it = tab_list_.find(tab_id);
if (tab_it != tab_list_.end())
tab_list_.erase(tab_id);
CleanupClosedPage(url);
}
void UmaPolicy::SetupOpenedPage(const std::string& url) {
url_status_[url][kNumberOfTabs]++;
}
void UmaPolicy::CleanupClosedPage(const std::string& url) {
SiteMap::iterator old_site_lookup = url_status_.find(url);
if (old_site_lookup == url_status_.end())
return;
old_site_lookup->second[kNumberOfTabs]--;
if (old_site_lookup->second[kNumberOfTabs] == 0) {
HistogramOnClose(url);
url_status_.erase(url);
}
}
// Helpers. --------------------------------------------------------------------
// We don't want to treat # ref navigations as if they were new pageloads.
// So we get rid of the ref if it has it.
// We convert to a string in the hopes that this is faster than Replacements.
std::string UmaPolicy::CleanURL(const GURL& gurl) {
if (gurl.spec().empty())
return GURL("about:blank").spec();
if (!gurl.is_valid())
return gurl.spec();
if (!gurl.has_ref())
return gurl.spec();
std::string port = "";
if (gurl.has_port())
port = ":" + gurl.port();
std::string query = "";
if (gurl.has_query())
query = "?" + gurl.query();
return base::StringPrintf("%s://%s%s%s%s",
gurl.scheme().c_str(),
gurl.host().c_str(),
port.c_str(),
gurl.path().c_str(),
query.c_str());
}
const char* UmaPolicy::GetHistogramName(PageStatus status) {
switch (status) {
case CONTENT_SCRIPT:
return "ContentScript";
case READ_DOM:
return "ReadDom";
case MODIFIED_DOM:
return "ModifiedDom";
case DOM_METHOD:
return "InvokedDomMethod";
case DOCUMENT_WRITE:
return "DocumentWrite";
case INNER_HTML:
return "InnerHtml";
case CREATED_SCRIPT:
return "CreatedScript";
case CREATED_IFRAME:
return "CreatedIframe";
case CREATED_DIV:
return "CreatedDiv";
case CREATED_LINK:
return "CreatedLink";
case CREATED_INPUT:
return "CreatedInput";
case CREATED_EMBED:
return "CreatedEmbed";
case CREATED_OBJECT:
return "CreatedObject";
case NONE:
case MAX_STATUS:
default:
NOTREACHED();
return "";
}
}
} // namespace extensions