1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/extensions/activity_log/uma_policy.h" 6 7 #include "base/metrics/histogram.h" 8 #include "base/strings/stringprintf.h" 9 #include "chrome/browser/extensions/activity_log/activity_action_constants.h" 10 #include "chrome/browser/sessions/session_id.h" 11 #include "chrome/browser/ui/browser.h" 12 #include "chrome/browser/ui/browser_list.h" 13 #include "chrome/browser/ui/tabs/tab_strip_model.h" 14 #include "chrome/common/extensions/dom_action_types.h" 15 #include "content/public/browser/web_contents.h" 16 17 namespace { 18 19 // For convenience. 20 const int kNoStatus = extensions::UmaPolicy::NONE; 21 const int kContentScript = 1 << extensions::UmaPolicy::CONTENT_SCRIPT; 22 const int kReadDom = 1 << extensions::UmaPolicy::READ_DOM; 23 const int kModifiedDom = 1 << extensions::UmaPolicy::MODIFIED_DOM; 24 const int kDomMethod = 1 << extensions::UmaPolicy::DOM_METHOD; 25 const int kDocumentWrite = 1 << extensions::UmaPolicy::DOCUMENT_WRITE; 26 const int kInnerHtml = 1 << extensions::UmaPolicy::INNER_HTML; 27 const int kCreatedScript = 1 << extensions::UmaPolicy::CREATED_SCRIPT; 28 const int kCreatedIframe = 1 << extensions::UmaPolicy::CREATED_IFRAME; 29 const int kCreatedDiv = 1 << extensions::UmaPolicy::CREATED_DIV; 30 const int kCreatedLink = 1 << extensions::UmaPolicy::CREATED_LINK; 31 const int kCreatedInput = 1 << extensions::UmaPolicy::CREATED_INPUT; 32 const int kCreatedEmbed = 1 << extensions::UmaPolicy::CREATED_EMBED; 33 const int kCreatedObject = 1 << extensions::UmaPolicy::CREATED_OBJECT; 34 35 } // namespace 36 37 namespace extensions { 38 39 // Class constants, also used in testing. -------------------------------------- 40 41 const char UmaPolicy::kNumberOfTabs[] = "num_tabs"; 42 const size_t UmaPolicy::kMaxTabsTracked = 50; 43 44 // Setup and shutdown. --------------------------------------------------------- 45 46 UmaPolicy::UmaPolicy(Profile* profile) 47 : ActivityLogPolicy(profile), profile_(profile) { 48 DCHECK(!profile->IsOffTheRecord()); 49 BrowserList::AddObserver(this); 50 } 51 52 UmaPolicy::~UmaPolicy() { 53 BrowserList::RemoveObserver(this); 54 } 55 56 // Unlike the other policies, UmaPolicy can commit suicide directly because it 57 // doesn't have a dependency on a database. 58 void UmaPolicy::Close() { 59 delete this; 60 } 61 62 // Process actions. ------------------------------------------------------------ 63 64 void UmaPolicy::ProcessAction(scoped_refptr<Action> action) { 65 if (!action->page_url().is_valid() && !action->arg_url().is_valid()) 66 return; 67 if (action->page_incognito() || action->arg_incognito()) 68 return; 69 std::string url; 70 int status = MatchActionToStatus(action); 71 if (action->page_url().is_valid()) { 72 url = CleanURL(action->page_url()); 73 } else if (status & kContentScript) { 74 // This is for the tabs.executeScript case. 75 url = CleanURL(action->arg_url()); 76 } 77 if (url.empty()) 78 return; 79 80 SiteMap::iterator site_lookup = url_status_.find(url); 81 if (site_lookup != url_status_.end()) 82 site_lookup->second[action->extension_id()] |= status; 83 } 84 85 int UmaPolicy::MatchActionToStatus(scoped_refptr<Action> action) { 86 if (action->action_type() == Action::ACTION_CONTENT_SCRIPT) { 87 return kContentScript; 88 } else if (action->action_type() == Action::ACTION_API_CALL && 89 action->api_name() == "tabs.executeScript") { 90 return kContentScript; 91 } else if (action->action_type() != Action::ACTION_DOM_ACCESS) { 92 return kNoStatus; 93 } 94 95 int dom_verb; 96 if (!action->other() || 97 !action->other()->GetIntegerWithoutPathExpansion( 98 activity_log_constants::kActionDomVerb, &dom_verb)) { 99 return kNoStatus; 100 } 101 102 int ret_bit = kNoStatus; 103 DomActionType::Type dom_type = static_cast<DomActionType::Type>(dom_verb); 104 if (dom_type == DomActionType::GETTER) 105 return kReadDom; 106 if (dom_type == DomActionType::SETTER) { 107 ret_bit |= kModifiedDom; 108 } else if (dom_type == DomActionType::METHOD) { 109 ret_bit |= kDomMethod; 110 } else { 111 return kNoStatus; 112 } 113 114 if (action->api_name() == "HTMLDocument.write" || 115 action->api_name() == "HTMLDocument.writeln") { 116 ret_bit |= kDocumentWrite; 117 } else if (action->api_name() == "Element.innerHTML") { 118 ret_bit |= kInnerHtml; 119 } else if (action->api_name() == "Document.createElement") { 120 std::string arg; 121 action->args()->GetString(0, &arg); 122 if (arg == "script") { 123 ret_bit |= kCreatedScript; 124 } else if (arg == "iframe") { 125 ret_bit |= kCreatedIframe; 126 } else if (arg == "div") { 127 ret_bit |= kCreatedDiv; 128 } else if (arg == "a") { 129 ret_bit |= kCreatedLink; 130 } else if (arg == "input") { 131 ret_bit |= kCreatedInput; 132 } else if (arg == "embed") { 133 ret_bit |= kCreatedEmbed; 134 } else if (arg == "object") { 135 ret_bit |= kCreatedObject; 136 } 137 } 138 return ret_bit; 139 } 140 141 void UmaPolicy::HistogramOnClose(const std::string& url) { 142 // Let's try to avoid histogramming useless URLs. 143 if (url == "about:blank" || url.empty() || url == "chrome://newtab/") 144 return; 145 146 int statuses[MAX_STATUS-1]; 147 std::memset(statuses, 0, sizeof(statuses)); 148 149 SiteMap::iterator site_lookup = url_status_.find(url); 150 ExtensionMap exts = site_lookup->second; 151 ExtensionMap::iterator ext_iter; 152 for (ext_iter = exts.begin(); ext_iter != exts.end(); ++ext_iter) { 153 if (ext_iter->first == kNumberOfTabs) 154 continue; 155 for (int i = NONE + 1; i < MAX_STATUS; ++i) { 156 if (ext_iter->second & (1 << i)) 157 statuses[i-1]++; 158 } 159 } 160 161 std::string prefix = "ExtensionActivity."; 162 if (GURL(url).host() != "www.google.com") { 163 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CONTENT_SCRIPT), 164 statuses[CONTENT_SCRIPT - 1]); 165 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(READ_DOM), 166 statuses[READ_DOM - 1]); 167 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(MODIFIED_DOM), 168 statuses[MODIFIED_DOM - 1]); 169 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(DOM_METHOD), 170 statuses[DOM_METHOD - 1]); 171 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(DOCUMENT_WRITE), 172 statuses[DOCUMENT_WRITE - 1]); 173 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(INNER_HTML), 174 statuses[INNER_HTML - 1]); 175 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_SCRIPT), 176 statuses[CREATED_SCRIPT - 1]); 177 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_IFRAME), 178 statuses[CREATED_IFRAME - 1]); 179 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_DIV), 180 statuses[CREATED_DIV - 1]); 181 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_LINK), 182 statuses[CREATED_LINK - 1]); 183 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_INPUT), 184 statuses[CREATED_INPUT - 1]); 185 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_EMBED), 186 statuses[CREATED_EMBED - 1]); 187 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_OBJECT), 188 statuses[CREATED_OBJECT - 1]); 189 } else { 190 prefix += "Google."; 191 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CONTENT_SCRIPT), 192 statuses[CONTENT_SCRIPT - 1]); 193 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(READ_DOM), 194 statuses[READ_DOM - 1]); 195 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(MODIFIED_DOM), 196 statuses[MODIFIED_DOM - 1]); 197 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(DOM_METHOD), 198 statuses[DOM_METHOD - 1]); 199 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(DOCUMENT_WRITE), 200 statuses[DOCUMENT_WRITE - 1]); 201 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(INNER_HTML), 202 statuses[INNER_HTML - 1]); 203 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_SCRIPT), 204 statuses[CREATED_SCRIPT - 1]); 205 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_IFRAME), 206 statuses[CREATED_IFRAME - 1]); 207 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_DIV), 208 statuses[CREATED_DIV - 1]); 209 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_LINK), 210 statuses[CREATED_LINK - 1]); 211 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_INPUT), 212 statuses[CREATED_INPUT - 1]); 213 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_EMBED), 214 statuses[CREATED_EMBED - 1]); 215 UMA_HISTOGRAM_COUNTS_100(prefix + GetHistogramName(CREATED_OBJECT), 216 statuses[CREATED_OBJECT - 1]); 217 } 218 } 219 220 // Handle tab tracking. -------------------------------------------------------- 221 222 void UmaPolicy::OnBrowserAdded(Browser* browser) { 223 if (!profile_->IsSameProfile(browser->profile())) 224 return; 225 browser->tab_strip_model()->AddObserver(this); 226 } 227 228 void UmaPolicy::OnBrowserRemoved(Browser* browser) { 229 if (!profile_->IsSameProfile(browser->profile())) 230 return; 231 browser->tab_strip_model()->RemoveObserver(this); 232 } 233 234 // Use the value from SessionID::IdForTab, *not* |index|. |index| will be 235 // duplicated across tabs in a session, whereas IdForTab uniquely identifies 236 // each tab. 237 void UmaPolicy::TabChangedAt(content::WebContents* contents, 238 int index, 239 TabChangeType change_type) { 240 if (change_type != TabStripModelObserver::LOADING_ONLY) 241 return; 242 if (!contents) 243 return; 244 245 std::string url = CleanURL(contents->GetLastCommittedURL()); 246 int32 tab_id = SessionID::IdForTab(contents); 247 248 std::map<int32, std::string>::iterator tab_it = tab_list_.find(tab_id); 249 250 // Ignore tabs that haven't changed status. 251 if (tab_it != tab_list_.end() && tab_it->second == url) 252 return; 253 254 // Is this an existing tab whose URL has changed. 255 if (tab_it != tab_list_.end()) { 256 CleanupClosedPage(tab_it->second); 257 tab_list_.erase(tab_id); 258 } 259 260 // Check that tab_list_ isn't over the kMaxTabsTracked budget. 261 if (tab_list_.size() >= kMaxTabsTracked) 262 return; 263 264 // Set up the new entries. 265 tab_list_[tab_id] = url; 266 SetupOpenedPage(url); 267 } 268 269 // Use the value from SessionID::IdForTab, *not* |index|. |index| will be 270 // duplicated across tabs in a session, whereas IdForTab uniquely identifies 271 // each tab. 272 void UmaPolicy::TabClosingAt(TabStripModel* tab_strip_model, 273 content::WebContents* contents, 274 int index) { 275 if (!contents) 276 return; 277 std::string url = CleanURL(contents->GetLastCommittedURL()); 278 int32 tab_id = SessionID::IdForTab(contents); 279 std::map<int, std::string>::iterator tab_it = tab_list_.find(tab_id); 280 if (tab_it != tab_list_.end()) 281 tab_list_.erase(tab_id); 282 283 CleanupClosedPage(url); 284 } 285 286 void UmaPolicy::SetupOpenedPage(const std::string& url) { 287 url_status_[url][kNumberOfTabs]++; 288 } 289 290 void UmaPolicy::CleanupClosedPage(const std::string& url) { 291 SiteMap::iterator old_site_lookup = url_status_.find(url); 292 if (old_site_lookup == url_status_.end()) 293 return; 294 old_site_lookup->second[kNumberOfTabs]--; 295 if (old_site_lookup->second[kNumberOfTabs] == 0) { 296 HistogramOnClose(url); 297 url_status_.erase(url); 298 } 299 } 300 301 // Helpers. -------------------------------------------------------------------- 302 303 // We don't want to treat # ref navigations as if they were new pageloads. 304 // So we get rid of the ref if it has it. 305 // We convert to a string in the hopes that this is faster than Replacements. 306 std::string UmaPolicy::CleanURL(const GURL& gurl) { 307 if (gurl.spec().empty()) 308 return GURL("about:blank").spec(); 309 if (!gurl.is_valid()) 310 return gurl.spec(); 311 if (!gurl.has_ref()) 312 return gurl.spec(); 313 std::string port = ""; 314 if (gurl.has_port()) 315 port = ":" + gurl.port(); 316 std::string query = ""; 317 if (gurl.has_query()) 318 query = "?" + gurl.query(); 319 return base::StringPrintf("%s://%s%s%s%s", 320 gurl.scheme().c_str(), 321 gurl.host().c_str(), 322 port.c_str(), 323 gurl.path().c_str(), 324 query.c_str()); 325 } 326 327 const char* UmaPolicy::GetHistogramName(PageStatus status) { 328 switch (status) { 329 case CONTENT_SCRIPT: 330 return "ContentScript"; 331 case READ_DOM: 332 return "ReadDom"; 333 case MODIFIED_DOM: 334 return "ModifiedDom"; 335 case DOM_METHOD: 336 return "InvokedDomMethod"; 337 case DOCUMENT_WRITE: 338 return "DocumentWrite"; 339 case INNER_HTML: 340 return "InnerHtml"; 341 case CREATED_SCRIPT: 342 return "CreatedScript"; 343 case CREATED_IFRAME: 344 return "CreatedIframe"; 345 case CREATED_DIV: 346 return "CreatedDiv"; 347 case CREATED_LINK: 348 return "CreatedLink"; 349 case CREATED_INPUT: 350 return "CreatedInput"; 351 case CREATED_EMBED: 352 return "CreatedEmbed"; 353 case CREATED_OBJECT: 354 return "CreatedObject"; 355 case NONE: 356 case MAX_STATUS: 357 default: 358 NOTREACHED(); 359 return ""; 360 } 361 } 362 363 } // namespace extensions 364