340 changes: 340 additions & 0 deletions browser/devtools/devtools_frontend.cc
@@ -0,0 +1,340 @@
// Copyright (c) 2015 University of Szeged.
// Copyright (c) 2015 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 "sprocket/browser/devtools/devtools_frontend.h"

#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "components/devtools_http_handler/devtools_http_handler.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "sprocket/browser/ui/window.h"
#include "sprocket/browser/web_contents.h"
#include "sprocket/browser/browser_context.h"
#include "sprocket/browser/browser_main_parts.h"
#include "sprocket/browser/content_browser_client.h"
#include "sprocket/browser/devtools/devtools_manager_delegate.h"
#include "sprocket/common/switches.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_response_writer.h"

namespace {


// ResponseWriter -------------------------------------------------------------

class ResponseWriter : public net::URLFetcherResponseWriter {
public:
ResponseWriter(base::WeakPtr<SprocketDevToolsFrontend> sprocket_devtools_,
int stream_id);
~ResponseWriter() override;

// URLFetcherResponseWriter overrides:
int Initialize(const net::CompletionCallback& callback) override;
int Write(net::IOBuffer* buffer,
int num_bytes,
const net::CompletionCallback& callback) override;
int Finish(const net::CompletionCallback& callback) override;

private:
base::WeakPtr<SprocketDevToolsFrontend> sprocket_devtools_;
int stream_id_;

DISALLOW_COPY_AND_ASSIGN(ResponseWriter);
};

ResponseWriter::ResponseWriter(
base::WeakPtr<SprocketDevToolsFrontend> sprocket_devtools,
int stream_id)
: sprocket_devtools_(sprocket_devtools),
stream_id_(stream_id) {
}

ResponseWriter::~ResponseWriter() {
}

int ResponseWriter::Initialize(const net::CompletionCallback& callback) {
return net::OK;
}

int ResponseWriter::Write(net::IOBuffer* buffer,
int num_bytes,
const net::CompletionCallback& callback) {
base::FundamentalValue* id = new base::FundamentalValue(stream_id_);
base::StringValue* chunk =
new base::StringValue(std::string(buffer->data(), num_bytes));

content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&SprocketDevToolsFrontend::CallClientFunction,
sprocket_devtools_, "DevToolsAPI.streamWrite",
base::Owned(id), base::Owned(chunk), nullptr));
return num_bytes;
}

int ResponseWriter::Finish(const net::CompletionCallback& callback) {
return net::OK;
}

} // namespace

// This constant should be in sync with
// the constant at devtools_ui_bindings.cc.
const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4;

// static
SprocketDevToolsFrontend* SprocketDevToolsFrontend::Show(
content::WebContents* inspected_contents) {
SprocketWindow* window = SprocketWindow::CreateNewWindow(gfx::Size());
SprocketWebContents* web_contents =
SprocketWebContents::CreateSprocketWebContents(window,
inspected_contents->GetBrowserContext(),
GURL(),
gfx::Size());
SprocketDevToolsFrontend* devtools_frontend = new SprocketDevToolsFrontend(
web_contents,
inspected_contents);

devtools_http_handler::DevToolsHttpHandler* http_handler =
SprocketContentBrowserClient::Get()
->browser_main_parts()
->devtools_http_handler();
web_contents->LoadURL(http_handler->GetFrontendURL("/devtools/inspector.html"));

return devtools_frontend;
}

void SprocketDevToolsFrontend::Activate() {
frontend_contents_->ActivateContents(web_contents());
}

void SprocketDevToolsFrontend::Focus() {
web_contents()->Focus();
}

void SprocketDevToolsFrontend::InspectElementAt(int x, int y) {
if (agent_host_)
agent_host_->InspectElement(x, y);
}

void SprocketDevToolsFrontend::Close() {
frontend_contents_->Close();
}

void SprocketDevToolsFrontend::DisconnectFromTarget() {
if (!agent_host_)
return;
agent_host_->DetachClient();
agent_host_ = NULL;
}

SprocketDevToolsFrontend::SprocketDevToolsFrontend(SprocketWebContents* frontend_contents,
content::WebContents* inspected_contents)
: content::WebContentsObserver(frontend_contents->web_contents()),
frontend_contents_(frontend_contents),
inspected_contents_(inspected_contents),
weak_factory_(this) {
}

SprocketDevToolsFrontend::~SprocketDevToolsFrontend() {
for (const auto& pair : pending_requests_)
delete pair.first;
}

void SprocketDevToolsFrontend::RenderViewCreated(
content::RenderViewHost* render_view_host) {
if (!frontend_host_) {
frontend_host_.reset(
content::DevToolsFrontendHost::Create(web_contents()->GetMainFrame(), this));
}
}

void SprocketDevToolsFrontend::DocumentAvailableInMainFrame() {
agent_host_ = content::DevToolsAgentHost::GetOrCreateFor(inspected_contents_);
agent_host_->AttachClient(this);
}

void SprocketDevToolsFrontend::WebContentsDestroyed() {
if (agent_host_)
agent_host_->DetachClient();
delete this;
}

void SprocketDevToolsFrontend::HandleMessageFromDevToolsFrontend(
const std::string& message) {
if (!agent_host_)
return;
std::string method;
base::ListValue* params = NULL;
base::DictionaryValue* dict = NULL;
scoped_ptr<base::Value> parsed_message = base::JSONReader::Read(message);
if (!parsed_message ||
!parsed_message->GetAsDictionary(&dict) ||
!dict->GetString("method", &method)) {
return;
}
int request_id = 0;
dict->GetInteger("id", &request_id);
dict->GetList("params", &params);

std::string browser_message;
if (method == "sendMessageToBrowser" && params &&
params->GetSize() == 1 && params->GetString(0, &browser_message)) {
agent_host_->DispatchProtocolMessage(browser_message);
} else if (method == "loadCompleted") {
web_contents()->GetMainFrame()->ExecuteJavaScript(
base::ASCIIToUTF16("DevToolsAPI.setUseSoftMenu(true);"));
} else if (method == "loadNetworkResource" && params->GetSize() == 3) {
// TODO(pfeldman): handle some of the embedder messages in content.
std::string url;
std::string headers;
int stream_id;
if (!params->GetString(0, &url) ||
!params->GetString(1, &headers) ||
!params->GetInteger(2, &stream_id)) {
return;
}

GURL gurl(url);
if (!gurl.is_valid()) {
base::DictionaryValue response;
response.SetInteger("statusCode", 404);
SendMessageAck(request_id, &response);
return;
}

net::URLFetcher* fetcher =
net::URLFetcher::Create(gurl, net::URLFetcher::GET, this).release();
pending_requests_[fetcher] = request_id;
fetcher->SetRequestContext(web_contents()->GetBrowserContext()->
GetRequestContext());
fetcher->SetExtraRequestHeaders(headers);
fetcher->SaveResponseWithWriter(scoped_ptr<net::URLFetcherResponseWriter>(
new ResponseWriter(weak_factory_.GetWeakPtr(), stream_id)));
fetcher->Start();
return;
} else if (method == "getPreferences") {
SendMessageAck(request_id, &preferences_);
return;
} else if (method == "setPreference") {
std::string name;
std::string value;
if (!params->GetString(0, &name) ||
!params->GetString(1, &value)) {
return;
}
preferences_.SetStringWithoutPathExpansion(name, value);
} else if (method == "removePreference") {
std::string name;
if (!params->GetString(0, &name))
return;
preferences_.RemoveWithoutPathExpansion(name, nullptr);
} else {
return;
}

if (request_id)
SendMessageAck(request_id, nullptr);
}

void SprocketDevToolsFrontend::HandleMessageFromDevToolsFrontendToBackend(
const std::string& message) {
if (agent_host_)
agent_host_->DispatchProtocolMessage(message);
}

void SprocketDevToolsFrontend::DispatchProtocolMessage(
content::DevToolsAgentHost* agent_host,
const std::string& message) {
if (message.length() < kMaxMessageChunkSize) {
base::string16 javascript = base::UTF8ToUTF16(
"DevToolsAPI.dispatchMessage(" + message + ");");
web_contents()->GetMainFrame()->ExecuteJavaScript(javascript);
return;
}

base::FundamentalValue total_size(static_cast<int>(message.length()));
for (size_t pos = 0; pos < message.length(); pos += kMaxMessageChunkSize) {
std::string param;
base::JSONWriter::Write(
base::StringValue(message.substr(pos, kMaxMessageChunkSize)), &param);
std::string code = "DevToolsAPI.dispatchMessageChunk(" + param + ");";
base::string16 javascript = base::UTF8ToUTF16(code);
web_contents()->GetMainFrame()->ExecuteJavaScript(javascript);
}
}

void SprocketDevToolsFrontend::OnURLFetchComplete(const net::URLFetcher* source) {
// TODO(pfeldman): this is a copy of chrome's devtools_ui_bindings.cc.
// We should handle some of the commands including this one in content.
DCHECK(source);
PendingRequestsMap::iterator it = pending_requests_.find(source);
DCHECK(it != pending_requests_.end());

base::DictionaryValue response;
base::DictionaryValue* headers = new base::DictionaryValue();
net::HttpResponseHeaders* rh = source->GetResponseHeaders();
response.SetInteger("statusCode", rh ? rh->response_code() : 200);
response.Set("headers", headers);

void* iterator = NULL;
std::string name;
std::string value;
while (rh && rh->EnumerateHeaderLines(&iterator, &name, &value))
headers->SetString(name, value);

SendMessageAck(it->second, &response);
pending_requests_.erase(it);
delete source;
}

void SprocketDevToolsFrontend::CallClientFunction(
const std::string& function_name,
const base::Value* arg1,
const base::Value* arg2,
const base::Value* arg3) {
std::string javascript = function_name + "(";
if (arg1) {
std::string json;
base::JSONWriter::Write(*arg1, &json);
javascript.append(json);
if (arg2) {
base::JSONWriter::Write(*arg2, &json);
javascript.append(", ").append(json);
if (arg3) {
base::JSONWriter::Write(*arg3, &json);
javascript.append(", ").append(json);
}
}
}
javascript.append(");");
web_contents()->GetMainFrame()->ExecuteJavaScript(
base::UTF8ToUTF16(javascript));
}

void SprocketDevToolsFrontend::SendMessageAck(int request_id,
const base::Value* arg) {
base::FundamentalValue id_value(request_id);
CallClientFunction("DevToolsAPI.embedderMessageAck",
&id_value, arg, nullptr);
}

void SprocketDevToolsFrontend::AgentHostClosed(
content::DevToolsAgentHost* agent_host,
bool replaced) {
frontend_contents_->Close();
}
93 changes: 93 additions & 0 deletions browser/devtools/devtools_frontend.h
@@ -0,0 +1,93 @@
// Copyright (c) 2015 University of Szeged.
// Copyright (c) 2015 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 SPROCKET_BROWSER_DEVTOOLS_FRONTEND_H_
#define SPROCKET_BROWSER_DEVTOOLS_FRONTEND_H_

#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/devtools_frontend_host.h"
#include "content/public/browser/web_contents_observer.h"
#include "net/url_request/url_fetcher_delegate.h"

namespace base {
class Value;
}

namespace content {
class RenderViewHost;
class WebContents;
}

class SprocketWebContents;

class SprocketDevToolsFrontend : public content::WebContentsObserver,
public content::DevToolsFrontendHost::Delegate,
public content::DevToolsAgentHostClient,
public net::URLFetcherDelegate {
public:
static SprocketDevToolsFrontend* Show(content::WebContents* inspected_contents);

void Activate();
void Focus();
void InspectElementAt(int x, int y);
void Close();

void DisconnectFromTarget();

SprocketWebContents* frontend_contents() const { return frontend_contents_; }

void CallClientFunction(const std::string& function_name,
const base::Value* arg1,
const base::Value* arg2,
const base::Value* arg3);

protected:
SprocketDevToolsFrontend(SprocketWebContents* frontend_contents,
content::WebContents* inspected_contents);
~SprocketDevToolsFrontend() override;

// content::DevToolsAgentHostClient implementation.
void AgentHostClosed(content::DevToolsAgentHost* agent_host, bool replaced) override;
void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host,
const std::string& message) override;
base::DictionaryValue* preferences() { return &preferences_; }

private:
// WebContentsObserver overrides
void RenderViewCreated(content::RenderViewHost* render_view_host) override;
void DocumentAvailableInMainFrame() override;
void WebContentsDestroyed() override;

// content::DevToolsFrontendHost::Delegate implementation.
void HandleMessageFromDevToolsFrontend(const std::string& message) override;
void HandleMessageFromDevToolsFrontendToBackend(
const std::string& message) override;

// net::URLFetcherDelegate overrides.
void OnURLFetchComplete(const net::URLFetcher* source) override;

void SendMessageAck(int request_id,
const base::Value* arg1);

SprocketWebContents* frontend_contents_;
content::WebContents* inspected_contents_;
scoped_refptr<content::DevToolsAgentHost> agent_host_;
scoped_ptr<content::DevToolsFrontendHost> frontend_host_;
using PendingRequestsMap = std::map<const net::URLFetcher*, int>;
PendingRequestsMap pending_requests_;
base::DictionaryValue preferences_;
base::WeakPtrFactory<SprocketDevToolsFrontend> weak_factory_;

DISALLOW_COPY_AND_ASSIGN(SprocketDevToolsFrontend);
};

#endif // SPROCKET_BROWSER_DEVTOOLS_FRONTEND_H_
231 changes: 231 additions & 0 deletions browser/devtools/devtools_manager_delegate.cc
@@ -0,0 +1,231 @@
// Copyright (c) 2015 University of Szeged.
// Copyright (c) 2015 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 "sprocket/browser/devtools/devtools_manager_delegate.h"

#include <vector>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "components/devtools_discovery/basic_target_descriptor.h"
#include "components/devtools_discovery/devtools_discovery_manager.h"
#include "components/devtools_http_handler/devtools_http_handler.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/devtools_frontend_host.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/user_agent.h"
#include "sprocket/browser/web_contents.h"
#include "sprocket/browser/ui/window.h"
#include "sprocket/common/content_client.h"
#include "grit/sprocket_resources.h"
#include "net/base/net_errors.h"
#include "net/socket/tcp_server_socket.h"
#include "ui/base/resource/resource_bundle.h"

#if defined(OS_ANDROID)
#include "content/public/browser/android/devtools_auth.h"
#include "net/socket/unix_domain_server_socket_posix.h"
#endif

using devtools_http_handler::DevToolsHttpHandler;

namespace {

#if defined(OS_ANDROID)
const char kFrontEndURL[] =
"http://chrome-devtools-frontend.appspot.com/serve_rev/%s/inspector.html";
#endif

const int kBackLog = 10;

#if defined(OS_ANDROID)
class UnixDomainServerSocketFactory
: public DevToolsHttpHandler::ServerSocketFactory {
public:
explicit UnixDomainServerSocketFactory(const std::string& socket_name)
: socket_name_(socket_name) {}

private:
// DevToolsHttpHandler::ServerSocketFactory.
scoped_ptr<net::ServerSocket> CreateForHttpServer() override {
scoped_ptr<net::ServerSocket> socket(
new net::UnixDomainServerSocket(
base::Bind(&content::CanUserConnectToDevTools),
true /* use_abstract_namespace */));
if (socket->ListenWithAddressAndPort(socket_name_, 0, kBackLog) != net::OK)
return scoped_ptr<net::ServerSocket>();

return socket;
}

std::string socket_name_;

DISALLOW_COPY_AND_ASSIGN(UnixDomainServerSocketFactory);
};
#else
class TCPServerSocketFactory
: public DevToolsHttpHandler::ServerSocketFactory {
public:
TCPServerSocketFactory(const std::string& address, uint16 port)
: address_(address), port_(port) {
}

private:
// DevToolsHttpHandler::ServerSocketFactory.
scoped_ptr<net::ServerSocket> CreateForHttpServer() override {
scoped_ptr<net::ServerSocket> socket(
new net::TCPServerSocket(nullptr, net::NetLog::Source()));
if (socket->ListenWithAddressAndPort(address_, port_, kBackLog) != net::OK)
return scoped_ptr<net::ServerSocket>();

return socket;
}

std::string address_;
uint16 port_;

DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory);
};
#endif

scoped_ptr<DevToolsHttpHandler::ServerSocketFactory>
CreateSocketFactory() {
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
#if defined(OS_ANDROID)
std::string socket_name = "sprocket_devtools_remote";
if (command_line.HasSwitch(switches::kRemoteDebuggingSocketName)) {
socket_name = command_line.GetSwitchValueASCII(
switches::kRemoteDebuggingSocketName);
}
return scoped_ptr<DevToolsHttpHandler::ServerSocketFactory>(
new UnixDomainServerSocketFactory(socket_name));
#else
// See if the user specified a port on the command line (useful for
// automation). If not, use an ephemeral port by specifying 0.
uint16 port = 0;
if (command_line.HasSwitch(switches::kRemoteDebuggingPort)) {
int temp_port;
std::string port_str =
command_line.GetSwitchValueASCII(switches::kRemoteDebuggingPort);
if (base::StringToInt(port_str, &temp_port) &&
temp_port > 0 && temp_port < 65535) {
port = static_cast<uint16>(temp_port);
} else {
DLOG(WARNING) << "Invalid http debugger port number " << temp_port;
}
}
return scoped_ptr<DevToolsHttpHandler::ServerSocketFactory>(
new TCPServerSocketFactory("127.0.0.1", port));
#endif
}

scoped_ptr<devtools_discovery::DevToolsTargetDescriptor>
CreateNewSprocketTarget(content::BrowserContext* browser_context, const GURL& url) {
SprocketWindow* window = SprocketWindow::CreateNewWindow(gfx::Size());
SprocketWebContents* web_contents =
SprocketWebContents::CreateSprocketWebContents(window,
browser_context,
url,
gfx::Size());
return make_scoped_ptr(new devtools_discovery::BasicTargetDescriptor(
content::DevToolsAgentHost::GetOrCreateFor(web_contents->web_contents())));
}

// SprocketDevToolsDelegate ----------------------------------------------------

class SprocketDevToolsDelegate :
public devtools_http_handler::DevToolsHttpHandlerDelegate {
public:
explicit SprocketDevToolsDelegate(content::BrowserContext* browser_context);
~SprocketDevToolsDelegate() override;

// devtools_http_handler::DevToolsHttpHandlerDelegate implementation.
std::string GetDiscoveryPageHTML() override;
std::string GetFrontendResource(const std::string& path) override;
std::string GetPageThumbnailData(const GURL& url) override;

private:
content::BrowserContext* browser_context_;

DISALLOW_COPY_AND_ASSIGN(SprocketDevToolsDelegate);
};

SprocketDevToolsDelegate::SprocketDevToolsDelegate(content::BrowserContext* browser_context)
: browser_context_(browser_context) {
devtools_discovery::DevToolsDiscoveryManager::GetInstance()->
SetCreateCallback(base::Bind(&CreateNewSprocketTarget,
base::Unretained(browser_context)));
}

SprocketDevToolsDelegate::~SprocketDevToolsDelegate() {
devtools_discovery::DevToolsDiscoveryManager::GetInstance()->
SetCreateCallback(
devtools_discovery::DevToolsDiscoveryManager::CreateCallback());
}

std::string SprocketDevToolsDelegate::GetDiscoveryPageHTML() {
#if defined(OS_ANDROID)
return std::string();
#else
return ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_SPROCKET_DEVTOOLS_DISCOVERY_PAGE).as_string();
#endif
}

std::string SprocketDevToolsDelegate::GetFrontendResource(
const std::string& path) {
return content::DevToolsFrontendHost::GetFrontendResource(path).as_string();
}

std::string SprocketDevToolsDelegate::GetPageThumbnailData(const GURL& url) {
return std::string();
}

} // namespace

// SprocketDevToolsManagerDelegate ----------------------------------------------

// static
DevToolsHttpHandler*
SprocketDevToolsManagerDelegate::CreateHttpHandler(
content::BrowserContext* browser_context) {
std::string frontend_url;
#if defined(OS_ANDROID)
frontend_url = base::StringPrintf(kFrontEndURL, content::GetWebKitRevision().c_str());
#endif
return new DevToolsHttpHandler(
CreateSocketFactory(),
frontend_url,
new SprocketDevToolsDelegate(browser_context),
base::FilePath(),
base::FilePath(),
std::string(),
GetSprocketUserAgent());
}

SprocketDevToolsManagerDelegate::SprocketDevToolsManagerDelegate(
content::BrowserContext* browser_context)
: browser_context_(browser_context) {
}

SprocketDevToolsManagerDelegate::~SprocketDevToolsManagerDelegate() {
}

base::DictionaryValue* SprocketDevToolsManagerDelegate::HandleCommand(
content::DevToolsAgentHost* agent_host,
base::DictionaryValue* command) {
return NULL;
}
45 changes: 45 additions & 0 deletions browser/devtools/devtools_manager_delegate.h
@@ -0,0 +1,45 @@
// Copyright (c) 2015 University of Szeged.
// Copyright (c) 2015 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 SPROCKET_BROWSER_DEVTOOLS_MANAGER_DELEGATE_H_
#define SPROCKET_BROWSER_DEVTOOLS_MANAGER_DELEGATE_H_

#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "components/devtools_http_handler/devtools_http_handler_delegate.h"
#include "content/public/browser/devtools_manager_delegate.h"

namespace devtools_http_handler {
class DevToolsHttpHandler;
}

namespace content {
class BrowserContext;
}

class SprocketDevToolsManagerDelegate : public content::DevToolsManagerDelegate {
public:
static devtools_http_handler::DevToolsHttpHandler* CreateHttpHandler(
content::BrowserContext* browser_context);

explicit SprocketDevToolsManagerDelegate(content::BrowserContext* browser_context);
~SprocketDevToolsManagerDelegate() override;

// DevToolsManagerDelegate implementation.
void Inspect(content::BrowserContext* browser_context,
content::DevToolsAgentHost* agent_host) override {}
void DevToolsAgentStateChanged(content::DevToolsAgentHost* agent_host,
bool attached) override {}
base::DictionaryValue* HandleCommand(content::DevToolsAgentHost* agent_host,
base::DictionaryValue* command) override;

private:
content::BrowserContext* browser_context_;

DISALLOW_COPY_AND_ASSIGN(SprocketDevToolsManagerDelegate);
};

#endif // SPROCKET_BROWSER_DEVTOOLS_MANAGER_DELEGATE_H_
5 changes: 5 additions & 0 deletions browser/ui/window_delegate_view_aura.cc
Expand Up @@ -52,6 +52,8 @@ void SprocketWindowDelegateView::InitSprocketWindow() {
void SprocketWindowDelegateView::InitAccelerators() {
GetFocusManager()->RegisterAccelerator(ui::Accelerator(ui::VKEY_F11, ui::EF_NONE),
ui::AcceleratorManager::kNormalPriority, this);
GetFocusManager()->RegisterAccelerator(ui::Accelerator(ui::VKEY_F10, ui::EF_NONE),
ui::AcceleratorManager::kNormalPriority, this);
}

bool SprocketWindowDelegateView::CanResize() const {
Expand Down Expand Up @@ -96,6 +98,9 @@ void SprocketWindowDelegateView::HandleKeyboardEvent(const content::NativeWebKey

bool SprocketWindowDelegateView::AcceleratorPressed(const ui::Accelerator& accelerator) {
switch (accelerator.key_code()) {
case ui::VKEY_F10:
sprocket_web_contents_->ShowDevTools();
return true;
case ui::VKEY_F11:
sprocket_web_contents_->window()->PlatformToggleFullscreenModeForTab(
!sprocket_web_contents_->window()->PlatformIsFullscreenForTabOrPending());
Expand Down
67 changes: 65 additions & 2 deletions browser/web_contents.cc
Expand Up @@ -6,11 +6,34 @@

#include "sprocket/browser/web_contents.h"

#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "sprocket/browser/devtools/devtools_frontend.h"
#include "sprocket/browser/ui/window.h"
#include "ui/events/event.h"

class SprocketWebContents::DevToolsWebContentsObserver : public WebContentsObserver {
public:
DevToolsWebContentsObserver(SprocketWebContents* sprocket_web_contents,
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
sprocket_web_contents_(sprocket_web_contents) {
}

// WebContentsObserver
void WebContentsDestroyed() override {
sprocket_web_contents_->OnDevToolsWebContentsDestroyed();
}

private:
SprocketWebContents* sprocket_web_contents_;

DISALLOW_COPY_AND_ASSIGN(DevToolsWebContentsObserver);
};

// static
SprocketWebContents* SprocketWebContents::CreateSprocketWebContents(
SprocketWindow* window,
Expand Down Expand Up @@ -38,7 +61,9 @@ SprocketWebContents* SprocketWebContents::AdoptWebContents(

SprocketWebContents::SprocketWebContents(SprocketWindow* window,
content::WebContents* web_contents)
: window_(window),
: WebContentsObserver(web_contents),
window_(window),
devtools_frontend_(NULL),
is_fullscreen_(false) {
web_contents_.reset(web_contents);
window->PlatformSetContents(this);
Expand Down Expand Up @@ -83,6 +108,23 @@ void SprocketWebContents::Close() {
window_->PlatformCloseWindow();
}

void SprocketWebContents::ShowDevTools() {
InnerShowDevTools();
}

void SprocketWebContents::ShowDevToolsForElementAt(int x, int y) {
InnerShowDevTools();
devtools_frontend_->InspectElementAt(x, y);
}

void SprocketWebContents::CloseDevTools() {
if (!devtools_frontend_)
return;
devtools_observer_.reset();
devtools_frontend_->Close();
devtools_frontend_ = NULL;
}

content::WebContents* SprocketWebContents::OpenURLFromTab(content::WebContents* source,
const content::OpenURLParams& params) {
// CURRENT_TAB is the only one we implement for now.
Expand Down Expand Up @@ -171,7 +213,28 @@ void SprocketWebContents::DeactivateContents(content::WebContents* contents) {
void SprocketWebContents::HandleKeyboardEvent(content::WebContents* source,
const content::NativeWebKeyboardEvent& event) {
#if defined(USE_AURA)
if (event.os_event->type() == ui::ET_KEY_PRESSED)
if (event.os_event && event.os_event->type() == ui::ET_KEY_PRESSED)
window_->PlatformHandleKeyboardEvent(event);
#endif
}

void SprocketWebContents::TitleWasSet(content::NavigationEntry* entry, bool explicit_set) {
if (entry)
window_->PlatformSetTitle(entry->GetTitle());
}

void SprocketWebContents::InnerShowDevTools() {
if (!devtools_frontend_) {
devtools_frontend_ = SprocketDevToolsFrontend::Show(web_contents());
devtools_observer_.reset(new DevToolsWebContentsObserver(
this, devtools_frontend_->frontend_contents()->web_contents()));
}

devtools_frontend_->Activate();
devtools_frontend_->Focus();
}

void SprocketWebContents::OnDevToolsWebContentsDestroyed() {
devtools_observer_.reset();
devtools_frontend_ = NULL;
}
20 changes: 19 additions & 1 deletion browser/web_contents.h
Expand Up @@ -9,8 +9,10 @@

#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"

class SprocketWindow;
class SprocketDevToolsFrontend;

namespace content {
class BrowserContext;
Expand All @@ -25,7 +27,8 @@ class Event;
// WebContentsDelegate: Objects implement this interface to get notified about
// changes in the WebContents and to provide necessary functionality.

class SprocketWebContents : public content::WebContentsDelegate {
class SprocketWebContents : public content::WebContentsDelegate,
public content::WebContentsObserver {

public:
~SprocketWebContents() override;
Expand All @@ -48,6 +51,9 @@ class SprocketWebContents : public content::WebContentsDelegate {
void Reload();
void Stop();
void Close();
void ShowDevTools();
void ShowDevToolsForElementAt(int x, int y);
void CloseDevTools();

// content::WebContentsDelegate overrides.

Expand Down Expand Up @@ -107,15 +113,27 @@ class SprocketWebContents : public content::WebContentsDelegate {
content::WebContents* web_contents() const { return web_contents_.get(); }

private:

class DevToolsWebContentsObserver;

explicit SprocketWebContents(SprocketWindow* window,
content::WebContents* web_contents);

void ToggleFullscreenModeForTab(content::WebContents* web_contents,
bool enter_fullscreen);

// WebContentsObserver
void TitleWasSet(content::NavigationEntry* entry, bool explicit_set) override;

void InnerShowDevTools();
void OnDevToolsWebContentsDestroyed();

scoped_ptr<content::WebContents> web_contents_;
SprocketWindow* window_;

scoped_ptr<DevToolsWebContentsObserver> devtools_observer_;
SprocketDevToolsFrontend* devtools_frontend_;

bool is_fullscreen_;
};

Expand Down
17 changes: 17 additions & 0 deletions patch/chromedriver.patch
@@ -0,0 +1,17 @@
diff --git a/chrome/test/chromedriver/chrome/device_manager.cc b/chrome/test/chromedriver/chrome/device_manager.cc
index 8e77f0c..48ec7e1 100644
--- a/chrome/test/chromedriver/chrome/device_manager.cc
+++ b/chrome/test/chromedriver/chrome/device_manager.cc
@@ -55,6 +55,12 @@ Status Device::SetUp(const std::string& package,
device_socket = "content_shell_devtools_remote";
command_line_file = "/data/local/tmp/content-shell-command-line";
exec_name = "content_shell";
+ } else if (package.compare("hu.uszeged.sprocket_apk") == 0) {
+ // Sprocket.
+ known_activity = ".SprocketActivity";
+ device_socket = "sprocket_devtools_remote";
+ command_line_file = "/data/local/tmp/sprocket-command-line";
+ exec_name = "sprocket";
} else if (package.compare("org.chromium.chrome.shell") == 0) {
// ChromeShell
known_activity = ".ChromeShellActivity";
14 changes: 14 additions & 0 deletions patch/gritsettings.patch
@@ -0,0 +1,14 @@
diff --git a/tools/gritsettings/resource_ids b/tools/gritsettings/resource_ids
index a64ae83..653becf 100644
--- a/tools/gritsettings/resource_ids
+++ b/tools/gritsettings/resource_ids
@@ -160,6 +160,9 @@
"content/shell/shell_resources.grd": {
"includes": [25500],
},
+ "sprocket/sprocket_resources.grd": {
+ "includes": [25600],
+ },
# This file is generated during the build.
"<(SHARED_INTERMEDIATE_DIR)/content/browser/tracing/tracing_resources.grd": {
"includes": [25750],
54 changes: 54 additions & 0 deletions resources/devtools_discovery_page.html
@@ -0,0 +1,54 @@
<html>
<head>
<title>Sprocket remote debugging</title>
<style>
</style>

<script>
function onLoad() {
var tabs_list_request = new XMLHttpRequest();
tabs_list_request.open("GET", "/json/list?t=" + new Date().getTime(), true);
tabs_list_request.onreadystatechange = onReady;
tabs_list_request.send();
}

function onReady() {
if(this.readyState == 4 && this.status == 200) {
if(this.response != null)
var responseJSON = JSON.parse(this.response);
for (var i = 0; i < responseJSON.length; ++i)
appendItem(responseJSON[i]);
}
}

function appendItem(item_object) {
var frontend_ref;
if (item_object.devtoolsFrontendUrl) {
frontend_ref = document.createElement("a");
frontend_ref.href = item_object.devtoolsFrontendUrl;
frontend_ref.title = item_object.title;
} else {
frontend_ref = document.createElement("div");
frontend_ref.title = "The tab already has active debugging session";
}

var text = document.createElement("div");
if (item_object.title)
text.innerText = item_object.title;
else
text.innerText = "(untitled tab)";
text.style.cssText = "background-image:url(" + item_object.faviconUrl + ")";
frontend_ref.appendChild(text);

var item = document.createElement("p");
item.appendChild(frontend_ref);

document.getElementById("items").appendChild(item);
}
</script>
</head>
<body onload='onLoad()'>
<div id='caption'>Inspectable WebContents</div>
<div id='items'></div>
</body>
</html>
20 changes: 20 additions & 0 deletions sprocket.gyp
Expand Up @@ -8,6 +8,7 @@
'pkg-config': 'pkg-config',
'chromium_code': 1,
'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/sprocket',
'protoc_out_dir': '<(SHARED_INTERMEDIATE_DIR)/protoc_out',
'sprocket_version': '0.1.0.0',
},
'targets': [
Expand Down Expand Up @@ -38,6 +39,8 @@
'<(DEPTH)/content/content.gyp:content_resources',
'<(DEPTH)/content/content.gyp:content_utility',
'<(DEPTH)/content/content.gyp:content_browser',
'<(DEPTH)/components/components.gyp:devtools_discovery',
'<(DEPTH)/components/components.gyp:devtools_http_handler',
'sprocket_pak',
],
'sources': [
Expand All @@ -53,6 +56,10 @@
'browser/content_browser_client.h',
'browser/web_contents.cc',
'browser/web_contents.h',
'browser/devtools/devtools_frontend.cc',
'browser/devtools/devtools_frontend.h',
'browser/devtools/devtools_manager_delegate.cc',
'browser/devtools/devtools_manager_delegate.h',
'browser/net/url_request_context_getter.h',
'browser/net/url_request_context_getter.cc',
'browser/ui/window.cc',
Expand Down Expand Up @@ -128,6 +135,11 @@
'<(DEPTH)/ui/strings/ui_strings.gyp:ui_strings',
],
'conditions': [
['OS!="android"', {
'dependencies': [
'<(DEPTH)/content/browser/devtools/devtools_resources.gyp:devtools_resources',
],
}],
['OS=="android"', {
'copies': [
{
Expand Down Expand Up @@ -157,6 +169,7 @@
],
'conditions': [
['OS!="android"', {
'pak_inputs': ['<(SHARED_INTERMEDIATE_DIR)/blink/devtools_resources.pak',],
'pak_output': '<(PRODUCT_DIR)/sprocket.pak',
}, {
'pak_output': '<(PRODUCT_DIR)/sprocket/assets/sprocket.pak',
Expand All @@ -174,6 +187,13 @@
'<(DEPTH)/sandbox/sandbox.gyp:chrome_sandbox',
],
},
{
'target_name': 'chromedriver',
'type': 'executable',
'dependencies': [
'<(DEPTH)/chrome/chrome.gyp:chromedriver',
],
},
{
'target_name': 'sprocket',
'type': 'executable',
Expand Down
5 changes: 5 additions & 0 deletions sprocket_resources.grd
Expand Up @@ -8,4 +8,9 @@
<output filename="sprocket_resources.rc" type="rc_all" />
</outputs>
<translations />
<release seq="1">
<includes>
<include name="IDR_SPROCKET_DEVTOOLS_DISCOVERY_PAGE" file="resources/devtools_discovery_page.html" type="BINDATA" />
</includes>
</release>
</grit>