1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/devtools/devtools_browser_target.h"
6
7#include "base/bind.h"
8#include "base/lazy_instance.h"
9#include "base/location.h"
10#include "base/logging.h"
11#include "base/message_loop/message_loop_proxy.h"
12#include "base/stl_util.h"
13#include "base/strings/stringprintf.h"
14#include "base/values.h"
15#include "content/public/browser/browser_thread.h"
16#include "net/server/http_server.h"
17
18namespace content {
19
20DevToolsBrowserTarget::DevToolsBrowserTarget(net::HttpServer* http_server,
21                                             int connection_id)
22    : message_loop_proxy_(base::MessageLoopProxy::current()),
23      http_server_(http_server),
24      connection_id_(connection_id),
25      weak_factory_(this) {
26}
27
28void DevToolsBrowserTarget::RegisterDomainHandler(
29    const std::string& domain,
30    DevToolsProtocol::Handler* handler,
31    bool handle_on_ui_thread) {
32  DCHECK_EQ(message_loop_proxy_, base::MessageLoopProxy::current());
33
34  DCHECK(handlers_.find(domain) == handlers_.end());
35  handlers_[domain] = handler;
36  if (handle_on_ui_thread) {
37    handle_on_ui_thread_.insert(domain);
38    handler->SetNotifier(base::Bind(&DevToolsBrowserTarget::RespondFromUIThread,
39                                    weak_factory_.GetWeakPtr()));
40  } else {
41    handler->SetNotifier(base::Bind(&DevToolsBrowserTarget::Respond,
42                                    base::Unretained(this)));
43  }
44}
45
46typedef std::map<std::string, DevToolsBrowserTarget*> DomainMap;
47base::LazyInstance<DomainMap>::Leaky g_used_domains = LAZY_INSTANCE_INITIALIZER;
48
49void DevToolsBrowserTarget::HandleMessage(const std::string& data) {
50  DCHECK_EQ(message_loop_proxy_, base::MessageLoopProxy::current());
51  std::string error_response;
52  scoped_refptr<DevToolsProtocol::Command> command =
53      DevToolsProtocol::ParseCommand(data, &error_response);
54  if (!command.get()) {
55    Respond(error_response);
56    return;
57  }
58
59  DomainHandlerMap::iterator it = handlers_.find(command->domain());
60  if (it == handlers_.end()) {
61    Respond(command->NoSuchMethodErrorResponse()->Serialize());
62    return;
63  }
64  DomainMap& used_domains(g_used_domains.Get());
65  std::string domain = command->domain();
66  DomainMap::iterator jt = used_domains.find(domain);
67  if (jt == used_domains.end()) {
68    used_domains[domain] = this;
69  } else if (jt->second != this) {
70    std::string message =
71        base::StringPrintf("'%s' is held by another connection",
72                           domain.c_str());
73    Respond(command->ServerErrorResponse(message)->Serialize());
74    return;
75  }
76
77  DevToolsProtocol::Handler* handler = it->second;
78  bool handle_directly = handle_on_ui_thread_.find(domain) ==
79      handle_on_ui_thread_.end();
80  if (handle_directly) {
81    scoped_refptr<DevToolsProtocol::Response> response =
82        handler->HandleCommand(command);
83    if (response.get() && response->is_async_promise())
84      return;
85    if (response.get())
86      Respond(response->Serialize());
87    else
88      Respond(command->NoSuchMethodErrorResponse()->Serialize());
89    return;
90  }
91
92  BrowserThread::PostTask(
93      BrowserThread::UI,
94      FROM_HERE,
95      base::Bind(&DevToolsBrowserTarget::HandleCommandOnUIThread,
96                 this,
97                 handler,
98                 command));
99}
100
101void DevToolsBrowserTarget::Detach() {
102  DCHECK_EQ(message_loop_proxy_, base::MessageLoopProxy::current());
103  DCHECK(http_server_);
104
105  http_server_ = NULL;
106
107  DomainMap& used_domains(g_used_domains.Get());
108  for (DomainMap::iterator it = used_domains.begin();
109       it != used_domains.end();) {
110    if (it->second == this) {
111      DomainMap::iterator to_erase = it;
112      ++it;
113      used_domains.erase(to_erase);
114    } else {
115      ++it;
116    }
117  }
118
119  std::vector<DevToolsProtocol::Handler*> ui_handlers;
120  for (std::set<std::string>::iterator domain_it = handle_on_ui_thread_.begin();
121       domain_it != handle_on_ui_thread_.end();
122       ++domain_it) {
123    DomainHandlerMap::iterator handler_it = handlers_.find(*domain_it);
124    CHECK(handler_it != handlers_.end());
125    ui_handlers.push_back(handler_it->second);
126    handlers_.erase(handler_it);
127  }
128
129  STLDeleteValues(&handlers_);
130
131  BrowserThread::PostTask(
132      BrowserThread::UI,
133      FROM_HERE,
134      base::Bind(&DevToolsBrowserTarget::DeleteHandlersOnUIThread,
135                 this,
136                 ui_handlers));
137}
138
139DevToolsBrowserTarget::~DevToolsBrowserTarget() {
140  // DCHECK that Detach has been called or no handler has ever been registered.
141  DCHECK(handlers_.empty());
142}
143
144void DevToolsBrowserTarget::HandleCommandOnUIThread(
145    DevToolsProtocol::Handler* handler,
146    scoped_refptr<DevToolsProtocol::Command> command) {
147  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
148  scoped_refptr<DevToolsProtocol::Response> response =
149      handler->HandleCommand(command);
150  if (response.get() && response->is_async_promise())
151    return;
152
153  if (response.get())
154    RespondFromUIThread(response->Serialize());
155  else
156    RespondFromUIThread(command->NoSuchMethodErrorResponse()->Serialize());
157}
158
159void DevToolsBrowserTarget::DeleteHandlersOnUIThread(
160    std::vector<DevToolsProtocol::Handler*> handlers) {
161  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
162  STLDeleteElements(&handlers);
163}
164
165void DevToolsBrowserTarget::Respond(const std::string& message) {
166  DCHECK_EQ(message_loop_proxy_, base::MessageLoopProxy::current());
167  if (!http_server_)
168    return;
169  http_server_->SendOverWebSocket(connection_id_, message);
170}
171
172void DevToolsBrowserTarget::RespondFromUIThread(const std::string& message) {
173  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
174  message_loop_proxy_->PostTask(
175      FROM_HERE,
176      base::Bind(&DevToolsBrowserTarget::Respond, this, message));
177}
178
179}  // namespace content
180