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