WebDevToolsAgentImpl.cpp revision 65f03d4f644ce73618e5f4f50dd694b26f55ae12
1/*
2 * Copyright (C) 2010 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 "PlatformString.h"
44#include "ResourceError.h"
45#include "ResourceRequest.h"
46#include "ResourceResponse.h"
47#include "ScriptDebugServer.h"
48#include "V8Binding.h"
49#include "V8Node.h"
50#include "V8Proxy.h"
51#include "V8Utilities.h"
52#include "WebDataSource.h"
53#include "WebDevToolsAgentClient.h"
54#include "WebFrameImpl.h"
55#include "WebRect.h"
56#include "WebString.h"
57#include "WebURL.h"
58#include "WebURLError.h"
59#include "WebURLRequest.h"
60#include "WebURLResponse.h"
61#include "WebViewClient.h"
62#include "WebViewImpl.h"
63#include <wtf/CurrentTime.h>
64#include <wtf/Noncopyable.h>
65#include <wtf/OwnPtr.h>
66
67using WebCore::DocumentLoader;
68using WebCore::FrameLoader;
69using WebCore::InjectedScriptHost;
70using WebCore::InspectorArray;
71using WebCore::InspectorBackendDispatcher;
72using WebCore::InspectorController;
73using WebCore::InspectorInstrumentation;
74using WebCore::InspectorInstrumentationCookie;
75using WebCore::Node;
76using WebCore::Page;
77using WebCore::ResourceError;
78using WebCore::ResourceRequest;
79using WebCore::ResourceResponse;
80using WTF::String;
81using WebCore::V8DOMWrapper;
82using WebCore::V8Node;
83using WebCore::V8Proxy;
84
85namespace WebKit {
86
87namespace {
88
89static const char kFrontendConnectedFeatureName[] = "frontend-connected";
90static const char kInspectorStateFeatureName[] = "inspector-state";
91
92class ClientMessageLoopAdapter : public WebCore::ScriptDebugServer::ClientMessageLoop {
93public:
94    static void ensureClientMessageLoopCreated(WebDevToolsAgentClient* client)
95    {
96        if (s_instance)
97            return;
98        s_instance = new ClientMessageLoopAdapter(client->createClientMessageLoop());
99        WebCore::ScriptDebugServer::shared().setClientMessageLoop(s_instance);
100    }
101
102    static void inspectedViewClosed(WebViewImpl* view)
103    {
104        if (s_instance)
105            s_instance->m_frozenViews.remove(view);
106    }
107
108    static void didNavigate()
109    {
110        // Release render thread if necessary.
111        if (s_instance && s_instance->m_running)
112            WebCore::ScriptDebugServer::shared().continueProgram();
113    }
114
115private:
116    ClientMessageLoopAdapter(PassOwnPtr<WebKit::WebDevToolsAgentClient::WebKitClientMessageLoop> messageLoop)
117        : m_running(false)
118        , m_messageLoop(messageLoop) { }
119
120
121    virtual void run(Page* page)
122    {
123        if (m_running)
124            return;
125        m_running = true;
126
127        Vector<WebViewImpl*> views;
128
129        // 1. Disable input events.
130        HashSet<Page*>::const_iterator end =  page->group().pages().end();
131        for (HashSet<Page*>::const_iterator it =  page->group().pages().begin(); it != end; ++it) {
132            WebViewImpl* view = WebViewImpl::fromPage(*it);
133            m_frozenViews.add(view);
134            views.append(view);
135            view->setIgnoreInputEvents(true);
136        }
137
138        // 2. Disable active objects
139        WebView::willEnterModalLoop();
140
141        // 3. Process messages until quitNow is called.
142        m_messageLoop->run();
143
144        // 4. Resume active objects
145        WebView::didExitModalLoop();
146
147        // 5. Resume input events.
148        for (Vector<WebViewImpl*>::iterator it = views.begin(); it != views.end(); ++it) {
149            if (m_frozenViews.contains(*it)) {
150                // The view was not closed during the dispatch.
151                (*it)->setIgnoreInputEvents(false);
152            }
153        }
154
155        // 6. All views have been resumed, clear the set.
156        m_frozenViews.clear();
157
158        m_running = false;
159    }
160
161    virtual void quitNow()
162    {
163        m_messageLoop->quitNow();
164    }
165
166    bool m_running;
167    OwnPtr<WebKit::WebDevToolsAgentClient::WebKitClientMessageLoop> m_messageLoop;
168    typedef HashSet<WebViewImpl*> FrozenViewsSet;
169    FrozenViewsSet m_frozenViews;
170    static ClientMessageLoopAdapter* s_instance;
171
172};
173
174ClientMessageLoopAdapter* ClientMessageLoopAdapter::s_instance = 0;
175
176} //  namespace
177
178WebDevToolsAgentImpl::WebDevToolsAgentImpl(
179    WebViewImpl* webViewImpl,
180    WebDevToolsAgentClient* client)
181    : m_hostId(client->hostIdentifier())
182    , m_client(client)
183    , m_webViewImpl(webViewImpl)
184    , m_attached(false)
185{
186    DebuggerAgentManager::setExposeV8DebuggerProtocol(
187        client->exposeV8DebuggerProtocol());
188}
189
190WebDevToolsAgentImpl::~WebDevToolsAgentImpl()
191{
192    DebuggerAgentManager::onWebViewClosed(m_webViewImpl);
193    ClientMessageLoopAdapter::inspectedViewClosed(m_webViewImpl);
194}
195
196void WebDevToolsAgentImpl::attach()
197{
198    if (m_attached)
199        return;
200
201    if (!m_client->exposeV8DebuggerProtocol())
202        ClientMessageLoopAdapter::ensureClientMessageLoopCreated(m_client);
203
204    m_debuggerAgentImpl.set(
205        new DebuggerAgentImpl(m_webViewImpl, this, m_client));
206    WebCString debuggerScriptJs = m_client->debuggerScriptSource();
207    WebCore::ScriptDebugServer::shared().setDebuggerScriptSource(
208        WTF::String(debuggerScriptJs.data(), debuggerScriptJs.length()));
209    m_attached = true;
210}
211
212void WebDevToolsAgentImpl::detach()
213{
214    // Prevent controller from sending messages to the frontend.
215    InspectorController* ic = inspectorController();
216    ic->disconnectFrontend();
217    ic->hideHighlight();
218    ic->close();
219    m_debuggerAgentImpl.set(0);
220    m_attached = false;
221}
222
223void WebDevToolsAgentImpl::frontendLoaded()
224{
225    inspectorController()->connectFrontend();
226}
227
228void WebDevToolsAgentImpl::didNavigate()
229{
230    ClientMessageLoopAdapter::didNavigate();
231    DebuggerAgentManager::onNavigate();
232}
233
234void WebDevToolsAgentImpl::didClearWindowObject(WebFrameImpl* webframe)
235{
236    DebuggerAgentManager::setHostId(webframe, m_hostId);
237}
238
239void WebDevToolsAgentImpl::dispatchOnInspectorBackend(const WebString& message)
240{
241    inspectorController()->inspectorBackendDispatcher()->dispatch(message);
242}
243
244void WebDevToolsAgentImpl::inspectElementAt(const WebPoint& point)
245{
246    m_webViewImpl->inspectElementAt(point);
247}
248
249void WebDevToolsAgentImpl::inspectNode(v8::Handle<v8::Value> node)
250{
251    if (!V8Node::HasInstance(node))
252        V8Proxy::setDOMException(WebCore::TYPE_MISMATCH_ERR);
253    else
254        inspectorController()->inspect(V8Node::toNative(v8::Handle<v8::Object>::Cast(node)));
255}
256
257void WebDevToolsAgentImpl::setRuntimeProperty(const WebString& name, const WebString& value)
258{
259    if (name == kInspectorStateFeatureName) {
260        InspectorController* ic = inspectorController();
261        ic->restoreInspectorStateFromCookie(value);
262    }
263}
264
265WebCore::InspectorController* WebDevToolsAgentImpl::inspectorController()
266{
267    if (Page* page = m_webViewImpl->page())
268        return page->inspectorController();
269    return 0;
270}
271
272WebCore::Frame* WebDevToolsAgentImpl::mainFrame()
273{
274    if (Page* page = m_webViewImpl->page())
275        return page->mainFrame();
276    return 0;
277}
278
279
280//------- plugin resource load notifications ---------------
281void WebDevToolsAgentImpl::identifierForInitialRequest(
282    unsigned long resourceId,
283    WebFrame* webFrame,
284    const WebURLRequest& request)
285{
286    WebFrameImpl* webFrameImpl = static_cast<WebFrameImpl*>(webFrame);
287    WebCore::Frame* frame = webFrameImpl->frame();
288    DocumentLoader* loader = frame->loader()->activeDocumentLoader();
289    InspectorInstrumentation::identifierForInitialRequest(frame, resourceId, loader, request.toResourceRequest());
290}
291
292void WebDevToolsAgentImpl::willSendRequest(unsigned long resourceId, WebURLRequest& request)
293{
294    if (InspectorController* ic = inspectorController()) {
295        InspectorInstrumentation::willSendRequest(mainFrame(), resourceId, request.toMutableResourceRequest(), ResourceResponse());
296        if (ic->hasFrontend() && request.reportLoadTiming())
297            request.setReportRawHeaders(true);
298    }
299}
300
301void WebDevToolsAgentImpl::didReceiveData(unsigned long resourceId, int length)
302{
303    InspectorInstrumentation::didReceiveContentLength(mainFrame(), resourceId, length);
304}
305
306void WebDevToolsAgentImpl::didReceiveResponse(unsigned long resourceId, const WebURLResponse& response)
307{
308    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willReceiveResourceResponse(mainFrame(), resourceId, response.toResourceResponse());
309    InspectorInstrumentation::didReceiveResourceResponse(cookie, resourceId, 0, response.toResourceResponse());
310}
311
312void WebDevToolsAgentImpl::didFinishLoading(unsigned long resourceId)
313{
314    InspectorInstrumentation::didFinishLoading(mainFrame(), resourceId, 0);
315}
316
317void WebDevToolsAgentImpl::didFailLoading(unsigned long resourceId, const WebURLError& error)
318{
319    InspectorInstrumentation::didFailLoading(mainFrame(), resourceId, error);
320}
321
322void WebDevToolsAgentImpl::inspectorDestroyed()
323{
324    // Our lifetime is bound to the WebViewImpl.
325}
326
327void WebDevToolsAgentImpl::openInspectorFrontend(InspectorController*)
328{
329}
330
331void WebDevToolsAgentImpl::highlight(Node* node)
332{
333    // InspectorController does the actuall tracking of the highlighted node
334    // and the drawing of the highlight. Here we just make sure to invalidate
335    // the rects of the old and new nodes.
336    hideHighlight();
337}
338
339void WebDevToolsAgentImpl::hideHighlight()
340{
341    // FIXME: able to invalidate a smaller rect.
342    // FIXME: Is it important to just invalidate the rect of the node region
343    // given that this is not on a critical codepath?  In order to do so, we'd
344    // have to take scrolling into account.
345    const WebSize& size = m_webViewImpl->size();
346    WebRect damagedRect(0, 0, size.width, size.height);
347    if (m_webViewImpl->client())
348        m_webViewImpl->client()->didInvalidateRect(damagedRect);
349}
350
351void WebDevToolsAgentImpl::populateSetting(const String& key, String* value)
352{
353    WebString string;
354    m_webViewImpl->inspectorSetting(key, &string);
355    *value = string;
356}
357
358void WebDevToolsAgentImpl::storeSetting(const String& key, const String& value)
359{
360    m_webViewImpl->setInspectorSetting(key, value);
361}
362
363bool WebDevToolsAgentImpl::sendMessageToFrontend(const WTF::String& message)
364{
365    WebDevToolsAgentImpl* devToolsAgent = static_cast<WebDevToolsAgentImpl*>(m_webViewImpl->devToolsAgent());
366    if (!devToolsAgent)
367        return false;
368
369    m_client->sendMessageToInspectorFrontend(message);
370    return true;
371}
372
373void WebDevToolsAgentImpl::updateInspectorStateCookie(const WTF::String& state)
374{
375    m_client->runtimePropertyChanged(kInspectorStateFeatureName, state);
376}
377
378void WebDevToolsAgentImpl::evaluateInWebInspector(long callId, const WebString& script)
379{
380    InspectorController* ic = inspectorController();
381    ic->evaluateForTestInFrontend(callId, script);
382}
383
384void WebDevToolsAgentImpl::setTimelineProfilingEnabled(bool enabled)
385{
386    InspectorController* ic = inspectorController();
387    if (enabled)
388        ic->startTimelineProfiler();
389    else
390        ic->stopTimelineProfiler();
391}
392
393void WebDevToolsAgent::executeDebuggerCommand(const WebString& command, int callerId)
394{
395    DebuggerAgentManager::executeDebuggerCommand(command, callerId);
396}
397
398void WebDevToolsAgent::debuggerPauseScript()
399{
400    DebuggerAgentManager::pauseScript();
401}
402
403void WebDevToolsAgent::interruptAndDispatch(MessageDescriptor* d)
404{
405    class DebuggerTask : public WebCore::ScriptDebugServer::Task {
406    public:
407        DebuggerTask(WebDevToolsAgent::MessageDescriptor* descriptor) : m_descriptor(descriptor) { }
408        virtual ~DebuggerTask() { }
409        virtual void run()
410        {
411            if (WebDevToolsAgent* webagent = m_descriptor->agent())
412                webagent->dispatchOnInspectorBackend(m_descriptor->message());
413        }
414    private:
415        OwnPtr<WebDevToolsAgent::MessageDescriptor> m_descriptor;
416    };
417    WebCore::ScriptDebugServer::interruptAndRun(new DebuggerTask(d));
418}
419
420bool WebDevToolsAgent::shouldInterruptForMessage(const WebString& message)
421{
422    String commandName;
423    if (!InspectorBackendDispatcher::getCommandName(message, &commandName))
424        return false;
425    return commandName == InspectorBackendDispatcher::pauseCmd
426        || commandName == InspectorBackendDispatcher::setBreakpointCmd
427        || commandName == InspectorBackendDispatcher::removeBreakpointCmd
428        || commandName == InspectorBackendDispatcher::activateBreakpointsCmd
429        || commandName == InspectorBackendDispatcher::deactivateBreakpointsCmd
430        || commandName == InspectorBackendDispatcher::startProfilingCmd
431        || commandName == InspectorBackendDispatcher::stopProfilingCmd
432        || commandName == InspectorBackendDispatcher::getProfileCmd;
433}
434
435void WebDevToolsAgent::processPendingMessages()
436{
437    WebCore::ScriptDebugServer::shared().runPendingTasks();
438}
439
440void WebDevToolsAgent::setMessageLoopDispatchHandler(MessageLoopDispatchHandler handler)
441{
442    DebuggerAgentManager::setMessageLoopDispatchHandler(handler);
443}
444
445} // namespace WebKit
446