// 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_shell.h" #include "build/build_config.h" #include "base/file_util.h" #include "base/path_service.h" #include "base/string_util.h" #include "base/thread.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/debugger/debugger_io.h" #include "chrome/browser/debugger/debugger_node.h" #include "chrome/browser/renderer_host/render_process_host.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/resource_bundle.h" #include "grit/debugger_resources.h" #if defined(OS_WIN) #include "chrome/browser/tab_contents/tab_contents.h" #elif defined(OS_POSIX) #include "chrome/common/temp_scaffolding_stubs.h" #endif DebuggerShell::DebuggerShell(DebuggerInputOutput* io) : io_(io), debugger_ready_(true) { } DebuggerShell::~DebuggerShell() { io_->Stop(); io_ = NULL; v8::Locker locked; v8::HandleScope scope; SubshellFunction("exit", 0, NULL); v8::V8::RemoveMessageListeners(&DelegateMessageListener); v8_this_.Dispose(); v8_context_.Dispose(); shell_.Dispose(); } void DebuggerShell::Start() { io_->Start(this); v8::Locker locked; v8::HandleScope scope; v8_this_ = v8::Persistent::New(v8::External::New(this)); v8::V8::AddMessageListener(&DelegateMessageListener, v8_this_); v8::Local global_template = v8::ObjectTemplate::New(); // shell function v8::Local shell_template = v8::FunctionTemplate::New(&DelegateSubshell, v8_this_); global_template->Set(v8::String::New("shell"), shell_template); // print function v8::Local print_template = v8::FunctionTemplate::New(&DelegatePrint, v8_this_); global_template->Set(v8::String::New("print"), print_template); // source function v8::Local source_template = v8::FunctionTemplate::New(&DelegateSource, v8_this_); global_template->Set(v8::String::New("source"), source_template); v8_context_ = v8::Context::New(NULL, global_template); v8::Context::Scope ctx(v8_context_); // This doesn't really leak. It's wrapped by the NewInstance() return value. ChromeNode* chrome = new ChromeNode(this); v8_context_->Global()->Set(v8::String::New("chrome"), chrome->NewInstance()); ResourceBundle& rb = ResourceBundle::GetSharedInstance(); const std::string& debugger_shell_js = rb.GetDataResource(IDR_DEBUGGER_SHELL_JS); CompileAndRun(debugger_shell_js, "chrome.dll/debugger_shell.js"); } void DebuggerShell::HandleWeakReference(v8::Persistent obj, void* data) { DebuggerNodeWrapper* node = static_cast(data); node->Release(); } v8::Handle DebuggerShell::SetDebuggerReady(const v8::Arguments& args, DebuggerShell* debugger) { if (args[0]->IsBoolean()) { bool flag = args[0]->BooleanValue(); debugger->debugger_ready_ = flag; debugger->GetIo()->SetDebuggerReady(flag); } return v8::Undefined(); } v8::Handle DebuggerShell::SetDebuggerBreak(const v8::Arguments& args, DebuggerShell* debugger) { if (args[0]->IsBoolean()) { bool flag = args[0]->BooleanValue(); debugger->GetIo()->SetDebuggerBreak(flag); } return v8::Undefined(); } DebuggerInputOutput* DebuggerShell::GetIo() { return io_.get(); } v8::Handle DebuggerShell::DelegateSubshell( const v8::Arguments& args) { DebuggerShell* debugger = static_cast(v8::External::Cast(*args.Data())->Value()); return debugger->Subshell(args); } v8::Handle DebuggerShell::Subshell(const v8::Arguments& args) { if (args.Length() != 1) { return v8::Undefined(); } if (!shell_.IsEmpty()) { shell_.Dispose(); shell_.Clear(); v8_context_->Global()->Delete(v8::String::New("shell_")); } if (args[0]->IsFunction()) { v8::Handle func = v8::Handle::Cast(args[0]); v8::Local obj = func->NewInstance(); if (!obj->IsUndefined()) { shell_ = v8::Persistent::New(obj); v8_context_->Global()->Set(v8::String::New("shell_"), shell_); } } else if (args[0]->IsObject()) { shell_ = v8::Persistent::New(v8::Local::Cast(args[0])); v8_context_->Global()->Set(v8::String::New("shell_"), shell_); } return v8::Undefined(); } v8::Handle DebuggerShell::SubshellFunction(const char* func, int argc, v8::Handle* argv) { if (!shell_.IsEmpty()) { v8::Context::Scope scope(v8_context_); v8::Local function = shell_->Get(v8::String::New(func)); if (function->IsFunction()) { v8::Local ret = v8::Function::Cast(*function)->Call(shell_, argc, argv); return ret; } } return v8::Undefined(); } v8::Handle DebuggerShell::DelegatePrint(const v8::Arguments& args) { DebuggerShell* debugger = static_cast(v8::External::Cast(*args.Data())->Value()); return debugger->Print(args); } v8::Handle DebuggerShell::Print(const v8::Arguments& args) { int len = args.Length(); for (int i = 0; i < len; i++) { PrintObject(args[i]); } return v8::Undefined(); } v8::Handle DebuggerShell::DelegateSource(const v8::Arguments& args) { DebuggerShell* debugger = static_cast(v8::External::Cast(*args.Data())->Value()); std::wstring path; if (args.Length() == 0) { debugger->LoadUserConfig(); } else { ObjectToString(args[0], &path); bool ret = debugger->LoadFile(path); if (!ret) { return v8::String::New("failed to load"); } } return v8::Undefined(); } void DebuggerShell::DelegateMessageListener(v8::Handle message, v8::Handle data) { DCHECK(!data.IsEmpty()); DebuggerShell* debugger = static_cast(v8::External::Cast(*data)->Value()); debugger->MessageListener(message); } void DebuggerShell::MessageListener(v8::Handle message) { v8::HandleScope scope; v8::Local msg_str = message->Get(); PrintObject(msg_str); v8::Handle data = message->GetScriptResourceName(); if (!data.IsEmpty() && !data->IsUndefined()) { std::wstring out; ObjectToString(data, &out); int line_number = message->GetLineNumber(); if (line_number >= 0) out += StringPrintf(L":%d", line_number); PrintLine(out); data = message->GetSourceLine(); if (!data->IsUndefined()) { ObjectToString(data, &out); PrintLine(out); } } } void DebuggerShell::Debug(TabContents* tab) { v8::Locker locked; v8::HandleScope outer; v8::Context::Scope scope(v8_context_); v8::Local global = v8_context_->Global(); v8::Local function = global->Get(v8::String::New("debug")); if (function->IsFunction()) { TabNode* node = new TabNode(tab); v8::Handle argv[] = {node->NewInstance()}; PrintObject(v8::Function::Cast(*function)->Call(global, 1, argv)); } } void DebuggerShell::DebugMessage(const std::wstring& msg) { v8::Locker locked; v8::HandleScope scope; if (msg.length()) { if ((msg[0] == L'{' || msg[0] == L'[' || msg[0] == L'(') && (!shell_.IsEmpty())) { // v8's wide String constructor requires uint16 rather than wchar const uint16* data = reinterpret_cast(msg.c_str()); v8::Handle argv[] = {v8::String::New(data)}; PrintObject(SubshellFunction("response", 1, argv)); PrintPrompt(); } else { if (msg[msg.length() - 1] == L'\n') PrintString(msg); else PrintLine(msg); } } } void DebuggerShell::OnDebugAttach() { v8::Locker locked; v8::HandleScope scope; SubshellFunction("on_attach", 0, NULL); } void DebuggerShell::OnDebugDisconnect() { v8::Locker locked; v8::HandleScope scope; SubshellFunction("on_disconnect", 0, NULL); } void DebuggerShell::ObjectToString(v8::Handle result, std::wstring* str) { v8::HandleScope scope; if (!result.IsEmpty() && !result->IsUndefined()) { v8::Local str_obj = result->ToString(); if (!str_obj.IsEmpty()) { int length = str_obj->Length(); wchar_t* buf = new wchar_t[length + 1]; int size = str_obj->Write(reinterpret_cast(buf)); str->clear(); str->append(buf, size); delete[] buf; } } } void DebuggerShell::ObjectToString(v8::Handle result, std::string* str) { v8::HandleScope scope; if (!result.IsEmpty() && !result->IsUndefined()) { v8::Local str_obj = result->ToString(); if (!str_obj.IsEmpty()) { int length = str_obj->Length(); char* buf = new char[length + 1]; str_obj->WriteAscii(buf); str->clear(); str->append(buf); delete[] buf; } } } void DebuggerShell::PrintObject(v8::Handle result, bool crlf) { if (!result.IsEmpty() && !result->IsUndefined()) { std::wstring out; ObjectToString(result, &out); if (crlf) { PrintLine(out); } else if (out.length()) { PrintString(out); } } } void DebuggerShell::PrintString(const std::wstring& out) { if (io_) io_->Output(out); } void DebuggerShell::PrintLine(const std::wstring& out) { if (io_) io_->OutputLine(out); } void DebuggerShell::PrintString(const std::string& out) { if (io_) io_->Output(out); } void DebuggerShell::PrintLine(const std::string& out) { if (io_) io_->OutputLine(out); } void DebuggerShell::PrintPrompt() { std::wstring out = L"Chrome> "; if (!shell_.IsEmpty()) { if (!debugger_ready_) return; v8::Locker locked; v8::HandleScope outer; v8::Handle result = CompileAndRun("shell_.prompt()"); if (!result.IsEmpty() && !result->IsUndefined()) { ObjectToString(result, &out); } } if (io_) io_->OutputPrompt(out); } void DebuggerShell::ProcessCommand(const std::wstring& data) { v8::Locker locked; v8::HandleScope outer; v8::Context::Scope scope(v8_context_); if (!shell_.IsEmpty() && data.substr(0, 7) != L"source(") { if (data == L"exit") { PrintObject(SubshellFunction("exit", 0, NULL)); v8_context_->Global()->Delete(v8::String::New("shell_")); shell_.Dispose(); shell_.Clear(); } else { const uint16* utf16 = reinterpret_cast(data.c_str()); v8::Handle argv[] = {v8::String::New(utf16)}; PrintObject(SubshellFunction("command", 1, argv)); } } else if (data.length()) { //TODO(erikkay): change everything to wstring v8::Handle result = CompileAndRun(data); PrintObject(result); } PrintPrompt(); } bool DebuggerShell::LoadFile(const std::wstring& file) { if (file_util::PathExists(file)) { std::string contents; if (file_util::ReadFileToString(file, &contents)) { std::string afile = WideToUTF8(file); CompileAndRun(contents, afile); return true; } } return false; } void DebuggerShell::LoadUserConfig() { std::wstring path; PathService::Get(chrome::DIR_USER_DATA, &path); file_util::AppendToPath(&path, L"debugger_custom.js"); LoadFile(path); } void DebuggerShell::DidConnect() { v8::Locker locked; v8::HandleScope outer; v8::Context::Scope scope(v8_context_); LoadUserConfig(); PrintPrompt(); } void DebuggerShell::DidDisconnect() { v8::Locker locked; v8::HandleScope outer; SubshellFunction("exit", 0, NULL); } v8::Handle DebuggerShell::CompileAndRun( const std::string& str, const std::string& filename) { const std::wstring wstr = UTF8ToWide(str); return CompileAndRun(wstr, filename); } v8::Handle DebuggerShell::CompileAndRun( const std::wstring& wstr, const std::string& filename) { v8::Locker locked; v8::Context::Scope scope(v8_context_); v8::Handle scriptname; if (filename.length() > 0) { scriptname = v8::String::New(filename.c_str()); } else { scriptname = v8::String::New(""); } const uint16* utf16 = reinterpret_cast(wstr.c_str()); v8::ScriptOrigin origin = v8::ScriptOrigin(scriptname); v8::Local code = v8::Script::Compile(v8::String::New(utf16), &origin); if (!code.IsEmpty()) { v8::Local result = code->Run(); if (!result.IsEmpty()) { return result; } } return v8::Undefined(); }