| // 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() == "HTMLElement.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 |