summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/debugger/debugger.vcproj12
-rw-r--r--chrome/browser/debugger/debugger_contents.cc27
-rw-r--r--chrome/browser/debugger/debugger_host.h43
-rw-r--r--chrome/browser/debugger/debugger_host_impl.cpp188
-rw-r--r--chrome/browser/debugger/debugger_host_impl.h51
-rw-r--r--chrome/browser/debugger/debugger_io.h12
-rw-r--r--chrome/browser/debugger/debugger_io_socket.cc6
-rw-r--r--chrome/browser/debugger/debugger_io_socket.h4
-rw-r--r--chrome/browser/debugger/debugger_shell.h3
-rw-r--r--chrome/browser/debugger/debugger_view.cc46
-rw-r--r--chrome/browser/debugger/debugger_view.h9
-rw-r--r--chrome/browser/debugger/debugger_window.cc43
-rw-r--r--chrome/browser/debugger/debugger_window.h17
-rw-r--r--chrome/browser/debugger/debugger_wrapper.cc11
-rw-r--r--chrome/browser/debugger/debugger_wrapper.h8
-rw-r--r--webkit/build/port/port.vcproj25
-rw-r--r--webkit/port/page/inspector/DebuggerConsole.js173
-rw-r--r--webkit/port/page/inspector/DebuggerIPC.js109
-rw-r--r--webkit/port/page/inspector/DebuggerPanel.js201
-rw-r--r--webkit/port/page/inspector/DebuggerShell.js1327
-rw-r--r--webkit/port/page/inspector/debugger.css35
-rw-r--r--webkit/port/page/inspector/debugger.html31
22 files changed, 2292 insertions, 89 deletions
diff --git a/chrome/browser/debugger/debugger.vcproj b/chrome/browser/debugger/debugger.vcproj
index 38c32fd..2ef5241 100644
--- a/chrome/browser/debugger/debugger.vcproj
+++ b/chrome/browser/debugger/debugger.vcproj
@@ -150,6 +150,18 @@
>
</File>
<File
+ RelativePath=".\debugger_host.h"
+ >
+ </File>
+ <File
+ RelativePath=".\debugger_host_impl.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\debugger_host_impl.h"
+ >
+ </File>
+ <File
RelativePath=".\debugger_io.h"
>
</File>
diff --git a/chrome/browser/debugger/debugger_contents.cc b/chrome/browser/debugger/debugger_contents.cc
index 2b3e998..37cb1d0 100644
--- a/chrome/browser/debugger/debugger_contents.cc
+++ b/chrome/browser/debugger/debugger_contents.cc
@@ -78,33 +78,32 @@ class DebuggerHTMLSource : public ChromeURLDataManager::DataSource {
class DebuggerHandler : public DOMMessageHandler {
public:
explicit DebuggerHandler(DOMUIHost* host) {
- host->RegisterMessageCallback("command",
- NewCallback(this, &DebuggerHandler::HandleCommand));
+ host->RegisterMessageCallback("DebuggerHostMessage",
+ NewCallback(this, &DebuggerHandler::HandleDebuggerHostMessage));
}
- void HandleCommand(const Value* content) {
- // Extract the parameters out of the input list.
+ void HandleDebuggerHostMessage(const Value* content) {
if (!content || !content->IsType(Value::TYPE_LIST)) {
NOTREACHED();
return;
}
const ListValue* args = static_cast<const ListValue*>(content);
- if (args->GetSize() != 1) {
+ if (args->GetSize() < 1) {
NOTREACHED();
return;
}
- std::wstring command;
- Value* value = NULL;
- if (!args->Get(0, &value) || !value->GetAsString(&command)) {
+
+#ifndef CHROME_DEBUGGER_DISABLED
+ DebuggerWrapper* wrapper = g_browser_process->debugger_wrapper();
+ DebuggerHost* debugger_host = wrapper->GetDebugger();
+ if (!debugger_host) {
NOTREACHED();
return;
}
-#ifndef CHROME_DEBUGGER_DISABLED
- DebuggerWrapper* wrapper = g_browser_process->debugger_wrapper();
- DebuggerShell* shell = wrapper->GetDebugger();
- shell->ProcessCommand(command);
+ debugger_host->OnDebuggerHostMsg(args);
#endif
}
+
private:
DISALLOW_EVIL_CONSTRUCTORS(DebuggerHandler);
};
@@ -127,8 +126,6 @@ void DebuggerContents::AttachMessageHandlers() {
// static
bool DebuggerContents::IsDebuggerUrl(const GURL& url) {
- if (url.SchemeIs("chrome-resource") && url.host() == "debugger")
- return true;
- return false;
+ return (url.SchemeIs("chrome-resource") && url.host() == "inspector");
}
diff --git a/chrome/browser/debugger/debugger_host.h b/chrome/browser/debugger/debugger_host.h
new file mode 100644
index 0000000..bfb2efa
--- /dev/null
+++ b/chrome/browser/debugger/debugger_host.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2006-2008 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.
+
+// A part of browser-side server debugger exposed to DebuggerWrapper.
+
+#ifndef CHROME_BROWSER_DEBUGGER_DEBUGGER_HOST_H_
+#define CHROME_BROWSER_DEBUGGER_DEBUGGER_HOST_H_
+
+#include "base/basictypes.h"
+#include "base/thread.h"
+
+class ListValue;
+
+class DebuggerHost : public base::RefCountedThreadSafe<DebuggerHost> {
+ public:
+ DebuggerHost() {};
+ virtual ~DebuggerHost() {};
+
+ // call before other methods
+ virtual void Start() = 0;
+
+ // A message from the V8 debugger in the renderer being debugged via
+ // RenderViewHost
+ virtual void DebugMessage(const std::wstring& msg) = 0;
+ // We've been successfully attached to a renderer.
+ virtual void OnDebugAttach() = 0;
+ // The renderer we're attached to is gone.
+ virtual void OnDebugDisconnect() = 0;
+
+ virtual void DidDisconnect() = 0;
+ virtual void DidConnect() {}
+ virtual void ProcessCommand(const std::wstring& data) {}
+
+ // Handles messages from debugger UI.
+ virtual void OnDebuggerHostMsg(const ListValue* args) {}
+ private:
+
+ DISALLOW_COPY_AND_ASSIGN(DebuggerHost);
+};
+
+#endif // CHROME_BROWSER_DEBUGGER_DEBUGGER_HOST_H_
+
diff --git a/chrome/browser/debugger/debugger_host_impl.cpp b/chrome/browser/debugger/debugger_host_impl.cpp
new file mode 100644
index 0000000..c2eb41d
--- /dev/null
+++ b/chrome/browser/debugger/debugger_host_impl.cpp
@@ -0,0 +1,188 @@
+// Copyright (c) 2006-2008 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/debugger_host_impl.h"
+
+#include "base/json_reader.h"
+#include "base/string_util.h"
+#include "chrome/browser/debugger/debugger_io.h"
+#include "chrome/browser/debugger/debugger_wrapper.h"
+#include "chrome/browser/render_view_host.h"
+#include "chrome/browser/web_contents.h"
+#include "chrome/common/notification_service.h"
+
+class TabContentsReference : public NotificationObserver {
+ public:
+ TabContentsReference(TabContents *c) : navigation_controller_(NULL) {
+ navigation_controller_ = c->controller();
+
+ NotificationService* service = NotificationService::current();
+ DCHECK(service);
+ service->AddObserver(this, NOTIFY_TAB_CLOSING,
+ Source<NavigationController>(navigation_controller_));
+ observing_ = true;
+ }
+
+ virtual ~TabContentsReference() {
+ StopObserving();
+ }
+
+ // NotificationObserver impl
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ StopObserving();
+ navigation_controller_ = NULL;
+ }
+
+ TabContents* GetTabContents() {
+ if (navigation_controller_) {
+ return navigation_controller_->active_contents();
+ } else {
+ return NULL;
+ }
+ }
+ private:
+ void StopObserving() {
+ if (observing_ && navigation_controller_) {
+ NotificationService* service = NotificationService::current();
+ DCHECK(service);
+ service->RemoveObserver(
+ this,
+ NOTIFY_TAB_CLOSING,
+ Source<NavigationController>(navigation_controller_));
+ observing_ = false;
+ }
+ }
+
+ NavigationController* navigation_controller_;
+ bool observing_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabContentsReference);
+};
+
+
+DebuggerHostImpl::DebuggerHostImpl(DebuggerInputOutput* io)
+ : io_(io),
+ debugger_ready_(true) {
+}
+
+DebuggerHostImpl::~DebuggerHostImpl() {
+ io_->Stop();
+ io_ = NULL;
+}
+
+void DebuggerHostImpl::Start() {
+ io_->Start(this);
+}
+
+void DebuggerHostImpl::Debug(TabContents* tab) {
+ tab_reference_.reset(new TabContentsReference(tab));
+}
+
+void DebuggerHostImpl::DebugMessage(const std::wstring& msg) {
+
+ Value* msg_value = NULL;
+ if (!JSONReader::Read(WideToUTF8(msg), &msg_value, false)) {
+ msg_value = Value::CreateStringValue(L"Message parse error!");
+ }
+ ListValue* argv = new ListValue;
+ argv->Append(msg_value);
+ io_->CallFunctionInPage(L"response", argv);
+}
+
+void DebuggerHostImpl::OnDebugAttach() {
+ std::wstring title;
+ const TabContents* t = GetTabContentsBeingDebugged();
+ if (t) {
+ title = t->GetTitle();
+ }
+
+ ListValue* argv = new ListValue;
+ argv->Append(Value::CreateStringValue(title));
+ io_->CallFunctionInPage(L"on_attach", argv);
+}
+
+void DebuggerHostImpl::OnDebugDisconnect() {
+ ListValue* argv = new ListValue;
+ io_->CallFunctionInPage(L"on_disconnect", argv);
+}
+
+void DebuggerHostImpl::DidDisconnect() {
+ // TODO(yurys): just send Detach from here?
+ ListValue* argv = new ListValue;
+ io_->CallFunctionInPage(L"exit", argv);
+}
+
+void DebuggerHostImpl::OnDebuggerHostMsg(const ListValue* args) {
+ if (args->GetSize() < 1) {
+ NOTREACHED();
+ return;
+ }
+ std::wstring methodName;
+ Value* value = NULL;
+ if (!args->Get(0, &value) || !value->GetAsString(&methodName)) {
+ NOTREACHED();
+ return;
+ }
+
+ const TabContents* t = GetTabContentsBeingDebugged();
+ if (t == NULL) {
+ NOTREACHED();
+ return;
+ }
+ const WebContents* web = t->AsWebContents();
+ if (web == NULL) {
+ NOTREACHED();
+ return;
+ }
+ RenderViewHost* host = web->render_view_host();
+
+ if (methodName == L"attach") {
+ host->DebugAttach();
+ } else if (methodName == L"detach") {
+ host->DebugDetach();
+ } else if (methodName == L"debugBreak") {
+ std::wstring force;
+ Value* value = NULL;
+ if (!args->Get(1, &value) || !value->GetAsString(&force)) {
+ NOTREACHED();
+ return;
+ }
+ host->DebugBreak(force == L"true");
+ } else if (methodName == L"sendToDebugger") {
+ std::wstring cmd;
+ Value* value = NULL;
+ if (!args->Get(1, &value) || !value->GetAsString(&cmd)) {
+ NOTREACHED();
+ return;
+ }
+ host->DebugCommand(cmd);
+
+ } else if (methodName == L"setDebuggerReady") {
+ std::wstring ready;
+ Value* value = NULL;
+ if (!args->Get(1, &value) || !value->GetAsString(&ready)) {
+ NOTREACHED();
+ return;
+ }
+ io_->SetDebuggerReady(ready == L"true");
+ } else if (methodName == L"setDebuggerBreak") {
+ std::wstring brk;
+ Value* value = NULL;
+ if (!args->Get(1, &value) || !value->GetAsString(&brk)) {
+ NOTREACHED();
+ return;
+ }
+ io_->SetDebuggerBreak(brk == L"true");
+ }
+}
+
+TabContents* DebuggerHostImpl::GetTabContentsBeingDebugged() const {
+ if (tab_reference_ != NULL) {
+ return tab_reference_->GetTabContents();
+ } else {
+ return NULL;
+ }
+}
diff --git a/chrome/browser/debugger/debugger_host_impl.h b/chrome/browser/debugger/debugger_host_impl.h
new file mode 100644
index 0000000..ce2c3bb
--- /dev/null
+++ b/chrome/browser/debugger/debugger_host_impl.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_BROWSER_DEBUGGER_DEBUGGER_HOST_IMPL_H__
+#define CHROME_BROWSER_DEBUGGER_DEBUGGER_HOST_IMPL_H__
+
+#include "base/scoped_ptr.h"
+#include "chrome/browser/debugger/debugger_host.h"
+
+class DebuggerInputOutput;
+class TabContents;
+class TabContentsReference;
+
+class DebuggerHostImpl : public DebuggerHost {
+ public:
+ DebuggerHostImpl(DebuggerInputOutput *io);
+ virtual ~DebuggerHostImpl();
+
+ virtual void Start();
+
+ // Start debugging the specified tab
+ virtual void Debug(TabContents* tab);
+ // A message from the V8 debugger in the renderer being debugged via
+ // RenderViewHost
+ virtual void DebugMessage(const std::wstring& msg);
+ // We've been successfully attached to a renderer.
+ virtual void OnDebugAttach();
+ // The renderer we're attached to is gone.
+ virtual void OnDebugDisconnect();
+
+ virtual void DidDisconnect();
+
+ // Handles messages from debugger UI.
+ virtual void OnDebuggerHostMsg(const ListValue* args);
+
+ private:
+
+ TabContents* GetTabContentsBeingDebugged() const;
+
+ scoped_refptr<DebuggerInputOutput> io_;
+ // reference to the tab being debugged by this instance
+ scoped_ptr<TabContentsReference> tab_reference_;
+ // If the debugger is ready to process another command or is busy.
+ bool debugger_ready_;
+
+ DISALLOW_COPY_AND_ASSIGN(DebuggerHostImpl);
+};
+
+#endif // CHROME_BROWSER_DEBUGGER_DEBUGGER_HOST_IMPL_H__
+
diff --git a/chrome/browser/debugger/debugger_io.h b/chrome/browser/debugger/debugger_io.h
index 1e4a5f4..b2ea381 100644
--- a/chrome/browser/debugger/debugger_io.h
+++ b/chrome/browser/debugger/debugger_io.h
@@ -6,11 +6,13 @@
#define CHROME_BROWSER_DEBUGGER_DEBUGGER_IO_H_
#include <string>
+#include <vector>
#include "base/basictypes.h"
#include "base/ref_counted.h"
-class DebuggerShell;
+class DebuggerHost;
+class ListValue;
class DebuggerInputOutput
: public base::RefCountedThreadSafe<DebuggerInputOutput> {
@@ -19,7 +21,7 @@ public:
virtual ~DebuggerInputOutput() {}
// Called when Debugger is ready to begin.
- virtual void Start(DebuggerShell* debugger) { debugger_ = debugger; }
+ virtual void Start(DebuggerHost* debugger) { debugger_ = debugger; }
// Called when Debugger is shutting down
virtual void Stop() {}
@@ -39,8 +41,12 @@ public:
// is running, and true when the page is stopped at a breakpoint
virtual void SetDebuggerBreak(bool brk) {}
+ // sends message to debugger UI page in order to invoke JS function in it
+ virtual void CallFunctionInPage(const std::wstring& name,
+ ListValue* argv) {}
+
protected:
- DebuggerShell* debugger_;
+ DebuggerHost* debugger_;
private:
DISALLOW_COPY_AND_ASSIGN(DebuggerInputOutput);
diff --git a/chrome/browser/debugger/debugger_io_socket.cc b/chrome/browser/debugger/debugger_io_socket.cc
index eb3431d..5280600 100644
--- a/chrome/browser/debugger/debugger_io_socket.cc
+++ b/chrome/browser/debugger/debugger_io_socket.cc
@@ -23,7 +23,7 @@ DebuggerInputOutputSocket::DebuggerInputOutputSocket(int port)
io_loop_ = g_browser_process->io_thread()->message_loop();
}
-void DebuggerInputOutputSocket::Start(DebuggerShell* debugger) {
+void DebuggerInputOutputSocket::Start(DebuggerHost* debugger) {
DebuggerInputOutput::Start(debugger);
io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
this, &DebuggerInputOutputSocket::StartListening));
@@ -57,7 +57,7 @@ void DebuggerInputOutputSocket::DidAccept(ListenSocket *server,
connection_ = connection;
connection_->AddRef();
ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(
- debugger_, &DebuggerShell::DidConnect));
+ debugger_, &DebuggerHost::DidConnect));
} else {
delete connection;
}
@@ -114,7 +114,7 @@ void DebuggerInputOutputSocket::DidRead(ListenSocket *connection,
if (connection == connection_) {
const std::wstring wstr = UTF8ToWide(data);
ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(
- debugger_, &DebuggerShell::ProcessCommand, wstr));
+ debugger_, &DebuggerHost::ProcessCommand, wstr));
} else {
// TODO(erikkay): assert?
}
diff --git a/chrome/browser/debugger/debugger_io_socket.h b/chrome/browser/debugger/debugger_io_socket.h
index 884a5d5..551e14d 100644
--- a/chrome/browser/debugger/debugger_io_socket.h
+++ b/chrome/browser/debugger/debugger_io_socket.h
@@ -8,7 +8,7 @@
#include "chrome/browser/debugger/debugger_io.h"
#include "net/base/telnet_server.h"
-class DebuggerShell;
+class DebuggerHost;
class MessageLoop;
// Interaction with the underlying Socket object MUST happen in the IO thread.
@@ -35,7 +35,7 @@ public:
virtual void Output(const std::string& out);
virtual void OutputLine(const std::string& out);
virtual void OutputPrompt(const std::string& prompt);
- virtual void Start(DebuggerShell* debugger);
+ virtual void Start(DebuggerHost* debugger);
// Stop must be called prior to this object being released, so that cleanup
// can happen in the IO thread.
virtual void Stop();
diff --git a/chrome/browser/debugger/debugger_shell.h b/chrome/browser/debugger/debugger_shell.h
index 6068393..bdb5d8a 100644
--- a/chrome/browser/debugger/debugger_shell.h
+++ b/chrome/browser/debugger/debugger_shell.h
@@ -17,6 +17,7 @@
#include "base/basictypes.h"
#include "base/ref_counted.h"
+#include "chrome/browser/debugger/debugger_host.h"
#ifdef CHROME_DEBUGGER_DISABLED
@@ -44,7 +45,7 @@ class DebuggerInputOutput;
class MessageLoop;
class TabContents;
-class DebuggerShell : public base::RefCountedThreadSafe<DebuggerShell> {
+class DebuggerShell : public DebuggerHost {
public:
DebuggerShell(DebuggerInputOutput *io);
virtual ~DebuggerShell();
diff --git a/chrome/browser/debugger/debugger_view.cc b/chrome/browser/debugger/debugger_view.cc
index 6b59cd6..5fd62f1 100644
--- a/chrome/browser/debugger/debugger_view.cc
+++ b/chrome/browser/debugger/debugger_view.cc
@@ -70,6 +70,12 @@ void DebuggerView::SetOutputViewReady() {
Output(*i);
}
pending_output_.clear();
+
+ for (std::vector<std::string>::const_iterator i = pending_events_.begin();
+ i != pending_events_.end(); ++i) {
+ ExecuteJavascript(*i);
+ }
+ pending_events_.clear();
}
void DebuggerView::Output(const std::string& out) {
@@ -81,11 +87,10 @@ void DebuggerView::Output(const std::wstring& out) {
pending_output_.push_back(out);
return;
}
- Value* str_value = Value::CreateStringValue(out);
- std::string json;
- JSONWriter::Write(str_value, false, &json);
- const std::string js = StringPrintf("appendText(%s)", json.c_str());
- ExecuteJavascript(js);
+
+ DictionaryValue* body = new DictionaryValue;
+ body->Set(L"text", Value::CreateStringValue(out));
+ SendEventToPage(L"appendText", body);
}
void DebuggerView::OnInit() {
@@ -100,15 +105,14 @@ void DebuggerView::OnInit() {
web_container_->SetTabContents(web_contents_);
web_contents_->render_view_host()->AllowDOMUIBindings();
- GURL contents("chrome-resource://debugger/");
+ GURL contents("chrome-resource://inspector/debugger.html");
+
web_contents_->controller()->LoadURL(contents, GURL(),
PageTransition::START_PAGE);
}
void DebuggerView::OnShow() {
web_contents_->Focus();
- if (output_ready_)
- ExecuteJavascript("focusOnCommandLine()");
}
void DebuggerView::OnClose() {
@@ -116,12 +120,6 @@ void DebuggerView::OnClose() {
web_contents_->CloseContents();
}
-void DebuggerView::SetDebuggerBreak(bool is_broken) {
- const std::string js =
- StringPrintf("setDebuggerBreak(%s)", is_broken ? "true" : "false");
- ExecuteJavascript(js);
-}
-
void DebuggerView::OpenURLFromTab(TabContents* source,
const GURL& url,
const GURL& referrer,
@@ -131,6 +129,26 @@ void DebuggerView::OpenURLFromTab(TabContents* source,
transition);
}
+
+void DebuggerView::SendEventToPage(const std::wstring& name,
+ Value* body) {
+ DictionaryValue msg;
+ msg.SetString(L"type", L"event");
+ msg.SetString(L"event", name);
+ msg.Set(L"body", body);
+
+ std::string json;
+ JSONWriter::Write(&msg, false, &json);
+
+ const std::string js =
+ StringPrintf("DebuggerIPC.onMessageReceived(%s)", json.c_str());
+ if (output_ready_) {
+ ExecuteJavascript(js);
+ } else {
+ pending_events_.push_back(js);
+ }
+}
+
void DebuggerView::ExecuteJavascript(const std::string& js) {
web_contents_->render_view_host()->ExecuteJavascriptInWebFrame(L"",
UTF8ToWide(js));
diff --git a/chrome/browser/debugger/debugger_view.h b/chrome/browser/debugger/debugger_view.h
index 5a7f775..9603777 100644
--- a/chrome/browser/debugger/debugger_view.h
+++ b/chrome/browser/debugger/debugger_view.h
@@ -18,6 +18,7 @@
class DebuggerView;
class DebuggerWindow;
class TabContentsContainerView;
+class Value;
class WebContents;
class DebuggerView : public views::View,
@@ -38,9 +39,6 @@ class DebuggerView : public views::View,
// Called when the window is being closed.
void OnClose();
- // Called when the debugger hits a breakpoint or continues.
- void SetDebuggerBreak(bool is_broken);
-
void SetOutputViewReady();
// Overridden from views::View:
@@ -81,6 +79,10 @@ class DebuggerView : public views::View,
virtual void UpdateTargetURL(TabContents* source, const GURL& url) {}
virtual bool CanBlur() const { return false; }
+ // To pass messages from DebuggerHost to debugger UI.
+ // Note that this method will take ownership of body.
+ void SendEventToPage(const std::wstring& name, Value* body);
+
private:
void ExecuteJavascript(const std::string& js);
@@ -88,6 +90,7 @@ class DebuggerView : public views::View,
WebContents* web_contents_;
TabContentsContainerView* web_container_;
std::vector<std::wstring> pending_output_;
+ std::vector<std::string> pending_events_;
bool output_ready_;
DISALLOW_EVIL_CONSTRUCTORS(DebuggerView);
diff --git a/chrome/browser/debugger/debugger_window.cc b/chrome/browser/debugger/debugger_window.cc
index 731c907..e340bff 100644
--- a/chrome/browser/debugger/debugger_window.cc
+++ b/chrome/browser/debugger/debugger_window.cc
@@ -5,7 +5,7 @@
#include "base/string_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/constrained_window.h"
-#include "chrome/browser/debugger/debugger_shell.h"
+#include "chrome/browser/debugger/debugger_host_impl.h"
#include "chrome/browser/debugger/debugger_view.h"
#include "chrome/browser/debugger/debugger_window.h"
#include "chrome/browser/debugger/debugger_wrapper.h"
@@ -42,7 +42,7 @@ void DebuggerWindow::Show(TabContents* tab) {
view_->OnShow();
debugger_ready_ = true;
debugger_break_ = false;
- DebuggerShell* debugger = new DebuggerShell(this);
+ DebuggerHostImpl* debugger = new DebuggerHostImpl(this);
DebuggerWrapper* wrapper = g_browser_process->debugger_wrapper();
if (!wrapper) {
g_browser_process->InitDebuggerWrapper(0);
@@ -95,7 +95,7 @@ void DebuggerWindow::OutputLine(const std::string &out) {
void DebuggerWindow::OutputPrompt(const std::string& prompt) {
}
-void DebuggerWindow::Start(DebuggerShell* debugger) {
+void DebuggerWindow::Start(DebuggerHost* debugger) {
#ifndef CHROME_DEBUGGER_DISABLED
DebuggerInputOutput::Start(debugger);
#endif
@@ -116,8 +116,6 @@ void DebuggerWindow::SetDebuggerBreak(bool brk) {
if (debugger_break_ != brk) {
debugger_break_ = brk;
if (window_) {
- if (view_)
- view_->SetDebuggerBreak(brk);
window_->UpdateWindowTitle();
if (brk)
window_->Activate();
@@ -126,6 +124,17 @@ void DebuggerWindow::SetDebuggerBreak(bool brk) {
#endif
}
+void DebuggerWindow::CallFunctionInPage(const std::wstring& name,
+ ListValue* argv) {
+ if (view_) {
+ DictionaryValue* body = new DictionaryValue;
+ body->Set(L"arguments", argv);
+ view_->SendEventToPage(name, body);
+ } else {
+ delete argv;
+ }
+}
+
///////////////////////////////////////////////////////////////////
// views::WindowDelegate methods
@@ -160,27 +169,3 @@ bool DebuggerWindow::CanResize() const {
views::View* DebuggerWindow::GetContentsView() {
return view_;
}
-
-///////////////////////////////////////////////////////////////////
-// Overridden from views::TextField::Controller:
-
-void DebuggerWindow::ContentsChanged(views::TextField* sender,
- const std::wstring& new_contents) {
- //
-}
-
-void DebuggerWindow::HandleKeystroke(views::TextField* sender, UINT message,
- TCHAR key, UINT repeat_count,
- UINT flags) {
-#ifndef CHROME_DEBUGGER_DISABLED
- if (key == VK_RETURN) {
- std::wstring txt = sender->GetText();
- if (txt.length()) {
- view_->Output(L"$ " + txt);
- debugger_->ProcessCommand(txt);
- sender->SetText(L"");
- }
- }
-#endif
-}
-
diff --git a/chrome/browser/debugger/debugger_window.h b/chrome/browser/debugger/debugger_window.h
index 5ea1e57..4b980f3 100644
--- a/chrome/browser/debugger/debugger_window.h
+++ b/chrome/browser/debugger/debugger_window.h
@@ -6,16 +6,15 @@
#define CHROME_BROWSER_DEBUGGER_DEBUGGER_WINDOW_H__
#include "chrome/browser/debugger/debugger_io.h"
-#include "chrome/views/text_field.h"
#include "chrome/views/window.h"
#include "chrome/views/window_delegate.h"
class DebuggerView;
+class ListValue;
class TabContents;
class DebuggerWindow : public DebuggerInputOutput,
- public views::WindowDelegate,
- public views::TextField::Controller {
+ public views::WindowDelegate {
public:
DebuggerWindow();
virtual ~DebuggerWindow();
@@ -33,22 +32,20 @@ class DebuggerWindow : public DebuggerInputOutput,
virtual void Output(const std::string& out);
virtual void OutputLine(const std::string& out);
virtual void OutputPrompt(const std::string& prompt);
- virtual void Start(DebuggerShell* debugger);
+ virtual void Start(DebuggerHost* debugger);
virtual void SetDebuggerReady(bool ready);
virtual void SetDebuggerBreak(bool brk);
+ // Note that this method will take ownership of argv.
+ virtual void CallFunctionInPage(const std::wstring& name,
+ ListValue* argv);
+
// views::WindowDelegate methods:
virtual std::wstring GetWindowTitle() const;
virtual void WindowClosing();
virtual bool CanResize() const;
virtual views::View* GetContentsView();
- // Overridden from views::TextField::Controller:
- virtual void ContentsChanged(views::TextField* sender,
- const std::wstring& new_contents);
- virtual void HandleKeystroke(views::TextField* sender, UINT message,
- TCHAR key, UINT repeat_count, UINT flags);
-
private:
views::Window* window_;
DebuggerView* view_;
diff --git a/chrome/browser/debugger/debugger_wrapper.cc b/chrome/browser/debugger/debugger_wrapper.cc
index 9b29903..674b092 100644
--- a/chrome/browser/debugger/debugger_wrapper.cc
+++ b/chrome/browser/debugger/debugger_wrapper.cc
@@ -2,9 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "debugger_wrapper.h"
-#include "debugger_shell.h"
-#include "debugger_io_socket.h"
+#include "chrome/browser/debugger/debugger_wrapper.h"
+#include "chrome/browser/debugger/debugger_shell.h"
+#include "chrome/browser/debugger/debugger_io_socket.h"
+#include "chrome/browser/debugger/debugger_host.h"
DebuggerWrapper::DebuggerWrapper(int port) {
#ifndef CHROME_DEBUGGER_DISABLED
@@ -19,11 +20,11 @@ DebuggerWrapper::DebuggerWrapper(int port) {
DebuggerWrapper::~DebuggerWrapper() {
}
-void DebuggerWrapper::SetDebugger(DebuggerShell* debugger) {
+void DebuggerWrapper::SetDebugger(DebuggerHost* debugger) {
debugger_ = debugger;
}
-DebuggerShell* DebuggerWrapper::GetDebugger() {
+DebuggerHost* DebuggerWrapper::GetDebugger() {
return debugger_.get();
}
diff --git a/chrome/browser/debugger/debugger_wrapper.h b/chrome/browser/debugger/debugger_wrapper.h
index 2befe70..f1e68cc 100644
--- a/chrome/browser/debugger/debugger_wrapper.h
+++ b/chrome/browser/debugger/debugger_wrapper.h
@@ -23,7 +23,7 @@
#include "base/basictypes.h"
#include "base/ref_counted.h"
-class DebuggerShell;
+class DebuggerHost;
class DebuggerWrapper : public base::RefCountedThreadSafe<DebuggerWrapper> {
public:
@@ -31,8 +31,8 @@ class DebuggerWrapper : public base::RefCountedThreadSafe<DebuggerWrapper> {
virtual ~DebuggerWrapper();
- void SetDebugger(DebuggerShell* debugger);
- DebuggerShell* GetDebugger();
+ void SetDebugger(DebuggerHost* debugger);
+ DebuggerHost* GetDebugger();
void DebugMessage(const std::wstring& msg);
@@ -40,7 +40,7 @@ class DebuggerWrapper : public base::RefCountedThreadSafe<DebuggerWrapper> {
void OnDebugDisconnect();
private:
- scoped_refptr<DebuggerShell> debugger_;
+ scoped_refptr<DebuggerHost> debugger_;
};
#endif // CHROME_BROWSER_DEBUGGER_DEBUGGER_INTERFACE_H_
diff --git a/webkit/build/port/port.vcproj b/webkit/build/port/port.vcproj
index 8412ecf..df93fe1 100644
--- a/webkit/build/port/port.vcproj
+++ b/webkit/build/port/port.vcproj
@@ -224,6 +224,31 @@
>
</File>
<File
+ RelativePath="..\..\port\page\inspector\debugger.css"
+ >
+ </File>
+ <File
+ RelativePath="..\..\port\page\inspector\debugger.html"
+ >
+ </File>
+ <File
+ RelativePath="..\..\port\page\inspector\DebuggerConsole.js"
+ >
+ </File>
+ <File
+ RelativePath="..\..\port\page\inspector\DebuggerIPC.js"
+ >
+ </File>
+ <File
+ RelativePath="..\..\port\page\inspector\DebuggerPanel.js"
+ DeploymentContent="true"
+ >
+ </File>
+ <File
+ RelativePath="..\..\port\page\inspector\DebuggerShell.js"
+ >
+ </File>
+ <File
RelativePath="..\..\..\third_party\WebKit\WebCore\inspector\front-end\ElementsPanel.js"
>
</File>
diff --git a/webkit/port/page/inspector/DebuggerConsole.js b/webkit/port/page/inspector/DebuggerConsole.js
new file mode 100644
index 0000000..0ba3b28
--- /dev/null
+++ b/webkit/port/page/inspector/DebuggerConsole.js
@@ -0,0 +1,173 @@
+// Copyright (c) 2006-2008 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.
+
+/**
+ * @fileoverview Helper functions and objects for the JS debugger UI.
+ * @see debugger.html
+ */
+
+/**
+ * Document load listener.
+ */
+function onLoad() {
+ var debuggerConsole = new DebuggerConsole();
+ DebuggerIPC.init(debuggerConsole);
+ DebugShell.initDebugShell(debuggerConsole);
+ debuggerConsole.focusOnCommandLine();
+}
+
+/**
+ * @constructor
+ */
+function DebuggerConsole()
+{
+ this._output = document.getElementById("output");
+
+ var input = document.getElementById("command-line-text");
+ var self = this;
+ input.addEventListener(
+ 'keydown',
+ function(e) {
+ return self._onInputKeyDown(e);
+ },
+ true);
+ input.addEventListener(
+ 'keypress',
+ function(e) {
+ return self._onInputKeyPress(e);
+ },
+ true);
+ this._commandLineInput = input;
+
+ // command object stores command-line history state.
+ this._command = {
+ history: [],
+ history_index: 0,
+ pending: null
+ };
+};
+
+DebuggerConsole.prototype = {
+ /**
+ * Sets focus to command-line-text element.
+ */
+ focusOnCommandLine: function() {
+ this._commandLineInput.focus();
+ },
+
+ /**
+ * Called by chrome code when there's output to display.
+ * @param {string} txt
+ */
+ appendText: function(txt)
+ {
+ this._output.appendChild(document.createTextNode(txt));
+ this._output.appendChild(document.createElement('br'));
+ document.body.scrollTop = document.body.scrollHeight;
+ },
+
+ /**
+ * Called by chrome code to set the current state as to whether the debugger
+ * is stopped at a breakpoint or is running.
+ * @param {boolean} isBroken
+ */
+ setDebuggerBreak: function(isBroken)
+ {
+ var out = this._output;
+ if (isBroken) {
+ out.style.color = "black";
+ this.focusOnCommandLine();
+ } else {
+ out.style.color = "gray";
+ }
+ },
+
+ /**
+ * Execute a debugger command, add it to the command history and display it in
+ * the output window.
+ * @param {string} str
+ */
+ executeCommand: function(str) {
+ this.appendText("$ " + str);
+ // Sends message to DebuggerContents.HandleCommand.
+ if (DebugShell.singleton) {
+ DebugShell.singleton.command(str);
+ } else {
+ this.appendText("FAILED to send the command as DebugShell is null");
+ }
+
+ this._command.history.push(str);
+ this._command.history_index = this._command.history.length;
+ this._command.pending = null;
+ },
+
+
+ /**
+ * Display the previous history item in the given text field.
+ * @param {HTMLInputElement} field
+ */
+ selectPreviousCommand_: function(field) {
+ var command = this._command;
+ if (command.history_index > 0) {
+ // Remember the current field value as a pending command if we're at the
+ // end (it's something the user typed in).
+ if (command.history_index == command.history.length)
+ command.pending = field.value;
+ command.history_index--;
+ field.value = command.history[command.history_index];
+ field.select();
+ }
+ },
+
+ /**
+ * Display the next history item in the given text field.
+ * @param {HTMLInputElement} field
+ */
+ selectNextCommand_: function(field) {
+ var command = this._command;
+ if (command.history_index < command.history.length) {
+ command.history_index++;
+ if (command.history_index == command.history.length) {
+ field.value = command.pending || "";
+ } else {
+ field.value = command.history[command.history_index];
+ }
+ field.select();
+ }
+ },
+
+ /**
+ * command-line-text's onkeydown handler.
+ * @param {KeyboardEvent} e
+ * @return {boolean}
+ */
+ _onInputKeyDown: function (e) {
+ var field = e.target;
+ var key = e.keyCode;
+ if (key == 38) { // up arrow
+ this.selectPreviousCommand_(field);
+ return false;
+ } else if (key == 40) { // down arrow
+ this.selectNextCommand_(field);
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * command-line-text's onkeypress handler.
+ * @param {KeyboardEvent} e
+ * @return {boolean}
+ */
+ _onInputKeyPress: function (e) {
+ var field = e.target;
+ var key = e.keyCode;
+ if (key == 13) { // enter
+ this.executeCommand(field.value);
+ field.value = "";
+ return false;
+ }
+ return true;
+ }
+};
diff --git a/webkit/port/page/inspector/DebuggerIPC.js b/webkit/port/page/inspector/DebuggerIPC.js
new file mode 100644
index 0000000..15835fc
--- /dev/null
+++ b/webkit/port/page/inspector/DebuggerIPC.js
@@ -0,0 +1,109 @@
+// Copyright (c) 2006-2008 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.
+
+/**
+ * @fileoverview Implementation of debugger inter-process communication.
+ * Debugger UI can send messages to the DebuggerHost object living in
+ * Chrome browser process. The messages are either processed by DebuggerHost
+ * itself or trigger IPC message(such as break, evaluate script etc) from
+ * browser process to the renderer where the v8 instance being debugged
+ * will process them.
+ */
+
+var DebuggerIPC = {};
+
+/**
+ * Set up default debugger UI.
+ * @param {DebuggerPanel|DebuggerConsole} debuggerUI
+ */
+DebuggerIPC.init = function(debuggerUI) {
+ DebuggerIPC.debuggerUI = debuggerUI;
+}
+
+/**
+ * Sends JSON message to DebuggerHost.
+ * @param {Array.<Object>} nameAndArguments
+ */
+DebuggerIPC.sendMessage = function(nameAndArguments) {
+ //alert("DebuggerIPC.sendMessage " + nameAndArguments);
+ dprint("DebuggerIPC.callMethod([" + nameAndArguments + "])");
+ // convert all arguments to strings
+ // TODO(yurys): extend chrome.send to accept array of any value
+ // objects not only strings
+ for (var i = 0; i < nameAndArguments.length; i++) {
+ if (typeof nameAndArguments[i] != "string") {
+ nameAndArguments[i] = "" + nameAndArguments[i];
+ }
+ }
+
+ chrome.send("DebuggerHostMessage", nameAndArguments);
+};
+
+/**
+ * Handles messages from DebuggerHost.
+ * @param {Object} msg An object representing the message.
+ */
+DebuggerIPC.onMessageReceived = function(msg) {
+ //alert("DebuggerIPC.onMessageReceived " + msg.event);
+ var ui = DebuggerIPC.debuggerUI;
+ dprint("onMessageReceived: " + (msg && msg.event));
+ if (msg.type == "event") {
+ if (msg.event == "setDebuggerBreak") {
+ var val = msg.body.argument;
+ ui.setDebuggerBreak(val);
+ } else if (msg.event == "appendText") {
+ var text = msg.body.text;
+ ui.appendText(text);
+ } else if (msg.event == "focusOnCommandLine") {
+ dprint("focusOnCommandLine event received");
+ // messages to DebugShell
+ } else if (msg.event == "initDebugShell") {
+ // DebugShell.initDebugShell();
+ dprint(msg.event + " done");
+ } else if (msg.event == "on_attach") {
+ if (DebugShell.singleton) {
+ var args = msg.body.arguments;
+ if (!args || args.length != 1) {
+ dprint(msg.event + " failed: invalid arguments");
+ return;
+ }
+ var title = args[0];
+ DebugShell.singleton.on_attach(title);
+ dprint(msg.event + " done");
+ } else {
+ dprint(msg.event + " failed: DebugShell.singleton == null");
+ }
+ } else if (msg.event == "on_disconnect") {
+ if (DebugShell.singleton) {
+ DebugShell.singleton.on_disconnect();
+ dprint(msg.event + " done");
+ } else {
+ dprint(msg.event + " failed: DebugShell.singleton == null");
+ }
+ } else if (msg.event == "exit") {
+ if (DebugShell.singleton) {
+ DebugShell.singleton.exit();
+ dprint(msg.event + " done");
+ } else {
+ dprint(msg.event + " failed: DebugShell.singleton == null");
+ }
+ } else if (msg.event == "response") {
+ if (DebugShell.singleton) {
+ var args = msg.body.arguments;
+ if (!args || args.length != 1) {
+ dprint(msg.event + " failed: invalid argument");
+ return;
+ }
+ DebugShell.singleton.response(args[0]);
+ dprint(msg.event + " done");
+ } else {
+ ui.appendText(msg.event + " failed: DebugShell.singleton == null");
+ }
+ } else {
+ ui.appendText("Unknown event: " + msg.event);
+ }
+ } else {
+ ui.appendText("Unknown message type: " + msg.type);
+ }
+};
diff --git a/webkit/port/page/inspector/DebuggerPanel.js b/webkit/port/page/inspector/DebuggerPanel.js
new file mode 100644
index 0000000..bb059ac
--- /dev/null
+++ b/webkit/port/page/inspector/DebuggerPanel.js
@@ -0,0 +1,201 @@
+// Copyright (c) 2006-2008 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.
+
+/**
+ * @fileoverview WebInspector panel representing command line debugger.
+ */
+
+/**
+ * Command line debugger panel.
+ * @constructor
+ * @extends {WebInspector.Panel}
+ */
+WebInspector.DebuggerPanel = function()
+{
+ WebInspector.Panel.call(this);
+
+ this.contentElement = document.createElement("div");
+ this.contentElement.id = "debugger-content";
+
+ var table = document.createElement("table");
+ table.id = 'outer';
+ var tr = document.createElement("tr");
+ this._output = document.createElement("td");
+ this._output.id = "output";
+ this._output.valign = "bottom";
+ this.appendText("Chrome JavaScript Debugger");
+ this.appendText("Type 'help' for a list of commands.");
+
+ tr.appendChild(this._output);
+ table.appendChild(tr);
+ this.contentElement.appendChild(table);
+
+
+ var commandLine = document.createElement("div");
+ commandLine.id = "command-line";
+ var input = document.createElement("input");
+ input.id = "command-line-text";
+ input.addEventListener('keydown', this._onInputKeyDown.bind(this), true);
+ input.addEventListener('keypress', this._onInputKeyPress.bind(this), true);
+ input.type = "text";
+ this.commandLineInput_ = input;
+
+ commandLine.appendChild(input);
+ this.contentElement.appendChild(commandLine);
+
+ // command object stores command-line history state.
+ this._command = {
+ history: [],
+ history_index: 0,
+ pending: null
+ };
+
+ this.element.appendChild(this.contentElement);
+ p('DebuggerPanel created');
+};
+
+WebInspector.DebuggerPanel.prototype = {
+ toolbarItemClass: "debugger",
+
+ get toolbarItemLabel()
+ {
+ return "Debugger"; //WebInspector.UIString("Debugger");
+ },
+
+ show: function()
+ {
+ WebInspector.Panel.prototype.show.call(this);
+ this.focusOnCommandLine();
+ },
+
+ /**
+ * Sets focus to command-line-text element.
+ */
+ focusOnCommandLine: function()
+ {
+ if (this.visible) {
+ this.commandLineInput_.focus();
+ }
+ },
+
+ /**
+ * Called by chrome code when there's output to display.
+ * @param {string} txt
+ */
+ appendText: function(txt)
+ {
+ this._output.appendChild(document.createTextNode(txt));
+ this._output.appendChild(document.createElement('br'));
+ },
+
+ /**
+ * Called by chrome code to set the current state as to whether the debugger
+ * is stopped at a breakpoint or is running.
+ * @param {boolean} isBroken
+ */
+ setDebuggerBreak: function(isBroken)
+ {
+ var out = this._output;
+ if (isBroken) {
+ out.style.color = "black";
+ this.focusOnCommandLine();
+ } else {
+ out.style.color = "gray";
+ }
+ },
+
+ /**
+ * Execute a debugger command, add it to the command history and display
+ * it in the output window.
+ * @param {string} str
+ */
+ executeCommand: function(str)
+ {
+ this.appendText("$ " + str);
+ // Sends message to DebuggerContents.HandleCommand.
+ if (DebugShell.singleton) {
+ DebugShell.singleton.command(str);
+ } else {
+ this.appendText("FAILED to send the command as DebugShell is null");
+ }
+
+ this._command.history.push(str);
+ this._command.history_index = this._command.history.length;
+ this._command.pending = null;
+ },
+
+ /**
+ * Display the previous history item in the given text field.
+ * @param {HTMLInputElement} field
+ */
+ _selectPreviousCommand: function(field)
+ {
+ var command = this._command;
+ if (command.history_index > 0) {
+ // Remember the current field value as a pending command if we're at the
+ // end (it's something the user typed in).
+ if (command.history_index == command.history.length)
+ command.pending = field.value;
+ command.history_index--;
+ field.value = command.history[command.history_index];
+ field.select();
+ }
+ },
+
+ /**
+ * Display the next history item in the given text field.
+ * @param {HTMLInputElement} field
+ */
+ _selectNextCommand: function(field)
+ {
+ var command = this._command;
+ if (command.history_index < command.history.length) {
+ command.history_index++;
+ if (command.history_index == command.history.length) {
+ field.value = command.pending || "";
+ } else {
+ field.value = command.history[command.history_index];
+ }
+ field.select();
+ }
+ },
+
+ /**
+ * command-line-text's onkeydown handler.
+ * @param {KeyboardEvent} e
+ * @return {boolean}
+ */
+ _onInputKeyDown: function (e)
+ {
+ var field = e.target;
+ var key = e.keyCode;
+ if (key == 38) { // up arrow
+ this._selectPreviousCommand(field);
+ return false;
+ } else if (key == 40) { // down arrow
+ this._selectNextCommand(field);
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * command-line-text's onkeypress handler.
+ * @param {KeyboardEvent} e
+ * @return {boolean}
+ */
+ _onInputKeyPress: function (e)
+ {
+ var field = e.target;
+ var key = e.keyCode;
+ if (key == 13) { // enter
+ this.executeCommand(field.value);
+ field.value = "";
+ return false;
+ }
+ return true;
+ }
+};
+
+WebInspector.DebuggerPanel.prototype.__proto__ = WebInspector.Panel.prototype;
diff --git a/webkit/port/page/inspector/DebuggerShell.js b/webkit/port/page/inspector/DebuggerShell.js
new file mode 100644
index 0000000..804bb9a
--- /dev/null
+++ b/webkit/port/page/inspector/DebuggerShell.js
@@ -0,0 +1,1327 @@
+// Copyright (c) 2006-2008 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.
+
+/**
+ * @fileoverview Shell objects and global helper functions for Chrome
+ * automation shell / debugger. This file is loaded into the global namespace
+ * of the interactive shell, so users can simply call global functions
+ * directly.
+ */
+
+// TODO(erikkay): look into how this can be split up into multiple files
+// It's currently loaded explicitly by Chrome, so maybe I need an "include"
+// or "source" builtin to allow a core source file to reference multiple
+// sub-files.
+
+/**
+ * Sequence number of the DebugCommand.
+ */
+DebugCommand.next_seq_ = 0;
+
+/**
+ * Command messages to be sent to the debugger.
+ * @constructor
+ */
+function DebugCommand(str) {
+ this.command = undefined;
+ // first, strip off of the leading word as the command
+ var argv = str.split(' ');
+ this.user_command = argv.shift();
+ // the rest of the string is argv to the command
+ str = argv.join(' ');
+ if (DebugCommand.aliases[this.user_command])
+ this.user_command = DebugCommand.aliases[this.user_command];
+ if (this.parseArgs_(str) == 1)
+ this.type = "request";
+ if (this.command == undefined)
+ this.command = this.user_command;
+};
+
+// Mapping of some control characters to avoid the \uXXXX syntax for most
+// commonly used control cahracters.
+const ctrlCharMap_ = {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+};
+
+// Regular expression matching ", \ and control characters (0x00 - 0x1F)
+// globally.
+const ctrlCharMatch_ = /["\\\\\x00-\x1F]/g;
+
+/**
+ * Convert a String to its JSON representation.
+ * @param {String} value - String to be converted
+ * @return {String} JSON formatted String
+ */
+DebugCommand.stringToJSON = function(value) {
+ // Check for" , \ and control characters (0x00 - 0x1F).
+ if (ctrlCharMatch_.test(value)) {
+ // Replace ", \ and control characters (0x00 - 0x1F).
+ return '"' + value.replace(ctrlCharMatch_, function (char) {
+ // Use charmap if possible.
+ var mapped = ctrlCharMap_[char];
+ if (mapped) return mapped;
+ mapped = char.charCodeAt();
+ // Convert control character to unicode escape sequence.
+ var dig1 = (Math.floor(mapped / 16));
+ var dig2 = (mapped % 16)
+ return '\\u00' + dig1.toString(16) + dig2.toString(16);
+ })
+ + '"';
+ }
+
+ // Simple string with no special characters.
+ return '"' + value + '"';
+};
+
+/**
+ * @return {bool} True if x is an integer.
+ */
+DebugCommand.isInt = function(x) {
+ var y = parseInt(x);
+ if (isNaN(y))
+ return false;
+ return x == y && x.toString() == y.toString();
+};
+
+/**
+ * @return {float} log base 10 of num
+ */
+DebugCommand.log10 = function(num) {
+ return Math.log(num)/Math.log(10);
+};
+
+/**
+ * Take an object and encode it (non-recursively) as a JSON dict.
+ * @param {Object} obj - object to encode
+ */
+DebugCommand.toJSON = function(obj) {
+ // TODO(erikkay): use a real JSON library
+ var json = '{';
+ for (var key in obj) {
+ if (json.length > 1)
+ json += ",";
+ var val = obj[key];
+ if (!DebugCommand.isInt(val)) {
+ val = DebugCommand.stringToJSON(val.toString());
+ }
+ json += '"' + key + '":' + val;
+ }
+ json += '}';
+ return json;
+};
+
+/**
+ * Encode the DebugCommand object into the V8 debugger JSON protocol format.
+ */
+DebugCommand.prototype.toJSONProtocol = function() {
+ // TODO(erikkay): use a real JSON library
+ var json = '{';
+ json += '"seq":"' + this.seq;
+ json += '","type":"' + this.type;
+ json += '","command":"' + this.command + '"';
+ if (this.arguments) {
+ json += ',"arguments":' + DebugCommand.toJSON(this.arguments);
+ }
+ json += '}'
+ return json;
+}
+
+/**
+ * Encode the contents of this message and send it to the debugger.
+ * @param {Object} tab - tab being debugged. This is an internal
+ * Chrome object.
+ */
+DebugCommand.prototype.sendToDebugger = function(tab) {
+ this.seq = DebugCommand.next_seq_++;
+ str = this.toJSONProtocol();
+ dprint("sending: " + str);
+ tab.sendToDebugger(str);
+};
+
+DebugCommand.trim = function(str) {
+ return str.replace(/^\s*/, '').replace(/\s*$/, '');
+};
+
+/**
+ * Strip off a trailing parameter after a ':'. As the identifier for the
+ * source can contain ':' characters (e.g. 'http://www....) something after
+ * a ':' is only considered a parameter if it is numeric.
+ * @return {Array} two element array, the trimmed string and the parameter,
+ * or -1 if no parameter
+ */
+DebugCommand.stripTrailingParameter = function(str, opt_separator) {
+ var sep = opt_separator || ':';
+ var index = str.lastIndexOf(sep);
+ // If a separator character if found strip if numeric.
+ if (index != -1) {
+ var value = parseInt(str.substring(index + 1, str.length), 10);
+ if (isNaN(value) || value < 0) {
+ return [str, -1];
+ }
+ str = str.substring(0, index);
+ return [str, value];
+ }
+ return [str, -1];
+};
+
+/**
+ * Format source and location strings based on source location input data.
+ * @param {Object} script - script information object
+ * @param {String} source - source code for the current location
+ * @param {int} line - line number (0-based)
+ * @param {String} func - function name
+ * @return {array} [location(string), source line(string), line number(int)]
+ */
+DebugCommand.getSourceLocation = function(script, source, line, func) {
+ // source line is 0-based, we present as 1-based
+ line++;
+
+ // TODO(erikkay): take column into account as well
+ if (source)
+ source = "" + line + ": " + source;
+ var location = '';
+ if (func) {
+ location = func + ", ";
+ }
+ location += script ? script.name : '[no source]';
+ return [location, source, line];
+};
+
+/**
+ * Aliases for debugger commands.
+ */
+DebugCommand.aliases = {
+ 'b': 'break',
+ 'bi': 'break_info',
+ 'br': 'break',
+ 'bt': 'backtrace',
+ 'c': 'continue',
+ 'f': 'frame',
+ 'h': 'help',
+ '?': 'help',
+ 'ls': 'source',
+ 'n': 'next',
+ 'p': 'print',
+ 's': 'step',
+ 'so': 'stepout',
+};
+
+/**
+ * Parses arguments to "args" and "locals" command, and initializes
+ * the underlying DebugCommand (which is a frame request).
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+DebugCommand.prototype.parseArgsAndLocals_ = function(str) {
+ this.command = "frame";
+ return str.length ? -1 : 1;
+};
+
+/**
+ * Parses arguments to "break_info" command, and executes it.
+ * "break_info" has an optional argument, which is the breakpoint
+ * identifier.
+ * @see DebugCommand.commands
+ * @param {string} str - The arguments to be parsed.
+ * @return -1 for usage error, 0 for success
+ */
+DebugCommand.prototype.parseBreakInfo_ = function(str) {
+ this.type = "shell";
+
+ // Array of breakpoints to be printed by this command
+ // (default to all breakpoints)
+ var breakpointsToPrint = shell_.breakpoints;
+
+ if (str.length > 0) {
+ // User specified an invalid breakpoint (not a number)
+ if (!str.match(/^\s*\d+\s*$/))
+ return -1; // invalid usage
+
+ // Check that the specified breakpoint identifier exists
+ var id = parseInt(str);
+ var info = shell_.breakpoints[id];
+ if (!info) {
+ print("Error: Invalid breakpoint");
+ return 0; // success (of sorts)
+ }
+ breakpointsToPrint = [info];
+ } else {
+ // breakpointsToPrint.length isn't accurate, because of
+ // deletions
+ var num_breakpoints = 0;
+ for (var i in breakpointsToPrint) num_breakpoints++;
+
+ print("Num breakpoints: " + num_breakpoints);
+ }
+
+ DebugShell.printBreakpoints_(breakpointsToPrint);
+
+ return 0; // success
+}
+
+/**
+ * Parses arguments to "step" command.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+DebugCommand.prototype.parseStep_ = function(str, opt_stepaction) {
+ this.command = "continue";
+ action = opt_stepaction || "in";
+ this.arguments = {"stepaction" : action}
+ if (str.length) {
+ count = parseInt(str);
+ if (count > 0) {
+ this.arguments["stepcount"] = count;
+ } else {
+ return -1;
+ }
+ }
+ return 1;
+};
+
+/**
+ * Parses arguments to "step" command.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+DebugCommand.prototype.parseStepOut_ = function(str) {
+ return this.parseStep_(str, "out");
+};
+
+/**
+ * Parses arguments to "next" command.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+DebugCommand.prototype.parseNext_ = function(str) {
+ return this.parseStep_(str, "next");
+};
+
+/**
+ * Parse the arguments to "print" command.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return 1 - always succeeds
+ */
+DebugCommand.prototype.parsePrint_ = function(str) {
+ this.command = "evaluate";
+ this.arguments = { "expression" : str };
+ // If the page is in the running state, then we force the expression to
+ // evaluate in the global context to avoid evaluating in a random context.
+ if (shell_.running)
+ this.arguments["global"] = true;
+ return 1;
+};
+
+/**
+ * Handle the response to a "print" command and display output to user.
+ * @param {Object} msg - the V8 debugger response object
+ */
+DebugCommand.responsePrint_ = function(msg) {
+ body = msg["body"];
+ if (body['text'] != undefined) {
+ print(body['text']);
+ } else {
+ // TODO(erikkay): is "text" ever not set?
+ print("can't print response");
+ }
+};
+
+/**
+ * Parses arguments to "break" command. See DebugCommand.commands below
+ * for syntax details.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return -1 for usage error, 1 for success, 0 for handled internally
+ */
+DebugCommand.prototype.parseBreak_ = function(str) {
+ function stripTrailingParameter() {
+ var ret = DebugCommand.stripTrailingParameter(str, ':');
+ str = ret[0];
+ return ret[1];
+ }
+
+ if (str.length == 0) {
+ this.command = "break";
+ return 1;
+ } else {
+ var parts = str.split(/\s+/);
+ var condition = null;
+ if (parts.length > 1) {
+ str = parts.shift();
+ condition = parts.join(" ");
+ }
+
+ this.command = "setbreakpoint";
+
+ // Locate ...[:line[:column]] if present.
+ var line = -1;
+ var column = -1;
+ line = stripTrailingParameter();
+ if (line != -1) {
+ line -= 1;
+ var l = stripTrailingParameter();
+ if (l != -1) {
+ column = line;
+ line = l - 1;
+ }
+ }
+
+ if (line == -1 && column == -1) {
+ this.arguments = { 'type' : 'function',
+ 'target' : str };
+ } else {
+ var script = shell_.matchScript(str, line);
+ if (script) {
+ this.arguments = { 'type' : 'script',
+ 'target' : script.name };
+ } else {
+ this.arguments = { 'type' : 'function',
+ 'target' : str };
+ }
+ this.arguments.line = line;
+ if (column != -1)
+ this.arguments.position = column;
+ }
+ if (condition)
+ this.arguments.condition = condition;
+ }
+ return 1;
+};
+
+/**
+ * Handle the response to a "break" command and display output to user.
+ * @param {Object} msg - the V8 debugger response object
+ */
+DebugCommand.responseBreak_ = function(msg) {
+ var info = new BreakpointInfo(
+ parseInt(msg.body.breakpoint),
+ msg.command.arguments.type,
+ msg.command.arguments.target,
+ msg.command.arguments.line,
+ msg.command.arguments.position,
+ msg.command.arguments.condition);
+ shell_.addedBreakpoint(info);
+};
+
+/**
+ * Parses arguments to "backtrace" command. See DebugCommand.commands below
+ * for syntax details.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+ DebugCommand.prototype.parseBacktrace_ = function(str) {
+ if (str.length > 0) {
+ var parts = str.split(/\s+/);
+ var non_empty_parts = parts.filter(function(s) { return s.length > 0; });
+ // We need exactly two arguments.
+ if (non_empty_parts.length != 2) {
+ return -1;
+ }
+ var from = parseInt(non_empty_parts[0], 10);
+ var to = parseInt(non_empty_parts[1], 10);
+ // The two arguments have to be integers.
+ if (from != non_empty_parts[0] || to != non_empty_parts[1]) {
+ return -1;
+ }
+ this.arguments = { 'fromFrame': from, 'toFrame': to + 1 };
+ } else {
+ // Default to fetching the first 10 frames.
+ this.arguments = { 'fromFrame': 0, 'toFrame': 10 };
+ }
+ return 1;
+};
+
+/**
+ * Handle the response to a "backtrace" command and display output to user.
+ * @param {Object} msg - the V8 debugger response object
+ */
+DebugCommand.responseBacktrace_ = function(msg) {
+ body = msg["body"];
+ if (body && body.totalFrames) {
+ print('Frames #' + body.fromFrame + ' to #' + (body.toFrame - 1) +
+ ' of ' + body.totalFrames + ":");
+ for (var i = 0; i < body.frames.length; i++) {
+ print(body.frames[i].text);
+ }
+ } else {
+ print("unimplemented (sorry)");
+ }
+};
+
+
+/**
+ * Parses arguments to "clear" command. See DebugCommand.commands below
+ * for syntax details.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+DebugCommand.prototype.parseClearCommand_ = function(str) {
+ this.command = "clearbreakpoint";
+ if (str.length > 0) {
+ var i = parseInt(str, 10);
+ if (i != str) {
+ return -1;
+ }
+ this.arguments = { 'breakpoint': i };
+ }
+ return 1;
+}
+
+/**
+ * Handle the response to a "clear" command and display output to user.
+ * @param {Object} msg - the V8 debugger response object
+ */
+DebugCommand.responseClear_ = function(msg) {
+ shell_.clearedBreakpoint(parseInt(msg.command.arguments.breakpoint));
+}
+
+
+/**
+ * Parses arguments to "continue" command. See DebugCommand.commands below
+ * for syntax details.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+DebugCommand.prototype.parseContinueCommand_ = function(str) {
+ this.command = "continue";
+ if (str.length > 0) {
+ return -1;
+ }
+ return 1;
+}
+
+/**
+ * Parses arguments to "frame" command. See DebugCommand.commands below
+ * for syntax details.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+DebugCommand.prototype.parseFrame_ = function(str) {
+ if (str.length > 0) {
+ var i = parseInt(str, 10);
+ if (i != str) {
+ return -1;
+ }
+ this.arguments = { 'number': i };
+ }
+ return 1;
+};
+
+/**
+ * Handle the response to a "frame" command and display output to user.
+ * @param {Object} msg - the V8 debugger response object
+ */
+DebugCommand.responseFrame_ = function(msg) {
+ body = msg.body;
+ loc = DebugCommand.getSourceLocation(body.func.script,
+ body.sourceLineText, body.line, body.func.name);
+ print("#" + (body.index <= 9 ? '0' : '') + body.index + " " + loc[0]);
+ print(loc[1]);
+ shell_.current_frame = body.index;
+ shell_.current_line = loc[2];
+ shell_.current_script = body.func.script;
+};
+
+/**
+ * Handle the response to a "args" command and display output to user.
+ * @param {Object} msg - the V8 debugger response object (for "frame" command)
+ */
+DebugCommand.responseArgs_ = function(msg) {
+ DebugCommand.printVariables_(msg.body.arguments);
+}
+
+/**
+ * Handle the response to a "locals" command and display output to user.
+ * @param {Object} msg - the V8 debugger response object (for "frame" command)
+ */
+DebugCommand.responseLocals_ = function(msg) {
+ DebugCommand.printVariables_(msg.body.locals);
+}
+
+DebugCommand.printVariables_ = function(variables) {
+ for (var i = 0; i < variables.length; i++) {
+ print(variables[i].name + " = " +
+ DebugCommand.toPreviewString_(variables[i].value));
+ }
+}
+
+DebugCommand.toPreviewString_ = function(value) {
+ // TODO(ericroman): pretty print arrays and objects, recursively.
+ // TODO(ericroman): truncate length of preview if too long?
+ if (value.type == "string") {
+ // Wrap the string in quote marks and JS-escape
+ return DebugCommand.stringToJSON(value.text);
+ }
+ return value.text;
+}
+
+/**
+ * Parses arguments to "scripts" command.
+ * @see DebugCommand.commands
+ * @param {string} str - The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+DebugCommand.prototype.parseScripts_ = function(str) {
+ return 1
+};
+
+/**
+ * Handle the response to a "scripts" command and display output to user.
+ * @param {Object} msg - the V8 debugger response object
+ */
+DebugCommand.responseScripts_ = function(msg) {
+ scripts = msg.body;
+ shell_.scripts = [];
+ for (var i in scripts) {
+ var script = scripts[i];
+
+ // Add this script to the internal list of scripts.
+ shell_.scripts.push(script);
+
+ // Print result if this response was the result of a user command.
+ if (msg.command.from_user) {
+ var name = script.name;
+ if (name) {
+ if (script.lineOffset > 0) {
+ print(name + " (lines " + script.lineOffset + "-" +
+ (script.lineOffset + script.lineCount - 1) + ")");
+ } else {
+ print(name + " (lines " + script.lineCount + ")");
+ }
+ } else {
+ // For unnamed scripts (typically eval) display some source.
+ var sourceStart = script.sourceStart;
+ if (sourceStart.length > 40)
+ sourceStart = sourceStart.substring(0, 37) + '...';
+ print("[unnamed] (source:\"" + sourceStart + "\")");
+ }
+ }
+ }
+};
+
+/**
+ * Parses arguments to "source" command.
+ * @see DebugCommand.commands
+ * @param {string} str - The arguments to be parsed.
+ * @return -1 for usage error, 1 for success
+ */
+DebugCommand.prototype.parseSource_ = function(str) {
+ this.arguments = {};
+ if (this.current_frame > 0)
+ this.arguments.frame = this.current_frame;
+ if (str.length) {
+ var args = str.split(" ");
+ if (args.length == 1) {
+ // with 1 argument n, we print 10 lines starting at n
+ var num = parseInt(args[0]);
+ if (num > 0) {
+ this.arguments.fromLine = num - 1;
+ this.arguments.toLine = this.arguments.fromLine + 10;
+ } else {
+ return -1;
+ }
+ } else if (args.length == 2) {
+ // with 2 arguments x and y, we print from line x to line x + y
+ var from = parseInt(args[0]);
+ var len = parseInt(args[1]);
+ if (from > 0 && len > 0) {
+ this.arguments.fromLine = from - 1;
+ this.arguments.toLine = this.arguments.fromLine + len;
+ } else {
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+ if (this.arguments.fromLine < 0)
+ return -1;
+ if (this.arguments.toLine <= this.arguments.fromLine)
+ return -1;
+ } else if (shell_.current_line > 0) {
+ // with no arguments, we print 11 lines with the current line as the center
+ this.arguments.fromLine =
+ Math.max(0, shell_.current_line - 6);
+ this.arguments.toLine = this.arguments.fromLine + 11;
+ }
+ return 1;
+};
+
+/**
+ * Handle the response to a "source" command and display output to user.
+ * @param {Object} msg - the V8 debugger response object
+ */
+DebugCommand.responseSource_ = function(msg) {
+ var body = msg.body;
+ var from_line = parseInt(body.fromLine) + 1;
+ var source = body.source;
+ var lines = source.split('\n');
+ var maxdigits = 1 + Math.floor(DebugCommand.log10(from_line + lines.length))
+ for (var num in lines) {
+ // there's an extra newline at the end
+ if (num >= (lines.length - 1) && lines[num].length == 0)
+ break;
+ spacer = maxdigits - (1 + Math.floor(DebugCommand.log10(from_line)))
+ var line = "";
+ if (from_line == shell_.current_line) {
+ for (var i = 0; i < (maxdigits + 2); i++)
+ line += ">";
+ } else {
+ for (var i = 0; i < spacer; i++)
+ line += " ";
+ line += from_line + ": ";
+ }
+ line += lines[num];
+ print(line);
+ from_line++;
+ }
+};
+
+/**
+ * Parses arguments to "help" command. See DebugCommand.commands below
+ * for syntax details.
+ * @see DebugCommand.commands
+ * @param {string} str The arguments to be parsed.
+ * @return 0 for handled internally
+ */
+DebugCommand.parseHelp_ = function(str) {
+ DebugCommand.help(str);
+ return 0;
+};
+
+/**
+ * Takes argument and evaluates it in the context of the shell to allow commands
+ * to be escaped to the outer shell. Used primarily for development purposes.
+ * @see DebugCommand.commands
+ * @param {string} str The expression to be evaluated
+ * @return 0 for handled internally
+ */
+DebugCommand.parseShell_ = function(str) {
+ print(eval(str));
+ return 0;
+}
+
+DebugCommand.parseShellDebug_ = function(str) {
+ shell_.debug = !shell_.debug;
+ if (shell_.debug) {
+ print("shell debugging enabled");
+ } else {
+ print("shell debugging disabled");
+ }
+ return 0;
+}
+
+/**
+ * Parses a user-entered command string.
+ * @param {string} str The arguments to be parsed.
+ */
+DebugCommand.prototype.parseArgs_ = function(str) {
+ if (str.length)
+ str = DebugCommand.trim(str);
+ var cmd = DebugCommand.commands[this.user_command];
+ if (cmd) {
+ var parse = cmd['parse'];
+ if (parse == undefined) {
+ print('>>>can\'t find parse func for ' + this.user_command);
+ this.type = "error";
+ } else {
+ var ret = parse.call(this, str);
+ if (ret > 0) {
+ // Command gererated a debugger request.
+ this.type = "request";
+ } else if (ret == 0) {
+ // Command handeled internally.
+ this.type = "handled";
+ } else if (ret < 0) {
+ // Command error.
+ this.type = "handled";
+ DebugCommand.help(this.user_command);
+ }
+ }
+ } else {
+ this.type = "handled";
+ print('unknown command: ' + this.user_command);
+ DebugCommand.help();
+ }
+};
+
+/**
+ * Displays command help or all help.
+ * @param {string} opt_str Which command to print help for.
+ */
+DebugCommand.help = function(opt_str) {
+ if (opt_str) {
+ var cmd = DebugCommand.commands[opt_str];
+ var usage = cmd.usage;
+ print('usage: ' + usage);
+ // Print additional details for the command.
+ if (cmd.help) {
+ print(cmd.help);
+ }
+ } else {
+ if (shell_.running) {
+ print('Status: page is running');
+ } else {
+ print('Status: page is paused');
+ }
+ print('Available commands:');
+ for (var key in DebugCommand.commands) {
+ var cmd = DebugCommand.commands[key];
+ if (!cmd['hidden'] && (!shell_.running || cmd['while_running'])) {
+ var usage = cmd.usage;
+ print(' ' + usage);
+ }
+ }
+ }
+};
+
+/**
+ * Valid commands, their argument parser and their associated usage text.
+ */
+DebugCommand.commands = {
+ 'args': { 'parse': DebugCommand.prototype.parseArgsAndLocals_,
+ 'usage': 'args',
+ 'help': 'summarize the arguments to the current function.',
+ 'response': DebugCommand.responseArgs_ },
+ 'break': { 'parse': DebugCommand.prototype.parseBreak_,
+ 'response': DebugCommand.responseBreak_,
+ 'usage': 'break [location] <condition>',
+ 'help': 'location is one of <function> | <script:function> | <script:line> | <script:line:pos>',
+ 'while_running': true },
+ 'break_info': { 'parse': DebugCommand.prototype.parseBreakInfo_,
+ 'usage': 'break_info [breakpoint #]',
+ 'help': 'list the current breakpoints, or the details on a single one',
+ 'while_running': true },
+ 'backtrace': { 'parse': DebugCommand.prototype.parseBacktrace_,
+ 'response': DebugCommand.responseBacktrace_,
+ 'usage': 'backtrace [from frame #] [to frame #]' },
+ 'clear': { 'parse': DebugCommand.prototype.parseClearCommand_,
+ 'response': DebugCommand.responseClear_,
+ 'usage': 'clear <breakpoint #>',
+ 'while_running': true },
+ 'continue': { 'parse': DebugCommand.prototype.parseContinueCommand_,
+ 'usage': 'continue' },
+ 'frame': { 'parse': DebugCommand.prototype.parseFrame_,
+ 'response': DebugCommand.responseFrame_,
+ 'usage': 'frame <frame #>' },
+ 'help': { 'parse': DebugCommand.parseHelp_,
+ 'usage': 'help [command]',
+ 'while_running': true },
+ 'locals': { 'parse': DebugCommand.prototype.parseArgsAndLocals_,
+ 'usage': 'locals',
+ 'help': 'summarize the local variables for current frame',
+ 'response': DebugCommand.responseLocals_ },
+ 'next': { 'parse': DebugCommand.prototype.parseNext_,
+ 'usage': 'next' } ,
+ 'print': { 'parse': DebugCommand.prototype.parsePrint_,
+ 'response': DebugCommand.responsePrint_,
+ 'usage': 'print <expression>',
+ 'while_running': true },
+ 'scripts': { 'parse': DebugCommand.prototype.parseScripts_,
+ 'response': DebugCommand.responseScripts_,
+ 'usage': 'scripts',
+ 'while_running': true },
+ 'source': { 'parse': DebugCommand.prototype.parseSource_,
+ 'response': DebugCommand.responseSource_,
+ 'usage': 'source [from line] | [<from line> <num lines>]' },
+ 'step': { 'parse': DebugCommand.prototype.parseStep_,
+ 'usage': 'step' },
+ 'stepout': { 'parse': DebugCommand.prototype.parseStepOut_,
+ 'usage': 'stepout' },
+ // local eval for debugging - remove this later
+ 'shell': { 'parse': DebugCommand.parseShell_,
+ 'usage': 'shell <expression>',
+ 'while_running': true,
+ 'hidden': true },
+ 'shelldebug': { 'parse': DebugCommand.parseShellDebug_,
+ 'usage': 'shelldebug',
+ 'while_running': true,
+ 'hidden': true },
+};
+
+
+/**
+ * Debug shell using the new JSON protocol
+ * @param {Object} tab - which tab is to be debugged. This is an internal
+ * Chrome object.
+ * @constructor
+ */
+function DebugShell(tab) {
+ this.tab = tab;
+ this.tab.attach();
+ this.ready = true;
+ this.running = true;
+ this.current_command = undefined;
+ this.pending_commands = [];
+ // The auto continue flag is used to indicate whether the JavaScript execution
+ // should automatically continue after a break event and the processing of
+ // pending commands. This is used to make it possible for the user to issue
+ // commands, e.g. setting break points, without making an explicit break. In
+ // this case the debugger will silently issue a forced break issue the command
+ // and silently continue afterwards.
+ this.auto_continue = false;
+ this.debug = false;
+ this.last_msg = undefined;
+ this.current_line = -1;
+ this.current_pos = -1;
+ this.current_frame = 0;
+ this.current_script = undefined;
+ this.scripts = [];
+
+ // Mapping of breakpoints id --> info.
+ // Must use numeric keys.
+ this.breakpoints = [];
+};
+
+DebugShell.prototype.set_ready = function(ready) {
+ if (ready != this.ready) {
+ this.ready = ready;
+ ChromeNode.setDebuggerReady(this.ready);
+ }
+};
+
+DebugShell.prototype.set_running = function(running) {
+ if (running != this.running) {
+ this.running = running;
+ ChromeNode.setDebuggerBreak(!this.running);
+ }
+};
+
+/**
+ * Execute a constructed DebugCommand object if possible, otherwise pend.
+ * @param cmd {DebugCommand} - command to execute
+ */
+DebugShell.prototype.process_command = function(cmd) {
+ dprint("Running: " + (this.running ? "yes" : "no"));
+
+ // The "break" commands needs to be handled seperatly
+ if (cmd.command == "break") {
+ if (this.running) {
+ // Schedule a break.
+ print("Stopping JavaScript execution...");
+ this.tab.debugBreak(false);
+ } else {
+ print("JavaScript execution already stopped.");
+ }
+ return;
+ }
+
+ // If page is running an break needs to be issued.
+ if (this.running) {
+ // Some requests are not valid when the page is running.
+ var cmd_info = DebugCommand.commands[cmd.user_command];
+ if (!cmd_info['while_running']) {
+ print(cmd.user_command + " can only be run while paused");
+ return;
+ }
+
+ // Add the command as pending before scheduling a break.
+ this.pending_commands.push(cmd);
+ dprint("pending command: " + cmd.toJSONProtocol());
+
+ // Schedule a forced break and enable auto continue.
+ this.tab.debugBreak(true);
+ this.auto_continue = true;
+ this.set_ready(false);
+ return;
+ }
+
+ // If waiting for a response add command as pending otherwise send the
+ // command.
+ if (this.current_command) {
+ this.pending_commands.push(cmd);
+ dprint("pending command: " + cmd.toJSONProtocol());
+ } else {
+ this.current_command = cmd;
+ cmd.sendToDebugger(this.tab);
+ this.set_ready(false);
+ }
+};
+
+/**
+ * Handle a break event from the debugger.
+ * @param msg {Object} - event protocol message to handle
+ */
+DebugShell.prototype.event_break = function(msg) {
+ this.current_frame = 0;
+ this.set_running(false);
+ if (msg.body) {
+ var body = msg.body;
+ this.current_script = body.script;
+ var loc = DebugCommand.getSourceLocation(body.script,
+ body.sourceLineText, body.sourceLine, body.invocationText);
+ var location = loc[0];
+ var source = loc[1];
+ this.current_line = loc[2];
+ if (msg.body.breakpoints) {
+ // Always disable auto continue if a real break point is hit.
+ this.auto_continue = false;
+ var breakpoints = msg.body.breakpoints;
+ print("paused at breakpoint " + breakpoints.join(",") + ": " +
+ location);
+ for (var i = 0; i < breakpoints.length; i++)
+ this.didHitBreakpoint(parseInt(breakpoints[i]));
+ } else if (body.scriptData == "") {
+ print("paused");
+ } else {
+ // step, stepout, next, "break" and a "debugger" line in the code
+ // are all treated the same (they're not really distinguishable anyway)
+ if (location != this.last_break_location) {
+ // We only print the location (function + script) when it changes,
+ // so as we step, you only see the source line when you transition
+ // to a new script and/or function. Also if auto continue is enables
+ // don't print the break location.
+ if (!this.auto_continue)
+ print(location);
+ }
+ }
+ // Print th current source line unless auto continue is enabled.
+ if (source && !this.auto_continue)
+ print(source);
+ this.last_break_location = location;
+ }
+ if (!this.auto_continue)
+ this.set_ready(true);
+};
+
+/**
+ * Handle an exception event from the debugger.
+ * @param msg {Object} - event protocol message to handle
+ */
+DebugShell.prototype.event_exception = function(msg) {
+ this.set_running(false);
+ this.set_ready(true);
+ if (msg.body) {
+ if (msg.body["uncaught"]) {
+ print("uncaught exception " + msg.body["exception"].text);
+ } else {
+ print("paused at exception " + msg.body["exception"].text);
+ }
+ }
+};
+
+DebugShell.prototype.matchScript = function(script_match, line) {
+ var script = null;
+ // In the v8 debugger, all scripts have a name, line offset and line count
+ // Script names are usually URLs which are a pain to have to type again and
+ // again, so we match the tail end of the script name. This makes it easy
+ // to type break foo.js:23 rather than
+ // http://www.foo.com/bar/baz/quux/test/foo.js:23. In addition to the tail
+ // of the name we also look at the lines the script cover. If there are
+ // several scripts with the same tail including the requested line we match
+ // the first one encountered.
+ // TODO(sgjesse) Find how to handle several matching scripts.
+ var candidate_scripts = [];
+ for (var i in this.scripts) {
+ if (this.scripts[i].name &&
+ this.scripts[i].name.indexOf(script_match) >= 0) {
+ candidate_scripts.push(this.scripts[i]);
+ }
+ }
+ for (var i in candidate_scripts) {
+ var s = candidate_scripts[i];
+ var from = s.lineOffset;
+ var to = from + s.lineCount;
+ if (from <= line && line < to) {
+ script = s;
+ break;
+ }
+ }
+ if (script)
+ return script;
+ else
+ return null;
+}
+
+// The Chrome Subshell interface requires:
+// prompt(), command(), response(), exit() and on_disconnect()
+
+/**
+ * Called by Chrome Shell to get a prompt string to display.
+ */
+DebugShell.prototype.prompt = function() {
+ if (this.current_command)
+ return '';
+ if (!this.running)
+ return 'v8(paused)> ';
+ else
+ return 'v8(running)> ';
+};
+
+/**
+ * Called by Chrome Shell when command input has been received from the user.
+ */
+DebugShell.prototype.command = function(str) {
+ if (this.tab) {
+ str = DebugCommand.trim(str);
+ if (str.length) {
+ var cmd = new DebugCommand(str);
+ cmd.from_user = true;
+ if (cmd.type == "request")
+ this.process_command(cmd);
+ }
+ } else {
+ print(">>not connected to a tab");
+ }
+};
+
+/**
+ * Called when a response to a previous command has been received.
+ * @param {Object} msg Message object.
+ */
+DebugShell.prototype.response = function(msg) {
+ dprint("received: " + (msg && msg.type));
+ this.last_msg = msg;
+ if (msg.type == "event") {
+ ev = msg["event"]
+ if (ev == "break") {
+ this.event_break(msg);
+ } else if (ev == "exception") {
+ this.event_exception(msg);
+ }
+ } else if (msg.type == "response") {
+ if (msg.request_seq != undefined) {
+ if (!this.current_command || this.current_command.seq != msg.request_seq){
+ throw("received response to unknown command " + DebugCommand.toJSON(msg));
+ }
+ } else {
+ // TODO(erikkay): should we reject these when they happen?
+ print(">>no request_seq in response " + DebugCommand.toJSON(msg));
+ }
+ var cmd = DebugCommand.commands[this.current_command.user_command]
+ msg.command = this.current_command;
+ this.current_command = null
+ if (msg.running != undefined) {
+ this.set_running(msg.running);
+ }
+ if (!msg['success']) {
+ print(msg['message']);
+ } else {
+ var response = cmd['response'];
+ if (response != undefined) {
+ response.call(this, msg);
+ }
+ }
+ this.set_ready(true);
+ }
+
+ // Process next pending command if any.
+ if (this.pending_commands.length) {
+ this.process_command(this.pending_commands.shift());
+ } else if (this.auto_continue) {
+ // If no more pending commands and auto continue is active issue a continue command.
+ this.auto_continue = false;
+ this.process_command(new DebugCommand("continue"));
+ }
+};
+
+/**
+ * Called when a breakpoint has been set.
+ * @param {BreakpointInfo} info - details of breakpoint set.
+ */
+DebugShell.prototype.addedBreakpoint = function(info) {
+ print("set breakpoint #" + info.id);
+ this.breakpoints[info.id] = info;
+}
+
+/**
+ * Called when a breakpoint has been cleared.
+ * @param {int} id - the breakpoint number that was cleared.
+ */
+DebugShell.prototype.clearedBreakpoint = function(id) {
+ assertIsNumberType(id, "clearedBreakpoint called with invalid id");
+
+ print("cleared breakpoint #" + id);
+ delete this.breakpoints[id];
+}
+
+/**
+ * Called when a breakpoint has been reached.
+ * @param {int} id - the breakpoint number that was hit.
+ */
+DebugShell.prototype.didHitBreakpoint = function(id) {
+ assertIsNumberType(id, "didHitBreakpoint called with invalid id");
+
+ var info = this.breakpoints[id];
+ if (!info)
+ throw "Could not find breakpoint #" + id;
+
+ info.hit_count ++;
+}
+
+/**
+ * Print a summary of the specified breakpoints.
+ *
+ * @param {Array<BreakpointInfo>} breakpointsToPrint - List of breakpoints. The
+ * index is unused (id is determined from the info).
+ */
+DebugShell.printBreakpoints_ = function(breakpoints) {
+ // TODO(ericroman): this would look much nicer if we could output as an HTML
+ // table. I tried outputting as formatted text table, but this looks aweful
+ // once it triggers wrapping (which is very likely if the target is a script)
+
+ // Output as a comma separated list of key=value
+ for (var i in breakpoints) {
+ var b = breakpoints[i];
+ var props = ["id", "hit_count", "type", "target", "line", "position",
+ "condition"];
+ var propertyList = [];
+ for (var i = 0; i < props.length; i++) {
+ var prop = props[i];
+ var val = b[prop];
+ if (val != undefined)
+ propertyList.push(prop + "=" + val);
+ }
+ print(propertyList.join(", "));
+ }
+}
+
+/**
+ * Called by Chrome Shell when the outer shell is detaching from debugging
+ * this tab.
+ */
+DebugShell.prototype.exit = function() {
+ if (this.tab) {
+ this.tab.detach();
+ this.tab = null;
+ }
+};
+
+/**
+ * Called by the Chrome Shell when the tab that the shell is debugging
+ * have attached.
+ */
+DebugShell.prototype.on_attach = function(title) {
+ if (!title)
+ title = "Untitled";
+ print('attached to ' + title);
+ // on attach, we update our current script list
+ var cmd = new DebugCommand("scripts");
+ cmd.from_user = false;
+ this.process_command(cmd);
+};
+
+
+/**
+ * Called by the Chrome Shell when the tab that the shell is debugging
+ * went away.
+ */
+DebugShell.prototype.on_disconnect = function() {
+ print(">>lost connection to tab");
+ this.tab = null;
+};
+
+
+/**
+ * Structure that holds the details about a breakpoint.
+ * @constructor
+ *
+ * @param {int} id - breakpoint number
+ * @param {string} type - "script" or "function"
+ * @param {string} target - either a function name, or script url
+ * @param {int} line - line number in the script, or undefined
+ * @param {int} position - column in the script, or undefined
+ * @param {string} condition - boolean expression, or undefined
+ */
+function BreakpointInfo(id, type, target, line, position, condition) {
+ this.id = id;
+ this.type = type;
+ this.target = target;
+
+ if (line != undefined)
+ this.line = line;
+ if (position != undefined)
+ this.position = position;
+ if (condition != undefined)
+ this.condition = condition;
+
+ this.hit_count = 0;
+
+ // Check that the id is numeric, otherwise will run into problems later
+ assertIsNumberType(this.id, "id is not a number");
+}
+
+var shell_ = null;
+DebugShell.initDebugShell = function(debuggerUI) {
+ if (!DebugShell.singleton) {
+ DebugShell.ui = debuggerUI;
+ DebugShell.singleton = new DebugShell(TabNode);
+ shell_ = DebugShell.singleton;
+
+ // enable debug output
+ //shell_.debug = true;
+ }
+};
+
+/**
+ * Print debugging message when DebugShell's debug flag is true.
+ */
+function dprint(str) {
+ if (shell_ && shell_.debug) {
+ print(str);
+ }
+};
+
+/**
+ * Helper that throws error if x is not a number
+ * @param x {object} - object to test type of
+ * @param error_message {string} - error to throw on failure
+ */
+function assertIsNumberType(x, error_message) {
+ if (typeof x != "number")
+ throw error_message;
+}
+
+////////////////////// migration staff //////////////////////////
+// This file was copied from chrome\browser\debugger\resources\debugger_shell.js
+
+function print(txt) {
+ var ui = DebugShell.ui;
+ if (ui) {
+ ui.appendText(txt);
+ }
+}
+
+var TabNode = {
+ debugBreak: function(force) {
+ DebuggerIPC.sendMessage(["debugBreak", force]);
+ },
+ attach: function() {
+ DebuggerIPC.sendMessage(["attach"]);
+ },
+ detach: function() {
+ // TODO(yurys): send this from DebugHandler when it's being destroyed?
+ DebuggerIPC.sendMessage(["detach"]);
+ },
+ sendToDebugger: function(str) {
+ DebuggerIPC.sendMessage(["sendToDebugger", str]);
+ }
+};
+
+var ChromeNode = {
+ setDebuggerReady: function(isReady) {
+ DebuggerIPC.sendMessage(["setDebuggerReady", isReady]);
+ },
+ setDebuggerBreak: function(isBreak) {
+ var ui = DebugShell.ui;
+ if (ui) {
+ ui.setDebuggerBreak(isBreak);
+ }
+ DebuggerIPC.sendMessage(["setDebuggerBreak", isBreak]);
+ }
+};
diff --git a/webkit/port/page/inspector/debugger.css b/webkit/port/page/inspector/debugger.css
new file mode 100644
index 0000000..ed9df2f
--- /dev/null
+++ b/webkit/port/page/inspector/debugger.css
@@ -0,0 +1,35 @@
+/**
+ * Style for javascript debugger. See debugger.html.
+ */
+
+html,body {
+ margin: 0px;
+ padding: 0px;
+ height: 100%;
+}
+#output {
+ font-family: monospace;
+ background-color: #ffffff;
+ min-height: 100%;
+}
+#outer {
+ height: 100%;
+ width: 100%;
+ white-space: pre-wrap;
+ padding: 0px 0px 24px 0px;
+}
+#command-line {
+ bottom: 0px;
+ /* not quite sure why this 5px is necessary */
+ right: 5px;
+ left: 0px;
+ position: fixed;
+ padding: 0px;
+ margin: 0px;
+}
+#command-line-text {
+ height: 20px;
+ display: block;
+ width: 100%;
+ font-family: monospace;
+}
diff --git a/webkit/port/page/inspector/debugger.html b/webkit/port/page/inspector/debugger.html
new file mode 100644
index 0000000..f6bb917
--- /dev/null
+++ b/webkit/port/page/inspector/debugger.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<!--
+ The UI for the javascript debugger window.
+-->
+ <head>
+ <title>JavaScript Debugger</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+ <link rel="stylesheet" href="debugger.css" type="text/css" />
+ <script type="text/javascript" src="DebuggerConsole.js"></script>
+ <script type="text/javascript" src="DebuggerIPC.js"></script>
+ <script type="text/javascript" src="DebuggerShell.js"></script>
+ </head>
+
+ <body onload="onLoad();">
+
+ <table id='outer'>
+ <tr>
+ <td valign='bottom' id='output'>Chrome JavaScript Debugger<br />Type 'help' for a list of commands.<br /></td>
+ </tr>
+ </table>
+
+ <div id='command-line'>
+ <!-- TODO(erikkay) - use addEventListener instead -->
+ <input id='command-line-text'
+ type="text" />
+ </div>
+
+ </body>
+</html>