<commit_before>// Copyright (c) 2010 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/debugger/devtools_http_protocol_handler.h"

#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/message_loop_proxy.h"
#include "base/string_number_conversions.h"
#include "base/thread.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_list.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/debugger/devtools_client_host.h"
#include "chrome/browser/debugger/devtools_manager.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/devtools_messages.h"
#include "chrome/common/net/url_request_context_getter.h"
#include "googleurl/src/gurl.h"
#include "net/base/io_buffer.h"
#include "net/base/listen_socket.h"
#include "net/server/http_server_request_info.h"
#include "net/url_request/url_request_context.h"

const int kBufferSize = 16 * 1024;

namespace {

// An internal implementation of DevToolsClientHost that delegates
// messages sent for DevToolsClient to a DebuggerShell instance.
class DevToolsClientHostImpl : public DevToolsClientHost {
 public:
  explicit DevToolsClientHostImpl(HttpListenSocket* socket)
      : socket_(socket) {}
  ~DevToolsClientHostImpl() {}

  // DevToolsClientHost interface
  virtual void InspectedTabClosing() {
    ChromeThread::PostTask(
        ChromeThread::IO,
        FROM_HERE,
        NewRunnableMethod(socket_,
                          &HttpListenSocket::Close));
  }

  virtual void SendMessageToClient(const IPC::Message& msg) {
    IPC_BEGIN_MESSAGE_MAP(DevToolsClientHostImpl, msg)
      IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend,
                          OnDispatchOnInspectorFrontend);
      IPC_MESSAGE_UNHANDLED_ERROR()
    IPC_END_MESSAGE_MAP()
  }

  void NotifyCloseListener() {
    DevToolsClientHost::NotifyCloseListener();
  }
 private:
  // Message handling routines
  void OnDispatchOnInspectorFrontend(const std::string& data) {
    std::string message;
    message += "devtools$$dispatch(\"" + data + "\")";
    socket_->SendOverWebSocket(message);
  }
  HttpListenSocket* socket_;
};

}

DevToolsHttpProtocolHandler::~DevToolsHttpProtocolHandler() {
  // Stop() must be called prior to this being called
  DCHECK(server_.get() == NULL);
}

void DevToolsHttpProtocolHandler::Start() {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Init));
}

void DevToolsHttpProtocolHandler::Stop() {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Teardown));
}

void DevToolsHttpProtocolHandler::OnHttpRequest(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& info) {
  if (info.path == "" || info.path == "/") {
    // Pages discovery request.
    ChromeThread::PostTask(
        ChromeThread::UI,
        FROM_HERE,
        NewRunnableMethod(this,
                          &DevToolsHttpProtocolHandler::OnHttpRequestUI,
                          socket,
                          info));
    return;
  }

  size_t pos = info.path.find("/devtools/");
  if (pos != 0) {
    socket->Send404();
    return;
  }

  // Proxy static files from chrome://devtools/*.
  URLRequest* request = new URLRequest(GURL("chrome:/" + info.path), this);
  Bind(request, socket);
  request->set_context(
      Profile::GetDefaultRequestContext()->GetURLRequestContext());
  request->Start();
}

void DevToolsHttpProtocolHandler::OnWebSocketRequest(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& request) {
  ChromeThread::PostTask(
      ChromeThread::UI,
      FROM_HERE,
      NewRunnableMethod(
          this,
          &DevToolsHttpProtocolHandler::OnWebSocketRequestUI,
          socket,
          request));
}

void DevToolsHttpProtocolHandler::OnWebSocketMessage(HttpListenSocket* socket,
                                                     const std::string& data) {
  ChromeThread::PostTask(
      ChromeThread::UI,
      FROM_HERE,
      NewRunnableMethod(
          this,
          &DevToolsHttpProtocolHandler::OnWebSocketMessageUI,
          socket,
          data));
}

void DevToolsHttpProtocolHandler::OnClose(HttpListenSocket* socket) {
  SocketToRequestsMap::iterator it = socket_to_requests_io_.find(socket);
  if (it != socket_to_requests_io_.end()) {
    // Dispose delegating socket.
    for (std::set<URLRequest*>::iterator it2 = it->second.begin();
         it2 != it->second.end(); ++it2) {
      URLRequest* request = *it2;
      request->Cancel();
      request_to_socket_io_.erase(request);
      request_to_buffer_io_.erase(request);
      delete request;
    }
    socket_to_requests_io_.erase(socket);
  }

  ChromeThread::PostTask(
      ChromeThread::UI,
      FROM_HERE,
      NewRunnableMethod(
          this,
          &DevToolsHttpProtocolHandler::OnCloseUI,
          socket));
}

void DevToolsHttpProtocolHandler::OnHttpRequestUI(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& info) {
  std::string response = "";
  for (BrowserList::const_iterator it = BrowserList::begin(),
       end = BrowserList::end(); it != end; ++it) {
    TabStripModel* model = (*it)->tabstrip_model();
    for (int i = 0, size = model->count(); i < size; ++i) {
      TabContents* tab_contents = model->GetTabContentsAt(i);
      NavigationController& controller = tab_contents->controller();
      NavigationEntry* entry = controller.GetActiveEntry();
      if (entry == NULL)
        continue;

      if (!entry->url().is_valid())
        continue;

      DevToolsClientHost* client_host = DevToolsManager::GetInstance()->
          GetDevToolsClientHostFor(tab_contents->render_view_host());
      if (!client_host) {
        response += StringPrintf(
            "%s (%s)
",
            controller.session_id().id(),
            UTF16ToUTF8(entry->title()).c_str(),
            entry->url().spec().c_str());
      } else {
        response += StringPrintf(
            "%s (%s)
",
            UTF16ToUTF8(entry->title()).c_str(),
            entry->url().spec().c_str());
      }
    }
  }
  response += "";
  Send200(socket, response, "text/html");
}

void DevToolsHttpProtocolHandler::OnWebSocketRequestUI(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& request) {
  std::string prefix = "/devtools/page/";
  size_t pos = request.path.find(prefix);
  if (pos != 0) {
    Send404(socket);
    return;
  }
  std::string page_id = request.path.substr(prefix.length());
  int id = 0;
  if (!base::StringToInt(page_id, &id)) {
    Send500(socket, "Invalid page id: " + page_id);
    return;
  }

  TabContents* tab_contents = GetTabContents(id);
  if (tab_contents == NULL) {
    Send500(socket, "No such page id: " + page_id);
    return;
  }

  DevToolsManager* manager = DevToolsManager::GetInstance();
  if (manager->GetDevToolsClientHostFor(tab_contents->render_view_host())) {
    Send500(socket, "Page with given id is being inspected: " + page_id);
    return;
  }

  DevToolsClientHostImpl* client_host = new DevToolsClientHostImpl(socket);
  socket_to_client_host_ui_[socket] = client_host;

  manager->RegisterDevToolsClientHostFor(
      tab_contents->render_view_host(),
      client_host);
  AcceptWebSocket(socket, request);
}

void DevToolsHttpProtocolHandler::OnWebSocketMessageUI(
    HttpListenSocket* socket,
    const std::string& data) {
  SocketToClientHostMap::iterator it = socket_to_client_host_ui_.find(socket);
  if (it == socket_to_client_host_ui_.end())
    return;

  DevToolsManager* manager = DevToolsManager::GetInstance();
  manager->ForwardToDevToolsAgent(it->second,
      DevToolsAgentMsg_DispatchOnInspectorBackend(data));
}

void DevToolsHttpProtocolHandler::OnCloseUI(HttpListenSocket* socket) {
  SocketToClientHostMap::iterator it = socket_to_client_host_ui_.find(socket);
  if (it == socket_to_client_host_ui_.end())
    return;
  DevToolsClientHostImpl* client_host =
      static_cast<DevToolsClientHostImpl*>(it->second);
  client_host->NotifyCloseListener();
  delete client_host;
  socket_to_client_host_ui_.erase(socket);
}

void DevToolsHttpProtocolHandler::OnResponseStarted(URLRequest* request) {
  RequestToSocketMap::iterator it = request_to_socket_io_.find(request);
  if (it == request_to_socket_io_.end())
    return;

  HttpListenSocket* socket = it->second;

  int expected_size = static_cast<int>(request->GetExpectedContentSize());

  std::string content_type;
  request->GetMimeType(&content_type);

  if (request->status().is_success()) {
    socket->Send(StringPrintf("HTTP/1.1 200 OK\r\n"
                              "Content-Type:%s\r\n"
                              "Content-Length:%d\r\n"
                              "\r\n",
                              content_type.c_str(),
                              expected_size));
  } else {
    socket->Send404();
  }

  int bytes_read = 0;
  // Some servers may treat HEAD requests as GET requests.  To free up the
  // network connection as soon as possible, signal that the request has
  // completed immediately, without trying to read any data back (all we care
  // about is the response code and headers, which we already have).
  net::IOBuffer* buffer = request_to_buffer_io_[request].get();
  if (request->status().is_success())
    request->Read(buffer, kBufferSize, &bytes_read);
  OnReadCompleted(request, bytes_read);
}

void DevToolsHttpProtocolHandler::OnReadCompleted(URLRequest* request,
                                                  int bytes_read) {
  RequestToSocketMap::iterator it = request_to_socket_io_.find(request);
  if (it == request_to_socket_io_.end())
    return;

  HttpListenSocket* socket = it->second;

  net::IOBuffer* buffer = request_to_buffer_io_[request].get();
  do {
    if (!request->status().is_success() || bytes_read <= 0)
      break;
    socket->Send(buffer->data(), bytes_read);
  } while (request->Read(buffer, kBufferSize, &bytes_read));

  // See comments re: HEAD requests in OnResponseStarted().
  if (!request->status().is_io_pending())
    RequestCompleted(request);
}

DevToolsHttpProtocolHandler::DevToolsHttpProtocolHandler(int port)
    : port_(port),
      server_(NULL) {
}

void DevToolsHttpProtocolHandler::Init() {
  server_ = HttpListenSocket::Listen("127.0.0.1", port_, this);
}

// Run on I/O thread
void DevToolsHttpProtocolHandler::Teardown() {
  server_ = NULL;
}

void DevToolsHttpProtocolHandler::Bind(URLRequest* request,
                                       HttpListenSocket* socket) {
  request_to_socket_io_[request] = socket;
  SocketToRequestsMap::iterator it = socket_to_requests_io_.find(socket);
  if (it == socket_to_requests_io_.end()) {
    std::pair<HttpListenSocket*, std::set<URLRequest*> > value(
        socket,
        std::set<URLRequest*>());
    it = socket_to_requests_io_.insert(value).first;
  }
  it->second.insert(request);
  request_to_buffer_io_[request] = new net::IOBuffer(kBufferSize);
}

void DevToolsHttpProtocolHandler::RequestCompleted(URLRequest* request) {
  RequestToSocketMap::iterator it = request_to_socket_io_.find(request);
  if (it == request_to_socket_io_.end())
    return;

  HttpListenSocket* socket = it->second;
  request_to_socket_io_.erase(request);
  SocketToRequestsMap::iterator it2 = socket_to_requests_io_.find(socket);
  it2->second.erase(request);
  request_to_buffer_io_.erase(request);
  delete request;
}

void DevToolsHttpProtocolHandler::Send200(HttpListenSocket* socket,
                                          const std::string& data,
                                          const std::string& mime_type) {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(socket,
                        &HttpListenSocket::Send200,
                        data,
                        mime_type));
}

void DevToolsHttpProtocolHandler::Send404(HttpListenSocket* socket) {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(socket,
                        &HttpListenSocket::Send404));
}

void DevToolsHttpProtocolHandler::Send500(HttpListenSocket* socket,
                                          const std::string& message) {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(socket,
                        &HttpListenSocket::Send500,
                        message));
}

void DevToolsHttpProtocolHandler::AcceptWebSocket(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& request) {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(socket,
                        &HttpListenSocket::AcceptWebSocket,
                        request));
}

TabContents* DevToolsHttpProtocolHandler::GetTabContents(int session_id) {
  for (BrowserList::const_iterator it = BrowserList::begin(),
       end = BrowserList::end(); it != end; ++it) {
    TabStripModel* model = (*it)->tabstrip_model();
    for (int i = 0, size = model->count(); i < size; ++i) {
      NavigationController& controller =
          model->GetTabContentsAt(i)->controller();
      if (controller.session_id().id() == session_id)
        return controller.tab_contents();
    }
  }
  return NULL;
}
<commit_msg>DevTools: fix remote debugging dispatch, provide encoding in the main connect page.<commit_after>// Copyright (c) 2010 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/debugger/devtools_http_protocol_handler.h"

#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/message_loop_proxy.h"
#include "base/string_number_conversions.h"
#include "base/thread.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_list.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/debugger/devtools_client_host.h"
#include "chrome/browser/debugger/devtools_manager.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/devtools_messages.h"
#include "chrome/common/net/url_request_context_getter.h"
#include "googleurl/src/gurl.h"
#include "net/base/io_buffer.h"
#include "net/base/listen_socket.h"
#include "net/server/http_server_request_info.h"
#include "net/url_request/url_request_context.h"

const int kBufferSize = 16 * 1024;

namespace {

// An internal implementation of DevToolsClientHost that delegates
// messages sent for DevToolsClient to a DebuggerShell instance.
class DevToolsClientHostImpl : public DevToolsClientHost {
 public:
  explicit DevToolsClientHostImpl(HttpListenSocket* socket)
      : socket_(socket) {}
  ~DevToolsClientHostImpl() {}

  // DevToolsClientHost interface
  virtual void InspectedTabClosing() {
    ChromeThread::PostTask(
        ChromeThread::IO,
        FROM_HERE,
        NewRunnableMethod(socket_,
                          &HttpListenSocket::Close));
  }

  virtual void SendMessageToClient(const IPC::Message& msg) {
    IPC_BEGIN_MESSAGE_MAP(DevToolsClientHostImpl, msg)
      IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend,
                          OnDispatchOnInspectorFrontend);
      IPC_MESSAGE_UNHANDLED_ERROR()
    IPC_END_MESSAGE_MAP()
  }

  void NotifyCloseListener() {
    DevToolsClientHost::NotifyCloseListener();
  }
 private:
  // Message handling routines
  void OnDispatchOnInspectorFrontend(const std::string& data) {
    socket_->SendOverWebSocket("devtools$$dispatch(" + data + ")");
  }
  HttpListenSocket* socket_;
};

}

DevToolsHttpProtocolHandler::~DevToolsHttpProtocolHandler() {
  // Stop() must be called prior to this being called
  DCHECK(server_.get() == NULL);
}

void DevToolsHttpProtocolHandler::Start() {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Init));
}

void DevToolsHttpProtocolHandler::Stop() {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Teardown));
}

void DevToolsHttpProtocolHandler::OnHttpRequest(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& info) {
  if (info.path == "" || info.path == "/") {
    // Pages discovery request.
    ChromeThread::PostTask(
        ChromeThread::UI,
        FROM_HERE,
        NewRunnableMethod(this,
                          &DevToolsHttpProtocolHandler::OnHttpRequestUI,
                          socket,
                          info));
    return;
  }

  size_t pos = info.path.find("/devtools/");
  if (pos != 0) {
    socket->Send404();
    return;
  }

  // Proxy static files from chrome://devtools/*.
  URLRequest* request = new URLRequest(GURL("chrome:/" + info.path), this);
  Bind(request, socket);
  request->set_context(
      Profile::GetDefaultRequestContext()->GetURLRequestContext());
  request->Start();
}

void DevToolsHttpProtocolHandler::OnWebSocketRequest(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& request) {
  ChromeThread::PostTask(
      ChromeThread::UI,
      FROM_HERE,
      NewRunnableMethod(
          this,
          &DevToolsHttpProtocolHandler::OnWebSocketRequestUI,
          socket,
          request));
}

void DevToolsHttpProtocolHandler::OnWebSocketMessage(HttpListenSocket* socket,
                                                     const std::string& data) {
  ChromeThread::PostTask(
      ChromeThread::UI,
      FROM_HERE,
      NewRunnableMethod(
          this,
          &DevToolsHttpProtocolHandler::OnWebSocketMessageUI,
          socket,
          data));
}

void DevToolsHttpProtocolHandler::OnClose(HttpListenSocket* socket) {
  SocketToRequestsMap::iterator it = socket_to_requests_io_.find(socket);
  if (it != socket_to_requests_io_.end()) {
    // Dispose delegating socket.
    for (std::set<URLRequest*>::iterator it2 = it->second.begin();
         it2 != it->second.end(); ++it2) {
      URLRequest* request = *it2;
      request->Cancel();
      request_to_socket_io_.erase(request);
      request_to_buffer_io_.erase(request);
      delete request;
    }
    socket_to_requests_io_.erase(socket);
  }

  ChromeThread::PostTask(
      ChromeThread::UI,
      FROM_HERE,
      NewRunnableMethod(
          this,
          &DevToolsHttpProtocolHandler::OnCloseUI,
          socket));
}

void DevToolsHttpProtocolHandler::OnHttpRequestUI(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& info) {
  std::string response = "";
  for (BrowserList::const_iterator it = BrowserList::begin(),
       end = BrowserList::end(); it != end; ++it) {
    TabStripModel* model = (*it)->tabstrip_model();
    for (int i = 0, size = model->count(); i < size; ++i) {
      TabContents* tab_contents = model->GetTabContentsAt(i);
      NavigationController& controller = tab_contents->controller();
      NavigationEntry* entry = controller.GetActiveEntry();
      if (entry == NULL)
        continue;

      if (!entry->url().is_valid())
        continue;

      DevToolsClientHost* client_host = DevToolsManager::GetInstance()->
          GetDevToolsClientHostFor(tab_contents->render_view_host());
      if (!client_host) {
        response += StringPrintf(
            "%s (%s)
",
            controller.session_id().id(),
            UTF16ToUTF8(entry->title()).c_str(),
            entry->url().spec().c_str());
      } else {
        response += StringPrintf(
            "%s (%s)
",
            UTF16ToUTF8(entry->title()).c_str(),
            entry->url().spec().c_str());
      }
    }
  }
  response += "";
  Send200(socket, response, "text/html; charset=UTF-8");
}

void DevToolsHttpProtocolHandler::OnWebSocketRequestUI(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& request) {
  std::string prefix = "/devtools/page/";
  size_t pos = request.path.find(prefix);
  if (pos != 0) {
    Send404(socket);
    return;
  }
  std::string page_id = request.path.substr(prefix.length());
  int id = 0;
  if (!base::StringToInt(page_id, &id)) {
    Send500(socket, "Invalid page id: " + page_id);
    return;
  }

  TabContents* tab_contents = GetTabContents(id);
  if (tab_contents == NULL) {
    Send500(socket, "No such page id: " + page_id);
    return;
  }

  DevToolsManager* manager = DevToolsManager::GetInstance();
  if (manager->GetDevToolsClientHostFor(tab_contents->render_view_host())) {
    Send500(socket, "Page with given id is being inspected: " + page_id);
    return;
  }

  DevToolsClientHostImpl* client_host = new DevToolsClientHostImpl(socket);
  socket_to_client_host_ui_[socket] = client_host;

  manager->RegisterDevToolsClientHostFor(
      tab_contents->render_view_host(),
      client_host);
  AcceptWebSocket(socket, request);
}

void DevToolsHttpProtocolHandler::OnWebSocketMessageUI(
    HttpListenSocket* socket,
    const std::string& data) {
  SocketToClientHostMap::iterator it = socket_to_client_host_ui_.find(socket);
  if (it == socket_to_client_host_ui_.end())
    return;

  DevToolsManager* manager = DevToolsManager::GetInstance();
  manager->ForwardToDevToolsAgent(it->second,
      DevToolsAgentMsg_DispatchOnInspectorBackend(data));
}

void DevToolsHttpProtocolHandler::OnCloseUI(HttpListenSocket* socket) {
  SocketToClientHostMap::iterator it = socket_to_client_host_ui_.find(socket);
  if (it == socket_to_client_host_ui_.end())
    return;
  DevToolsClientHostImpl* client_host =
      static_cast<DevToolsClientHostImpl*>(it->second);
  client_host->NotifyCloseListener();
  delete client_host;
  socket_to_client_host_ui_.erase(socket);
}

void DevToolsHttpProtocolHandler::OnResponseStarted(URLRequest* request) {
  RequestToSocketMap::iterator it = request_to_socket_io_.find(request);
  if (it == request_to_socket_io_.end())
    return;

  HttpListenSocket* socket = it->second;

  int expected_size = static_cast<int>(request->GetExpectedContentSize());

  std::string content_type;
  request->GetMimeType(&content_type);

  if (request->status().is_success()) {
    socket->Send(StringPrintf("HTTP/1.1 200 OK\r\n"
                              "Content-Type:%s\r\n"
                              "Content-Length:%d\r\n"
                              "\r\n",
                              content_type.c_str(),
                              expected_size));
  } else {
    socket->Send404();
  }

  int bytes_read = 0;
  // Some servers may treat HEAD requests as GET requests.  To free up the
  // network connection as soon as possible, signal that the request has
  // completed immediately, without trying to read any data back (all we care
  // about is the response code and headers, which we already have).
  net::IOBuffer* buffer = request_to_buffer_io_[request].get();
  if (request->status().is_success())
    request->Read(buffer, kBufferSize, &bytes_read);
  OnReadCompleted(request, bytes_read);
}

void DevToolsHttpProtocolHandler::OnReadCompleted(URLRequest* request,
                                                  int bytes_read) {
  RequestToSocketMap::iterator it = request_to_socket_io_.find(request);
  if (it == request_to_socket_io_.end())
    return;

  HttpListenSocket* socket = it->second;

  net::IOBuffer* buffer = request_to_buffer_io_[request].get();
  do {
    if (!request->status().is_success() || bytes_read <= 0)
      break;
    socket->Send(buffer->data(), bytes_read);
  } while (request->Read(buffer, kBufferSize, &bytes_read));

  // See comments re: HEAD requests in OnResponseStarted().
  if (!request->status().is_io_pending())
    RequestCompleted(request);
}

DevToolsHttpProtocolHandler::DevToolsHttpProtocolHandler(int port)
    : port_(port),
      server_(NULL) {
}

void DevToolsHttpProtocolHandler::Init() {
  server_ = HttpListenSocket::Listen("127.0.0.1", port_, this);
}

// Run on I/O thread
void DevToolsHttpProtocolHandler::Teardown() {
  server_ = NULL;
}

void DevToolsHttpProtocolHandler::Bind(URLRequest* request,
                                       HttpListenSocket* socket) {
  request_to_socket_io_[request] = socket;
  SocketToRequestsMap::iterator it = socket_to_requests_io_.find(socket);
  if (it == socket_to_requests_io_.end()) {
    std::pair<HttpListenSocket*, std::set<URLRequest*> > value(
        socket,
        std::set<URLRequest*>());
    it = socket_to_requests_io_.insert(value).first;
  }
  it->second.insert(request);
  request_to_buffer_io_[request] = new net::IOBuffer(kBufferSize);
}

void DevToolsHttpProtocolHandler::RequestCompleted(URLRequest* request) {
  RequestToSocketMap::iterator it = request_to_socket_io_.find(request);
  if (it == request_to_socket_io_.end())
    return;

  HttpListenSocket* socket = it->second;
  request_to_socket_io_.erase(request);
  SocketToRequestsMap::iterator it2 = socket_to_requests_io_.find(socket);
  it2->second.erase(request);
  request_to_buffer_io_.erase(request);
  delete request;
}

void DevToolsHttpProtocolHandler::Send200(HttpListenSocket* socket,
                                          const std::string& data,
                                          const std::string& mime_type) {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(socket,
                        &HttpListenSocket::Send200,
                        data,
                        mime_type));
}

void DevToolsHttpProtocolHandler::Send404(HttpListenSocket* socket) {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(socket,
                        &HttpListenSocket::Send404));
}

void DevToolsHttpProtocolHandler::Send500(HttpListenSocket* socket,
                                          const std::string& message) {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(socket,
                        &HttpListenSocket::Send500,
                        message));
}

void DevToolsHttpProtocolHandler::AcceptWebSocket(
    HttpListenSocket* socket,
    const HttpServerRequestInfo& request) {
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE,
      NewRunnableMethod(socket,
                        &HttpListenSocket::AcceptWebSocket,
                        request));
}

TabContents* DevToolsHttpProtocolHandler::GetTabContents(int session_id) {
  for (BrowserList::const_iterator it = BrowserList::begin(),
       end = BrowserList::end(); it != end; ++it) {
    TabStripModel* model = (*it)->tabstrip_model();
    for (int i = 0, size = model->count(); i < size; ++i) {
      NavigationController& controller =
          model->GetTabContentsAt(i)->controller();
      if (controller.session_id().id() == session_id)
        return controller.tab_contents();
    }
  }
  return NULL;
}
<|endoftext|>


<commit_before>// Copyright (c) 2010 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 "base/command_line.h"
#include "base/lock.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
#include "chrome/browser/safe_browsing/protocol_manager.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

// This starts the browser and keeps status of states related to SafeBrowsing.
class SafeBrowsingServiceTest : public InProcessBrowserTest {
 public:
  SafeBrowsingServiceTest()
    : safe_browsing_service_(NULL),
      is_update_in_progress_(false),
      is_initial_request_(false),
      is_update_scheduled_(false),
      is_url_match_in_db_(false) {
  }

  void UpdateSafeBrowsingStatus() {
    CHECK(safe_browsing_service_);
    AutoLock lock(update_status_mutex_);
    is_update_in_progress_ = safe_browsing_service_->IsUpdateInProgress();
    is_initial_request_ =
        safe_browsing_service_->protocol_manager_->is_initial_request();
    last_update_ = safe_browsing_service_->protocol_manager_->last_update();
    is_update_scheduled_ =
        safe_browsing_service_->protocol_manager_->update_timer_.IsRunning();
  }

  void CheckUrl(SafeBrowsingService::Client* helper, const GURL& url) {
    CHECK(safe_browsing_service_);
    AutoLock lock(update_status_mutex_);
    if (!safe_browsing_service_->CheckUrl(url, helper)) {
      safe_browsing_service_->CancelCheck(helper);
      is_url_match_in_db_ = false;
    }
    is_url_match_in_db_ = true;
  }

  bool is_url_match_in_db() {
    AutoLock l(update_status_mutex_);
    return is_url_match_in_db_;
  }

  bool is_update_in_progress() {
    AutoLock l(update_status_mutex_);
    return is_update_in_progress_;
  }

  bool is_initial_request() {
    AutoLock l(update_status_mutex_);
    return is_initial_request_;
  }

  base::Time last_update() {
    AutoLock l(update_status_mutex_);
    return last_update_;
  }

  bool is_update_scheduled() {
    AutoLock l(update_status_mutex_);
    return is_update_scheduled_;
  }

 protected:
  void InitSafeBrowsingService() {
    safe_browsing_service_ =
        g_browser_process->resource_dispatcher_host()->safe_browsing_service();
  }

  virtual void SetUpCommandLine(CommandLine* command_line) {
    // Makes sure the auto update is not triggered. This test will force the
    // update when needed.
    command_line->AppendSwitch(switches::kSbDisableAutoUpdate);
  }

 private:
  SafeBrowsingService* safe_browsing_service_;

  // Protects all variables below since they are updated on IO thread but
  // read on UI thread.
  Lock update_status_mutex_;
  // States associated with safebrowsing service updates.
  bool is_update_in_progress_;
  bool is_initial_request_;
  base::Time last_update_;
  bool is_update_scheduled_;
  // Indicates if there is a match between a URL's prefix and safebrowsing
  // database (thus potentially it is a phishing url).
  bool is_url_match_in_db_;
  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingServiceTest);
};

// A ref counted helper class that handles callbacks between IO thread and UI
// thread.
class SafeBrowsingServiceTestHelper
    : public base::RefCountedThreadSafe<SafeBrowsingServiceTestHelper>,
      public SafeBrowsingService::Client {
 public:
  explicit SafeBrowsingServiceTestHelper(
      SafeBrowsingServiceTest* safe_browsing_test)
      : safe_browsing_test_(safe_browsing_test) {
  }

  // Callbacks for SafeBrowsingService::Client. Not implemented yet.
  virtual void OnUrlCheckResult(const GURL& url,
                                SafeBrowsingService::UrlCheckResult result) {
    NOTREACHED() << "Not implemented.";
  }
  virtual void OnBlockingPageComplete(bool proceed) {
    NOTREACHED() << "Not implemented.";
  }

  // Functions and callbacks related to CheckUrl. These are used to verify if
  // a URL is a phishing URL.
  void CheckUrl(const GURL& url) {
    ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, NewRunnableMethod(this,
        &SafeBrowsingServiceTestHelper::CheckUrlOnIOThread, url));
  }
  void CheckUrlOnIOThread(const GURL& url) {
    CHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
    safe_browsing_test_->CheckUrl(this, url);
    ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod(this,
        &SafeBrowsingServiceTestHelper::OnCheckUrlOnIOThreadDone));
  }
  void OnCheckUrlOnIOThreadDone() {
    StopUILoop();
  }

  // Functions and callbacks related to safebrowsing server status.
  void CheckStatusOnIOThread() {
    DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
    safe_browsing_test_->UpdateSafeBrowsingStatus();
    ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod(this,
        &SafeBrowsingServiceTestHelper::OnCheckStatusOnIOThreadDone));
  }
  void OnCheckStatusOnIOThreadDone() {
    StopUILoop();
  }
  void CheckStatusAfterDelay(int64 wait_time_sec) {
    ChromeThread::PostDelayedTask(
        ChromeThread::IO,
        FROM_HERE,
        NewRunnableMethod(this,
            &SafeBrowsingServiceTestHelper::CheckStatusOnIOThread),
        wait_time_sec * 1000);
  }

 private:
  // Stops UI loop after desired status is updated.
  void StopUILoop() {
    CHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
    MessageLoopForUI::current()->Quit();
  }

  base::OneShotTimer<SafeBrowsingServiceTestHelper> check_update_timer_;
  SafeBrowsingServiceTest* safe_browsing_test_;
  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingServiceTestHelper);
};

IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, SafeBrowsingSystemTest) {
  InitSafeBrowsingService();
  scoped_refptr<SafeBrowsingServiceTestHelper> safe_browsing_helper =
      new SafeBrowsingServiceTestHelper(this);

  // Waits for 1 sec and makes sure safebrowsing update is not happening.
  safe_browsing_helper->CheckStatusAfterDelay(1);
  // Loop will stop once OnCheckStatusOnIOThreadDone in safe_browsing_helper
  // is called and status from safe_browsing_service_ is checked.
  ui_test_utils::RunMessageLoop();
  EXPECT_FALSE(is_update_in_progress());
  EXPECT_TRUE(is_initial_request());
  EXPECT_FALSE(is_update_scheduled());
  EXPECT_TRUE(last_update().is_null());

  // Verify URL.
  const char test_url[] = "http://ianfette.org";
  safe_browsing_helper->CheckUrl(GURL(test_url));
  // Loop will stop once OnCheckUrlOnIOThreadDone in safe_browsing_helper
  // is called and url check is done.
  ui_test_utils::RunMessageLoop();
  EXPECT_TRUE(is_url_match_in_db());

  // TODO(lzheng): Add tests to launch a testing safebrowsing server
  // and issue requests repeatedly:
  // http://code.google.com/p/google-safe-browsing/wiki/ProtocolTesting
}
<commit_msg>Full end to end test using safebrowsing test server suite.<commit_after>// Copyright (c) 2010 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 <vector>

#include "base/command_line.h"
#include "base/lock.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/renderer_host/resource_dispatcher_host.h"
#include "chrome/browser/safe_browsing/protocol_manager.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"
#include "net/base/host_resolver.h"
#include "net/base/load_flags.h"
#include "net/base/net_log.h"
#include "net/socket/tcp_pinger.h"
#include "testing/gtest/include/gtest/gtest.h"

const char kHost[] = "127.0.0.1";
const char kPort[] = "40101";
const char kDataFile[] = "datafile.dat";
const char kUrlVerifyPath[] = "/safebrowsing/verify_urls";
const char kTestCompletePath[] = "/test_complete";

class SafeBrowsingTestServer {
 public:
  SafeBrowsingTestServer(const std::string& hostname,
                         const std::string& port,
                         const std::string& datafile)
      : hostname_(hostname),
        port_(port),
        datafile_(datafile),
        server_handle_(base::kNullProcessHandle) {
  }
  ~SafeBrowsingTestServer() {
    EXPECT_EQ(server_handle_, base::kNullProcessHandle);
  }

  // Start the python server test suite.
  bool Start() {
    // Get path to python server script
    FilePath testserver_path;
    if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path))
      return false;
    testserver_path = testserver_path
        .Append(FILE_PATH_LITERAL("chrome"))
        .Append(FILE_PATH_LITERAL("browser"))
        .Append(FILE_PATH_LITERAL("safe_browsing"))
        .Append(FILE_PATH_LITERAL("testserver"));
    AppendToPythonPath(testserver_path);

    FilePath testserver = testserver_path.Append(
        FILE_PATH_LITERAL("safebrowsing_test_server.py"));

#if defined(OS_WIN)
    FilePath datafile = testserver_path.Append(ASCIIToWide(datafile_));
    FilePath python_runtime;
    // Get path to python interpreter
    if (!PathService::Get(base::DIR_SOURCE_ROOT, &python_runtime))
      return false;
    python_runtime = python_runtime
        .Append(FILE_PATH_LITERAL("third_party"))
        .Append(FILE_PATH_LITERAL("python_24"))
        .Append(FILE_PATH_LITERAL("python.exe"));

    std::wstring command_line =
        L"\"" + python_runtime.ToWStringHack() + L"\" " +
        L"\"" + testserver.ToWStringHack() +
        L"\" --port=" + UTF8ToWide(port_) +
        L"\" --datafile=" + datafile.ToWStringHack() + L"\"";

    if (!base::LaunchApp(command_line, false, true, &server_handle_)) {
      LOG(ERROR) << "Failed to launch " << command_line;
      return false;
    }
#elif defined(OS_POSIX)
    FilePath datafile = testserver_path.Append(datafile_);
    std::vector<std::string> command_line;
    command_line.push_back("python");
    command_line.push_back(testserver.value());
    command_line.push_back("--port=" + port_);
    command_line.push_back("--datafile=" + datafile.value());

    base::file_handle_mapping_vector no_mappings;
    LOG(INFO) << "Trying to launch " << command_line[0] << " ...";
    if (!base::LaunchApp(command_line, no_mappings, false, &server_handle_)) {
      LOG(ERROR) << "Failed to launch " << command_line[0] << " ...";
      return false;
    }
#endif

    // Let the server start, then verify that it's up.
    // Our server is Python, and takes about 500ms to start
    // up the first time, and about 200ms after that.
    if (!WaitServerToStart(hostname_, StringToInt(port_))) {
      LOG(ERROR) << "Failed to connect to server";
      Stop();
      return false;
    }

    LOG(INFO) << "Started on port " << port_;
    return true;
  }

  // Stop the python server test suite.
  bool Stop() {
    if (!server_handle_) {
      return true;
    }

    // First check if the process has already terminated.
    bool ret = base::WaitForSingleProcess(server_handle_, 0);
    if (!ret) {
      ret = base::KillProcess(server_handle_, 1, true);
    }

    if (ret) {
      base::CloseProcessHandle(server_handle_);
      server_handle_ = base::kNullProcessHandle;
      LOG(INFO) << "Stopped.";
    } else {
      LOG(INFO) << "Kill failed?";
      return false;
    }
    return true;
  }

 private:
  // Make sure the test server is actually started and ready to serve.
  bool WaitServerToStart(const std::string& host_name, int port) {
    net::AddressList addr;
    scoped_refptr<net::HostResolver> resolver(net::CreateSystemHostResolver(
        net::HostResolver::kDefaultParallelism));
    net::HostResolver::RequestInfo info(host_name, port);
    int rv = resolver->Resolve(info, &addr, NULL, NULL, net::BoundNetLog());
    if (rv != net::OK)
      return false;

    net::TCPPinger pinger(addr);
    rv = pinger.Ping(base::TimeDelta::FromSeconds(kConnectionTimeoutSec),
                     kRetryAttempts);
    return rv == net::OK;
  }

  void AppendToPythonPath(const FilePath& dir) {
#if defined(OS_WIN)
    const wchar_t kPythonPath[] = L"PYTHONPATH";
    wchar_t oldpath[8192];
    if (GetEnvironmentVariable(kPythonPath, oldpath, arraysize(oldpath)) == 0) {
      SetEnvironmentVariableW(kPythonPath, dir.value().c_str());
    } else if (!wcsstr(oldpath, dir.value().c_str())) {
      std::wstring newpath(oldpath);
      newpath.append(L";");
      newpath.append(dir.value());
      SetEnvironmentVariableW(kPythonPath, newpath.c_str());
    }
#elif defined(OS_POSIX)
    const char kPythonPath[] = "PYTHONPATH";
    const char* oldpath = getenv(kPythonPath);
    // setenv() leaks memory intentionally on Mac
    if (!oldpath) {
      setenv(kPythonPath, dir.value().c_str(), 1);
    } else if (!strstr(oldpath, dir.value().c_str())) {
      std::string newpath(oldpath);
      newpath.append(":");
      newpath.append(dir.value());
      setenv(kPythonPath, newpath.c_str(), 1);
    }
#endif
  }

  std::string hostname_;
  std::string port_;
  std::string datafile_;
  static const int kConnectionTimeoutSec = 5;
  static const int kRetryAttempts = 3;
  base::ProcessHandle server_handle_;
};

typedef struct {
  std::string url;
  std::string list_name;
  bool is_phishing;
} PhishingUrl;

// Parses server response for verify_urls. The expected format is:
//
// first.random.url.com/   internal-test-shavar   yes
// second.random.url.com/  internal-test-shavar   yes
// ...
static bool ParsePhishingUrls(const std::string& data,
                              std::vector<PhishingUrl>* phishing_urls) {
  std::vector<std::string> urls;
  SplitString(data, '\n', &urls);
  for (size_t i = 0; i < urls.size(); ++i) {
    PhishingUrl phishing_url;
    std::vector<std::string> url_parts;
    SplitStringAlongWhitespace(urls[i], &url_parts);
    if (url_parts.size() < 3) {
      CHECK(false) << "Unexpected URL format in phishing URL list: "
                   << urls[i];
      return false;
    }
    phishing_url.url = url_parts[0];
    phishing_url.list_name = url_parts[1];
    if (url_parts[2] == "yes") {
      phishing_url.is_phishing = true;
    } else {
      CHECK_EQ("no", url_parts[2]);
      phishing_url.is_phishing = false;
    }
    phishing_urls->push_back(phishing_url);
  }
  return true;
}

// This starts the browser and keeps status of states related to SafeBrowsing.
class SafeBrowsingServiceTest : public InProcessBrowserTest {
 public:
  SafeBrowsingServiceTest()
    : safe_browsing_service_(NULL),
      is_update_in_progress_(false),
      is_initial_request_(false),
      is_update_scheduled_(false),
      is_checked_url_in_db_(false),
      is_checked_url_safe_(false) {
  }

  void UpdateSafeBrowsingStatus() {
    CHECK(safe_browsing_service_);
    AutoLock lock(update_status_mutex_);
    is_update_in_progress_ = safe_browsing_service_->IsUpdateInProgress();
    is_initial_request_ =
        safe_browsing_service_->protocol_manager_->is_initial_request();
    last_update_ = safe_browsing_service_->protocol_manager_->last_update();
    is_update_scheduled_ =
        safe_browsing_service_->protocol_manager_->update_timer_.IsRunning();
  }

  void ForceUpdate() {
    CHECK(safe_browsing_service_);
    safe_browsing_service_->protocol_manager_->ForceScheduleNextUpdate(0);
  }

  void CheckUrl(SafeBrowsingService::Client* helper, const GURL& url) {
    CHECK(safe_browsing_service_);
    AutoLock lock(update_status_mutex_);
    if (safe_browsing_service_->CheckUrl(url, helper)) {
      is_checked_url_in_db_ = false;
      return;
    }
    is_checked_url_in_db_ = true;
  }

  bool is_checked_url_in_db() {
    AutoLock l(update_status_mutex_);
    return is_checked_url_in_db_;
  }

  void set_is_checked_url_safe(bool safe) {
    AutoLock l(update_status_mutex_);
    is_checked_url_safe_ = safe;
  }

  bool is_checked_url_safe() {
    AutoLock l(update_status_mutex_);
    return is_checked_url_safe_;
  }

  bool is_update_in_progress() {
    AutoLock l(update_status_mutex_);
    return is_update_in_progress_;
  }

  bool is_initial_request() {
    AutoLock l(update_status_mutex_);
    return is_initial_request_;
  }

  base::Time last_update() {
    AutoLock l(update_status_mutex_);
    return last_update_;
  }

  bool is_update_scheduled() {
    AutoLock l(update_status_mutex_);
    return is_update_scheduled_;
  }

 protected:
  void InitSafeBrowsingService() {
    safe_browsing_service_ =
        g_browser_process->resource_dispatcher_host()->safe_browsing_service();
  }

  virtual void SetUpCommandLine(CommandLine* command_line) {
    // Makes sure the auto update is not triggered. This test will force the
    // update when needed.
    command_line->AppendSwitch(switches::kSbDisableAutoUpdate);
    command_line->AppendSwitchWithValue(
        switches::kSbInfoURLPrefix, "http://localhost:40101/safebrowsing");
    command_line->AppendSwitchWithValue(
        switches::kSbMacKeyURLPrefix, "http://localhost:40101/safebrowsing");
  }

  void SetTestStep(int step) {
    std::string test_step = StringPrintf("&test_step=%d", step);
    safe_browsing_service_->protocol_manager_->set_additional_query(test_step);
  }

 private:
  SafeBrowsingService* safe_browsing_service_;

  // Protects all variables below since they are updated on IO thread but
  // read on UI thread.
  Lock update_status_mutex_;
  // States associated with safebrowsing service updates.
  bool is_update_in_progress_;
  bool is_initial_request_;
  base::Time last_update_;
  bool is_update_scheduled_;
  // Indicates if there is a match between a URL's prefix and safebrowsing
  // database (thus potentially it is a phishing url).
  bool is_checked_url_in_db_;
  // True if last verified URL is not a phishing URL and thus it is safe.
  bool is_checked_url_safe_;
  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingServiceTest);
};

// A ref counted helper class that handles callbacks between IO thread and UI
// thread.
class SafeBrowsingServiceTestHelper
    : public base::RefCountedThreadSafe<SafeBrowsingServiceTestHelper>,
      public SafeBrowsingService::Client,
      public URLFetcher::Delegate {
 public:
  explicit SafeBrowsingServiceTestHelper(
      SafeBrowsingServiceTest* safe_browsing_test)
      : safe_browsing_test_(safe_browsing_test) {
  }

  // Callbacks for SafeBrowsingService::Client.
  virtual void OnUrlCheckResult(const GURL& url,
                                SafeBrowsingService::UrlCheckResult result) {
    CHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
    CHECK(safe_browsing_test_->is_checked_url_in_db());
    safe_browsing_test_->set_is_checked_url_safe(
        result == SafeBrowsingService::URL_SAFE);
    ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod(this,
        &SafeBrowsingServiceTestHelper::OnCheckUrlOnIOThreadDone));
  }
  virtual void OnBlockingPageComplete(bool proceed) {
    NOTREACHED() << "Not implemented.";
  }

  // Functions and callbacks to start the safebrowsing database update.
  void ForceUpdate() {
    ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, NewRunnableMethod(this,
        &SafeBrowsingServiceTestHelper::ForceUpdateInIOThread));
  }
  void ForceUpdateInIOThread() {
    CHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
    safe_browsing_test_->ForceUpdate();
    ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod(this,
        &SafeBrowsingServiceTestHelper::OnForceUpdateDone));
  }
  void OnForceUpdateDone() {
    StopUILoop();
  }

  // Functions and callbacks related to CheckUrl. These are used to verify
  // phishing URLs.
  void CheckUrl(const GURL& url) {
    ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, NewRunnableMethod(this,
        &SafeBrowsingServiceTestHelper::CheckUrlOnIOThread, url));
  }
  void CheckUrlOnIOThread(const GURL& url) {
    CHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
    safe_browsing_test_->CheckUrl(this, url);
    if (!safe_browsing_test_->is_checked_url_in_db()) {
      // Ends the checking since this url's prefix is not in database.
      ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod(
          this, &SafeBrowsingServiceTestHelper::OnCheckUrlOnIOThreadDone));
    }
    // Otherwise, OnCheckUrlOnIOThreadDone is called in OnUrlCheckResult since
    // safebrowsing service further fetches hashes from safebrowsing server.
  }
  void OnCheckUrlOnIOThreadDone() {
    StopUILoop();
  }

  // Functions and callbacks related to safebrowsing server status.
  void CheckStatusAfterDelay(int64 wait_time_sec) {
    ChromeThread::PostDelayedTask(
        ChromeThread::IO,
        FROM_HERE,
        NewRunnableMethod(this,
            &SafeBrowsingServiceTestHelper::CheckStatusOnIOThread),
        wait_time_sec * 1000);
  }
  void CheckStatusOnIOThread() {
    DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
    safe_browsing_test_->UpdateSafeBrowsingStatus();
    ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod(this,
        &SafeBrowsingServiceTestHelper::OnCheckStatusOnIOThreadDone));
  }
  void OnCheckStatusOnIOThreadDone() {
    StopUILoop();
  }

  // Calls test server to fetch urls for verification.
  void FetchUrlsToVerify(const std::string& host,
                         const std::string& port,
                         int test_step) {
    GURL url(StringPrintf("http://%s:%s/%s?"
                          "client=chromium&appver=1.0&pver=2.2&test_step=%d",
                          host.c_str(), port.c_str(), kUrlVerifyPath,
                          test_step));
    url_fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
    url_fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE);
    url_fetcher_->set_request_context(Profile::GetDefaultRequestContext());
    url_fetcher_->Start();
  }

  // Calls test server to check if test data is done.
  void VerifyTestComplete(const std::string& host,
                          const std::string& port,
                          int test_step) {
    GURL url(StringPrintf("http://%s:%s/%s?test_step=%d",
                          host.c_str(), port.c_str(), kTestCompletePath,
                          test_step));
    url_fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
    url_fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE);
    url_fetcher_->set_request_context(Profile::GetDefaultRequestContext());
    url_fetcher_->Start();
  }

  // Callback for URLFetcher.
  virtual void OnURLFetchComplete(const URLFetcher* source,
                                  const GURL& url,
                                  const URLRequestStatus& status,
                                  int response_code,
                                  const ResponseCookies& cookies,
                                  const std::string& data) {
    response_data_ = data;
    StopUILoop();
  }

  const std::string& response_data() {
    return response_data_;
  }

 private:
  // Stops UI loop after desired status is updated.
  void StopUILoop() {
    CHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
    MessageLoopForUI::current()->Quit();
  }

  base::OneShotTimer<SafeBrowsingServiceTestHelper> check_update_timer_;
  SafeBrowsingServiceTest* safe_browsing_test_;
  scoped_ptr<URLFetcher> url_fetcher_;
  std::string response_data_;
  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingServiceTestHelper);
};

IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, SafeBrowsingSystemTest) {
  InitSafeBrowsingService();
  scoped_refptr<SafeBrowsingServiceTestHelper> safe_browsing_helper =
      new SafeBrowsingServiceTestHelper(this);

  // Waits for 1 sec and makes sure safebrowsing update is not happening.
  // MessageLoop will stop once OnCheckStatusOnIOThreadDone in
  // safe_browsing_helper is called and status from safe_browsing_service_
  // is checked.
  safe_browsing_helper->CheckStatusAfterDelay(1);
  ui_test_utils::RunMessageLoop();
  EXPECT_FALSE(is_update_in_progress());
  EXPECT_TRUE(is_initial_request());
  EXPECT_FALSE(is_update_scheduled());
  EXPECT_TRUE(last_update().is_null());

  // Verify with a test phishing URL. Loop will stop once
  // OnCheckUrlOnIOThreadDone in safe_browsing_helper is called and URL check
  // is done.
  const char test_url[] = "http://ianfette.org";
  safe_browsing_helper->CheckUrl(GURL(test_url));
  ui_test_utils::RunMessageLoop();
  EXPECT_TRUE(is_checked_url_in_db());

  SafeBrowsingTestServer test_server(kHost, kPort, kDataFile);
  test_server.Start();
  // Limits max test steps so the test won't run wild.
  const int kMaxSteps = 10;
  int last_step = 0;
  // Starts from test step 1.
  for (int step = 1; step < kMaxSteps; step++) {
    // Every step should be a fresh start.
    EXPECT_FALSE(is_update_in_progress());
    EXPECT_FALSE(is_update_scheduled());

    // Starts safebrowsing update on IO thread. Waits till scheduled
    // update finishes. Stops waiting after kMaxWaitSec if the update
    // could not finish.
    base::Time now = base::Time::Now();
    SetTestStep(step);
    safe_browsing_helper->ForceUpdate();
    ui_test_utils::RunMessageLoop();
    int wait_sec = 0;
    const int kMaxWaitSec = 10;
    while ((is_update_scheduled() || is_update_in_progress()) &&
           wait_sec++ < kMaxWaitSec) {
      safe_browsing_helper->CheckStatusAfterDelay(1);
      ui_test_utils::RunMessageLoop();
    }
    EXPECT_LT(wait_sec, kMaxWaitSec)
        << "Can't finish update in required time limit!";
    if (last_update() < now) {
      // This means no data available anymore.
      break;
    }

    // Fetches urls to verify and waits till server responses with data.
    safe_browsing_helper->FetchUrlsToVerify(kHost, kPort, step);
    ui_test_utils::RunMessageLoop();
    LOG(INFO) << safe_browsing_helper->response_data();

    std::vector<PhishingUrl> phishing_urls;
    EXPECT_TRUE(ParsePhishingUrls(safe_browsing_helper->response_data(),
                                  &phishing_urls));
    for (size_t j = 0; j < phishing_urls.size(); ++j) {
      // Verifes with server if a URL is a phishing URL and waits till server
      // responses.
      safe_browsing_helper->CheckUrl(GURL(phishing_urls[j].url));
      ui_test_utils::RunMessageLoop();
      if (phishing_urls[j].is_phishing) {
        EXPECT_TRUE(is_checked_url_in_db());
        EXPECT_FALSE(is_checked_url_safe());
      } else {
        EXPECT_TRUE(is_checked_url_in_db());
      }
    }
    last_step = step;
  }

  // Verifies with server if test is done and waits till server responses.
  safe_browsing_helper->VerifyTestComplete(kHost, kPort, last_step);
  ui_test_utils::RunMessageLoop();
  EXPECT_EQ("yes", safe_browsing_helper->response_data());
  test_server.Stop();
}
<|endoftext|>


<commit_before>// 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/ui/views/bookmarks/bookmark_bubble_view.h"

#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/bookmarks/bookmark_editor.h"
#include "chrome/browser/ui/sync/sync_promo_ui.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view_observer.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_sync_promo_view.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "content/public/browser/user_metrics.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"

using base::UserMetricsAction;
using views::ColumnSet;
using views::GridLayout;

namespace {

// Width of the border of a button.
const int kControlBorderWidth = 2;

// This combobox prevents any lengthy content from stretching the bubble view.
class UnsizedCombobox : public views::Combobox {
 public:
  explicit UnsizedCombobox(ui::ComboboxModel* model) : views::Combobox(model) {}
  virtual ~UnsizedCombobox() {}

  virtual gfx::Size GetPreferredSize() const OVERRIDE {
    return gfx::Size(0, views::Combobox::GetPreferredSize().height());
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(UnsizedCombobox);
};

}  // namespace

BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL;

// static
void BookmarkBubbleView::ShowBubble(views::View* anchor_view,
                                    BookmarkBubbleViewObserver* observer,
                                    scoped_ptr<BookmarkBubbleDelegate> delegate,
                                    Profile* profile,
                                    const GURL& url,
                                    bool newly_bookmarked) {
  if (IsShowing())
    return;

  bookmark_bubble_ = new BookmarkBubbleView(anchor_view,
                                            observer,
                                            delegate.Pass(),
                                            profile,
                                            url,
                                            newly_bookmarked);
  views::BubbleDelegateView::CreateBubble(bookmark_bubble_)->Show();
  // Select the entire title textfield contents when the bubble is first shown.
  bookmark_bubble_->title_tf_->SelectAll(true);
  bookmark_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);

  if (bookmark_bubble_->observer_)
    bookmark_bubble_->observer_->OnBookmarkBubbleShown(url);
}

// static
bool BookmarkBubbleView::IsShowing() {
  return bookmark_bubble_ != NULL;
}

void BookmarkBubbleView::Hide() {
  if (IsShowing())
    bookmark_bubble_->GetWidget()->Close();
}

BookmarkBubbleView::~BookmarkBubbleView() {
  if (apply_edits_) {
    ApplyEdits();
  } else if (remove_bookmark_) {
    BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
    const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
    if (node)
      model->Remove(node->parent(), node->parent()->GetIndexOf(node));
  }
  // |parent_combobox_| needs to be destroyed before |parent_model_| as it
  // uses |parent_model_| in its destructor.
  delete parent_combobox_;
}

views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
  return title_tf_;
}

void BookmarkBubbleView::WindowClosing() {
  // We have to reset |bubble_| here, not in our destructor, because we'll be
  // destroyed asynchronously and the shown state will be checked before then.
  DCHECK_EQ(bookmark_bubble_, this);
  bookmark_bubble_ = NULL;

  if (observer_)
    observer_->OnBookmarkBubbleHidden();
}

bool BookmarkBubbleView::AcceleratorPressed(
    const ui::Accelerator& accelerator) {
  if (accelerator.key_code() == ui::VKEY_RETURN) {
     if (edit_button_->HasFocus())
       HandleButtonPressed(edit_button_);
     else
       HandleButtonPressed(close_button_);
     return true;
  } else if (accelerator.key_code() == ui::VKEY_ESCAPE) {
    remove_bookmark_ = newly_bookmarked_;
    apply_edits_ = false;
  }

  return BubbleDelegateView::AcceleratorPressed(accelerator);
}

void BookmarkBubbleView::Init() {
  views::Label* title_label = new views::Label(
      l10n_util::GetStringUTF16(
          newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
                              IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK));
  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
  title_label->SetFontList(rb->GetFontList(ui::ResourceBundle::MediumFont));

  remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(
      IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
  remove_button_->SetStyle(views::Button::STYLE_BUTTON);

  edit_button_ = new views::LabelButton(
      this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
  edit_button_->SetStyle(views::Button::STYLE_BUTTON);

  close_button_ = new views::LabelButton(
      this, l10n_util::GetStringUTF16(IDS_DONE));
  close_button_->SetStyle(views::Button::STYLE_BUTTON);
  close_button_->SetIsDefault(true);

  views::Label* combobox_label = new views::Label(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT));

  parent_combobox_ = new UnsizedCombobox(&parent_model_);
  parent_combobox_->set_listener(this);
  parent_combobox_->SetAccessibleName(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_FOLDER_TEXT));

  GridLayout* layout = new GridLayout(this);
  SetLayoutManager(layout);

  // Column sets used in the layout of the bubble.
  enum ColumnSetID {
    TITLE_COLUMN_SET_ID,
    CONTENT_COLUMN_SET_ID,
    SYNC_PROMO_COLUMN_SET_ID
  };

  ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID);
  cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
  cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
                0, 0);
  cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);

  // The column layout used for middle and bottom rows.
  cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID);
  cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
  cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
                GridLayout::USE_PREF, 0, 0);
  cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);

  cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
                GridLayout::USE_PREF, 0, 0);
  cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing);

  cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
                GridLayout::USE_PREF, 0, 0);
  cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
  cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
                GridLayout::USE_PREF, 0, 0);
  cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);

  layout->StartRow(0, TITLE_COLUMN_SET_ID);
  layout->AddView(title_label);
  layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);

  layout->StartRow(0, CONTENT_COLUMN_SET_ID);
  views::Label* label = new views::Label(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT));
  layout->AddView(label);
  title_tf_ = new views::Textfield();
  title_tf_->SetText(GetTitle());
  title_tf_->SetAccessibleName(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_TITLE_TEXT));

  layout->AddView(title_tf_, 5, 1);

  layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);

  layout->StartRow(0, CONTENT_COLUMN_SET_ID);
  layout->AddView(combobox_label);
  layout->AddView(parent_combobox_, 5, 1);

  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  layout->StartRow(0, CONTENT_COLUMN_SET_ID);
  layout->SkipColumns(2);
  layout->AddView(remove_button_);
  layout->AddView(edit_button_);
  layout->AddView(close_button_);

  layout->AddPaddingRow(
      0,
      views::kUnrelatedControlVerticalSpacing - kControlBorderWidth);

  if (SyncPromoUI::ShouldShowSyncPromo(profile_)) {
    // The column layout used for the sync promo.
    cs = layout->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID);
    cs->AddColumn(GridLayout::FILL,
                  GridLayout::FILL,
                  1,
                  GridLayout::USE_PREF,
                  0,
                  0);
    layout->StartRow(0, SYNC_PROMO_COLUMN_SET_ID);

    sync_promo_view_ = new BookmarkSyncPromoView(delegate_.get());
    layout->AddView(sync_promo_view_);
  }

  AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
}

BookmarkBubbleView::BookmarkBubbleView(
    views::View* anchor_view,
    BookmarkBubbleViewObserver* observer,
    scoped_ptr<BookmarkBubbleDelegate> delegate,
    Profile* profile,
    const GURL& url,
    bool newly_bookmarked)
    : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
      observer_(observer),
      delegate_(delegate.Pass()),
      profile_(profile),
      url_(url),
      newly_bookmarked_(newly_bookmarked),
      parent_model_(
          BookmarkModelFactory::GetForProfile(profile_),
          BookmarkModelFactory::GetForProfile(profile_)->
              GetMostRecentlyAddedUserNodeForURL(url)),
      remove_button_(NULL),
      edit_button_(NULL),
      close_button_(NULL),
      title_tf_(NULL),
      parent_combobox_(NULL),
      sync_promo_view_(NULL),
      remove_bookmark_(false),
      apply_edits_(true) {
  set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0));
  // Compensate for built-in vertical padding in the anchor view's image.
  set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
}

base::string16 BookmarkBubbleView::GetTitle() {
  BookmarkModel* bookmark_model =
      BookmarkModelFactory::GetForProfile(profile_);
  const BookmarkNode* node =
      bookmark_model->GetMostRecentlyAddedUserNodeForURL(url_);
  if (node)
    return node->GetTitle();
  else
    NOTREACHED();
  return base::string16();
}

void BookmarkBubbleView::GetAccessibleState(ui::AXViewState* state) {
  BubbleDelegateView::GetAccessibleState(state);
  state->name =
      l10n_util::GetStringUTF16(
          newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
                              IDS_BOOKMARK_AX_BUBBLE_PAGE_BOOKMARK);
}

void BookmarkBubbleView::ButtonPressed(views::Button* sender,
                                       const ui::Event& event) {
  HandleButtonPressed(sender);
}

void BookmarkBubbleView::OnPerformAction(views::Combobox* combobox) {
  if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) {
    content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
    ShowEditor();
  }
}

void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) {
  if (sender == remove_button_) {
    content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
    // Set this so we remove the bookmark after the window closes.
    remove_bookmark_ = true;
    apply_edits_ = false;
    GetWidget()->Close();
  } else if (sender == edit_button_) {
    content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
    ShowEditor();
  } else {
    DCHECK_EQ(close_button_, sender);
    GetWidget()->Close();
  }
}

void BookmarkBubbleView::ShowEditor() {
  const BookmarkNode* node = BookmarkModelFactory::GetForProfile(
      profile_)->GetMostRecentlyAddedUserNodeForURL(url_);
  views::Widget* parent = anchor_widget();
  DCHECK(parent);

  Profile* profile = profile_;
  ApplyEdits();
  GetWidget()->Close();

  if (node && parent)
    BookmarkEditor::Show(parent->GetNativeWindow(), profile,
                         BookmarkEditor::EditDetails::EditNode(node),
                         BookmarkEditor::SHOW_TREE);
}

void BookmarkBubbleView::ApplyEdits() {
  // Set this to make sure we don't attempt to apply edits again.
  apply_edits_ = false;

  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
  const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
  if (node) {
    const base::string16 new_title = title_tf_->text();
    if (new_title != node->GetTitle()) {
      model->SetTitle(node, new_title);
      content::RecordAction(
          UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
    }
    parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index());
  }
}
<commit_msg>Views: Implement keyboard shortcuts for the remove and edit button in the bookmark bubble.<commit_after>// 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/ui/views/bookmarks/bookmark_bubble_view.h"

#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/bookmarks/bookmark_editor.h"
#include "chrome/browser/ui/sync/sync_promo_ui.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view_observer.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_sync_promo_view.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "content/public/browser/user_metrics.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"

using base::UserMetricsAction;
using views::ColumnSet;
using views::GridLayout;

namespace {

// Width of the border of a button.
const int kControlBorderWidth = 2;

// This combobox prevents any lengthy content from stretching the bubble view.
class UnsizedCombobox : public views::Combobox {
 public:
  explicit UnsizedCombobox(ui::ComboboxModel* model) : views::Combobox(model) {}
  virtual ~UnsizedCombobox() {}

  virtual gfx::Size GetPreferredSize() const OVERRIDE {
    return gfx::Size(0, views::Combobox::GetPreferredSize().height());
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(UnsizedCombobox);
};

}  // namespace

BookmarkBubbleView* BookmarkBubbleView::bookmark_bubble_ = NULL;

// static
void BookmarkBubbleView::ShowBubble(views::View* anchor_view,
                                    BookmarkBubbleViewObserver* observer,
                                    scoped_ptr<BookmarkBubbleDelegate> delegate,
                                    Profile* profile,
                                    const GURL& url,
                                    bool newly_bookmarked) {
  if (IsShowing())
    return;

  bookmark_bubble_ = new BookmarkBubbleView(anchor_view,
                                            observer,
                                            delegate.Pass(),
                                            profile,
                                            url,
                                            newly_bookmarked);
  views::BubbleDelegateView::CreateBubble(bookmark_bubble_)->Show();
  // Select the entire title textfield contents when the bubble is first shown.
  bookmark_bubble_->title_tf_->SelectAll(true);
  bookmark_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);

  if (bookmark_bubble_->observer_)
    bookmark_bubble_->observer_->OnBookmarkBubbleShown(url);
}

// static
bool BookmarkBubbleView::IsShowing() {
  return bookmark_bubble_ != NULL;
}

void BookmarkBubbleView::Hide() {
  if (IsShowing())
    bookmark_bubble_->GetWidget()->Close();
}

BookmarkBubbleView::~BookmarkBubbleView() {
  if (apply_edits_) {
    ApplyEdits();
  } else if (remove_bookmark_) {
    BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
    const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
    if (node)
      model->Remove(node->parent(), node->parent()->GetIndexOf(node));
  }
  // |parent_combobox_| needs to be destroyed before |parent_model_| as it
  // uses |parent_model_| in its destructor.
  delete parent_combobox_;
}

views::View* BookmarkBubbleView::GetInitiallyFocusedView() {
  return title_tf_;
}

void BookmarkBubbleView::WindowClosing() {
  // We have to reset |bubble_| here, not in our destructor, because we'll be
  // destroyed asynchronously and the shown state will be checked before then.
  DCHECK_EQ(bookmark_bubble_, this);
  bookmark_bubble_ = NULL;

  if (observer_)
    observer_->OnBookmarkBubbleHidden();
}

bool BookmarkBubbleView::AcceleratorPressed(
    const ui::Accelerator& accelerator) {
  ui::KeyboardCode key_code = accelerator.key_code();
  if (key_code == ui::VKEY_RETURN) {
    HandleButtonPressed(close_button_);
    return true;
  }
  if (key_code == ui::VKEY_E && accelerator.IsAltDown()) {
    HandleButtonPressed(edit_button_);
    return true;
  }
  if (key_code == ui::VKEY_R && accelerator.IsAltDown()) {
    HandleButtonPressed(remove_button_);
    return true;
  }
  if (key_code == ui::VKEY_ESCAPE) {
    remove_bookmark_ = newly_bookmarked_;
    apply_edits_ = false;
  }

  return BubbleDelegateView::AcceleratorPressed(accelerator);
}

void BookmarkBubbleView::Init() {
  views::Label* title_label = new views::Label(
      l10n_util::GetStringUTF16(
          newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
                              IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK));
  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
  title_label->SetFontList(rb->GetFontList(ui::ResourceBundle::MediumFont));

  remove_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(
      IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK));
  remove_button_->SetStyle(views::Button::STYLE_BUTTON);

  edit_button_ = new views::LabelButton(
      this, l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_OPTIONS));
  edit_button_->SetStyle(views::Button::STYLE_BUTTON);

  close_button_ = new views::LabelButton(
      this, l10n_util::GetStringUTF16(IDS_DONE));
  close_button_->SetStyle(views::Button::STYLE_BUTTON);
  close_button_->SetIsDefault(true);

  views::Label* combobox_label = new views::Label(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT));

  parent_combobox_ = new UnsizedCombobox(&parent_model_);
  parent_combobox_->set_listener(this);
  parent_combobox_->SetAccessibleName(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_FOLDER_TEXT));

  GridLayout* layout = new GridLayout(this);
  SetLayoutManager(layout);

  // Column sets used in the layout of the bubble.
  enum ColumnSetID {
    TITLE_COLUMN_SET_ID,
    CONTENT_COLUMN_SET_ID,
    SYNC_PROMO_COLUMN_SET_ID
  };

  ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID);
  cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
  cs->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0, GridLayout::USE_PREF,
                0, 0);
  cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);

  // The column layout used for middle and bottom rows.
  cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID);
  cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
  cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
                GridLayout::USE_PREF, 0, 0);
  cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);

  cs->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
                GridLayout::USE_PREF, 0, 0);
  cs->AddPaddingColumn(1, views::kUnrelatedControlLargeHorizontalSpacing);

  cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
                GridLayout::USE_PREF, 0, 0);
  cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
  cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
                GridLayout::USE_PREF, 0, 0);
  cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);

  layout->StartRow(0, TITLE_COLUMN_SET_ID);
  layout->AddView(title_label);
  layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);

  layout->StartRow(0, CONTENT_COLUMN_SET_ID);
  views::Label* label = new views::Label(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_BUBBLE_TITLE_TEXT));
  layout->AddView(label);
  title_tf_ = new views::Textfield();
  title_tf_->SetText(GetTitle());
  title_tf_->SetAccessibleName(
      l10n_util::GetStringUTF16(IDS_BOOKMARK_AX_BUBBLE_TITLE_TEXT));

  layout->AddView(title_tf_, 5, 1);

  layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing);

  layout->StartRow(0, CONTENT_COLUMN_SET_ID);
  layout->AddView(combobox_label);
  layout->AddView(parent_combobox_, 5, 1);

  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  layout->StartRow(0, CONTENT_COLUMN_SET_ID);
  layout->SkipColumns(2);
  layout->AddView(remove_button_);
  layout->AddView(edit_button_);
  layout->AddView(close_button_);

  layout->AddPaddingRow(
      0,
      views::kUnrelatedControlVerticalSpacing - kControlBorderWidth);

  if (SyncPromoUI::ShouldShowSyncPromo(profile_)) {
    // The column layout used for the sync promo.
    cs = layout->AddColumnSet(SYNC_PROMO_COLUMN_SET_ID);
    cs->AddColumn(GridLayout::FILL,
                  GridLayout::FILL,
                  1,
                  GridLayout::USE_PREF,
                  0,
                  0);
    layout->StartRow(0, SYNC_PROMO_COLUMN_SET_ID);

    sync_promo_view_ = new BookmarkSyncPromoView(delegate_.get());
    layout->AddView(sync_promo_view_);
  }

  AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
  AddAccelerator(ui::Accelerator(ui::VKEY_E, ui::EF_ALT_DOWN));
  AddAccelerator(ui::Accelerator(ui::VKEY_R, ui::EF_ALT_DOWN));
}

BookmarkBubbleView::BookmarkBubbleView(
    views::View* anchor_view,
    BookmarkBubbleViewObserver* observer,
    scoped_ptr<BookmarkBubbleDelegate> delegate,
    Profile* profile,
    const GURL& url,
    bool newly_bookmarked)
    : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
      observer_(observer),
      delegate_(delegate.Pass()),
      profile_(profile),
      url_(url),
      newly_bookmarked_(newly_bookmarked),
      parent_model_(
          BookmarkModelFactory::GetForProfile(profile_),
          BookmarkModelFactory::GetForProfile(profile_)->
              GetMostRecentlyAddedUserNodeForURL(url)),
      remove_button_(NULL),
      edit_button_(NULL),
      close_button_(NULL),
      title_tf_(NULL),
      parent_combobox_(NULL),
      sync_promo_view_(NULL),
      remove_bookmark_(false),
      apply_edits_(true) {
  set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0));
  // Compensate for built-in vertical padding in the anchor view's image.
  set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
}

base::string16 BookmarkBubbleView::GetTitle() {
  BookmarkModel* bookmark_model =
      BookmarkModelFactory::GetForProfile(profile_);
  const BookmarkNode* node =
      bookmark_model->GetMostRecentlyAddedUserNodeForURL(url_);
  if (node)
    return node->GetTitle();
  else
    NOTREACHED();
  return base::string16();
}

void BookmarkBubbleView::GetAccessibleState(ui::AXViewState* state) {
  BubbleDelegateView::GetAccessibleState(state);
  state->name =
      l10n_util::GetStringUTF16(
          newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED :
                              IDS_BOOKMARK_AX_BUBBLE_PAGE_BOOKMARK);
}

void BookmarkBubbleView::ButtonPressed(views::Button* sender,
                                       const ui::Event& event) {
  HandleButtonPressed(sender);
}

void BookmarkBubbleView::OnPerformAction(views::Combobox* combobox) {
  if (combobox->selected_index() + 1 == parent_model_.GetItemCount()) {
    content::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
    ShowEditor();
  }
}

void BookmarkBubbleView::HandleButtonPressed(views::Button* sender) {
  if (sender == remove_button_) {
    content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"));
    // Set this so we remove the bookmark after the window closes.
    remove_bookmark_ = true;
    apply_edits_ = false;
    GetWidget()->Close();
  } else if (sender == edit_button_) {
    content::RecordAction(UserMetricsAction("BookmarkBubble_Edit"));
    ShowEditor();
  } else {
    DCHECK_EQ(close_button_, sender);
    GetWidget()->Close();
  }
}

void BookmarkBubbleView::ShowEditor() {
  const BookmarkNode* node = BookmarkModelFactory::GetForProfile(
      profile_)->GetMostRecentlyAddedUserNodeForURL(url_);
  views::Widget* parent = anchor_widget();
  DCHECK(parent);

  Profile* profile = profile_;
  ApplyEdits();
  GetWidget()->Close();

  if (node && parent)
    BookmarkEditor::Show(parent->GetNativeWindow(), profile,
                         BookmarkEditor::EditDetails::EditNode(node),
                         BookmarkEditor::SHOW_TREE);
}

void BookmarkBubbleView::ApplyEdits() {
  // Set this to make sure we don't attempt to apply edits again.
  apply_edits_ = false;

  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
  const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url_);
  if (node) {
    const base::string16 new_title = title_tf_->text();
    if (new_title != node->GetTitle()) {
      model->SetTitle(node, new_title);
      content::RecordAction(
          UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
    }
    parent_model_.MaybeChangeParent(node, parent_combobox_->selected_index());
  }
}
<|endoftext|>