1f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# found in the LICENSE file.
4f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import json
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import logging
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import os
85d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import platform
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import shutil
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import socket
115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import sys
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import tempfile
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import time
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import urllib2
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import zipfile
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from telemetry.page import profile_creator
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
19f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)import page_sets
20f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def _ExternalExtensionsPath():
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Returns the OS-dependent path at which to install the extension deployment
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)   files"""
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if platform.system() == 'Darwin':
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return os.path.join('/Library', 'Application Support', 'Google', 'Chrome',
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        'External Extensions')
285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  elif platform.system() == 'Linux':
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return os.path.join('/opt', 'google', 'chrome', 'extensions' )
305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  else:
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise NotImplementedError('Extension install on %s is not yet supported' %
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        platform.system())
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def _DownloadExtension(extension_id, output_dir):
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Download an extension to disk.
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    extension_id: the extension id.
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    output_dir: Directory to download into.
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Returns:
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Extension file downloaded."""
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  extension_download_path = os.path.join(output_dir, "%s.crx" % extension_id)
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  extension_url = (
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      "https://clients2.google.com/service/update2/crx?response=redirect"
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      "&x=id%%3D%s%%26lang%%3Den-US%%26uc" % extension_id)
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  response = urllib2.urlopen(extension_url)
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  assert(response.getcode() == 200)
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  with open(extension_download_path, "w") as f:
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    f.write(response.read())
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return extension_download_path
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def _GetExtensionInfoFromCRX(crx_path):
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Parse an extension archive and return information.
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Note:
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    The extension name returned by this function may not be valid
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  (e.g. in the case of a localized extension name).  It's use is just
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  meant to be informational.
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Args:
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    crx_path: path to crx archive to look at.
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Returns:
675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Tuple consisting of:
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    (crx_version, extension_name)"""
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  crx_zip = zipfile.ZipFile(crx_path)
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  manifest_contents = crx_zip.read('manifest.json')
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  decoded_manifest = json.loads(manifest_contents)
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  crx_version = decoded_manifest['version']
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  extension_name = decoded_manifest['name']
745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return (crx_version, extension_name)
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class ExtensionsProfileCreator(profile_creator.ProfileCreator):
785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """Virtual base class for profile creators that install extensions.
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Extensions are installed using the mechanism described in
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  https://developer.chrome.com/extensions/external_extensions.html .
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  Subclasses are meant to be run interactively.
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  """
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __init__(self):
875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    super(ExtensionsProfileCreator, self).__init__()
88f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    self._page_set = page_sets.Typical25()
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Directory into which the output profile is written.
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._output_profile_path = None
925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # List of extensions to install.
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._extensions_to_install = []
955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Theme to install (if any).
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._theme_to_install = None
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Directory to download extension files into.
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._extension_download_dir = None
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Have the extensions been installed yet?
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._extensions_installed = False
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # List of files to delete after run.
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._files_to_cleanup = []
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def _PrepareExtensionInstallFiles(self):
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Download extension archives and create extension install files."""
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    extensions_to_install = self._extensions_to_install
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if self._theme_to_install:
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      extensions_to_install = extensions_to_install + [self._theme_to_install]
1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    num_extensions = len(extensions_to_install)
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not num_extensions:
1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise ValueError("No extensions or themes to install:",
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          extensions_to_install)
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Create external extensions path if it doesn't exist already.
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    external_extensions_dir = _ExternalExtensionsPath()
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not os.path.isdir(external_extensions_dir):
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      os.makedirs(external_extensions_dir)
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._extension_download_dir = tempfile.mkdtemp()
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    for i in xrange(num_extensions):
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      extension_id = extensions_to_install[i]
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      logging.info("Downloading %s - %d/%d" % (
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          extension_id, (i + 1), num_extensions))
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      extension_path = _DownloadExtension(extension_id,
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          self._extension_download_dir)
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      (version, name) = _GetExtensionInfoFromCRX(extension_path)
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      extension_info = {'external_crx' : extension_path,
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          'external_version' : version,
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          '_comment' : name}
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      extension_json_path = os.path.join(external_extensions_dir,
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          "%s.json" % extension_id)
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      with open(extension_json_path, 'w') as f:
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        f.write(json.dumps(extension_info))
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self._files_to_cleanup.append(extension_json_path)
1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def _CleanupExtensionInstallFiles(self):
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Cleanup stray files before exiting."""
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    logging.info("Cleaning up stray files")
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    for filename in self._files_to_cleanup:
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      os.remove(filename)
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if self._extension_download_dir:
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      # Simple sanity check to lessen the impact of a stray rmtree().
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if len(self._extension_download_dir.split(os.sep)) < 3:
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        raise Exception("Path too shallow: %s" % self._extension_download_dir)
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      shutil.rmtree(self._extension_download_dir)
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self._extension_download_dir = None
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def CustomizeBrowserOptions(self, options):
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._output_profile_path = options.output_profile_path
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
157a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def WillRunTest(self, options):
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Run before browser starts.
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Download extensions and write installation files."""
161a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    super(ExtensionsProfileCreator, self).WillRunTest(options)
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Running this script on a corporate network or other managed environment
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # could potentially alter the profile contents.
1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    hostname = socket.gethostname()
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if hostname.endswith('corp.google.com'):
1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise Exception("It appears you are connected to a corporate network "
1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          "(hostname=%s).  This script needs to be run off the corp "
1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          "network." % hostname)
1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    prompt = ("\n!!!This script must be run on a fresh OS installation, "
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        "disconnected from any corporate network. Are you sure you want to "
1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        "continue? (y/N) ")
1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (raw_input(prompt).lower() != 'y'):
1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      sys.exit(-1)
1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._PrepareExtensionInstallFiles()
1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def DidRunTest(self, browser, results):
1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Run before exit."""
180a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    super(ExtensionsProfileCreator, self).DidRunTest()
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Do some basic sanity checks to make sure the profile is complete.
1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    installed_extensions = browser.extensions.keys()
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not len(installed_extensions) == len(self._extensions_to_install):
1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      # Diagnosing errors:
1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      # Too many extensions: Managed environment may be installing additional
1865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      # extensions.
1875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise Exception("Unexpected number of extensions installed in browser",
1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          installed_extensions)
1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Check that files on this list exist and have content.
1915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    expected_files = [
1925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        os.path.join('Default', 'Network Action Predictor')]
1935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    for filename in expected_files:
1945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      filename = os.path.join(self._output_profile_path, filename)
1955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if not os.path.getsize(filename) > 0:
1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        raise Exception("Profile not complete: %s is zero length." % filename)
1975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._CleanupExtensionInstallFiles()
1995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def CanRunForPage(self, page):
2015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # No matter how many pages in the pageset, just perform two test iterations.
2025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return page.page_set.pages.index(page) < 2
2035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def MeasurePage(self, _, tab, results):
2055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Profile setup works in 2 phases:
2065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Phase 1: When the first page is loaded: we wait for a timeout to allow
2075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    #     all extensions to install and to prime safe browsing and other
2085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    #     caches.  Extensions may open tabs as part of the install process.
2095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Phase 2: When the second page loads, page_runner closes all tabs -
2105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    #     we are left with one open tab, wait for that to finish loading.
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Sleep for a bit to allow safe browsing and other data to load +
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # extensions to install.
2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not self._extensions_installed:
2155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      sleep_seconds = 5 * 60
2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      logging.info("Sleeping for %d seconds." % sleep_seconds)
2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      time.sleep(sleep_seconds)
2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self._extensions_installed = True
2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    else:
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      # Phase 2: Wait for tab to finish loading.
2215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      for i in xrange(len(tab.browser.tabs)):
2225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        t = tab.browser.tabs[i]
2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        t.WaitForDocumentReadyStateToBeComplete()
224