1// Copyright (c) 2011 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// Implements the Chrome Extensions Debugger API.
6
7#include "chrome/browser/extensions/extension_debugger_api.h"
8
9#include <map>
10#include <set>
11
12#include "base/json/json_reader.h"
13#include "base/json/json_writer.h"
14#include "base/memory/singleton.h"
15#include "base/string_number_conversions.h"
16#include "base/values.h"
17#include "chrome/browser/debugger/devtools_client_host.h"
18#include "chrome/browser/debugger/devtools_manager.h"
19#include "chrome/browser/extensions/extension_debugger_api_constants.h"
20#include "chrome/browser/extensions/extension_event_router.h"
21#include "chrome/browser/extensions/extension_tabs_module.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
24#include "chrome/common/devtools_messages.h"
25#include "chrome/common/extensions/extension.h"
26#include "chrome/common/extensions/extension_error_utils.h"
27#include "content/browser/tab_contents/tab_contents.h"
28#include "content/common/notification_service.h"
29
30namespace keys = extension_debugger_api_constants;
31
32class ExtensionDevToolsClientHost : public DevToolsClientHost,
33                                    public NotificationObserver {
34 public:
35  ExtensionDevToolsClientHost(TabContents* tab_contents,
36                              const std::string& extension_id,
37                              int tab_id);
38
39  ~ExtensionDevToolsClientHost();
40
41  bool MatchesContentsAndExtensionId(TabContents* tab_contents,
42                                     const std::string& extension_id);
43  void Close();
44  void SendMessageToBackend(SendRequestDebuggerFunction* function,
45                            const std::string& method,
46                            Value* params);
47
48  // DevToolsClientHost interface
49  virtual void InspectedTabClosing();
50  virtual void SendMessageToClient(const IPC::Message& msg);
51  virtual void TabReplaced(TabContentsWrapper* tab_contents);
52  virtual void FrameNavigating(const std::string& url) {}
53
54 private:
55  // NotificationObserver implementation.
56  virtual void Observe(NotificationType type,
57                       const NotificationSource& source,
58                       const NotificationDetails& details);
59  void OnDispatchOnInspectorFrontend(const std::string& data);
60
61  TabContents* tab_contents_;
62  std::string extension_id_;
63  int tab_id_;
64  NotificationRegistrar registrar_;
65  int last_request_id_;
66  typedef std::map<int, scoped_refptr<SendRequestDebuggerFunction> >
67      PendingRequests;
68  PendingRequests pending_requests_;
69
70  DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsClientHost);
71};
72
73namespace {
74
75class AttachedClientHosts {
76 public:
77  AttachedClientHosts() {}
78
79  // Returns the singleton instance of this class
80  static AttachedClientHosts* GetInstance() {
81    return Singleton<AttachedClientHosts>::get();
82  }
83
84  void Add(ExtensionDevToolsClientHost* client_host) {
85    client_hosts_.insert(client_host);
86  }
87
88  void Remove(ExtensionDevToolsClientHost* client_host) {
89    client_hosts_.erase(client_host);
90  }
91
92  ExtensionDevToolsClientHost* Lookup(RenderViewHost* rvh) {
93    DevToolsClientHost* client_host =
94        DevToolsManager::GetInstance()->GetDevToolsClientHostFor(rvh);
95    std::set<DevToolsClientHost*>::iterator it =
96        client_hosts_.find(client_host);
97    if (it == client_hosts_.end())
98      return NULL;
99    return static_cast<ExtensionDevToolsClientHost*>(client_host);
100  }
101
102 private:
103  std::set<DevToolsClientHost*> client_hosts_;
104};
105
106}  // namespace
107
108ExtensionDevToolsClientHost::ExtensionDevToolsClientHost(
109    TabContents* tab_contents,
110    const std::string& extension_id,
111    int tab_id)
112    : tab_contents_(tab_contents),
113      extension_id_(extension_id),
114      tab_id_(tab_id),
115      last_request_id_(0) {
116  AttachedClientHosts::GetInstance()->Add(this);
117
118  // Detach from debugger when extension unloads.
119  registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
120                 Source<Profile>(tab_contents_->profile()));
121
122  // Attach to debugger and tell it we are ready.
123  DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
124      tab_contents_->render_view_host(),
125      this);
126  DevToolsManager::GetInstance()->ForwardToDevToolsAgent(
127      this,
128      DevToolsAgentMsg_FrontendLoaded());
129}
130
131ExtensionDevToolsClientHost::~ExtensionDevToolsClientHost() {
132  AttachedClientHosts::GetInstance()->Remove(this);
133}
134
135bool ExtensionDevToolsClientHost::MatchesContentsAndExtensionId(
136    TabContents* tab_contents,
137    const std::string& extension_id) {
138  return tab_contents == tab_contents_ && extension_id_ == extension_id;
139}
140
141// DevToolsClientHost interface
142void ExtensionDevToolsClientHost::InspectedTabClosing() {
143  // Tell extension that this client host has been detached.
144  Profile* profile = tab_contents_->profile();
145  if (profile != NULL && profile->GetExtensionEventRouter()) {
146    ListValue args;
147    args.Append(Value::CreateIntegerValue(tab_id_));
148
149    std::string json_args;
150    base::JSONWriter::Write(&args, false, &json_args);
151
152    profile->GetExtensionEventRouter()->DispatchEventToExtension(
153        extension_id_, keys::kOnDetach, json_args, profile, GURL());
154  }
155  delete this;
156}
157
158void ExtensionDevToolsClientHost::SendMessageToClient(
159    const IPC::Message& msg) {
160  IPC_BEGIN_MESSAGE_MAP(ExtensionDevToolsClientHost, msg)
161    IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend,
162                        OnDispatchOnInspectorFrontend);
163    IPC_MESSAGE_UNHANDLED_ERROR()
164  IPC_END_MESSAGE_MAP()
165}
166
167void ExtensionDevToolsClientHost::TabReplaced(
168    TabContentsWrapper* tab_contents) {
169  tab_contents_ = tab_contents->tab_contents();
170}
171
172void ExtensionDevToolsClientHost::Close() {
173  DevToolsClientHost::NotifyCloseListener();
174  delete this;
175}
176
177void ExtensionDevToolsClientHost::SendMessageToBackend(
178    SendRequestDebuggerFunction* function,
179    const std::string& method,
180    Value* params) {
181  DictionaryValue protocol_request;
182  int request_id = ++last_request_id_;
183  pending_requests_[request_id] = function;
184  protocol_request.SetInteger("id", request_id);
185  protocol_request.SetString("method", method);
186  if (params)
187    protocol_request.Set("params", params->DeepCopy());
188
189  std::string json_args;
190  base::JSONWriter::Write(&protocol_request, false, &json_args);
191  DevToolsManager::GetInstance()->ForwardToDevToolsAgent(
192      this,
193      DevToolsAgentMsg_DispatchOnInspectorBackend(json_args));
194}
195
196void ExtensionDevToolsClientHost::Observe(
197    NotificationType type,
198    const NotificationSource& source,
199    const NotificationDetails& details) {
200  DCHECK(type == NotificationType::EXTENSION_UNLOADED);
201  Close();
202}
203
204void ExtensionDevToolsClientHost::OnDispatchOnInspectorFrontend(
205    const std::string& data) {
206  Profile* profile = tab_contents_->profile();
207  if (profile == NULL || !profile->GetExtensionEventRouter())
208    return;
209
210  scoped_ptr<Value> result(base::JSONReader::Read(data, false));
211  if (!result->IsType(Value::TYPE_DICTIONARY))
212    return;
213  DictionaryValue* dictionary = static_cast<DictionaryValue*>(result.get());
214
215  int id;
216  if (!dictionary->GetInteger("id", &id)) {
217    std::string method_name;
218    if (!dictionary->GetString("method", &method_name))
219      return;
220
221    ListValue args;
222    args.Append(Value::CreateIntegerValue(tab_id_));
223    args.Append(Value::CreateStringValue(method_name));
224    Value* params_value;
225    if (dictionary->Get("params", &params_value))
226      args.Append(params_value->DeepCopy());
227
228    std::string json_args;
229    base::JSONWriter::Write(&args, false, &json_args);
230
231    profile->GetExtensionEventRouter()->DispatchEventToExtension(
232        extension_id_, keys::kOnEvent, json_args, profile, GURL());
233  } else {
234    SendRequestDebuggerFunction* function = pending_requests_[id];
235    if (!function)
236      return;
237
238    function->SendResponseBody(dictionary);
239    pending_requests_.erase(id);
240  }
241}
242
243DebuggerFunction::DebuggerFunction()
244    : contents_(0),
245      client_host_(0) {
246}
247
248bool DebuggerFunction::InitTabContents(int tab_id) {
249  // Find the TabContents that contains this tab id.
250  contents_ = NULL;
251  TabContentsWrapper* wrapper = NULL;
252  bool result = ExtensionTabUtil::GetTabById(
253      tab_id, profile(), include_incognito(), NULL, NULL, &wrapper, NULL);
254  if (!result || !wrapper) {
255    error_ = error_ = ExtensionErrorUtils::FormatErrorMessage(
256        keys::kNoTabError,
257        base::IntToString(tab_id));
258    return false;
259  }
260  contents_ = wrapper->tab_contents();
261  return true;
262}
263
264bool DebuggerFunction::InitClientHost(int tab_id) {
265  if (!InitTabContents(tab_id))
266    return false;
267
268  RenderViewHost* rvh = contents_->render_view_host();
269  client_host_ = AttachedClientHosts::GetInstance()->Lookup(rvh);
270
271  if (!client_host_ ||
272      !client_host_->MatchesContentsAndExtensionId(contents_,
273                                                   GetExtension()->id())) {
274    error_ = ExtensionErrorUtils::FormatErrorMessage(
275        keys::kNotAttachedError,
276        base::IntToString(tab_id));
277    return false;
278  }
279  return true;
280}
281
282AttachDebuggerFunction::AttachDebuggerFunction() {}
283
284AttachDebuggerFunction::~AttachDebuggerFunction() {}
285
286bool AttachDebuggerFunction::RunImpl() {
287  int tab_id;
288  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
289
290  if (!InitTabContents(tab_id))
291    return false;
292
293  DevToolsClientHost* client_host = DevToolsManager::GetInstance()->
294      GetDevToolsClientHostFor(contents_->render_view_host());
295
296  if (client_host != NULL) {
297    error_ = ExtensionErrorUtils::FormatErrorMessage(
298        keys::kAlreadyAttachedError,
299        base::IntToString(tab_id));
300    return false;
301  }
302
303  new ExtensionDevToolsClientHost(contents_, GetExtension()->id(), tab_id);
304  SendResponse(true);
305  return true;
306}
307
308DetachDebuggerFunction::DetachDebuggerFunction() {}
309
310DetachDebuggerFunction::~DetachDebuggerFunction() {}
311
312bool DetachDebuggerFunction::RunImpl() {
313  int tab_id;
314  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
315
316  if (!InitClientHost(tab_id))
317    return false;
318
319  client_host_->Close();
320  SendResponse(true);
321  return true;
322}
323
324SendRequestDebuggerFunction::SendRequestDebuggerFunction() {}
325
326SendRequestDebuggerFunction::~SendRequestDebuggerFunction() {}
327
328bool SendRequestDebuggerFunction::RunImpl() {
329  int tab_id;
330  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
331
332  if (!InitClientHost(tab_id))
333    return false;
334
335  std::string method;
336  EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &method));
337
338  Value *params;
339  if (!args_->Get(2, &params))
340    params = NULL;
341
342  client_host_->SendMessageToBackend(this, method, params);
343  return true;
344}
345
346void SendRequestDebuggerFunction::SendResponseBody(
347    DictionaryValue* dictionary) {
348  Value* error_body;
349  if (dictionary->Get("error", &error_body)) {
350    base::JSONWriter::Write(error_body, false, &error_);
351    SendResponse(false);
352    return;
353  }
354
355  Value* result_body;
356  if (dictionary->Get("result", &result_body))
357    result_.reset(result_body->DeepCopy());
358  else
359    result_.reset(new DictionaryValue());
360  SendResponse(true);
361}
362