diff -r 000000000000 -r 4f2f89ce4247 WebKit/chromium/src/WebDevToolsAgentImpl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebKit/chromium/src/WebDevToolsAgentImpl.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,666 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "WebDevToolsAgentImpl.h" + +#include "BoundObject.h" +#include "DebuggerAgentImpl.h" +#include "DebuggerAgentManager.h" +#include "Document.h" +#include "EventListener.h" +#include "InjectedScriptHost.h" +#include "InspectorBackend.h" +#include "InspectorController.h" +#include "InspectorFrontend.h" +#include "InspectorResource.h" +#include "Node.h" +#include "Page.h" +#include "PageGroup.h" +#include "PlatformString.h" +#include "ProfilerAgentImpl.h" +#include "ResourceError.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "ScriptDebugServer.h" +#include "ScriptObject.h" +#include "ScriptState.h" +#include "ScriptValue.h" +#include "V8Binding.h" +#include "V8InspectorBackend.h" +#include "V8Proxy.h" +#include "V8Utilities.h" +#include "WebDataSource.h" +#include "WebDevToolsAgentClient.h" +#include "WebDevToolsMessageData.h" +#include "WebDevToolsMessageTransport.h" +#include "WebFrameImpl.h" +#include "WebRect.h" +#include "WebString.h" +#include "WebURL.h" +#include "WebURLError.h" +#include "WebURLRequest.h" +#include "WebURLResponse.h" +#include "WebViewClient.h" +#include "WebViewImpl.h" +#include +#include + +using WebCore::Document; +using WebCore::DocumentLoader; +using WebCore::FrameLoader; +using WebCore::InjectedScriptHost; +using WebCore::InspectorBackend; +using WebCore::InspectorController; +using WebCore::InspectorFrontend; +using WebCore::InspectorResource; +using WebCore::Node; +using WebCore::Page; +using WebCore::ResourceError; +using WebCore::ResourceRequest; +using WebCore::ResourceResponse; +using WebCore::SafeAllocation; +using WebCore::ScriptObject; +using WebCore::ScriptState; +using WebCore::ScriptValue; +using WebCore::String; +using WebCore::V8DOMWrapper; +using WebCore::V8InspectorBackend; +using WebCore::V8Proxy; + +namespace WebKit { + +namespace { + +void InspectorBackendWeakReferenceCallback(v8::Persistent object, void* parameter) +{ + InspectorBackend* backend = static_cast(parameter); + backend->deref(); + object.Dispose(); +} + +static const char kResourceTrackingFeatureName[] = "resource-tracking"; +static const char kTimelineFeatureName[] = "timeline-profiler"; +static const char kApuAgentFeatureName[] = "apu-agent"; + +class IORPCDelegate : public DevToolsRPC::Delegate, public Noncopyable { +public: + IORPCDelegate() : m_transport(0) { } + explicit IORPCDelegate(WebDevToolsMessageTransport* transport) : m_transport(transport) { } + virtual ~IORPCDelegate() { } + virtual void sendRpcMessage(const WebDevToolsMessageData& data) + { + if (m_transport) + m_transport->sendMessageToFrontendOnIOThread(data); + } + +private: + WebDevToolsMessageTransport* m_transport; +}; + +class ClientMessageLoopAdapter : public WebCore::ScriptDebugServer::ClientMessageLoop { +public: + static void ensureClientMessageLoopCreated(WebDevToolsAgentClient* client) + { + if (s_instance) + return; + s_instance = new ClientMessageLoopAdapter(client->createClientMessageLoop()); + WebCore::ScriptDebugServer::shared().setClientMessageLoop(s_instance); + } + + static void inspectedViewClosed(WebViewImpl* view) + { + if (s_instance) + s_instance->m_frozenViews.remove(view); + } + + static void didNavigate() + { + // Release render thread if necessary. + if (s_instance && s_instance->m_running) + WebCore::ScriptDebugServer::shared().continueProgram(); + } + +private: + ClientMessageLoopAdapter(PassOwnPtr messageLoop) + : m_running(false) + , m_messageLoop(messageLoop) { } + + + virtual void run(Page* page) + { + if (m_running) + return; + m_running = true; + + Vector views; + + // 1. Disable input events. + HashSet::const_iterator end = page->group().pages().end(); + for (HashSet::const_iterator it = page->group().pages().begin(); it != end; ++it) { + WebViewImpl* view = WebViewImpl::fromPage(*it); + m_frozenViews.add(view); + views.append(view); + view->setIgnoreInputEvents(true); + } + + // 2. Disable active objects + WebView::willEnterModalLoop(); + + // 3. Process messages until quitNow is called. + m_messageLoop->run(); + + // 4. Resume active objects + WebView::didExitModalLoop(); + + // 5. Resume input events. + for (Vector::iterator it = views.begin(); it != views.end(); ++it) { + if (m_frozenViews.contains(*it)) { + // The view was not closed during the dispatch. + (*it)->setIgnoreInputEvents(false); + } + } + + // 6. All views have been resumed, clear the set. + m_frozenViews.clear(); + + m_running = false; + } + + virtual void quitNow() + { + m_messageLoop->quitNow(); + } + + bool m_running; + OwnPtr m_messageLoop; + typedef HashSet FrozenViewsSet; + FrozenViewsSet m_frozenViews; + static ClientMessageLoopAdapter* s_instance; + +}; + +ClientMessageLoopAdapter* ClientMessageLoopAdapter::s_instance = 0; + +} // namespace + +WebDevToolsAgentImpl::WebDevToolsAgentImpl( + WebViewImpl* webViewImpl, + WebDevToolsAgentClient* client) + : m_hostId(client->hostIdentifier()) + , m_client(client) + , m_webViewImpl(webViewImpl) + , m_apuAgentEnabled(false) + , m_resourceTrackingWasEnabled(false) + , m_attached(false) +{ + DebuggerAgentManager::setExposeV8DebuggerProtocol( + client->exposeV8DebuggerProtocol()); + m_debuggerAgentDelegateStub.set(new DebuggerAgentDelegateStub(this)); + m_toolsAgentDelegateStub.set(new ToolsAgentDelegateStub(this)); + m_apuAgentDelegateStub.set(new ApuAgentDelegateStub(this)); +} + +WebDevToolsAgentImpl::~WebDevToolsAgentImpl() +{ + DebuggerAgentManager::onWebViewClosed(m_webViewImpl); + ClientMessageLoopAdapter::inspectedViewClosed(m_webViewImpl); + disposeUtilityContext(); +} + +void WebDevToolsAgentImpl::disposeUtilityContext() +{ + if (!m_utilityContext.IsEmpty()) { + m_utilityContext.Dispose(); + m_utilityContext.Clear(); + } +} + +void WebDevToolsAgentImpl::attach() +{ + if (m_attached) + return; + + if (!m_client->exposeV8DebuggerProtocol()) + ClientMessageLoopAdapter::ensureClientMessageLoopCreated(m_client); + + m_debuggerAgentImpl.set( + new DebuggerAgentImpl(m_webViewImpl, + m_debuggerAgentDelegateStub.get(), + this)); + createInspectorFrontendProxy(); + + // Allow controller to send messages to the frontend. + InspectorController* ic = inspectorController(); + + { // TODO(yurys): the source should have already been pushed by the frontend. + v8::HandleScope scope; + v8::Context::Scope contextScope(m_utilityContext); + v8::Handle constructorValue = m_utilityContext->Global()->Get( + v8::String::New("injectedScriptConstructor")); + if (constructorValue->IsFunction()) { + String source = WebCore::toWebCoreString(constructorValue); + ic->injectedScriptHost()->setInjectedScriptSource("(" + source + ")"); + } + } + + setInspectorFrontendProxyToInspectorController(); + m_attached = true; +} + +void WebDevToolsAgentImpl::detach() +{ + // Prevent controller from sending messages to the frontend. + InspectorController* ic = m_webViewImpl->page()->inspectorController(); + ic->disconnectFrontend(); + ic->hideHighlight(); + ic->close(); + disposeUtilityContext(); + m_debuggerAgentImpl.set(0); + m_attached = false; + m_apuAgentEnabled = false; +} + +void WebDevToolsAgentImpl::didNavigate() +{ + ClientMessageLoopAdapter::didNavigate(); + DebuggerAgentManager::onNavigate(); +} + +void WebDevToolsAgentImpl::didClearWindowObject(WebFrameImpl* webframe) +{ + DebuggerAgentManager::setHostId(webframe, m_hostId); + if (m_attached) { + // Push context id into the client if it is already attached. + m_debuggerAgentDelegateStub->setContextId(m_hostId); + } +} + +void WebDevToolsAgentImpl::forceRepaint() +{ + m_client->forceRepaint(); +} + +void WebDevToolsAgentImpl::dispatchOnInspectorController(int callId, const String& functionName, const String& jsonArgs) +{ + String result; + String exception; + result = m_debuggerAgentImpl->executeUtilityFunction(m_utilityContext, callId, + "InspectorControllerDispatcher", functionName, jsonArgs, false /* is sync */, &exception); + m_toolsAgentDelegateStub->didDispatchOn(callId, result, exception); +} + +void WebDevToolsAgentImpl::dispatchOnInjectedScript(int callId, int injectedScriptId, const String& functionName, const String& jsonArgs, bool async) +{ + inspectorController()->inspectorBackend()->dispatchOnInjectedScript( + callId, + injectedScriptId, + functionName, + jsonArgs, + async); +} + +void WebDevToolsAgentImpl::dispatchMessageFromFrontend(const WebDevToolsMessageData& data) +{ + if (ToolsAgentDispatch::dispatch(this, data)) + return; + + if (!m_attached) + return; + + if (m_debuggerAgentImpl.get() && DebuggerAgentDispatch::dispatch(m_debuggerAgentImpl.get(), data)) + return; +} + +void WebDevToolsAgentImpl::inspectElementAt(const WebPoint& point) +{ + m_webViewImpl->inspectElementAt(point); +} + +void WebDevToolsAgentImpl::setRuntimeFeatureEnabled(const WebString& feature, bool enabled) +{ + if (feature == kApuAgentFeatureName) + setApuAgentEnabled(enabled); + else if (feature == kTimelineFeatureName) + setTimelineProfilingEnabled(enabled); + else if (feature == kResourceTrackingFeatureName) { + InspectorController* ic = m_webViewImpl->page()->inspectorController(); + if (enabled) + ic->enableResourceTracking(false /* not sticky */, false /* no reload */); + else + ic->disableResourceTracking(false /* not sticky */); + } +} + +void WebDevToolsAgentImpl::sendRpcMessage(const WebDevToolsMessageData& data) +{ + m_client->sendMessageToFrontend(data); +} + +void WebDevToolsAgentImpl::compileUtilityScripts() +{ + v8::HandleScope handleScope; + v8::Context::Scope contextScope(m_utilityContext); + // Inject javascript into the context. + WebCString injectedScriptJs = m_client->injectedScriptSource(); + v8::Script::Compile(v8::String::New( + injectedScriptJs.data(), + injectedScriptJs.length()))->Run(); + WebCString injectDispatchJs = m_client->injectedScriptDispatcherSource(); + v8::Script::Compile(v8::String::New( + injectDispatchJs.data(), + injectDispatchJs.length()))->Run(); +} + +void WebDevToolsAgentImpl::initDevToolsAgentHost() +{ + BoundObject devtoolsAgentHost(m_utilityContext, this, "DevToolsAgentHost"); + devtoolsAgentHost.addProtoFunction( + "dispatch", + WebDevToolsAgentImpl::jsDispatchOnClient); + devtoolsAgentHost.build(); + + v8::HandleScope scope; + v8::Context::Scope utilityScope(m_utilityContext); + // Call custom code to create inspector backend wrapper in the utility context + // instead of calling V8DOMWrapper::convertToV8Object that would create the + // wrapper in the Page main frame context. + v8::Handle backendWrapper = createInspectorBackendV8Wrapper(); + if (backendWrapper.IsEmpty()) + return; + m_utilityContext->Global()->Set(v8::String::New("InspectorBackend"), backendWrapper); +} + +v8::Local WebDevToolsAgentImpl::createInspectorBackendV8Wrapper() +{ + v8::Handle function = V8InspectorBackend::GetTemplate()->GetFunction(); + if (function.IsEmpty()) { + // Return if allocation failed. + return v8::Local(); + } + v8::Local instance = SafeAllocation::newInstance(function); + if (instance.IsEmpty()) { + // Avoid setting the wrapper if allocation failed. + return v8::Local(); + } + InspectorBackend* backend = m_webViewImpl->page()->inspectorController()->inspectorBackend(); + V8DOMWrapper::setDOMWrapper(instance, &V8InspectorBackend::info, backend); + // Create a weak reference to the v8 wrapper of InspectorBackend to deref + // InspectorBackend when the wrapper is garbage collected. + backend->ref(); + v8::Persistent weakHandle = v8::Persistent::New(instance); + weakHandle.MakeWeak(backend, &InspectorBackendWeakReferenceCallback); + return instance; +} + +void WebDevToolsAgentImpl::createInspectorFrontendProxy() +{ + disposeUtilityContext(); + m_utilityContext = v8::Context::New(); + compileUtilityScripts(); + initDevToolsAgentHost(); + WebCString debuggerScriptJs = m_client->debuggerScriptSource(); + WebCore::ScriptDebugServer::shared().setDebuggerScriptSource( + WebCore::String(debuggerScriptJs.data(), debuggerScriptJs.length())); +} + +void WebDevToolsAgentImpl::setInspectorFrontendProxyToInspectorController() +{ + v8::HandleScope scope; + ScriptState* state = ScriptState::forContext( + v8::Local::New(m_utilityContext)); + InspectorController* ic = inspectorController(); + ic->connectFrontend(ScriptObject(state, m_utilityContext->Global())); +} + +void WebDevToolsAgentImpl::setApuAgentEnabled(bool enabled) +{ + m_apuAgentEnabled = enabled; + InspectorController* ic = m_webViewImpl->page()->inspectorController(); + if (enabled) { + m_resourceTrackingWasEnabled = ic->resourceTrackingEnabled(); + ic->startTimelineProfiler(); + if (!m_resourceTrackingWasEnabled) { + // TODO(knorton): Introduce some kind of agents dependency here so that + // user could turn off resource tracking while apu agent is on. + ic->enableResourceTracking(false, false); + } + m_debuggerAgentImpl->setAutoContinueOnException(true); + } else { + ic->stopTimelineProfiler(); + if (!m_resourceTrackingWasEnabled) + ic->disableResourceTracking(false); + m_resourceTrackingWasEnabled = false; + } + m_client->runtimeFeatureStateChanged( + kApuAgentFeatureName, + enabled); +} + +// static +v8::Handle WebDevToolsAgentImpl::jsDispatchOnClient(const v8::Arguments& args) +{ + v8::TryCatch exceptionCatcher; + String message = WebCore::toWebCoreStringWithNullCheck(args[0]); + if (message.isEmpty() || exceptionCatcher.HasCaught()) + return v8::Undefined(); + + WebDevToolsAgentImpl* agent = static_cast(v8::External::Cast(*args.Data())->Value()); + + if (!agent->m_apuAgentEnabled) { + agent->m_toolsAgentDelegateStub->dispatchOnClient(message); + return v8::Undefined(); + } + + String method = WebCore::toWebCoreStringWithNullCheck(args[1]); + if (method.isEmpty() || exceptionCatcher.HasCaught()) + return v8::Undefined(); + + if (method != "addRecordToTimeline" && method != "updateResource" && method != "addResource") + return v8::Undefined(); + + agent->m_apuAgentDelegateStub->dispatchToApu(message); + return v8::Undefined(); +} + +WebCore::InspectorController* WebDevToolsAgentImpl::inspectorController() +{ + if (Page* page = m_webViewImpl->page()) + return page->inspectorController(); + return 0; +} + + +//------- plugin resource load notifications --------------- +void WebDevToolsAgentImpl::identifierForInitialRequest( + unsigned long resourceId, + WebFrame* frame, + const WebURLRequest& request) +{ + if (InspectorController* ic = inspectorController()) { + WebFrameImpl* webFrameImpl = static_cast(frame); + FrameLoader* frameLoader = webFrameImpl->frame()->loader(); + DocumentLoader* loader = frameLoader->activeDocumentLoader(); + ic->identifierForInitialRequest(resourceId, loader, request.toResourceRequest()); + } +} + +void WebDevToolsAgentImpl::willSendRequest(unsigned long resourceId, WebURLRequest& request) +{ + if (InspectorController* ic = inspectorController()) + ic->willSendRequest(resourceId, request.toMutableResourceRequest(), ResourceResponse()); +} + +void WebDevToolsAgentImpl::didReceiveData(unsigned long resourceId, int length) +{ + if (InspectorController* ic = inspectorController()) + ic->didReceiveContentLength(resourceId, length); +} + +void WebDevToolsAgentImpl::didReceiveResponse(unsigned long resourceId, const WebURLResponse& response) +{ + if (InspectorController* ic = inspectorController()) + ic->didReceiveResponse(resourceId, response.toResourceResponse()); +} + +void WebDevToolsAgentImpl::didFinishLoading(unsigned long resourceId) +{ + if (InspectorController* ic = inspectorController()) + ic->didFinishLoading(resourceId); +} + +void WebDevToolsAgentImpl::didFailLoading(unsigned long resourceId, const WebURLError& error) +{ + ResourceError resourceError; + if (InspectorController* ic = inspectorController()) + ic->didFailLoading(resourceId, resourceError); +} + +void WebDevToolsAgentImpl::inspectorDestroyed() +{ + // Our lifetime is bound to the WebViewImpl. +} + +void WebDevToolsAgentImpl::openInspectorFrontend(InspectorController*) +{ +} + +void WebDevToolsAgentImpl::highlight(Node* node) +{ + // InspectorController does the actuall tracking of the highlighted node + // and the drawing of the highlight. Here we just make sure to invalidate + // the rects of the old and new nodes. + hideHighlight(); +} + +void WebDevToolsAgentImpl::hideHighlight() +{ + // FIXME: able to invalidate a smaller rect. + // FIXME: Is it important to just invalidate the rect of the node region + // given that this is not on a critical codepath? In order to do so, we'd + // have to take scrolling into account. + const WebSize& size = m_webViewImpl->size(); + WebRect damagedRect(0, 0, size.width, size.height); + if (m_webViewImpl->client()) + m_webViewImpl->client()->didInvalidateRect(damagedRect); +} + +void WebDevToolsAgentImpl::populateSetting(const String& key, String* value) +{ + WebString string; + m_webViewImpl->inspectorSetting(key, &string); + *value = string; +} + +void WebDevToolsAgentImpl::storeSetting(const String& key, const String& value) +{ + m_webViewImpl->setInspectorSetting(key, value); +} + +bool WebDevToolsAgentImpl::sendMessageToFrontend(const WebCore::String& message) +{ + WebDevToolsAgentImpl* devToolsAgent = static_cast(m_webViewImpl->devToolsAgent()); + if (!devToolsAgent) + return false; + + if (devToolsAgent->m_apuAgentEnabled && devToolsAgent->m_apuAgentDelegateStub) { + devToolsAgent->m_apuAgentDelegateStub->dispatchToApu(message); + return true; + } + + WebVector arguments(size_t(1)); + arguments[0] = message; + WebDevToolsMessageData data; + data.className = "ToolsAgentDelegate"; + data.methodName = "dispatchOnClient"; + data.arguments.swap(arguments); + devToolsAgent->sendRpcMessage(data); + return true; +} + +void WebDevToolsAgentImpl::resourceTrackingWasEnabled() +{ + m_client->runtimeFeatureStateChanged(kResourceTrackingFeatureName, true); +} + +void WebDevToolsAgentImpl::resourceTrackingWasDisabled() +{ + m_client->runtimeFeatureStateChanged(kResourceTrackingFeatureName, false); +} + +void WebDevToolsAgentImpl::timelineProfilerWasStarted() +{ + m_client->runtimeFeatureStateChanged(kTimelineFeatureName, true); +} + +void WebDevToolsAgentImpl::timelineProfilerWasStopped() +{ + m_client->runtimeFeatureStateChanged(kTimelineFeatureName, false); +} + +void WebDevToolsAgentImpl::evaluateInWebInspector(long callId, const WebString& script) +{ + InspectorController* ic = inspectorController(); + ic->evaluateForTestInFrontend(callId, script); +} + +void WebDevToolsAgentImpl::setTimelineProfilingEnabled(bool enabled) +{ + InspectorController* ic = inspectorController(); + if (enabled) + ic->startTimelineProfiler(); + else + ic->stopTimelineProfiler(); +} + +void WebDevToolsAgent::executeDebuggerCommand(const WebString& command, int callerId) +{ + DebuggerAgentManager::executeDebuggerCommand(command, callerId); +} + +void WebDevToolsAgent::debuggerPauseScript() +{ + DebuggerAgentManager::pauseScript(); +} + +void WebDevToolsAgent::setMessageLoopDispatchHandler(MessageLoopDispatchHandler handler) +{ + DebuggerAgentManager::setMessageLoopDispatchHandler(handler); +} + +bool WebDevToolsAgent::dispatchMessageFromFrontendOnIOThread(WebDevToolsMessageTransport* transport, const WebDevToolsMessageData& data) +{ + IORPCDelegate delegate(transport); + ProfilerAgentDelegateStub stub(&delegate); + ProfilerAgentImpl agent(&stub); + return ProfilerAgentDispatch::dispatch(&agent, data); +} + +} // namespace WebKit