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
UmaPolicy(Profile * profile)46 UmaPolicy::UmaPolicy(Profile* profile)
47 : ActivityLogPolicy(profile), profile_(profile) {
48 DCHECK(!profile->IsOffTheRecord());
49 BrowserList::AddObserver(this);
50 }
51
~UmaPolicy()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.
Close()58 void UmaPolicy::Close() {
59 delete this;
60 }
61
62 // Process actions. ------------------------------------------------------------
63
ProcessAction(scoped_refptr<Action> action)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
MatchActionToStatus(scoped_refptr<Action> action)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
HistogramOnClose(const std::string & url)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
OnBrowserAdded(Browser * browser)222 void UmaPolicy::OnBrowserAdded(Browser* browser) {
223 if (!profile_->IsSameProfile(browser->profile()))
224 return;
225 browser->tab_strip_model()->AddObserver(this);
226 }
227
OnBrowserRemoved(Browser * browser)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.
TabChangedAt(content::WebContents * contents,int index,TabChangeType change_type)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.
TabClosingAt(TabStripModel * tab_strip_model,content::WebContents * contents,int index)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
SetupOpenedPage(const std::string & url)286 void UmaPolicy::SetupOpenedPage(const std::string& url) {
287 url_status_[url][kNumberOfTabs]++;
288 }
289
CleanupClosedPage(const std::string & url)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.
CleanURL(const GURL & gurl)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
GetHistogramName(PageStatus status)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