Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / test / chromedriver / chrome_launcher.cc
index a501143..21595b0 100644 (file)
@@ -9,9 +9,11 @@
 
 #include "base/base64.h"
 #include "base/basictypes.h"
+#include "base/bind.h"
 #include "base/command_line.h"
-#include "base/file_util.h"
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
 #include "base/format_macros.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "base/values.h"
+#include "chrome/common/chrome_constants.h"
 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
-#include "chrome/test/chromedriver/chrome/chrome_existing_impl.h"
 #include "chrome/test/chromedriver/chrome/chrome_finder.h"
+#include "chrome/test/chromedriver/chrome/chrome_remote_impl.h"
 #include "chrome/test/chromedriver/chrome/device_manager.h"
+#include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
+#include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
 #include "chrome/test/chromedriver/chrome/embedded_automation_extension.h"
 #include "chrome/test/chromedriver/chrome/status.h"
 #include "chrome/test/chromedriver/chrome/user_data_dir.h"
 #include "chrome/test/chromedriver/chrome/version.h"
 #include "chrome/test/chromedriver/chrome/web_view.h"
-#include "chrome/test/chromedriver/chrome/zip.h"
 #include "chrome/test/chromedriver/net/port_server.h"
 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
+#include "crypto/rsa_private_key.h"
 #include "crypto/sha2.h"
+#include "third_party/zlib/google/zip.h"
 
 #if defined(OS_POSIX)
 #include <fcntl.h>
@@ -64,7 +70,7 @@ Status UnpackAutomationExtension(const base::FilePath& temp_dir,
 
   base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip");
   int size = static_cast<int>(decoded_extension.length());
-  if (file_util::WriteFile(extension_zip, decoded_extension.c_str(), size)
+  if (base::WriteFile(extension_zip, decoded_extension.c_str(), size)
       != size) {
     return Status(kUnknownError, "failed to write automation extension zip");
   }
@@ -95,13 +101,11 @@ Status PrepareCommandLine(int port,
   CommandLine command(program);
   Switches switches;
 
-  // TODO(chrisgao): Add "disable-sync" when chrome 30- is not supported.
-  // For chrome 30-, it leads to crash when opening chrome://settings.
   for (size_t i = 0; i < arraysize(kCommonSwitches); ++i)
     switches.SetSwitch(kCommonSwitches[i]);
   switches.SetSwitch("disable-hang-monitor");
   switches.SetSwitch("disable-prompt-on-repost");
-  switches.SetSwitch("full-memory-crash-report");
+  switches.SetSwitch("disable-sync");
   switches.SetSwitch("no-first-run");
   switches.SetSwitch("disable-background-networking");
   switches.SetSwitch("disable-web-resources");
@@ -111,10 +115,11 @@ Status PrepareCommandLine(int port,
   switches.SetSwitch("disable-component-update");
   switches.SetSwitch("disable-default-apps");
   switches.SetSwitch("enable-logging");
-  switches.SetSwitch("logging-level", "1");
+  switches.SetSwitch("log-level", "0");
   switches.SetSwitch("password-store", "basic");
   switches.SetSwitch("use-mock-keychain");
   switches.SetSwitch("remote-debugging-port", base::IntToString(port));
+  switches.SetSwitch("test-type", "webdriver");
 
   for (std::set<std::string>::const_iterator iter =
            capabilities.exclude_switches.begin();
@@ -156,15 +161,20 @@ Status WaitForDevToolsAndCheckVersion(
     const NetAddress& address,
     URLRequestContextGetter* context_getter,
     const SyncWebSocketFactory& socket_factory,
+    const Capabilities* capabilities,
     scoped_ptr<DevToolsHttpClient>* user_client) {
+  scoped_ptr<DeviceMetrics> device_metrics;
+  if (capabilities && capabilities->device_metrics)
+    device_metrics.reset(new DeviceMetrics(*capabilities->device_metrics));
+
   scoped_ptr<DevToolsHttpClient> client(new DevToolsHttpClient(
-      address, context_getter, socket_factory));
+      address, context_getter, socket_factory, device_metrics.Pass()));
   base::TimeTicks deadline =
-      base::TimeTicks::Now() + base::TimeDelta::FromSeconds(20);
+      base::TimeTicks::Now() + base::TimeDelta::FromSeconds(60);
   Status status = client->Init(deadline - base::TimeTicks::Now());
   if (status.IsError())
     return status;
-  if (client->build_no() < kMinimumSupportedChromeBuildNo) {
+  if (client->browser_info()->build_no < kMinimumSupportedChromeBuildNo) {
     return Status(kUnknownError, "Chrome version must be >= " +
         GetMinimumSupportedChromeVersion());
   }
@@ -183,24 +193,69 @@ Status WaitForDevToolsAndCheckVersion(
   return Status(kUnknownError, "unable to discover open pages");
 }
 
-Status LaunchExistingChromeSession(
+Status CreateBrowserwideDevToolsClientAndConnect(
+    const NetAddress& address,
+    const PerfLoggingPrefs& perf_logging_prefs,
+    const SyncWebSocketFactory& socket_factory,
+    ScopedVector<DevToolsEventListener>& devtools_event_listeners,
+    scoped_ptr<DevToolsClient>* browser_client) {
+  scoped_ptr<DevToolsClient> client(new DevToolsClientImpl(
+      socket_factory, base::StringPrintf("ws://%s/devtools/browser/",
+                                         address.ToString().c_str()),
+      DevToolsClientImpl::kBrowserwideDevToolsClientId));
+  for (ScopedVector<DevToolsEventListener>::const_iterator it =
+          devtools_event_listeners.begin();
+      it != devtools_event_listeners.end();
+      ++it) {
+    // Only add listeners that subscribe to the browser-wide |DevToolsClient|.
+    // Otherwise, listeners will think this client is associated with a webview,
+    // and will send unrecognized commands to it.
+    if ((*it)->subscribes_to_browser())
+      client->AddListener(*it);
+  }
+  // Provide the client regardless of whether it connects, so that Chrome always
+  // has a valid |devtools_websocket_client_|. If not connected, no listeners
+  // will be notified, and client will just return kDisconnected errors if used.
+  *browser_client = client.Pass();
+  // To avoid unnecessary overhead, only connect if tracing is enabled, since
+  // the browser-wide client is currently only used for tracing.
+  if (!perf_logging_prefs.trace_categories.empty()) {
+    Status status = (*browser_client)->ConnectIfNecessary();
+    if (status.IsError())
+      return status;
+  }
+  return Status(kOk);
+}
+
+Status LaunchRemoteChromeSession(
     URLRequestContextGetter* context_getter,
     const SyncWebSocketFactory& socket_factory,
     const Capabilities& capabilities,
     ScopedVector<DevToolsEventListener>& devtools_event_listeners,
     scoped_ptr<Chrome>* chrome) {
   Status status(kOk);
-  scoped_ptr<DevToolsHttpClient> devtools_client;
+  scoped_ptr<DevToolsHttpClient> devtools_http_client;
   status = WaitForDevToolsAndCheckVersion(
       capabilities.debugger_address, context_getter, socket_factory,
-      &devtools_client);
+      NULL, &devtools_http_client);
   if (status.IsError()) {
     return Status(kUnknownError, "cannot connect to chrome at " +
                       capabilities.debugger_address.ToString(),
                   status);
   }
-  chrome->reset(new ChromeExistingImpl(devtools_client.Pass(),
-                                       devtools_event_listeners));
+
+  scoped_ptr<DevToolsClient> devtools_websocket_client;
+  status = CreateBrowserwideDevToolsClientAndConnect(
+      capabilities.debugger_address, capabilities.perf_logging_prefs,
+      socket_factory, devtools_event_listeners, &devtools_websocket_client);
+  if (status.IsError()) {
+    LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
+                 << status.message();
+  }
+
+  chrome->reset(new ChromeRemoteImpl(devtools_http_client.Pass(),
+                                     devtools_websocket_client.Pass(),
+                                     devtools_event_listeners));
   return Status(kOk);
 }
 
@@ -239,6 +294,9 @@ Status LaunchDesktopChrome(
     if (!command.HasSwitch(kEnableCrashReport))
       command.AppendSwitch(kEnableCrashReport);
   }
+
+  // We need to allow new privileges so that chrome's setuid sandbox can run.
+  options.allow_new_privs = true;
 #endif
 
 #if !defined(OS_WIN)
@@ -250,15 +308,14 @@ Status LaunchDesktopChrome(
 
 #if defined(OS_POSIX)
   base::FileHandleMappingVector no_stderr;
-  int devnull = -1;
-  file_util::ScopedFD scoped_devnull(&devnull);
+  base::ScopedFD devnull;
   if (!CommandLine::ForCurrentProcess()->HasSwitch("verbose")) {
     // Redirect stderr to /dev/null, so that Chrome log spew doesn't confuse
     // users.
-    devnull = open("/dev/null", O_WRONLY);
-    if (devnull == -1)
+    devnull.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY)));
+    if (!devnull.is_valid())
       return Status(kUnknownError, "couldn't open /dev/null");
-    no_stderr.push_back(std::make_pair(devnull, STDERR_FILENO));
+    no_stderr.push_back(std::make_pair(devnull.get(), STDERR_FILENO));
     options.fds_to_remap = &no_stderr;
   }
 #endif
@@ -273,9 +330,10 @@ Status LaunchDesktopChrome(
   if (!base::LaunchProcess(command, options, &process))
     return Status(kUnknownError, "chrome failed to start");
 
-  scoped_ptr<DevToolsHttpClient> devtools_client;
+  scoped_ptr<DevToolsHttpClient> devtools_http_client;
   status = WaitForDevToolsAndCheckVersion(
-      NetAddress(port), context_getter, socket_factory, &devtools_client);
+      NetAddress(port), context_getter, socket_factory, &capabilities,
+      &devtools_http_client);
 
   if (status.IsError()) {
     int exit_code;
@@ -311,8 +369,19 @@ Status LaunchDesktopChrome(
     }
     return status;
   }
+
+  scoped_ptr<DevToolsClient> devtools_websocket_client;
+  status = CreateBrowserwideDevToolsClientAndConnect(
+      NetAddress(port), capabilities.perf_logging_prefs, socket_factory,
+      devtools_event_listeners, &devtools_websocket_client);
+  if (status.IsError()) {
+    LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
+                 << status.message();
+  }
+
   scoped_ptr<ChromeDesktopImpl> chrome_desktop(
-      new ChromeDesktopImpl(devtools_client.Pass(),
+      new ChromeDesktopImpl(devtools_http_client.Pass(),
+                            devtools_websocket_client.Pass(),
                             devtools_event_listeners,
                             port_reservation.Pass(),
                             process,
@@ -352,7 +421,7 @@ Status LaunchAndroidChrome(
     status = device_manager->AcquireSpecificDevice(
         capabilities.android_device_serial, &device);
   }
-  if (!status.IsOk())
+  if (status.IsError())
     return status;
 
   Switches switches(capabilities.switches);
@@ -360,24 +429,39 @@ Status LaunchAndroidChrome(
     switches.SetSwitch(kCommonSwitches[i]);
   switches.SetSwitch("disable-fre");
   switches.SetSwitch("enable-remote-debugging");
-  status = device->StartApp(capabilities.android_package,
-                            capabilities.android_activity,
-                            capabilities.android_process,
-                            switches.ToString(), port);
-  if (!status.IsOk()) {
-    device->StopApp();
+  status = device->SetUp(capabilities.android_package,
+                         capabilities.android_activity,
+                         capabilities.android_process,
+                         switches.ToString(),
+                         capabilities.android_use_running_app,
+                         port);
+  if (status.IsError()) {
+    device->TearDown();
     return status;
   }
 
-  scoped_ptr<DevToolsHttpClient> devtools_client;
+  scoped_ptr<DevToolsHttpClient> devtools_http_client;
   status = WaitForDevToolsAndCheckVersion(NetAddress(port),
                                           context_getter,
                                           socket_factory,
-                                          &devtools_client);
-  if (status.IsError())
+                                          &capabilities,
+                                          &devtools_http_client);
+  if (status.IsError()) {
+    device->TearDown();
     return status;
+  }
 
-  chrome->reset(new ChromeAndroidImpl(devtools_client.Pass(),
+  scoped_ptr<DevToolsClient> devtools_websocket_client;
+  status = CreateBrowserwideDevToolsClientAndConnect(
+      NetAddress(port), capabilities.perf_logging_prefs, socket_factory,
+      devtools_event_listeners, &devtools_websocket_client);
+  if (status.IsError()) {
+    LOG(WARNING) << "Browser-wide DevTools client failed to connect: "
+                 << status.message();
+  }
+
+  chrome->reset(new ChromeAndroidImpl(devtools_http_client.Pass(),
+                                      devtools_websocket_client.Pass(),
                                       devtools_event_listeners,
                                       port_reservation.Pass(),
                                       device.Pass()));
@@ -395,8 +479,8 @@ Status LaunchChrome(
     const Capabilities& capabilities,
     ScopedVector<DevToolsEventListener>& devtools_event_listeners,
     scoped_ptr<Chrome>* chrome) {
-  if (capabilities.IsExistingBrowser()) {
-    return LaunchExistingChromeSession(
+  if (capabilities.IsRemoteBrowser()) {
+    return LaunchRemoteChromeSession(
         context_getter, socket_factory,
         capabilities, devtools_event_listeners, chrome);
   }
@@ -404,14 +488,12 @@ Status LaunchChrome(
   int port = 0;
   scoped_ptr<PortReservation> port_reservation;
   Status port_status(kOk);
-  if (port_server)
-    port_status = port_server->ReservePort(&port, &port_reservation);
-  else
-    port_status = port_manager->ReservePort(&port, &port_reservation);
-  if (port_status.IsError())
-    return Status(kUnknownError, "cannot reserve port for Chrome", port_status);
 
   if (capabilities.IsAndroid()) {
+    port_status = port_manager->ReservePortFromPool(&port, &port_reservation);
+    if (port_status.IsError())
+      return Status(kUnknownError, "cannot reserve port for Chrome",
+                    port_status);
     return LaunchAndroidChrome(context_getter,
                                port,
                                port_reservation.Pass(),
@@ -421,6 +503,13 @@ Status LaunchChrome(
                                device_manager,
                                chrome);
   } else {
+    if (port_server)
+      port_status = port_server->ReservePort(&port, &port_reservation);
+    else
+      port_status = port_manager->ReservePort(&port, &port_reservation);
+    if (port_status.IsError())
+      return Status(kUnknownError, "cannot reserve port for Chrome",
+                    port_status);
     return LaunchDesktopChrome(context_getter,
                                port,
                                port_reservation.Pass(),
@@ -449,7 +538,8 @@ void ConvertHexadecimalToIDAlphabet(std::string* id) {
 std::string GenerateExtensionId(const std::string& input) {
   uint8 hash[16];
   crypto::SHA256HashString(input, hash, sizeof(hash));
-  std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
+  std::string output =
+      base::StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
   ConvertHexadecimalToIDAlphabet(&output);
   return output;
 }
@@ -480,23 +570,44 @@ Status ProcessExtension(const std::string& extension,
   // 'encoded lines be no more than 76 characters long'. Just remove any
   // newlines.
   std::string extension_base64;
-  RemoveChars(extension, "\n", &extension_base64);
+  base::RemoveChars(extension, "\n", &extension_base64);
   std::string decoded_extension;
   if (!base::Base64Decode(extension_base64, &decoded_extension))
     return Status(kUnknownError, "cannot base64 decode");
 
-  // Get extension's ID from public key in crx file.
-  // Assumes crx v2. See http://developer.chrome.com/extensions/crx.html.
-  std::string key_len_str = decoded_extension.substr(8, 4);
-  if (key_len_str.size() != 4)
-    return Status(kUnknownError, "cannot extract public key length");
-  uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str());
-  std::string public_key = decoded_extension.substr(16, key_len);
-  if (key_len != public_key.size())
-    return Status(kUnknownError, "invalid public key length");
+  // If the file is a crx file, extract the extension's ID from its public key.
+  // Otherwise generate a random public key and use its derived extension ID.
+  std::string public_key;
+  std::string magic_header = decoded_extension.substr(0, 4);
+  if (magic_header.size() != 4)
+    return Status(kUnknownError, "cannot extract magic number");
+
+  const bool is_crx_file = magic_header == "Cr24";
+
+  if (is_crx_file) {
+    // Assume a CRX v2 file - see https://developer.chrome.com/extensions/crx.
+    std::string key_len_str = decoded_extension.substr(8, 4);
+    if (key_len_str.size() != 4)
+      return Status(kUnknownError, "cannot extract public key length");
+    uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str());
+    public_key = decoded_extension.substr(16, key_len);
+    if (key_len != public_key.size())
+      return Status(kUnknownError, "invalid public key length");
+  } else {
+    // Not a CRX file. Generate RSA keypair to get a valid extension id.
+    scoped_ptr<crypto::RSAPrivateKey> key_pair(
+        crypto::RSAPrivateKey::Create(2048));
+    if (!key_pair)
+      return Status(kUnknownError, "cannot generate RSA key pair");
+    std::vector<uint8> public_key_vector;
+    if (!key_pair->ExportPublicKey(&public_key_vector))
+      return Status(kUnknownError, "cannot extract public key");
+    public_key =
+        std::string(reinterpret_cast<char*>(&public_key_vector.front()),
+                    public_key_vector.size());
+  }
   std::string public_key_base64;
-  if (!base::Base64Encode(public_key, &public_key_base64))
-    return Status(kUnknownError, "cannot base64 encode public key");
+  base::Base64Encode(public_key, &public_key_base64);
   std::string id = GenerateExtensionId(public_key);
 
   // Unzip the crx file.
@@ -505,7 +616,7 @@ Status ProcessExtension(const std::string& extension,
     return Status(kUnknownError, "cannot create temp dir");
   base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx");
   int size = static_cast<int>(decoded_extension.length());
-  if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size) !=
+  if (base::WriteFile(extension_crx, decoded_extension.c_str(), size) !=
       size) {
     return Status(kUnknownError, "cannot write file");
   }
@@ -522,10 +633,32 @@ Status ProcessExtension(const std::string& extension,
   base::DictionaryValue* manifest;
   if (!manifest_value || !manifest_value->GetAsDictionary(&manifest))
     return Status(kUnknownError, "invalid manifest");
-  if (!manifest->HasKey("key")) {
+
+  std::string manifest_key_base64;
+  if (manifest->GetString("key", &manifest_key_base64)) {
+    // If there is a key in both the header and the manifest, use the key in the
+    // manifest. This allows chromedriver users users who generate dummy crxs
+    // to set the manifest key and have a consistent ID.
+    std::string manifest_key;
+    if (!base::Base64Decode(manifest_key_base64, &manifest_key))
+      return Status(kUnknownError, "'key' in manifest is not base64 encoded");
+    std::string manifest_id = GenerateExtensionId(manifest_key);
+    if (id != manifest_id) {
+      if (is_crx_file) {
+        LOG(WARNING)
+            << "Public key in crx header is different from key in manifest"
+            << std::endl << "key from header:   " << public_key_base64
+            << std::endl << "key from manifest: " << manifest_key_base64
+            << std::endl << "generated extension id from header key:   " << id
+            << std::endl << "generated extension id from manifest key: "
+            << manifest_id;
+      }
+      id = manifest_id;
+    }
+  } else {
     manifest->SetString("key", public_key_base64);
     base::JSONWriter::Write(manifest, &manifest_data);
-    if (file_util::WriteFile(
+    if (base::WriteFile(
             manifest_path, manifest_data.c_str(), manifest_data.size()) !=
         static_cast<int>(manifest_data.size())) {
       return Status(kUnknownError, "cannot add 'key' to manifest");
@@ -623,7 +756,7 @@ Status WritePrefsFile(
   base::JSONWriter::Write(prefs, &prefs_str);
   VLOG(0) << "Populating " << path.BaseName().value()
           << " file: " << PrettyPrintValue(*prefs);
-  if (static_cast<int>(prefs_str.length()) != file_util::WriteFile(
+  if (static_cast<int>(prefs_str.length()) != base::WriteFile(
           path, prefs_str.c_str(), prefs_str.length())) {
     return Status(kUnknownError, "failed to write prefs file");
   }
@@ -634,28 +767,28 @@ Status PrepareUserDataDir(
     const base::FilePath& user_data_dir,
     const base::DictionaryValue* custom_prefs,
     const base::DictionaryValue* custom_local_state) {
-  base::FilePath default_dir = user_data_dir.AppendASCII("Default");
-  if (!file_util::CreateDirectory(default_dir))
+  base::FilePath default_dir =
+      user_data_dir.AppendASCII(chrome::kInitialProfile);
+  if (!base::CreateDirectory(default_dir))
     return Status(kUnknownError, "cannot create default profile directory");
 
-  Status status = WritePrefsFile(
-      kPreferences,
-      custom_prefs,
-      default_dir.AppendASCII("Preferences"));
+  Status status =
+      WritePrefsFile(kPreferences,
+                     custom_prefs,
+                     default_dir.Append(chrome::kPreferencesFilename));
   if (status.IsError())
     return status;
 
-  status = WritePrefsFile(
-      kLocalState,
-      custom_local_state,
-      user_data_dir.AppendASCII("Local State"));
+  status = WritePrefsFile(kLocalState,
+                          custom_local_state,
+                          user_data_dir.Append(chrome::kLocalStateFilename));
   if (status.IsError())
     return status;
 
   // Write empty "First Run" file, otherwise Chrome will wipe the default
   // profile that was written.
-  if (file_util::WriteFile(
-          user_data_dir.AppendASCII("First Run"), "", 0) != 0) {
+  if (base::WriteFile(
+          user_data_dir.Append(chrome::kFirstRunSentinel), "", 0) != 0) {
     return Status(kUnknownError, "failed to write first run file");
   }
   return Status(kOk);