text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/automation/automation_util.h"
  
  #include <string>
  
  #include "ash/shell.h"
  #include "ash/shell_delegate.h"
  #include "base/bind.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/string_number_conversions.h"
  #include "base/stringprintf.h"
  #include "base/synchronization/waitable_event.h"
  #include "base/time.h"
  #include "base/values.h"
  #include "chrome/browser/automation/automation_provider.h"
  #include "chrome/browser/automation/automation_provider_json.h"
  #include "chrome/browser/extensions/extension_process_manager.h"
  #include "chrome/browser/extensions/extension_service.h"
  #include "chrome/browser/printing/print_preview_tab_controller.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/sessions/session_id.h"
  #include "chrome/browser/sessions/session_tab_helper.h"
  #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h"
  #include "chrome/browser/ui/browser.h"
  #include "chrome/browser/ui/browser_list.h"
  #include "chrome/browser/ui/browser_tabstrip.h"
  #include "chrome/browser/ui/tab_contents/tab_contents.h"
  #include "chrome/browser/view_type_utils.h"
  #include "chrome/common/automation_id.h"
  #include "chrome/common/extensions/extension.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/render_process_host.h"
  #include "content/public/browser/render_view_host.h"
  #include "content/public/browser/web_contents.h"
  #include "net/cookies/canonical_cookie.h"
  #include "net/cookies/cookie_monster.h"
  #include "net/cookies/cookie_store.h"
  #include "net/url_request/url_request_context.h"
  #include "net/url_request/url_request_context_getter.h"
  
  #if defined(OS_CHROMEOS)
  #include "chrome/browser/chromeos/login/existing_user_controller.h"
  #include "chrome/browser/chromeos/login/login_display.h"
  #include "chrome/browser/chromeos/login/login_display_host.h"
  #include "chrome/browser/chromeos/login/webui_login_display.h"
  #include "chrome/browser/chromeos/login/webui_login_display_host.h"
  #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
  #endif
  
  using content::BrowserThread;
  using content::RenderViewHost;
  using content::WebContents;
  
  #if defined(OS_CHROMEOS)
  using chromeos::ExistingUserController;
  using chromeos::User;
  using chromeos::UserManager;
  #endif
  
  namespace {
  
  void GetCookiesCallback(base::WaitableEvent* event,
                          std::string* cookies,
                          const std::string& cookie_line) {
    *cookies = cookie_line;
    event->Signal();
  }
  
  void GetCookiesOnIOThread(
      const GURL& url,
      const scoped_refptr<net::URLRequestContextGetter>& context_getter,
      base::WaitableEvent* event,
      std::string* cookies) {
    context_getter->GetURLRequestContext()->cookie_store()->
        GetCookiesWithOptionsAsync(url, net::CookieOptions(),
                        base::Bind(&GetCookiesCallback, event, cookies));
  }
  
  void GetCanonicalCookiesCallback(
      base::WaitableEvent* event,
      net::CookieList* cookie_list,
      const net::CookieList& cookies) {
    *cookie_list = cookies;
    event->Signal();
  }
  
  void GetCanonicalCookiesOnIOThread(
      const GURL& url,
      const scoped_refptr<net::URLRequestContextGetter>& context_getter,
      base::WaitableEvent* event,
      net::CookieList* cookie_list) {
    context_getter->GetURLRequestContext()->cookie_store()->
        GetCookieMonster()->GetAllCookiesForURLAsync(
            url,
            base::Bind(&GetCanonicalCookiesCallback, event, cookie_list));
  }
  
  void SetCookieCallback(base::WaitableEvent* event,
                         bool* success,
                         bool result) {
    *success = result;
    event->Signal();
  }
  
  void SetCookieOnIOThread(
      const GURL& url,
      const std::string& value,
      const scoped_refptr<net::URLRequestContextGetter>& context_getter,
      base::WaitableEvent* event,
      bool* success) {
    context_getter->GetURLRequestContext()->cookie_store()->
        SetCookieWithOptionsAsync(
            url, value, net::CookieOptions(),
            base::Bind(&SetCookieCallback, event, success));
  }
  
  void SetCookieWithDetailsOnIOThread(
      const GURL& url,
      const net::CanonicalCookie& cookie,
      const std::string& original_domain,
      const scoped_refptr<net::URLRequestContextGetter>& context_getter,
      base::WaitableEvent* event,
      bool* success) {
    net::CookieMonster* cookie_monster =
        context_getter->GetURLRequestContext()->cookie_store()->
        GetCookieMonster();
    cookie_monster->SetCookieWithDetailsAsync(
        url, cookie.Name(), cookie.Value(), original_domain,
        cookie.Path(), cookie.ExpiryDate(), cookie.IsSecure(),
        cookie.IsHttpOnly(),
        base::Bind(&SetCookieCallback, event, success));
  }
  
  void DeleteCookieCallback(base::WaitableEvent* event) {
    event->Signal();
  }
  
  void DeleteCookieOnIOThread(
      const GURL& url,
      const std::string& name,
      const scoped_refptr<net::URLRequestContextGetter>& context_getter,
      base::WaitableEvent* event) {
    context_getter->GetURLRequestContext()->cookie_store()->
        DeleteCookieAsync(url, name,
                          base::Bind(&DeleteCookieCallback, event));
  }
  
  }  // namespace
  
  namespace automation_util {
  
  Browser* GetBrowserAt(int index) {
    if (index < 0 || index >= static_cast<int>(BrowserList::size()))
      return NULL;
    return *(BrowserList::begin() + index);
  }
  
  WebContents* GetWebContentsAt(int browser_index, int tab_index) {
    if (tab_index < 0)
      return NULL;
    Browser* browser = GetBrowserAt(browser_index);
    if (!browser || tab_index >= browser->tab_count())
      return NULL;
    return chrome::GetWebContentsAt(browser, tab_index);
  }
  
  #if defined(OS_CHROMEOS)
  Profile* GetCurrentProfileOnChromeOS(std::string* error_message) {
    const UserManager* user_manager = UserManager::Get();
    if (!user_manager) {
      *error_message = "No user manager.";
      return NULL;
    }
    if (!user_manager->IsUserLoggedIn()) {
      ExistingUserController* controller =
          ExistingUserController::current_controller();
      if (!controller) {
        *error_message = "Cannot get controller though user is not logged in.";
        return NULL;
      }
      chromeos::WebUILoginDisplayHost* webui_login_display_host =
          static_cast<chromeos::WebUILoginDisplayHost*>(
              controller->login_display_host());
      content::WebUI* web_ui = webui_login_display_host->GetOobeUI()->web_ui();
      if (!web_ui) {
        *error_message = "Unable to get webui from login display host.";
        return NULL;
      }
      return Profile::FromWebUI(web_ui);
    } else {
      ash::Shell* shell = ash::Shell::GetInstance();
      if (!shell || !shell->delegate()) {
        *error_message = "Unable to get shell delegate.";
        return NULL;
      }
      return Profile::FromBrowserContext(
          shell->delegate()->GetCurrentBrowserContext());
    }
    return NULL;
  }
  #endif  // defined(OS_CHROMEOS)
  
  Browser* GetBrowserForTab(WebContents* tab) {
    BrowserList::const_iterator browser_iter = BrowserList::begin();
    for (; browser_iter != BrowserList::end(); ++browser_iter) {
      Browser* browser = *browser_iter;
      for (int tab_index = 0; tab_index < browser->tab_count(); ++tab_index) {
        if (chrome::GetWebContentsAt(browser, tab_index) == tab)
          return browser;
      }
    }
    return NULL;
  }
  
  net::URLRequestContextGetter* GetRequestContext(WebContents* contents) {
    // Since we may be on the UI thread don't call GetURLRequestContext().
    // Get the request context specific to the current WebContents and app.
    return contents->GetBrowserContext()->GetRequestContextForRenderProcess(
        contents->GetRenderProcessHost()->GetID());
  }
  
  void GetCookies(const GURL& url,
                  WebContents* contents,
                  int* value_size,
                  std::string* value) {
    *value_size = -1;
    if (url.is_valid() && contents) {
      scoped_refptr<net::URLRequestContextGetter> context_getter =
          GetRequestContext(contents);
      base::WaitableEvent event(true /* manual reset */,
                                false /* not initially signaled */);
      CHECK(BrowserThread::PostTask(
          BrowserThread::IO, FROM_HERE,
          base::Bind(&GetCookiesOnIOThread, url, context_getter, &event, value)));
      event.Wait();
  
      *value_size = static_cast<int>(value->size());
    }
  }
  
  void SetCookie(const GURL& url,
                 const std::string& value,
                 WebContents* contents,
                 int* response_value) {
    *response_value = -1;
  
    if (url.is_valid() && contents) {
      scoped_refptr<net::URLRequestContextGetter> context_getter =
          GetRequestContext(contents);
      base::WaitableEvent event(true /* manual reset */,
                                false /* not initially signaled */);
      bool success = false;
      CHECK(BrowserThread::PostTask(
          BrowserThread::IO, FROM_HERE,
          base::Bind(&SetCookieOnIOThread, url, value, context_getter, &event,
                     &success)));
      event.Wait();
      if (success)
        *response_value = 1;
    }
  }
  
  void DeleteCookie(const GURL& url,
                    const std::string& cookie_name,
                    WebContents* contents,
                    bool* success) {
    *success = false;
    if (url.is_valid() && contents) {
      scoped_refptr<net::URLRequestContextGetter> context_getter =
          GetRequestContext(contents);
      base::WaitableEvent event(true /* manual reset */,
                                false /* not initially signaled */);
      CHECK(BrowserThread::PostTask(
          BrowserThread::IO, FROM_HERE,
          base::Bind(&DeleteCookieOnIOThread, url, cookie_name, context_getter,
                     &event)));
      event.Wait();
      *success = true;
    }
  }
  
  void GetCookiesJSON(AutomationProvider* provider,
                      DictionaryValue* args,
                      IPC::Message* reply_message) {
    AutomationJSONReply reply(provider, reply_message);
    std::string url;
    if (!args->GetString("url", &url)) {
      reply.SendError("'url' missing or invalid");
      return;
    }
  
    // Since we may be on the UI thread don't call GetURLRequestContext().
    scoped_refptr<net::URLRequestContextGetter> context_getter =
        provider->profile()->GetRequestContext();
  
    net::CookieList cookie_list;
    base::WaitableEvent event(true /* manual reset */,
                              false /* not initially signaled */);
    base::Closure task = base::Bind(&GetCanonicalCookiesOnIOThread, GURL(url),
                                    context_getter, &event, &cookie_list);
    if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task)) {
      reply.SendError("Couldn't post task to get the cookies");
      return;
    }
    event.Wait();
  
    ListValue* list = new ListValue();
    for (size_t i = 0; i < cookie_list.size(); ++i) {
      const net::CanonicalCookie& cookie = cookie_list[i];
      DictionaryValue* cookie_dict = new DictionaryValue();
      cookie_dict->SetString("name", cookie.Name());
      cookie_dict->SetString("value", cookie.Value());
      cookie_dict->SetString("path", cookie.Path());
      cookie_dict->SetString("domain", cookie.Domain());
      cookie_dict->SetBoolean("secure", cookie.IsSecure());
      cookie_dict->SetBoolean("http_only", cookie.IsHttpOnly());
      if (cookie.IsPersistent())
        cookie_dict->SetDouble("expiry", cookie.ExpiryDate().ToDoubleT());
      list->Append(cookie_dict);
    }
    DictionaryValue dict;
    dict.Set("cookies", list);
    reply.SendSuccess(&dict);
  }
  
  void DeleteCookieJSON(AutomationProvider* provider,
                        DictionaryValue* args,
                        IPC::Message* reply_message) {
    AutomationJSONReply reply(provider, reply_message);
    std::string url, name;
    if (!args->GetString("url", &url)) {
      reply.SendError("'url' missing or invalid");
      return;
    }
    if (!args->GetString("name", &name)) {
      reply.SendError("'name' missing or invalid");
      return;
    }
  
    // Since we may be on the UI thread don't call GetURLRequestContext().
    scoped_refptr<net::URLRequestContextGetter> context_getter =
        provider->profile()->GetRequestContext();
  
    base::WaitableEvent event(true /* manual reset */,
                              false /* not initially signaled */);
    base::Closure task = base::Bind(&DeleteCookieOnIOThread, GURL(url), name,
                                    context_getter, &event);
    if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task)) {
      reply.SendError("Couldn't post task to delete the cookie");
      return;
    }
    event.Wait();
    reply.SendSuccess(NULL);
  }
  
  void SetCookieJSON(AutomationProvider* provider,
                     DictionaryValue* args,
                     IPC::Message* reply_message) {
    AutomationJSONReply reply(provider, reply_message);
    std::string url;
    if (!args->GetString("url", &url)) {
      reply.SendError("'url' missing or invalid");
      return;
    }
    DictionaryValue* cookie_dict;
    if (!args->GetDictionary("cookie", &cookie_dict)) {
      reply.SendError("'cookie' missing or invalid");
      return;
    }
    std::string name, value;
    std::string domain;
    std::string path = "/";
    std::string mac_key;
    std::string mac_algorithm;
    bool secure = false;
    double expiry = 0;
    bool http_only = false;
    if (!cookie_dict->GetString("name", &name)) {
      reply.SendError("'name' missing or invalid");
      return;
    }
    if (!cookie_dict->GetString("value", &value)) {
      reply.SendError("'value' missing or invalid");
      return;
    }
    if (cookie_dict->HasKey("domain") &&
        !cookie_dict->GetString("domain", &domain)) {
      reply.SendError("optional 'domain' invalid");
      return;
    }
    if (cookie_dict->HasKey("path") &&
        !cookie_dict->GetString("path", &path)) {
      reply.SendError("optional 'path' invalid");
      return;
    }
    // mac_key and mac_algorithm are optional.
    if (cookie_dict->HasKey("mac_key"))
      cookie_dict->GetString("mac_key", &mac_key);
    if (cookie_dict->HasKey("mac_algorithm"))
      cookie_dict->GetString("mac_algorithm", &mac_algorithm);
    if (cookie_dict->HasKey("secure") &&
        !cookie_dict->GetBoolean("secure", &secure)) {
      reply.SendError("optional 'secure' invalid");
      return;
    }
    if (cookie_dict->HasKey("expiry")) {
      if (!cookie_dict->GetDouble("expiry", &expiry)) {
        reply.SendError("optional 'expiry' invalid");
        return;
      }
    }
    if (cookie_dict->HasKey("http_only") &&
        !cookie_dict->GetBoolean("http_only", &http_only)) {
      reply.SendError("optional 'http_only' invalid");
      return;
    }
  
    scoped_ptr<net::CanonicalCookie> cookie(
        net::CanonicalCookie::Create(
            GURL(url), name, value, domain, path,
            mac_key, mac_algorithm, base::Time(),
            base::Time::FromDoubleT(expiry), secure, http_only));
    if (!cookie.get()) {
      reply.SendError("given 'cookie' parameters are invalid");
      return;
    }
  
    // Since we may be on the UI thread don't call GetURLRequestContext().
    scoped_refptr<net::URLRequestContextGetter> context_getter =
        provider->profile()->GetRequestContext();
  
    base::WaitableEvent event(true /* manual reset */,
                              false /* not initially signaled */);
    bool success = false;
    base::Closure task = base::Bind(
        &SetCookieWithDetailsOnIOThread, GURL(url), *cookie.get(), domain,
        context_getter, &event, &success);
    if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task)) {
      reply.SendError("Couldn't post task to set the cookie");
      return;
    }
    event.Wait();
  
    if (!success) {
      reply.SendError("Could not set the cookie");
      return;
    }
    reply.SendSuccess(NULL);
  }
  
  bool SendErrorIfModalDialogActive(AutomationProvider* provider,
                                    IPC::Message* message) {
    bool active = AppModalDialogQueue::GetInstance()->HasActiveDialog();
    if (active) {
      AutomationJSONReply(provider, message).SendErrorCode(
          automation::kBlockedByModalDialog);
    }
    return active;
  }
  
  AutomationId GetIdForTab(const TabContents* tab) {
    SessionTabHelper* session_tab_helper =
        SessionTabHelper::FromWebContents(tab->web_contents());
    return AutomationId(AutomationId::kTypeTab,
                        base::IntToString(session_tab_helper->session_id().id()));
  }
  
  AutomationId GetIdForExtensionView(
      const content::RenderViewHost* render_view_host) {
    AutomationId::Type type;
    WebContents* web_contents = WebContents::FromRenderViewHost(render_view_host);
    switch (chrome::GetViewType(web_contents)) {
      case chrome::VIEW_TYPE_EXTENSION_POPUP:
        type = AutomationId::kTypeExtensionPopup;
        break;
      case chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE:
        type = AutomationId::kTypeExtensionBgPage;
        break;
      case chrome::VIEW_TYPE_EXTENSION_INFOBAR:
        type = AutomationId::kTypeExtensionInfobar;
        break;
      case chrome::VIEW_TYPE_APP_SHELL:
        type = AutomationId::kTypeAppShell;
        break;
      default:
        type = AutomationId::kTypeInvalid;
        break;
    }
    // Since these extension views do not permit navigation, using the
    // renderer process and view ID should suffice.
    std::string id = base::StringPrintf("%d|%d",
        render_view_host->GetRoutingID(),
        render_view_host->GetProcess()->GetID());
    return AutomationId(type, id);
  }
  
  AutomationId GetIdForExtension(const extensions::Extension* extension) {
    return AutomationId(AutomationId::kTypeExtension, extension->id());
  }
  
  bool GetTabForId(const AutomationId& id, WebContents** tab) {
    if (id.type() != AutomationId::kTypeTab)
      return false;
  
    printing::PrintPreviewTabController* preview_controller =
        printing::PrintPreviewTabController::GetInstance();
    BrowserList::const_iterator iter = BrowserList::begin();
    for (; iter != BrowserList::end(); ++iter) {
      Browser* browser = *iter;
      for (int tab_index = 0; tab_index < browser->tab_count(); ++tab_index) {
        TabContents* tab_contents = chrome::GetTabContentsAt(browser, tab_index);
        SessionTabHelper* session_tab_helper =
            SessionTabHelper::FromWebContents(tab_contents->web_contents());
        if (base::IntToString(
                session_tab_helper->session_id().id()) == id.id()) {
          *tab = tab_contents->web_contents();
          return true;
        }
        if (preview_controller) {
          TabContents* preview_tab_contents =
              preview_controller->GetPrintPreviewForTab(tab_contents);
          if (preview_tab_contents) {
            SessionTabHelper* preview_session_tab_helper =
                SessionTabHelper::FromWebContents(
                    preview_tab_contents->web_contents());
            std::string preview_id = base::IntToString(
                preview_session_tab_helper->session_id().id());
            if (preview_id == id.id()) {
              *tab = preview_tab_contents->web_contents();
              return true;
            }
          }
        }
      }
    }
    return false;
  }
  
  namespace {
  
  bool GetExtensionRenderViewForId(
      const AutomationId& id,
      Profile* profile,
      RenderViewHost** rvh) {
    ExtensionProcessManager* extension_mgr =
        profile->GetExtensionProcessManager();
    const ExtensionProcessManager::ViewSet view_set =
        extension_mgr->GetAllViews();
    for (ExtensionProcessManager::ViewSet::const_iterator iter = view_set.begin();
         iter != view_set.end(); ++iter) {
      content::RenderViewHost* host = *iter;
      AutomationId this_id = GetIdForExtensionView(host);
      if (id == this_id) {
        *rvh = host;
        return true;
      }
    }
    return false;
  }
  
  }  // namespace
  
  bool GetRenderViewForId(
      const AutomationId& id,
      Profile* profile,
      RenderViewHost** rvh) {
    switch (id.type()) {
      case AutomationId::kTypeTab: {
        WebContents* tab;
        if (!GetTabForId(id, &tab))
          return false;
        *rvh = tab->GetRenderViewHost();
        break;
      }
      case AutomationId::kTypeExtensionPopup:
      case AutomationId::kTypeExtensionBgPage:
      case AutomationId::kTypeExtensionInfobar:
      case AutomationId::kTypeAppShell:
        if (!GetExtensionRenderViewForId(id, profile, rvh))
          return false;
        break;
      default:
        return false;
    }
    return true;
  }
  
  bool GetExtensionForId(
      const AutomationId& id,
      Profile* profile,
      const extensions::Extension** extension) {
    if (id.type() != AutomationId::kTypeExtension)
      return false;
    ExtensionService* service = profile->GetExtensionService();
    const extensions::Extension* installed_extension =
        service->GetInstalledExtension(id.id());
    if (installed_extension)
      *extension = installed_extension;
    return !!installed_extension;
  }
  
  bool DoesObjectWithIdExist(const AutomationId& id, Profile* profile) {
    switch (id.type()) {
      case AutomationId::kTypeTab: {
        WebContents* tab;
        return GetTabForId(id, &tab);
      }
      case AutomationId::kTypeExtensionPopup:
      case AutomationId::kTypeExtensionBgPage:
      case AutomationId::kTypeExtensionInfobar:
      case AutomationId::kTypeAppShell: {
        RenderViewHost* rvh;
        return GetExtensionRenderViewForId(id, profile, &rvh);
      }
      case AutomationId::kTypeExtension: {
        const extensions::Extension* extension;
        return GetExtensionForId(id, profile, &extension);
      }
      default:
        break;
    }
    return false;
  }
  
  }  // namespace automation_util
  