1#!/usr/bin/env python
2#
3# Copyright (c) 2012 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Extract UserMetrics "actions" strings from the Chrome source.
8
9This program generates the list of known actions we expect to see in the
10user behavior logs.  It walks the Chrome source, looking for calls to
11UserMetrics functions, extracting actions and warning on improper calls,
12as well as generating the lists of possible actions in situations where
13there are many possible actions.
14
15See also:
16  content/browser/user_metrics.h
17  http://wiki.corp.google.com/twiki/bin/view/Main/ChromeUserExperienceMetrics
18
19If run with a "--hash" argument, chromeactions.txt will be updated.
20"""
21
22__author__ = 'evanm (Evan Martin)'
23
24import hashlib
25from HTMLParser import HTMLParser
26import os
27import re
28import sys
29
30sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'python'))
31from google import path_utils
32
33# Files that are known to use content::RecordComputedAction(), which means
34# they require special handling code in this script.
35# To add a new file, add it to this list and add the appropriate logic to
36# generate the known actions to AddComputedActions() below.
37KNOWN_COMPUTED_USERS = (
38  'back_forward_menu_model.cc',
39  'options_page_view.cc',
40  'render_view_host.cc',  # called using webkit identifiers
41  'user_metrics.cc',  # method definition
42  'new_tab_ui.cc',  # most visited clicks 1-9
43  'extension_metrics_module.cc', # extensions hook for user metrics
44  'safe_browsing_blocking_page.cc', # various interstitial types and actions
45  'language_options_handler_common.cc', # languages and input methods in CrOS
46  'cros_language_options_handler.cc', # languages and input methods in CrOS
47  'about_flags.cc', # do not generate a warning; see AddAboutFlagsActions()
48  'external_metrics.cc',  # see AddChromeOSActions()
49  'core_options_handler.cc',  # see AddWebUIActions()
50  'browser_render_process_host.cc',  # see AddRendererActions()
51  'render_thread_impl.cc',  # impl of RenderThread::RecordComputedAction()
52  'render_process_host_impl.cc',  # browser side impl for
53                                  # RenderThread::RecordComputedAction()
54  'mock_render_thread.cc',  # mock of RenderThread::RecordComputedAction()
55  'ppb_pdf_impl.cc',  # see AddClosedSourceActions()
56  'pepper_pdf_host.cc',  # see AddClosedSourceActions()
57  'key_systems_support_uma.cc',  # See AddKeySystemSupportActions()
58)
59
60# Language codes used in Chrome. The list should be updated when a new
61# language is added to app/l10n_util.cc, as follows:
62#
63# % (cat app/l10n_util.cc | \
64#    perl -n0e 'print $1 if /kAcceptLanguageList.*?\{(.*?)\}/s' | \
65#    perl -nle 'print $1, if /"(.*)"/'; echo 'es-419') | \
66#   sort | perl -pe "s/(.*)\n/'\$1', /" | \
67#   fold -w75 -s | perl -pe 's/^/  /;s/ $//'; echo
68#
69# The script extracts language codes from kAcceptLanguageList, but es-419
70# (Spanish in Latin America) is an exception.
71LANGUAGE_CODES = (
72  'af', 'am', 'ar', 'az', 'be', 'bg', 'bh', 'bn', 'br', 'bs', 'ca', 'co',
73  'cs', 'cy', 'da', 'de', 'de-AT', 'de-CH', 'de-DE', 'el', 'en', 'en-AU',
74  'en-CA', 'en-GB', 'en-NZ', 'en-US', 'en-ZA', 'eo', 'es', 'es-419', 'et',
75  'eu', 'fa', 'fi', 'fil', 'fo', 'fr', 'fr-CA', 'fr-CH', 'fr-FR', 'fy',
76  'ga', 'gd', 'gl', 'gn', 'gu', 'ha', 'haw', 'he', 'hi', 'hr', 'hu', 'hy',
77  'ia', 'id', 'is', 'it', 'it-CH', 'it-IT', 'ja', 'jw', 'ka', 'kk', 'km',
78  'kn', 'ko', 'ku', 'ky', 'la', 'ln', 'lo', 'lt', 'lv', 'mk', 'ml', 'mn',
79  'mo', 'mr', 'ms', 'mt', 'nb', 'ne', 'nl', 'nn', 'no', 'oc', 'om', 'or',
80  'pa', 'pl', 'ps', 'pt', 'pt-BR', 'pt-PT', 'qu', 'rm', 'ro', 'ru', 'sd',
81  'sh', 'si', 'sk', 'sl', 'sn', 'so', 'sq', 'sr', 'st', 'su', 'sv', 'sw',
82  'ta', 'te', 'tg', 'th', 'ti', 'tk', 'to', 'tr', 'tt', 'tw', 'ug', 'uk',
83  'ur', 'uz', 'vi', 'xh', 'yi', 'yo', 'zh', 'zh-CN', 'zh-TW', 'zu',
84)
85
86# Input method IDs used in Chrome OS. The list should be updated when a
87# new input method is added to
88# chrome/browser/chromeos/input_method/input_methods.txt in the Chrome tree, as
89# follows:
90#
91# % sort chrome/browser/chromeos/input_method/input_methods.txt | \
92#   perl -ne "print \"'\$1', \" if /^([^#]+?)\s/" | \
93#   fold -w75 -s | perl -pe 's/^/  /;s/ $//'; echo
94#
95# The script extracts input method IDs from input_methods.txt.
96INPUT_METHOD_IDS = (
97  'english-m', 'm17n:am:sera', 'm17n:ar:kbd', 'm17n:bn:itrans',
98  'm17n:fa:isiri', 'm17n:gu:itrans', 'm17n:hi:itrans', 'm17n:kn:itrans',
99  'm17n:ml:itrans', 'm17n:mr:itrans', 'm17n:ta:inscript', 'm17n:ta:itrans',
100  'm17n:ta:phonetic', 'm17n:ta:tamil99', 'm17n:ta:typewriter',
101  'm17n:te:itrans', 'm17n:th:kesmanee', 'm17n:th:pattachote',
102  'm17n:th:tis820', 'm17n:vi:tcvn', 'm17n:vi:telex', 'm17n:vi:viqr',
103  'm17n:vi:vni', 'm17n:zh:cangjie', 'm17n:zh:quick', 'mozc', 'mozc-chewing',
104  'mozc-dv', 'mozc-hangul', 'mozc-jp', 'pinyin', 'pinyin-dv', 'xkb:be::fra',
105  'xkb:be::ger', 'xkb:be::nld', 'xkb:bg::bul', 'xkb:bg:phonetic:bul',
106  'xkb:br::por', 'xkb:ca::fra', 'xkb:ca:eng:eng', 'xkb:ch::ger',
107  'xkb:ch:fr:fra', 'xkb:cz::cze', 'xkb:de::ger', 'xkb:de:neo:ger',
108  'xkb:dk::dan', 'xkb:ee::est', 'xkb:es::spa', 'xkb:es:cat:cat',
109  'xkb:fi::fin', 'xkb:fr::fra', 'xkb:gb:dvorak:eng', 'xkb:gb:extd:eng',
110  'xkb:gr::gre', 'xkb:hr::scr', 'xkb:hu::hun', 'xkb:il::heb', 'xkb:it::ita',
111  'xkb:jp::jpn', 'xkb:kr:kr104:kor', 'xkb:latam::spa', 'xkb:lt::lit',
112  'xkb:lv:apostrophe:lav', 'xkb:no::nob', 'xkb:pl::pol', 'xkb:pt::por',
113  'xkb:ro::rum', 'xkb:rs::srp', 'xkb:ru::rus', 'xkb:ru:phonetic:rus',
114  'xkb:se::swe', 'xkb:si::slv', 'xkb:sk::slo', 'xkb:tr::tur', 'xkb:ua::ukr',
115  'xkb:us::eng', 'xkb:us:altgr-intl:eng', 'xkb:us:colemak:eng',
116  'xkb:us:dvorak:eng', 'xkb:us:intl:eng',
117)
118
119# The path to the root of the repository.
120REPOSITORY_ROOT = os.path.join(path_utils.ScriptDir(), '..', '..', '..')
121
122number_of_files_total = 0
123
124
125def AddComputedActions(actions):
126  """Add computed actions to the actions list.
127
128  Arguments:
129    actions: set of actions to add to.
130  """
131
132  # Actions for back_forward_menu_model.cc.
133  for dir in ('BackMenu_', 'ForwardMenu_'):
134    actions.add(dir + 'ShowFullHistory')
135    actions.add(dir + 'Popup')
136    for i in range(1, 20):
137      actions.add(dir + 'HistoryClick' + str(i))
138      actions.add(dir + 'ChapterClick' + str(i))
139
140  # Actions for new_tab_ui.cc.
141  for i in range(1, 10):
142    actions.add('MostVisited%d' % i)
143
144  # Actions for safe_browsing_blocking_page.cc.
145  for interstitial in ('Phishing', 'Malware', 'Multiple'):
146    for action in ('Show', 'Proceed', 'DontProceed', 'ForcedDontProceed'):
147      actions.add('SBInterstitial%s%s' % (interstitial, action))
148
149  # Actions for language_options_handler.cc (Chrome OS specific).
150  for input_method_id in INPUT_METHOD_IDS:
151    actions.add('LanguageOptions_DisableInputMethod_%s' % input_method_id)
152    actions.add('LanguageOptions_EnableInputMethod_%s' % input_method_id)
153    actions.add('InputMethodOptions_Open_%s' % input_method_id)
154  for language_code in LANGUAGE_CODES:
155    actions.add('LanguageOptions_UiLanguageChange_%s' % language_code)
156    actions.add('LanguageOptions_SpellCheckLanguageChange_%s' % language_code)
157
158def AddWebKitEditorActions(actions):
159  """Add editor actions from editor_client_impl.cc.
160
161  Arguments:
162    actions: set of actions to add to.
163  """
164  action_re = re.compile(r'''\{ [\w']+, +\w+, +"(.*)" +\},''')
165
166  editor_file = os.path.join(REPOSITORY_ROOT, 'webkit', 'api', 'src',
167                             'EditorClientImpl.cc')
168  for line in open(editor_file):
169    match = action_re.search(line)
170    if match:  # Plain call to RecordAction
171      actions.add(match.group(1))
172
173def AddClosedSourceActions(actions):
174  """Add actions that are in code which is not checked out by default
175
176  Arguments
177    actions: set of actions to add to.
178  """
179  actions.add('PDF.FitToHeightButton')
180  actions.add('PDF.FitToWidthButton')
181  actions.add('PDF.LoadFailure')
182  actions.add('PDF.LoadSuccess')
183  actions.add('PDF.PreviewDocumentLoadFailure')
184  actions.add('PDF.PrintButton')
185  actions.add('PDF.PrintPage')
186  actions.add('PDF.SaveButton')
187  actions.add('PDF.ZoomFromBrowser')
188  actions.add('PDF.ZoomInButton')
189  actions.add('PDF.ZoomOutButton')
190  actions.add('PDF_Unsupported_3D')
191  actions.add('PDF_Unsupported_Attachment')
192  actions.add('PDF_Unsupported_Bookmarks')
193  actions.add('PDF_Unsupported_Digital_Signature')
194  actions.add('PDF_Unsupported_Movie')
195  actions.add('PDF_Unsupported_Portfolios_Packages')
196  actions.add('PDF_Unsupported_Rights_Management')
197  actions.add('PDF_Unsupported_Screen')
198  actions.add('PDF_Unsupported_Shared_Form')
199  actions.add('PDF_Unsupported_Shared_Review')
200  actions.add('PDF_Unsupported_Sound')
201  actions.add('PDF_Unsupported_XFA')
202
203def AddAndroidActions(actions):
204  """Add actions that are used by Chrome on Android.
205
206  Arguments
207    actions: set of actions to add to.
208  """
209  actions.add('Cast_Sender_CastEnterFullscreen');
210  actions.add('Cast_Sender_CastDeviceSelected');
211  actions.add('Cast_Sender_YouTubeDeviceSelected');
212  actions.add('Cast_Sender_CastPlayRequested');
213  actions.add('DataReductionProxy_PromoDisplayed');
214  actions.add('DataReductionProxy_PromoLearnMore');
215  actions.add('DataReductionProxy_TurnedOn');
216  actions.add('DataReductionProxy_TurnedOnFromPromo');
217  actions.add('DataReductionProxy_TurnedOff');
218  actions.add('MobileActionBarShown')
219  actions.add('MobileBeamCallbackSuccess')
220  actions.add('MobileBeamInvalidAppState')
221  actions.add('MobileBreakpadUploadAttempt')
222  actions.add('MobileBreakpadUploadFailure')
223  actions.add('MobileBreakpadUploadSuccess')
224  actions.add('MobileContextMenuCopyImageLinkAddress')
225  actions.add('MobileContextMenuCopyLinkAddress')
226  actions.add('MobileContextMenuCopyLinkText')
227  actions.add('MobileContextMenuDownloadImage')
228  actions.add('MobileContextMenuDownloadLink')
229  actions.add('MobileContextMenuDownloadVideo')
230  actions.add('MobileContextMenuImage')
231  actions.add('MobileContextMenuLink')
232  actions.add('MobileContextMenuOpenImageInNewTab')
233  actions.add('MobileContextMenuOpenLink')
234  actions.add('MobileContextMenuOpenLinkInIncognito')
235  actions.add('MobileContextMenuOpenLinkInNewTab')
236  actions.add('MobileContextMenuSaveImage')
237  actions.add('MobileContextMenuSearchByImage')
238  actions.add('MobileContextMenuShareLink')
239  actions.add('MobileContextMenuText')
240  actions.add('MobileContextMenuVideo')
241  actions.add('MobileContextMenuViewImage')
242  actions.add('MobileFocusedFakeboxOnNtp')
243  actions.add('MobileFocusedOmniboxNotOnNtp')
244  actions.add('MobileFocusedOmniboxOnNtp')
245  actions.add('MobileFreAttemptSignIn')
246  actions.add('MobileFreSignInSuccessful')
247  actions.add('MobileFreSkipSignIn')
248  actions.add('MobileMenuAddToBookmarks')
249  actions.add('MobileMenuAllBookmarks')
250  actions.add('MobileMenuBack')
251  actions.add('MobileMenuCloseAllTabs')
252  actions.add('MobileMenuCloseTab')
253  actions.add('MobileMenuFeedback')
254  actions.add('MobileMenuFindInPage')
255  actions.add('MobileMenuForward')
256  actions.add('MobileMenuFullscreen')
257  actions.add('MobileMenuNewIncognitoTab')
258  actions.add('MobileMenuNewTab')
259  actions.add('MobileMenuOpenTabs')
260  actions.add('MobileMenuQuit')
261  actions.add('MobileMenuReload')
262  actions.add('MobileMenuSettings')
263  actions.add('MobileMenuShare')
264  actions.add('MobileMenuShow')
265  actions.add('MobileNTPBookmark')
266  actions.add('MobileNTPForeignSession')
267  actions.add('MobileNTPMostVisited')
268  actions.add('MobileNTPRecentlyClosed')
269  actions.add('MobileNTPSwitchToBookmarks')
270  actions.add('MobileNTPSwitchToIncognito')
271  actions.add('MobileNTPSwitchToMostVisited')
272  actions.add('MobileNTPSwitchToOpenTabs')
273  actions.add('MobileNewTabOpened')
274  actions.add('MobileOmniboxSearch')
275  actions.add('MobileOmniboxVoiceSearch')
276  actions.add('MobileOmniboxRefineSuggestion')
277  actions.add('MobilePageLoaded')
278  actions.add('MobilePageLoadedDesktopUserAgent')
279  actions.add('MobilePageLoadedWithKeyboard')
280  actions.add('MobileReceivedExternalIntent')
281  actions.add('MobileRendererCrashed')
282  actions.add('MobileShortcutAllBookmarks')
283  actions.add('MobileShortcutFindInPage')
284  actions.add('MobileShortcutNewIncognitoTab')
285  actions.add('MobileShortcutNewTab')
286  actions.add('MobileSideSwipeFinished')
287  actions.add('MobileStackViewCloseTab')
288  actions.add('MobileStackViewSwipeCloseTab')
289  actions.add('MobileTabClobbered')
290  actions.add('MobileTabClosed')
291  actions.add('MobileTabStripCloseTab')
292  actions.add('MobileTabStripNewTab')
293  actions.add('MobileTabSwitched')
294  actions.add('MobileToolbarBack')
295  actions.add('MobileToolbarForward')
296  actions.add('MobileToolbarNewTab')
297  actions.add('MobileToolbarReload')
298  actions.add('MobileToolbarShowMenu')
299  actions.add('MobileToolbarShowStackView')
300  actions.add('MobileToolbarStackViewNewTab')
301  actions.add('MobileToolbarToggleBookmark')
302  actions.add('MobileUsingMenuByHwButtonDragging')
303  actions.add('MobileUsingMenuByHwButtonTap')
304  actions.add('MobileUsingMenuBySwButtonDragging')
305  actions.add('MobileUsingMenuBySwButtonTap')
306  actions.add('SystemBack')
307  actions.add('SystemBackForNavigation')
308
309def AddAboutFlagsActions(actions):
310  """This parses the experimental feature flags for UMA actions.
311
312  Arguments:
313    actions: set of actions to add to.
314  """
315  about_flags = os.path.join(REPOSITORY_ROOT, 'chrome', 'browser',
316                             'about_flags.cc')
317  flag_name_re = re.compile(r'\s*"([0-9a-zA-Z\-_]+)",\s*// FLAGS:RECORD_UMA')
318  for line in open(about_flags):
319    match = flag_name_re.search(line)
320    if match:
321      actions.add("AboutFlags_" + match.group(1))
322    # If the line contains the marker but was not matched by the regex, put up
323    # an error if the line is not a comment.
324    elif 'FLAGS:RECORD_UMA' in line and line[0:2] != '//':
325      print >>sys.stderr, 'WARNING: This line is marked for recording ' + \
326          'about:flags metrics, but is not in the proper format:\n' + line
327
328def AddBookmarkManagerActions(actions):
329  """Add actions that are used by BookmarkManager.
330
331  Arguments
332    actions: set of actions to add to.
333  """
334  actions.add('BookmarkManager_Command_AddPage')
335  actions.add('BookmarkManager_Command_Copy')
336  actions.add('BookmarkManager_Command_Cut')
337  actions.add('BookmarkManager_Command_Delete')
338  actions.add('BookmarkManager_Command_Edit')
339  actions.add('BookmarkManager_Command_Export')
340  actions.add('BookmarkManager_Command_Import')
341  actions.add('BookmarkManager_Command_NewFolder')
342  actions.add('BookmarkManager_Command_OpenIncognito')
343  actions.add('BookmarkManager_Command_OpenInNewTab')
344  actions.add('BookmarkManager_Command_OpenInNewWindow')
345  actions.add('BookmarkManager_Command_OpenInSame')
346  actions.add('BookmarkManager_Command_Paste')
347  actions.add('BookmarkManager_Command_ShowInFolder')
348  actions.add('BookmarkManager_Command_Sort')
349  actions.add('BookmarkManager_Command_UndoDelete')
350  actions.add('BookmarkManager_Command_UndoGlobal')
351  actions.add('BookmarkManager_Command_UndoNone')
352
353  actions.add('BookmarkManager_NavigateTo_BookmarkBar')
354  actions.add('BookmarkManager_NavigateTo_Mobile')
355  actions.add('BookmarkManager_NavigateTo_Other')
356  actions.add('BookmarkManager_NavigateTo_Recent')
357  actions.add('BookmarkManager_NavigateTo_Search')
358  actions.add('BookmarkManager_NavigateTo_SubFolder')
359
360def AddChromeOSActions(actions):
361  """Add actions reported by non-Chrome processes in Chrome OS.
362
363  Arguments:
364    actions: set of actions to add to.
365  """
366  # Actions sent by Chrome OS update engine.
367  actions.add('Updater.ServerCertificateChanged')
368  actions.add('Updater.ServerCertificateFailed')
369
370  # Actions sent by Chrome OS cryptohome.
371  actions.add('Cryptohome.PKCS11InitFail')
372
373def AddExtensionActions(actions):
374  """Add actions reported by extensions via chrome.metricsPrivate API.
375
376  Arguments:
377    actions: set of actions to add to.
378  """
379  # Actions sent by Chrome OS File Browser.
380  actions.add('FileBrowser.CreateNewFolder')
381  actions.add('FileBrowser.PhotoEditor.Edit')
382  actions.add('FileBrowser.PhotoEditor.View')
383  actions.add('FileBrowser.SuggestApps.ShowDialog')
384
385  # Actions sent by Google Now client.
386  actions.add('GoogleNow.MessageClicked')
387  actions.add('GoogleNow.ButtonClicked0')
388  actions.add('GoogleNow.ButtonClicked1')
389  actions.add('GoogleNow.Dismissed')
390
391  # Actions sent by Chrome Connectivity Diagnostics.
392  actions.add('ConnectivityDiagnostics.LaunchSource.OfflineChromeOS')
393  actions.add('ConnectivityDiagnostics.LaunchSource.WebStore')
394  actions.add('ConnectivityDiagnostics.UA.LogsShown')
395  actions.add('ConnectivityDiagnostics.UA.PassingTestsShown')
396  actions.add('ConnectivityDiagnostics.UA.SettingsShown')
397  actions.add('ConnectivityDiagnostics.UA.TestResultExpanded')
398  actions.add('ConnectivityDiagnostics.UA.TestSuiteRun')
399
400def GrepForActions(path, actions):
401  """Grep a source file for calls to UserMetrics functions.
402
403  Arguments:
404    path: path to the file
405    actions: set of actions to add to
406  """
407  global number_of_files_total
408  number_of_files_total = number_of_files_total + 1
409  # we look for the UserMetricsAction structure constructor
410  # this should be on one line
411  action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\("([^"]*)')
412  malformed_action_re = re.compile(r'[^a-zA-Z]UserMetricsAction\([^"]')
413  computed_action_re = re.compile(r'RecordComputedAction')
414  line_number = 0
415  for line in open(path):
416    line_number = line_number + 1
417    match = action_re.search(line)
418    if match:  # Plain call to RecordAction
419      actions.add(match.group(1))
420    elif malformed_action_re.search(line):
421      # Warn if this line is using RecordAction incorrectly.
422      print >>sys.stderr, ('WARNING: %s has malformed call to RecordAction'
423                           ' at %d' % (path, line_number))
424    elif computed_action_re.search(line):
425      # Warn if this file shouldn't be calling RecordComputedAction.
426      if os.path.basename(path) not in KNOWN_COMPUTED_USERS:
427        print >>sys.stderr, ('WARNING: %s has RecordComputedAction at %d' %
428                             (path, line_number))
429
430class WebUIActionsParser(HTMLParser):
431  """Parses an HTML file, looking for all tags with a 'metric' attribute.
432  Adds user actions corresponding to any metrics found.
433
434  Arguments:
435    actions: set of actions to add to
436  """
437  def __init__(self, actions):
438    HTMLParser.__init__(self)
439    self.actions = actions
440
441  def handle_starttag(self, tag, attrs):
442    # We only care to examine tags that have a 'metric' attribute.
443    attrs = dict(attrs)
444    if not 'metric' in attrs:
445      return
446
447    # Boolean metrics have two corresponding actions.  All other metrics have
448    # just one corresponding action.  By default, we check the 'dataType'
449    # attribute.
450    is_boolean = ('dataType' in attrs and attrs['dataType'] == 'boolean')
451    if 'type' in attrs and attrs['type'] in ('checkbox', 'radio'):
452      if attrs['type'] == 'checkbox':
453        is_boolean = True
454      else:
455        # Radio buttons are boolean if and only if their values are 'true' or
456        # 'false'.
457        assert(attrs['type'] == 'radio')
458        if 'value' in attrs and attrs['value'] in ['true', 'false']:
459          is_boolean = True
460
461    if is_boolean:
462      self.actions.add(attrs['metric'] + '_Enable')
463      self.actions.add(attrs['metric'] + '_Disable')
464    else:
465      self.actions.add(attrs['metric'])
466
467def GrepForWebUIActions(path, actions):
468  """Grep a WebUI source file for elements with associated metrics.
469
470  Arguments:
471    path: path to the file
472    actions: set of actions to add to
473  """
474  close_called = False
475  try:
476    parser = WebUIActionsParser(actions)
477    parser.feed(open(path).read())
478    # An exception can be thrown by parser.close(), so do it in the try to
479    # ensure the path of the file being parsed gets printed if that happens.
480    close_called = True
481    parser.close()
482  except Exception, e:
483    print "Error encountered for path %s" % path
484    raise e
485  finally:
486    if not close_called:
487      parser.close()
488
489def WalkDirectory(root_path, actions, extensions, callback):
490  for path, dirs, files in os.walk(root_path):
491    if '.svn' in dirs:
492      dirs.remove('.svn')
493    if '.git' in dirs:
494      dirs.remove('.git')
495    for file in files:
496      ext = os.path.splitext(file)[1]
497      if ext in extensions:
498        callback(os.path.join(path, file), actions)
499
500def AddLiteralActions(actions):
501  """Add literal actions specified via calls to UserMetrics functions.
502
503  Arguments:
504    actions: set of actions to add to.
505  """
506  EXTENSIONS = ('.cc', '.mm', '.c', '.m')
507
508  # Walk the source tree to process all .cc files.
509  ash_root = os.path.normpath(os.path.join(REPOSITORY_ROOT, 'ash'))
510  WalkDirectory(ash_root, actions, EXTENSIONS, GrepForActions)
511  chrome_root = os.path.normpath(os.path.join(REPOSITORY_ROOT, 'chrome'))
512  WalkDirectory(chrome_root, actions, EXTENSIONS, GrepForActions)
513  content_root = os.path.normpath(os.path.join(REPOSITORY_ROOT, 'content'))
514  WalkDirectory(content_root, actions, EXTENSIONS, GrepForActions)
515  webkit_root = os.path.normpath(os.path.join(REPOSITORY_ROOT, 'webkit'))
516  WalkDirectory(os.path.join(webkit_root, 'glue'), actions, EXTENSIONS,
517                GrepForActions)
518  WalkDirectory(os.path.join(webkit_root, 'port'), actions, EXTENSIONS,
519                GrepForActions)
520
521def AddWebUIActions(actions):
522  """Add user actions defined in WebUI files.
523
524  Arguments:
525    actions: set of actions to add to.
526  """
527  resources_root = os.path.join(REPOSITORY_ROOT, 'chrome', 'browser',
528                                'resources')
529  WalkDirectory(resources_root, actions, ('.html'), GrepForWebUIActions)
530
531def AddHistoryPageActions(actions):
532  """Add actions that are used in History page.
533
534  Arguments
535    actions: set of actions to add to.
536  """
537  actions.add('HistoryPage_BookmarkStarClicked')
538  actions.add('HistoryPage_EntryMenuRemoveFromHistory')
539  actions.add('HistoryPage_EntryLinkClick')
540  actions.add('HistoryPage_EntryLinkRightClick')
541  actions.add('HistoryPage_SearchResultClick')
542  actions.add('HistoryPage_EntryMenuShowMoreFromSite')
543  actions.add('HistoryPage_NewestHistoryClick')
544  actions.add('HistoryPage_NewerHistoryClick')
545  actions.add('HistoryPage_OlderHistoryClick')
546  actions.add('HistoryPage_Search')
547  actions.add('HistoryPage_InitClearBrowsingData')
548  actions.add('HistoryPage_RemoveSelected')
549  actions.add('HistoryPage_SearchResultRemove')
550  actions.add('HistoryPage_ConfirmRemoveSelected')
551  actions.add('HistoryPage_CancelRemoveSelected')
552
553def AddKeySystemSupportActions(actions):
554  """Add actions that are used for key system support metrics.
555
556  Arguments
557    actions: set of actions to add to.
558  """
559  actions.add('KeySystemSupport.Widevine.Queried')
560  actions.add('KeySystemSupport.WidevineWithType.Queried')
561  actions.add('KeySystemSupport.Widevine.Supported')
562  actions.add('KeySystemSupport.WidevineWithType.Supported')
563
564def AddAutomaticResetBannerActions(actions):
565  """Add actions that are used for the automatic profile settings reset banner
566  in chrome://settings.
567
568  Arguments
569    actions: set of actions to add to.
570  """
571  actions.add('AutomaticReset_WebUIBanner_BannerShown')
572  actions.add('AutomaticReset_WebUIBanner_ManuallyClosed')
573  actions.add('AutomaticReset_WebUIBanner_ResetClicked')
574
575def main(argv):
576  if '--hash' in argv:
577    hash_output = True
578  else:
579    hash_output = False
580    print >>sys.stderr, "WARNING: If you added new UMA tags, you must" + \
581           " use the --hash option to update chromeactions.txt."
582  # if we do a hash output, we want to only append NEW actions, and we know
583  # the file we want to work on
584  actions = set()
585
586  chromeactions_path = os.path.join(path_utils.ScriptDir(), "chromeactions.txt")
587
588  if hash_output:
589    f = open(chromeactions_path)
590    for line in f:
591      part = line.rpartition("\t")
592      part = part[2].strip()
593      actions.add(part)
594    f.close()
595
596
597  AddComputedActions(actions)
598  # TODO(fmantek): bring back webkit editor actions.
599  # AddWebKitEditorActions(actions)
600  AddAboutFlagsActions(actions)
601  AddWebUIActions(actions)
602
603  AddLiteralActions(actions)
604
605  # print "Scanned {0} number of files".format(number_of_files_total)
606  # print "Found {0} entries".format(len(actions))
607
608  AddAndroidActions(actions)
609  AddAutomaticResetBannerActions(actions)
610  AddBookmarkManagerActions(actions)
611  AddChromeOSActions(actions)
612  AddClosedSourceActions(actions)
613  AddExtensionActions(actions)
614  AddHistoryPageActions(actions)
615  AddKeySystemSupportActions(actions)
616
617  if hash_output:
618    f = open(chromeactions_path, "wb")
619
620
621  # Print out the actions as a sorted list.
622  for action in sorted(actions):
623    if hash_output:
624      hash = hashlib.md5()
625      hash.update(action)
626      print >>f, '0x%s\t%s' % (hash.hexdigest()[:16], action)
627    else:
628      print action
629
630  if hash_output:
631    print "Done. Do not forget to add chromeactions.txt to your changelist"
632  return 0
633
634
635if '__main__' == __name__:
636  sys.exit(main(sys.argv))
637