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 "BoundObject.h" 35 #include "DebuggerAgentImpl.h" 36 #include "DebuggerAgentManager.h" 37 #include "Document.h" 38 #include "EventListener.h" 39 #include "InjectedScriptHost.h" 40 #include "InspectorBackend.h" 41 #include "InspectorController.h" 42 #include "InspectorFrontend.h" 43 #include "InspectorResource.h" 44 #include "Node.h" 45 #include "Page.h" 46 #include "PlatformString.h" 47 #include "ProfilerAgentImpl.h" 48 #include "ResourceError.h" 49 #include "ResourceRequest.h" 50 #include "ResourceResponse.h" 51 #include "ScriptObject.h" 52 #include "ScriptState.h" 53 #include "ScriptValue.h" 54 #include "V8Binding.h" 55 #include "V8InspectorBackend.h" 56 #include "V8Proxy.h" 57 #include "V8Utilities.h" 58 #include "WebDataSource.h" 59 #include "WebDevToolsAgentClient.h" 60 #include "WebDevToolsMessageData.h" 61 #include "WebFrameImpl.h" 62 #include "WebString.h" 63 #include "WebURL.h" 64 #include "WebURLError.h" 65 #include "WebURLRequest.h" 66 #include "WebURLResponse.h" 67 #include "WebViewImpl.h" 68 #include <wtf/Noncopyable.h> 69 #include <wtf/OwnPtr.h> 70 71 using WebCore::Document; 72 using WebCore::DocumentLoader; 73 using WebCore::FrameLoader; 74 using WebCore::InjectedScriptHost; 75 using WebCore::InspectorBackend; 76 using WebCore::InspectorController; 77 using WebCore::InspectorFrontend; 78 using WebCore::InspectorResource; 79 using WebCore::Node; 80 using WebCore::Page; 81 using WebCore::ResourceError; 82 using WebCore::ResourceRequest; 83 using WebCore::ResourceResponse; 84 using WebCore::SafeAllocation; 85 using WebCore::ScriptObject; 86 using WebCore::ScriptState; 87 using WebCore::ScriptValue; 88 using WebCore::String; 89 using WebCore::V8ClassIndex; 90 using WebCore::V8DOMWrapper; 91 using WebCore::V8InspectorBackend; 92 using WebCore::V8Proxy; 93 94 namespace WebKit { 95 96 namespace { 97 98 void InspectorBackendWeakReferenceCallback(v8::Persistent<v8::Value> object, void* parameter) 99 { 100 InspectorBackend* backend = static_cast<InspectorBackend*>(parameter); 101 backend->deref(); 102 object.Dispose(); 103 } 104 105 void SetApuAgentEnabledInUtilityContext(v8::Handle<v8::Context> context, bool enabled) 106 { 107 v8::HandleScope handleScope; 108 v8::Context::Scope contextScope(context); 109 v8::Handle<v8::Object> dispatcher = v8::Local<v8::Object>::Cast( 110 context->Global()->Get(v8::String::New("ApuAgentDispatcher"))); 111 if (dispatcher.IsEmpty()) 112 return; 113 dispatcher->Set(v8::String::New("enabled"), v8::Boolean::New(enabled)); 114 } 115 116 // TODO(pfeldman): Make this public in WebDevToolsAgent API. 117 static const char kApuAgentFeatureName[] = "apu-agent"; 118 119 // Keep these in sync with the ones in inject_dispatch.js. 120 static const char kTimelineFeatureName[] = "timeline-profiler"; 121 static const char kResourceTrackingFeatureName[] = "resource-tracking"; 122 123 class IORPCDelegate : public DevToolsRPC::Delegate, public Noncopyable { 124 public: 125 IORPCDelegate() { } 126 virtual ~IORPCDelegate() { } 127 virtual void sendRpcMessage(const WebDevToolsMessageData& data) 128 { 129 WebDevToolsAgentClient::sendMessageToFrontendOnIOThread(data); 130 } 131 }; 132 133 } // namespace 134 135 WebDevToolsAgentImpl::WebDevToolsAgentImpl( 136 WebViewImpl* webViewImpl, 137 WebDevToolsAgentClient* client) 138 : m_hostId(client->hostIdentifier()) 139 , m_client(client) 140 , m_webViewImpl(webViewImpl) 141 , m_apuAgentEnabled(false) 142 , m_resourceTrackingWasEnabled(false) 143 , m_attached(false) 144 { 145 m_debuggerAgentDelegateStub.set(new DebuggerAgentDelegateStub(this)); 146 m_toolsAgentDelegateStub.set(new ToolsAgentDelegateStub(this)); 147 m_apuAgentDelegateStub.set(new ApuAgentDelegateStub(this)); 148 } 149 150 WebDevToolsAgentImpl::~WebDevToolsAgentImpl() 151 { 152 DebuggerAgentManager::onWebViewClosed(m_webViewImpl); 153 disposeUtilityContext(); 154 } 155 156 void WebDevToolsAgentImpl::disposeUtilityContext() 157 { 158 if (!m_utilityContext.IsEmpty()) { 159 m_utilityContext.Dispose(); 160 m_utilityContext.Clear(); 161 } 162 } 163 164 void WebDevToolsAgentImpl::unhideResourcesPanelIfNecessary() 165 { 166 InspectorController* ic = m_webViewImpl->page()->inspectorController(); 167 ic->ensureResourceTrackingSettingsLoaded(); 168 String command = String::format("[\"setResourcesPanelEnabled\", %s]", 169 ic->resourceTrackingEnabled() ? "true" : "false"); 170 m_toolsAgentDelegateStub->dispatchOnClient(command); 171 } 172 173 void WebDevToolsAgentImpl::attach() 174 { 175 if (m_attached) 176 return; 177 m_debuggerAgentImpl.set( 178 new DebuggerAgentImpl(m_webViewImpl, 179 m_debuggerAgentDelegateStub.get(), 180 this)); 181 resetInspectorFrontendProxy(); 182 unhideResourcesPanelIfNecessary(); 183 // Allow controller to send messages to the frontend. 184 InspectorController* ic = inspectorController(); 185 186 { // TODO(yurys): the source should have already been pushed by the frontend. 187 v8::HandleScope scope; 188 v8::Context::Scope contextScope(m_utilityContext); 189 v8::Handle<v8::Value> constructorValue = m_utilityContext->Global()->Get( 190 v8::String::New("injectedScriptConstructor")); 191 if (constructorValue->IsFunction()) { 192 String source = WebCore::toWebCoreString(constructorValue); 193 ic->injectedScriptHost()->setInjectedScriptSource("(" + source + ")"); 194 } 195 } 196 197 ic->setWindowVisible(true, false); 198 m_attached = true; 199 } 200 201 void WebDevToolsAgentImpl::detach() 202 { 203 // Prevent controller from sending messages to the frontend. 204 InspectorController* ic = m_webViewImpl->page()->inspectorController(); 205 ic->hideHighlight(); 206 ic->close(); 207 disposeUtilityContext(); 208 m_debuggerAgentImpl.set(0); 209 m_attached = false; 210 m_apuAgentEnabled = false; 211 } 212 213 void WebDevToolsAgentImpl::didNavigate() 214 { 215 DebuggerAgentManager::onNavigate(); 216 } 217 218 void WebDevToolsAgentImpl::didCommitProvisionalLoad(WebFrameImpl* webframe, bool isNewNavigation) 219 { 220 if (!m_attached) 221 return; 222 WebDataSource* ds = webframe->dataSource(); 223 const WebURLRequest& request = ds->request(); 224 WebURL url = ds->hasUnreachableURL() ? 225 ds->unreachableURL() : 226 request.url(); 227 if (!webframe->parent()) { 228 resetInspectorFrontendProxy(); 229 m_toolsAgentDelegateStub->frameNavigate(WebCore::KURL(url).string()); 230 SetApuAgentEnabledInUtilityContext(m_utilityContext, m_apuAgentEnabled); 231 unhideResourcesPanelIfNecessary(); 232 } 233 } 234 235 void WebDevToolsAgentImpl::didClearWindowObject(WebFrameImpl* webframe) 236 { 237 DebuggerAgentManager::setHostId(webframe, m_hostId); 238 if (m_attached) { 239 // Push context id into the client if it is already attached. 240 m_debuggerAgentDelegateStub->setContextId(m_hostId); 241 } 242 } 243 244 void WebDevToolsAgentImpl::forceRepaint() 245 { 246 m_client->forceRepaint(); 247 } 248 249 void WebDevToolsAgentImpl::dispatchOnInspectorController(int callId, const String& functionName, const String& jsonArgs) 250 { 251 String result; 252 String exception; 253 result = m_debuggerAgentImpl->executeUtilityFunction(m_utilityContext, callId, 254 "InspectorControllerDispatcher", functionName, jsonArgs, false /* is sync */, &exception); 255 m_toolsAgentDelegateStub->didDispatchOn(callId, result, exception); 256 } 257 258 void WebDevToolsAgentImpl::dispatchOnInjectedScript(int callId, int injectedScriptId, const String& functionName, const String& jsonArgs, bool async) 259 { 260 inspectorController()->inspectorBackend()->dispatchOnInjectedScript( 261 callId, 262 injectedScriptId, 263 functionName, 264 jsonArgs, 265 async); 266 } 267 268 void WebDevToolsAgentImpl::dispatchMessageFromFrontend(const WebDevToolsMessageData& data) 269 { 270 if (ToolsAgentDispatch::dispatch(this, data)) 271 return; 272 273 if (!m_attached) 274 return; 275 276 if (m_debuggerAgentImpl.get() && DebuggerAgentDispatch::dispatch(m_debuggerAgentImpl.get(), data)) 277 return; 278 } 279 280 void WebDevToolsAgentImpl::inspectElementAt(const WebPoint& point) 281 { 282 m_webViewImpl->inspectElementAt(point); 283 } 284 285 void WebDevToolsAgentImpl::setRuntimeFeatureEnabled(const WebString& feature, bool enabled) 286 { 287 if (feature == kApuAgentFeatureName) 288 setApuAgentEnabled(enabled); 289 else if (feature == kTimelineFeatureName) 290 setTimelineProfilingEnabled(enabled); 291 else if (feature == kResourceTrackingFeatureName) { 292 InspectorController* ic = m_webViewImpl->page()->inspectorController(); 293 if (enabled) 294 ic->enableResourceTracking(false /* not sticky */, false /* no reload */); 295 else 296 ic->disableResourceTracking(false /* not sticky */); 297 } 298 } 299 300 void WebDevToolsAgentImpl::sendRpcMessage(const WebDevToolsMessageData& data) 301 { 302 m_client->sendMessageToFrontend(data); 303 } 304 305 void WebDevToolsAgentImpl::compileUtilityScripts() 306 { 307 v8::HandleScope handleScope; 308 v8::Context::Scope contextScope(m_utilityContext); 309 // Inject javascript into the context. 310 WebCString injectedScriptJs = m_client->injectedScriptSource(); 311 v8::Script::Compile(v8::String::New( 312 injectedScriptJs.data(), 313 injectedScriptJs.length()))->Run(); 314 WebCString injectDispatchJs = m_client->injectedScriptDispatcherSource(); 315 v8::Script::Compile(v8::String::New( 316 injectDispatchJs.data(), 317 injectDispatchJs.length()))->Run(); 318 } 319 320 void WebDevToolsAgentImpl::initDevToolsAgentHost() 321 { 322 BoundObject devtoolsAgentHost(m_utilityContext, this, "DevToolsAgentHost"); 323 devtoolsAgentHost.addProtoFunction( 324 "dispatch", 325 WebDevToolsAgentImpl::jsDispatchOnClient); 326 devtoolsAgentHost.addProtoFunction( 327 "dispatchToApu", 328 WebDevToolsAgentImpl::jsDispatchToApu); 329 devtoolsAgentHost.addProtoFunction( 330 "evaluateOnSelf", 331 WebDevToolsAgentImpl::jsEvaluateOnSelf); 332 devtoolsAgentHost.addProtoFunction( 333 "runtimeFeatureStateChanged", 334 WebDevToolsAgentImpl::jsOnRuntimeFeatureStateChanged); 335 devtoolsAgentHost.build(); 336 337 v8::HandleScope scope; 338 v8::Context::Scope utilityScope(m_utilityContext); 339 // Call custom code to create inspector backend wrapper in the utility context 340 // instead of calling V8DOMWrapper::convertToV8Object that would create the 341 // wrapper in the Page main frame context. 342 v8::Handle<v8::Object> backendWrapper = createInspectorBackendV8Wrapper(); 343 if (backendWrapper.IsEmpty()) 344 return; 345 m_utilityContext->Global()->Set(v8::String::New("InspectorBackend"), backendWrapper); 346 } 347 348 v8::Local<v8::Object> WebDevToolsAgentImpl::createInspectorBackendV8Wrapper() 349 { 350 V8ClassIndex::V8WrapperType descriptorType = V8ClassIndex::INSPECTORBACKEND; 351 v8::Handle<v8::Function> function = V8InspectorBackend::GetTemplate()->GetFunction(); 352 if (function.IsEmpty()) { 353 // Return if allocation failed. 354 return v8::Local<v8::Object>(); 355 } 356 v8::Local<v8::Object> instance = SafeAllocation::newInstance(function); 357 if (instance.IsEmpty()) { 358 // Avoid setting the wrapper if allocation failed. 359 return v8::Local<v8::Object>(); 360 } 361 InspectorBackend* backend = m_webViewImpl->page()->inspectorController()->inspectorBackend(); 362 V8DOMWrapper::setDOMWrapper(instance, V8ClassIndex::ToInt(descriptorType), backend); 363 // Create a weak reference to the v8 wrapper of InspectorBackend to deref 364 // InspectorBackend when the wrapper is garbage collected. 365 backend->ref(); 366 v8::Persistent<v8::Object> weakHandle = v8::Persistent<v8::Object>::New(instance); 367 weakHandle.MakeWeak(backend, &InspectorBackendWeakReferenceCallback); 368 return instance; 369 } 370 371 void WebDevToolsAgentImpl::resetInspectorFrontendProxy() 372 { 373 disposeUtilityContext(); 374 m_debuggerAgentImpl->createUtilityContext(m_webViewImpl->page()->mainFrame(), &m_utilityContext); 375 compileUtilityScripts(); 376 initDevToolsAgentHost(); 377 378 v8::HandleScope scope; 379 v8::Context::Scope contextScope(m_utilityContext); 380 ScriptState* state = ScriptState::forContext( 381 v8::Local<v8::Context>::New(m_utilityContext)); 382 InspectorController* ic = inspectorController(); 383 ic->setFrontendProxyObject(state, ScriptObject(state, m_utilityContext->Global())); 384 } 385 386 void WebDevToolsAgentImpl::setApuAgentEnabled(bool enabled) 387 { 388 m_apuAgentEnabled = enabled; 389 SetApuAgentEnabledInUtilityContext(m_utilityContext, enabled); 390 InspectorController* ic = m_webViewImpl->page()->inspectorController(); 391 if (enabled) { 392 m_resourceTrackingWasEnabled = ic->resourceTrackingEnabled(); 393 ic->startTimelineProfiler(); 394 if (!m_resourceTrackingWasEnabled) { 395 // TODO(knorton): Introduce some kind of agents dependency here so that 396 // user could turn off resource tracking while apu agent is on. 397 ic->enableResourceTracking(false, false); 398 } 399 m_debuggerAgentImpl->setAutoContinueOnException(true); 400 } else { 401 ic->stopTimelineProfiler(); 402 if (!m_resourceTrackingWasEnabled) 403 ic->disableResourceTracking(false); 404 m_resourceTrackingWasEnabled = false; 405 } 406 m_client->runtimeFeatureStateChanged( 407 kApuAgentFeatureName, 408 enabled); 409 } 410 411 // static 412 v8::Handle<v8::Value> WebDevToolsAgentImpl::jsDispatchOnClient(const v8::Arguments& args) 413 { 414 v8::TryCatch exceptionCatcher; 415 String message = WebCore::toWebCoreStringWithNullCheck(args[0]); 416 if (message.isEmpty() || exceptionCatcher.HasCaught()) 417 return v8::Undefined(); 418 WebDevToolsAgentImpl* agent = static_cast<WebDevToolsAgentImpl*>(v8::External::Cast(*args.Data())->Value()); 419 agent->m_toolsAgentDelegateStub->dispatchOnClient(message); 420 return v8::Undefined(); 421 } 422 423 // static 424 v8::Handle<v8::Value> WebDevToolsAgentImpl::jsDispatchToApu(const v8::Arguments& args) 425 { 426 v8::TryCatch exceptionCatcher; 427 String message = WebCore::toWebCoreStringWithNullCheck(args[0]); 428 if (message.isEmpty() || exceptionCatcher.HasCaught()) 429 return v8::Undefined(); 430 WebDevToolsAgentImpl* agent = static_cast<WebDevToolsAgentImpl*>( 431 v8::External::Cast(*args.Data())->Value()); 432 agent->m_apuAgentDelegateStub->dispatchToApu(message); 433 return v8::Undefined(); 434 } 435 436 // static 437 v8::Handle<v8::Value> WebDevToolsAgentImpl::jsEvaluateOnSelf(const v8::Arguments& args) 438 { 439 String code; 440 { 441 v8::TryCatch exceptionCatcher; 442 code = WebCore::toWebCoreStringWithNullCheck(args[0]); 443 if (code.isEmpty() || exceptionCatcher.HasCaught()) 444 return v8::Undefined(); 445 } 446 WebDevToolsAgentImpl* agent = static_cast<WebDevToolsAgentImpl*>(v8::External::Cast(*args.Data())->Value()); 447 v8::Context::Scope(agent->m_utilityContext); 448 V8Proxy* proxy = V8Proxy::retrieve(agent->m_webViewImpl->page()->mainFrame()); 449 v8::Local<v8::Value> result = proxy->runScript(v8::Script::Compile(v8::String::New(code.utf8().data())), true); 450 return result; 451 } 452 453 // static 454 v8::Handle<v8::Value> WebDevToolsAgentImpl::jsOnRuntimeFeatureStateChanged(const v8::Arguments& args) 455 { 456 v8::TryCatch exceptionCatcher; 457 String feature = WebCore::toWebCoreStringWithNullCheck(args[0]); 458 bool enabled = args[1]->ToBoolean()->Value(); 459 if (feature.isEmpty() || exceptionCatcher.HasCaught()) 460 return v8::Undefined(); 461 WebDevToolsAgentImpl* agent = static_cast<WebDevToolsAgentImpl*>(v8::External::Cast(*args.Data())->Value()); 462 agent->m_client->runtimeFeatureStateChanged(feature, enabled); 463 return v8::Undefined(); 464 } 465 466 467 WebCore::InspectorController* WebDevToolsAgentImpl::inspectorController() 468 { 469 if (Page* page = m_webViewImpl->page()) 470 return page->inspectorController(); 471 return 0; 472 } 473 474 475 //------- plugin resource load notifications --------------- 476 void WebDevToolsAgentImpl::identifierForInitialRequest( 477 unsigned long resourceId, 478 WebFrame* frame, 479 const WebURLRequest& request) 480 { 481 if (InspectorController* ic = inspectorController()) { 482 WebFrameImpl* webFrameImpl = static_cast<WebFrameImpl*>(frame); 483 FrameLoader* frameLoader = webFrameImpl->frame()->loader(); 484 DocumentLoader* loader = frameLoader->activeDocumentLoader(); 485 ic->identifierForInitialRequest(resourceId, loader, request.toResourceRequest()); 486 } 487 } 488 489 void WebDevToolsAgentImpl::willSendRequest(unsigned long resourceId, const WebURLRequest& request) 490 { 491 if (InspectorController* ic = inspectorController()) 492 ic->willSendRequest(resourceId, request.toResourceRequest(), ResourceResponse()); 493 } 494 495 void WebDevToolsAgentImpl::didReceiveData(unsigned long resourceId, int length) 496 { 497 if (InspectorController* ic = inspectorController()) 498 ic->didReceiveContentLength(resourceId, length); 499 } 500 501 void WebDevToolsAgentImpl::didReceiveResponse(unsigned long resourceId, const WebURLResponse& response) 502 { 503 if (InspectorController* ic = inspectorController()) 504 ic->didReceiveResponse(resourceId, response.toResourceResponse()); 505 } 506 507 void WebDevToolsAgentImpl::didFinishLoading(unsigned long resourceId) 508 { 509 if (InspectorController* ic = inspectorController()) 510 ic->didFinishLoading(resourceId); 511 } 512 513 void WebDevToolsAgentImpl::didFailLoading(unsigned long resourceId, const WebURLError& error) 514 { 515 ResourceError resourceError; 516 if (InspectorController* ic = inspectorController()) 517 ic->didFailLoading(resourceId, resourceError); 518 } 519 520 void WebDevToolsAgentImpl::evaluateInWebInspector(long callId, const WebString& script) 521 { 522 InspectorController* ic = inspectorController(); 523 ic->evaluateForTestInFrontend(callId, script); 524 } 525 526 void WebDevToolsAgentImpl::setTimelineProfilingEnabled(bool enabled) 527 { 528 InspectorController* ic = inspectorController(); 529 if (enabled) 530 ic->startTimelineProfiler(); 531 else 532 ic->stopTimelineProfiler(); 533 } 534 535 WebDevToolsAgent* WebDevToolsAgent::create(WebView* webview, WebDevToolsAgentClient* client) 536 { 537 return new WebDevToolsAgentImpl(static_cast<WebViewImpl*>(webview), client); 538 } 539 540 void WebDevToolsAgent::executeDebuggerCommand(const WebString& command, int callerId) 541 { 542 DebuggerAgentManager::executeDebuggerCommand(command, callerId); 543 } 544 545 void WebDevToolsAgent::debuggerPauseScript() 546 { 547 DebuggerAgentManager::pauseScript(); 548 } 549 550 void WebDevToolsAgent::setMessageLoopDispatchHandler(MessageLoopDispatchHandler handler) 551 { 552 DebuggerAgentManager::setMessageLoopDispatchHandler(handler); 553 } 554 555 bool WebDevToolsAgent::dispatchMessageFromFrontendOnIOThread(const WebDevToolsMessageData& data) 556 { 557 IORPCDelegate transport; 558 ProfilerAgentDelegateStub stub(&transport); 559 ProfilerAgentImpl agent(&stub); 560 return ProfilerAgentDispatch::dispatch(&agent, data); 561 } 562 563 } // namespace WebKit 564