| // Copyright (c) 2012 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 "content/renderer/devtools/devtools_agent.h" |
| |
| #include <stddef.h> |
| |
| #include <map> |
| |
| #include "base/json/json_writer.h" |
| #include "base/lazy_instance.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/threading/non_thread_safe.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/common/devtools_messages.h" |
| #include "content/common/frame_messages.h" |
| #include "content/public/common/manifest.h" |
| #include "content/renderer/devtools/devtools_client.h" |
| #include "content/renderer/devtools/devtools_cpu_throttler.h" |
| #include "content/renderer/manifest/manifest_manager.h" |
| #include "content/renderer/render_frame_impl.h" |
| #include "content/renderer/render_widget.h" |
| #include "ipc/ipc_channel.h" |
| #include "third_party/WebKit/public/platform/WebFloatRect.h" |
| #include "third_party/WebKit/public/platform/WebPoint.h" |
| #include "third_party/WebKit/public/platform/WebString.h" |
| #include "third_party/WebKit/public/web/WebDevToolsAgent.h" |
| #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| |
| using blink::WebDevToolsAgent; |
| using blink::WebDevToolsAgentClient; |
| using blink::WebLocalFrame; |
| using blink::WebPoint; |
| using blink::WebString; |
| |
| using base::trace_event::TraceLog; |
| |
| namespace content { |
| |
| namespace { |
| |
| const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4; |
| const char kPageGetAppManifest[] = "Page.getAppManifest"; |
| |
| class WebKitClientMessageLoopImpl |
| : public WebDevToolsAgentClient::WebKitClientMessageLoop, |
| public base::NonThreadSafe { |
| public: |
| WebKitClientMessageLoopImpl() = default; |
| ~WebKitClientMessageLoopImpl() override { DCHECK(CalledOnValidThread()); } |
| void run() override { |
| DCHECK(CalledOnValidThread()); |
| |
| base::RunLoop* const previous_run_loop = run_loop_; |
| base::RunLoop run_loop; |
| run_loop_ = &run_loop; |
| |
| base::MessageLoop::ScopedNestableTaskAllower allow( |
| base::MessageLoop::current()); |
| run_loop.Run(); |
| |
| run_loop_ = previous_run_loop; |
| } |
| void quitNow() override { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(run_loop_); |
| |
| run_loop_->Quit(); |
| } |
| |
| private: |
| base::RunLoop* run_loop_ = nullptr; |
| }; |
| |
| typedef std::map<int, DevToolsAgent*> IdToAgentMap; |
| base::LazyInstance<IdToAgentMap>::Leaky |
| g_agent_for_routing_id = LAZY_INSTANCE_INITIALIZER; |
| |
| } // namespace |
| |
| DevToolsAgent::DevToolsAgent(RenderFrameImpl* frame) |
| : RenderFrameObserver(frame), |
| is_attached_(false), |
| is_devtools_client_(false), |
| paused_in_mouse_move_(false), |
| paused_(false), |
| frame_(frame), |
| cpu_throttler_(new DevToolsCPUThrottler()), |
| weak_factory_(this) { |
| g_agent_for_routing_id.Get()[routing_id()] = this; |
| frame_->GetWebFrame()->setDevToolsAgentClient(this); |
| } |
| |
| DevToolsAgent::~DevToolsAgent() { |
| g_agent_for_routing_id.Get().erase(routing_id()); |
| } |
| |
| // Called on the Renderer thread. |
| bool DevToolsAgent::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(DevToolsAgent, message) |
| IPC_MESSAGE_HANDLER(DevToolsAgentMsg_Attach, OnAttach) |
| IPC_MESSAGE_HANDLER(DevToolsAgentMsg_Reattach, OnReattach) |
| IPC_MESSAGE_HANDLER(DevToolsAgentMsg_Detach, OnDetach) |
| IPC_MESSAGE_HANDLER(DevToolsAgentMsg_DispatchOnInspectorBackend, |
| OnDispatchOnInspectorBackend) |
| IPC_MESSAGE_HANDLER(DevToolsAgentMsg_InspectElement, OnInspectElement) |
| IPC_MESSAGE_HANDLER(DevToolsAgentMsg_RequestNewWindow_ACK, |
| OnRequestNewWindowACK) |
| IPC_MESSAGE_HANDLER(DevToolsMsg_SetupDevToolsClient, OnSetupDevToolsClient) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| if (message.type() == FrameMsg_Navigate::ID) |
| ContinueProgram(); // Don't want to swallow the message. |
| |
| return handled; |
| } |
| |
| void DevToolsAgent::WidgetWillClose() { |
| ContinueProgram(); |
| } |
| |
| void DevToolsAgent::OnDestruct() { |
| delete this; |
| } |
| |
| void DevToolsAgent::sendProtocolMessage(int session_id, |
| int call_id, |
| const blink::WebString& message, |
| const blink::WebString& state_cookie) { |
| if (!send_protocol_message_callback_for_test_.is_null()) { |
| send_protocol_message_callback_for_test_.Run( |
| session_id, call_id, message.utf8(), state_cookie.utf8()); |
| return; |
| } |
| SendChunkedProtocolMessage(this, routing_id(), session_id, call_id, |
| message.utf8(), state_cookie.utf8()); |
| } |
| |
| // static |
| blink::WebDevToolsAgentClient::WebKitClientMessageLoop* |
| DevToolsAgent::createMessageLoopWrapper() { |
| return new WebKitClientMessageLoopImpl(); |
| } |
| |
| blink::WebDevToolsAgentClient::WebKitClientMessageLoop* |
| DevToolsAgent::createClientMessageLoop() { |
| return createMessageLoopWrapper(); |
| } |
| |
| void DevToolsAgent::willEnterDebugLoop() { |
| paused_ = true; |
| if (RenderWidget* widget = frame_->GetRenderWidget()) |
| paused_in_mouse_move_ = widget->SendAckForMouseMoveFromDebugger(); |
| } |
| |
| void DevToolsAgent::didExitDebugLoop() { |
| paused_ = false; |
| if (!paused_in_mouse_move_) |
| return; |
| if (RenderWidget* widget = frame_->GetRenderWidget()) { |
| widget->IgnoreAckForMouseMoveFromDebugger(); |
| paused_in_mouse_move_ = false; |
| } |
| } |
| |
| bool DevToolsAgent::requestDevToolsForFrame(blink::WebLocalFrame* webFrame) { |
| RenderFrameImpl* frame = RenderFrameImpl::FromWebFrame(webFrame); |
| if (!frame) |
| return false; |
| Send(new DevToolsAgentHostMsg_RequestNewWindow(routing_id(), |
| frame->GetRoutingID())); |
| return true; |
| } |
| |
| void DevToolsAgent::enableTracing(const WebString& category_filter) { |
| // Tracing is already started by DevTools TracingHandler::Start for the |
| // renderer target in the browser process. It will eventually start tracing in |
| // the renderer process via IPC. But we still need a redundant |
| // TraceLog::SetEnabled call here for |
| // InspectorTracingAgent::emitMetadataEvents(), at which point, we are not |
| // sure if tracing is already started in the renderer process. |
| TraceLog* trace_log = TraceLog::GetInstance(); |
| trace_log->SetEnabled( |
| base::trace_event::TraceConfig(category_filter.utf8(), ""), |
| TraceLog::RECORDING_MODE); |
| } |
| |
| void DevToolsAgent::disableTracing() { |
| TraceLog::GetInstance()->SetDisabled(); |
| } |
| |
| void DevToolsAgent::setCPUThrottlingRate(double rate) { |
| cpu_throttler_->SetThrottlingRate(rate); |
| } |
| |
| // static |
| DevToolsAgent* DevToolsAgent::FromRoutingId(int routing_id) { |
| IdToAgentMap::iterator it = g_agent_for_routing_id.Get().find(routing_id); |
| if (it != g_agent_for_routing_id.Get().end()) { |
| return it->second; |
| } |
| return NULL; |
| } |
| |
| // static |
| void DevToolsAgent::SendChunkedProtocolMessage(IPC::Sender* sender, |
| int routing_id, |
| int session_id, |
| int call_id, |
| const std::string& message, |
| const std::string& post_state) { |
| DevToolsMessageChunk chunk; |
| chunk.message_size = message.size(); |
| chunk.is_first = true; |
| |
| if (message.length() < kMaxMessageChunkSize) { |
| chunk.data = message; |
| chunk.session_id = session_id; |
| chunk.call_id = call_id; |
| chunk.post_state = post_state; |
| chunk.is_last = true; |
| sender->Send(new DevToolsClientMsg_DispatchOnInspectorFrontend( |
| routing_id, chunk)); |
| return; |
| } |
| |
| for (size_t pos = 0; pos < message.length(); pos += kMaxMessageChunkSize) { |
| chunk.is_last = pos + kMaxMessageChunkSize >= message.length(); |
| chunk.session_id = chunk.is_last ? session_id : 0; |
| chunk.call_id = chunk.is_last ? call_id : 0; |
| chunk.post_state = chunk.is_last ? post_state : std::string(); |
| chunk.data = message.substr(pos, kMaxMessageChunkSize); |
| sender->Send(new DevToolsClientMsg_DispatchOnInspectorFrontend( |
| routing_id, chunk)); |
| chunk.is_first = false; |
| chunk.message_size = 0; |
| } |
| } |
| |
| void DevToolsAgent::OnAttach(const std::string& host_id, int session_id) { |
| GetWebAgent()->attach(WebString::fromUTF8(host_id), session_id); |
| is_attached_ = true; |
| } |
| |
| void DevToolsAgent::OnReattach(const std::string& host_id, |
| int session_id, |
| const std::string& agent_state) { |
| GetWebAgent()->reattach(WebString::fromUTF8(host_id), session_id, |
| WebString::fromUTF8(agent_state)); |
| is_attached_ = true; |
| } |
| |
| void DevToolsAgent::OnDetach() { |
| GetWebAgent()->detach(); |
| is_attached_ = false; |
| } |
| |
| void DevToolsAgent::OnDispatchOnInspectorBackend(int session_id, |
| int call_id, |
| const std::string& method, |
| const std::string& message) { |
| TRACE_EVENT0("devtools", "DevToolsAgent::OnDispatchOnInspectorBackend"); |
| if (method == kPageGetAppManifest) { |
| ManifestManager* manager = frame_->manifest_manager(); |
| manager->GetManifest( |
| base::Bind(&DevToolsAgent::GotManifest, |
| weak_factory_.GetWeakPtr(), session_id, call_id)); |
| return; |
| } |
| GetWebAgent()->dispatchOnInspectorBackend(session_id, |
| call_id, |
| WebString::fromUTF8(method), |
| WebString::fromUTF8(message)); |
| } |
| |
| void DevToolsAgent::OnInspectElement(int session_id, int x, int y) { |
| blink::WebFloatRect point_rect(x, y, 0, 0); |
| frame_->GetRenderWidget()->convertWindowToViewport(&point_rect); |
| GetWebAgent()->inspectElementAt( |
| session_id, WebPoint(point_rect.x, point_rect.y)); |
| } |
| |
| void DevToolsAgent::OnRequestNewWindowACK(bool success) { |
| if (!success) |
| GetWebAgent()->failedToRequestDevTools(); |
| } |
| |
| void DevToolsAgent::ContinueProgram() { |
| GetWebAgent()->continueProgram(); |
| } |
| |
| void DevToolsAgent::OnSetupDevToolsClient( |
| const std::string& compatibility_script) { |
| // We only want to register once; and only in main frame. |
| DCHECK(!frame_->GetWebFrame()->parent()); |
| if (is_devtools_client_) |
| return; |
| is_devtools_client_ = true; |
| new DevToolsClient(frame_, compatibility_script); |
| } |
| |
| WebDevToolsAgent* DevToolsAgent::GetWebAgent() { |
| return frame_->GetWebFrame()->devToolsAgent(); |
| } |
| |
| bool DevToolsAgent::IsAttached() { |
| return is_attached_; |
| } |
| |
| void DevToolsAgent::GotManifest(int session_id, |
| int call_id, |
| const GURL& manifest_url, |
| const Manifest& manifest, |
| const ManifestDebugInfo& debug_info) { |
| if (!is_attached_) |
| return; |
| |
| std::unique_ptr<base::DictionaryValue> response(new base::DictionaryValue()); |
| response->SetInteger("id", call_id); |
| std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue()); |
| std::unique_ptr<base::ListValue> errors(new base::ListValue()); |
| |
| bool failed = false; |
| for (const auto& error : debug_info.errors) { |
| std::unique_ptr<base::DictionaryValue> error_value( |
| new base::DictionaryValue()); |
| error_value->SetString("message", error.message); |
| error_value->SetBoolean("critical", error.critical); |
| error_value->SetInteger("line", error.line); |
| error_value->SetInteger("column", error.column); |
| if (error.critical) |
| failed = true; |
| errors->Append(std::move(error_value)); |
| } |
| |
| WebString url = frame_->GetWebFrame()->document().manifestURL().string(); |
| result->SetString("url", url); |
| if (!failed) |
| result->SetString("data", debug_info.raw_data); |
| result->Set("errors", errors.release()); |
| response->Set("result", result.release()); |
| |
| std::string json_message; |
| base::JSONWriter::Write(*response, &json_message); |
| SendChunkedProtocolMessage(this, routing_id(), session_id, call_id, |
| json_message, std::string()); |
| } |
| |
| } // namespace content |