1 /*
2 * Copyright (C) 2010-2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32 #include "WebDevToolsAgentImpl.h"
33
34 #include "DebuggerAgentImpl.h"
35 #include "DebuggerAgentManager.h"
36 #include "ExceptionCode.h"
37 #include "InjectedScriptHost.h"
38 #include "InspectorBackendDispatcher.h"
39 #include "InspectorController.h"
40 #include "InspectorInstrumentation.h"
41 #include "Page.h"
42 #include "PageGroup.h"
43 #include "PageScriptDebugServer.h"
44 #include "PlatformString.h"
45 #include "ResourceError.h"
46 #include "ResourceRequest.h"
47 #include "ResourceResponse.h"
48 #include "V8Binding.h"
49 #include "V8Proxy.h"
50 #include "V8Utilities.h"
51 #include "WebDataSource.h"
52 #include "WebDevToolsAgentClient.h"
53 #include "WebFrameImpl.h"
54 #include "WebRect.h"
55 #include "WebString.h"
56 #include "WebURL.h"
57 #include "WebURLError.h"
58 #include "WebURLRequest.h"
59 #include "WebURLResponse.h"
60 #include "WebViewClient.h"
61 #include "WebViewImpl.h"
62 #include <wtf/CurrentTime.h>
63 #include <wtf/Noncopyable.h>
64 #include <wtf/OwnPtr.h>
65
66 using namespace WebCore;
67
68 namespace WebKit {
69
70 namespace {
71
72 static const char kFrontendConnectedFeatureName[] = "frontend-connected";
73 static const char kInspectorStateFeatureName[] = "inspector-state";
74
75 class ClientMessageLoopAdapter : public PageScriptDebugServer::ClientMessageLoop {
76 public:
ensureClientMessageLoopCreated(WebDevToolsAgentClient * client)77 static void ensureClientMessageLoopCreated(WebDevToolsAgentClient* client)
78 {
79 if (s_instance)
80 return;
81 s_instance = new ClientMessageLoopAdapter(client->createClientMessageLoop());
82 PageScriptDebugServer::shared().setClientMessageLoop(s_instance);
83 }
84
inspectedViewClosed(WebViewImpl * view)85 static void inspectedViewClosed(WebViewImpl* view)
86 {
87 if (s_instance)
88 s_instance->m_frozenViews.remove(view);
89 }
90
didNavigate()91 static void didNavigate()
92 {
93 // Release render thread if necessary.
94 if (s_instance && s_instance->m_running)
95 PageScriptDebugServer::shared().continueProgram();
96 }
97
98 private:
ClientMessageLoopAdapter(PassOwnPtr<WebKit::WebDevToolsAgentClient::WebKitClientMessageLoop> messageLoop)99 ClientMessageLoopAdapter(PassOwnPtr<WebKit::WebDevToolsAgentClient::WebKitClientMessageLoop> messageLoop)
100 : m_running(false)
101 , m_messageLoop(messageLoop) { }
102
103
run(Page * page)104 virtual void run(Page* page)
105 {
106 if (m_running)
107 return;
108 m_running = true;
109
110 Vector<WebViewImpl*> views;
111
112 // 1. Disable input events.
113 HashSet<Page*>::const_iterator end = page->group().pages().end();
114 for (HashSet<Page*>::const_iterator it = page->group().pages().begin(); it != end; ++it) {
115 WebViewImpl* view = WebViewImpl::fromPage(*it);
116 m_frozenViews.add(view);
117 views.append(view);
118 view->setIgnoreInputEvents(true);
119 }
120
121 // 2. Disable active objects
122 WebView::willEnterModalLoop();
123
124 // 3. Process messages until quitNow is called.
125 m_messageLoop->run();
126
127 // 4. Resume active objects
128 WebView::didExitModalLoop();
129
130 // 5. Resume input events.
131 for (Vector<WebViewImpl*>::iterator it = views.begin(); it != views.end(); ++it) {
132 if (m_frozenViews.contains(*it)) {
133 // The view was not closed during the dispatch.
134 (*it)->setIgnoreInputEvents(false);
135 }
136 }
137
138 // 6. All views have been resumed, clear the set.
139 m_frozenViews.clear();
140
141 m_running = false;
142 }
143
quitNow()144 virtual void quitNow()
145 {
146 m_messageLoop->quitNow();
147 }
148
149 bool m_running;
150 OwnPtr<WebKit::WebDevToolsAgentClient::WebKitClientMessageLoop> m_messageLoop;
151 typedef HashSet<WebViewImpl*> FrozenViewsSet;
152 FrozenViewsSet m_frozenViews;
153 static ClientMessageLoopAdapter* s_instance;
154
155 };
156
157 ClientMessageLoopAdapter* ClientMessageLoopAdapter::s_instance = 0;
158
159 } // namespace
160
WebDevToolsAgentImpl(WebViewImpl * webViewImpl,WebDevToolsAgentClient * client)161 WebDevToolsAgentImpl::WebDevToolsAgentImpl(
162 WebViewImpl* webViewImpl,
163 WebDevToolsAgentClient* client)
164 : m_hostId(client->hostIdentifier())
165 , m_client(client)
166 , m_webViewImpl(webViewImpl)
167 , m_attached(false)
168 {
169 DebuggerAgentManager::setExposeV8DebuggerProtocol(
170 client->exposeV8DebuggerProtocol());
171 }
172
~WebDevToolsAgentImpl()173 WebDevToolsAgentImpl::~WebDevToolsAgentImpl()
174 {
175 DebuggerAgentManager::onWebViewClosed(m_webViewImpl);
176 ClientMessageLoopAdapter::inspectedViewClosed(m_webViewImpl);
177 }
178
attach()179 void WebDevToolsAgentImpl::attach()
180 {
181 if (m_attached)
182 return;
183
184 if (!m_client->exposeV8DebuggerProtocol())
185 ClientMessageLoopAdapter::ensureClientMessageLoopCreated(m_client);
186
187 m_debuggerAgentImpl.set(
188 new DebuggerAgentImpl(m_webViewImpl, this, m_client));
189 m_attached = true;
190 }
191
detach()192 void WebDevToolsAgentImpl::detach()
193 {
194 // Prevent controller from sending messages to the frontend.
195 InspectorController* ic = inspectorController();
196 ic->disconnectFrontend();
197 ic->hideHighlight();
198 ic->close();
199 m_debuggerAgentImpl.set(0);
200 m_attached = false;
201 }
202
frontendLoaded()203 void WebDevToolsAgentImpl::frontendLoaded()
204 {
205 inspectorController()->connectFrontend();
206 }
207
didNavigate()208 void WebDevToolsAgentImpl::didNavigate()
209 {
210 ClientMessageLoopAdapter::didNavigate();
211 DebuggerAgentManager::onNavigate();
212 }
213
didClearWindowObject(WebFrameImpl * webframe)214 void WebDevToolsAgentImpl::didClearWindowObject(WebFrameImpl* webframe)
215 {
216 DebuggerAgentManager::setHostId(webframe, m_hostId);
217 }
218
dispatchOnInspectorBackend(const WebString & message)219 void WebDevToolsAgentImpl::dispatchOnInspectorBackend(const WebString& message)
220 {
221 inspectorController()->dispatchMessageFromFrontend(message);
222 }
223
inspectElementAt(const WebPoint & point)224 void WebDevToolsAgentImpl::inspectElementAt(const WebPoint& point)
225 {
226 m_webViewImpl->inspectElementAt(point);
227 }
228
setRuntimeProperty(const WebString & name,const WebString & value)229 void WebDevToolsAgentImpl::setRuntimeProperty(const WebString& name, const WebString& value)
230 {
231 if (name == kInspectorStateFeatureName) {
232 InspectorController* ic = inspectorController();
233 ic->restoreInspectorStateFromCookie(value);
234 }
235 }
236
inspectorController()237 InspectorController* WebDevToolsAgentImpl::inspectorController()
238 {
239 if (Page* page = m_webViewImpl->page())
240 return page->inspectorController();
241 return 0;
242 }
243
mainFrame()244 Frame* WebDevToolsAgentImpl::mainFrame()
245 {
246 if (Page* page = m_webViewImpl->page())
247 return page->mainFrame();
248 return 0;
249 }
250
inspectorDestroyed()251 void WebDevToolsAgentImpl::inspectorDestroyed()
252 {
253 // Our lifetime is bound to the WebViewImpl.
254 }
255
openInspectorFrontend(InspectorController *)256 void WebDevToolsAgentImpl::openInspectorFrontend(InspectorController*)
257 {
258 }
259
highlight(Node * node)260 void WebDevToolsAgentImpl::highlight(Node* node)
261 {
262 // InspectorController does the actuall tracking of the highlighted node
263 // and the drawing of the highlight. Here we just make sure to invalidate
264 // the rects of the old and new nodes.
265 hideHighlight();
266 }
267
hideHighlight()268 void WebDevToolsAgentImpl::hideHighlight()
269 {
270 // FIXME: able to invalidate a smaller rect.
271 // FIXME: Is it important to just invalidate the rect of the node region
272 // given that this is not on a critical codepath? In order to do so, we'd
273 // have to take scrolling into account.
274 const WebSize& size = m_webViewImpl->size();
275 WebRect damagedRect(0, 0, size.width, size.height);
276 if (m_webViewImpl->client())
277 m_webViewImpl->client()->didInvalidateRect(damagedRect);
278 }
279
sendMessageToFrontend(const String & message)280 bool WebDevToolsAgentImpl::sendMessageToFrontend(const String& message)
281 {
282 WebDevToolsAgentImpl* devToolsAgent = static_cast<WebDevToolsAgentImpl*>(m_webViewImpl->devToolsAgent());
283 if (!devToolsAgent)
284 return false;
285
286 m_client->sendMessageToInspectorFrontend(message);
287 return true;
288 }
289
updateInspectorStateCookie(const String & state)290 void WebDevToolsAgentImpl::updateInspectorStateCookie(const String& state)
291 {
292 m_client->runtimePropertyChanged(kInspectorStateFeatureName, state);
293 }
294
evaluateInWebInspector(long callId,const WebString & script)295 void WebDevToolsAgentImpl::evaluateInWebInspector(long callId, const WebString& script)
296 {
297 InspectorController* ic = inspectorController();
298 ic->evaluateForTestInFrontend(callId, script);
299 }
300
setTimelineProfilingEnabled(bool enabled)301 void WebDevToolsAgentImpl::setTimelineProfilingEnabled(bool enabled)
302 {
303 InspectorController* ic = inspectorController();
304 if (enabled)
305 ic->startTimelineProfiler();
306 else
307 ic->stopTimelineProfiler();
308 }
309
executeDebuggerCommand(const WebString & command,int callerId)310 void WebDevToolsAgent::executeDebuggerCommand(const WebString& command, int callerId)
311 {
312 DebuggerAgentManager::executeDebuggerCommand(command, callerId);
313 }
314
debuggerPauseScript()315 void WebDevToolsAgent::debuggerPauseScript()
316 {
317 DebuggerAgentManager::pauseScript();
318 }
319
interruptAndDispatch(MessageDescriptor * d)320 void WebDevToolsAgent::interruptAndDispatch(MessageDescriptor* d)
321 {
322 class DebuggerTask : public PageScriptDebugServer::Task {
323 public:
324 DebuggerTask(WebDevToolsAgent::MessageDescriptor* descriptor) : m_descriptor(descriptor) { }
325 virtual ~DebuggerTask() { }
326 virtual void run()
327 {
328 if (WebDevToolsAgent* webagent = m_descriptor->agent())
329 webagent->dispatchOnInspectorBackend(m_descriptor->message());
330 }
331 private:
332 OwnPtr<WebDevToolsAgent::MessageDescriptor> m_descriptor;
333 };
334 PageScriptDebugServer::interruptAndRun(new DebuggerTask(d));
335 }
336
shouldInterruptForMessage(const WebString & message)337 bool WebDevToolsAgent::shouldInterruptForMessage(const WebString& message)
338 {
339 String commandName;
340 if (!InspectorBackendDispatcher::getCommandName(message, &commandName))
341 return false;
342 return commandName == InspectorBackendDispatcher::Debugger_pauseCmd
343 || commandName == InspectorBackendDispatcher::Debugger_setBreakpointCmd
344 || commandName == InspectorBackendDispatcher::Debugger_setBreakpointByUrlCmd
345 || commandName == InspectorBackendDispatcher::Debugger_removeBreakpointCmd
346 || commandName == InspectorBackendDispatcher::Debugger_setBreakpointsActiveCmd
347 || commandName == InspectorBackendDispatcher::Profiler_startCmd
348 || commandName == InspectorBackendDispatcher::Profiler_stopCmd
349 || commandName == InspectorBackendDispatcher::Profiler_getProfileCmd;
350 }
351
processPendingMessages()352 void WebDevToolsAgent::processPendingMessages()
353 {
354 PageScriptDebugServer::shared().runPendingTasks();
355 }
356
setMessageLoopDispatchHandler(MessageLoopDispatchHandler handler)357 void WebDevToolsAgent::setMessageLoopDispatchHandler(MessageLoopDispatchHandler handler)
358 {
359 DebuggerAgentManager::setMessageLoopDispatchHandler(handler);
360 }
361
362 } // namespace WebKit
363