/* * Copyright (C) 2010-2011 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 "InspectorBackendDispatcher.h" #include "InspectorFrontend.h" #include "InspectorProtocolVersion.h" #include "RuntimeEnabledFeatures.h" #include "WebDataSource.h" #include "WebDevToolsAgentClient.h" #include "WebFrameImpl.h" #include "WebInputEventConversion.h" #include "WebMemoryUsageInfo.h" #include "WebSettings.h" #include "WebViewClient.h" #include "WebViewImpl.h" #include "bindings/v8/PageScriptDebugServer.h" #include "bindings/v8/ScriptController.h" #include "bindings/v8/V8Binding.h" #include "bindings/v8/V8Utilities.h" #include "core/dom/ExceptionCode.h" #include "core/fetch/MemoryCache.h" #include "core/frame/Frame.h" #include "core/frame/FrameView.h" #include "core/inspector/InjectedScriptHost.h" #include "core/inspector/InspectorController.h" #include "core/page/Page.h" #include "core/page/PageGroup.h" #include "core/rendering/RenderView.h" #include "platform/JSONValues.h" #include "platform/graphics/GraphicsContext.h" #include "platform/network/ResourceError.h" #include "platform/network/ResourceRequest.h" #include "platform/network/ResourceResponse.h" #include "public/platform/Platform.h" #include "public/platform/WebRect.h" #include "public/platform/WebString.h" #include "public/platform/WebURL.h" #include "public/platform/WebURLError.h" #include "public/platform/WebURLRequest.h" #include "public/platform/WebURLResponse.h" #include "wtf/CurrentTime.h" #include "wtf/MathExtras.h" #include "wtf/Noncopyable.h" #include "wtf/text/WTFString.h" using namespace WebCore; using namespace std; namespace OverlayZOrders { // Use 99 as a big z-order number so that highlight is above other overlays. static const int highlight = 99; } namespace blink { class ClientMessageLoopAdapter : public PageScriptDebugServer::ClientMessageLoop { public: static void ensureClientMessageLoopCreated(WebDevToolsAgentClient* client) { if (s_instance) return; OwnPtr<ClientMessageLoopAdapter> instance = adoptPtr(new ClientMessageLoopAdapter(adoptPtr(client->createClientMessageLoop()))); s_instance = instance.get(); PageScriptDebugServer::shared().setClientMessageLoop(instance.release()); } 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) PageScriptDebugServer::shared().continueProgram(); } private: ClientMessageLoopAdapter(PassOwnPtr<blink::WebDevToolsAgentClient::WebKitClientMessageLoop> messageLoop) : m_running(false) , m_messageLoop(messageLoop) { } virtual void run(Page* page) { if (m_running) return; m_running = true; Vector<WebViewImpl*> views; // 1. Disable input events. HashSet<Page*>::const_iterator end = page->group().pages().end(); for (HashSet<Page*>::const_iterator it = page->group().pages().begin(); it != end; ++it) { WebViewImpl* view = WebViewImpl::fromPage(*it); if (!view) continue; 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<WebViewImpl*>::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<blink::WebDevToolsAgentClient::WebKitClientMessageLoop> m_messageLoop; typedef HashSet<WebViewImpl*> FrozenViewsSet; FrozenViewsSet m_frozenViews; // FIXME: The ownership model for s_instance is somewhat complicated. Can we make this simpler? static ClientMessageLoopAdapter* s_instance; }; ClientMessageLoopAdapter* ClientMessageLoopAdapter::s_instance = 0; class DebuggerTask : public PageScriptDebugServer::Task { public: DebuggerTask(PassOwnPtr<WebDevToolsAgent::MessageDescriptor> descriptor) : m_descriptor(descriptor) { } virtual ~DebuggerTask() { } virtual void run() { if (WebDevToolsAgent* webagent = m_descriptor->agent()) webagent->dispatchOnInspectorBackend(m_descriptor->message()); } private: OwnPtr<WebDevToolsAgent::MessageDescriptor> m_descriptor; }; WebDevToolsAgentImpl::WebDevToolsAgentImpl( WebViewImpl* webViewImpl, WebDevToolsAgentClient* client) : m_hostId(client->hostIdentifier()) , m_client(client) , m_webViewImpl(webViewImpl) , m_attached(false) , m_generatingEvent(false) , m_deviceMetricsEnabled(false) , m_emulateViewportEnabled(false) , m_originalViewportEnabled(false) , m_isOverlayScrollbarsEnabled(false) { ASSERT(m_hostId > 0); ClientMessageLoopAdapter::ensureClientMessageLoopCreated(m_client); } WebDevToolsAgentImpl::~WebDevToolsAgentImpl() { ClientMessageLoopAdapter::inspectedViewClosed(m_webViewImpl); if (m_attached) blink::Platform::current()->currentThread()->removeTaskObserver(this); } void WebDevToolsAgentImpl::attach() { if (m_attached) return; inspectorController()->connectFrontend(this); inspectorController()->webViewResized(m_webViewImpl->size()); blink::Platform::current()->currentThread()->addTaskObserver(this); m_attached = true; } void WebDevToolsAgentImpl::reattach(const WebString& savedState) { if (m_attached) return; inspectorController()->reuseFrontend(this, savedState); blink::Platform::current()->currentThread()->addTaskObserver(this); m_attached = true; } void WebDevToolsAgentImpl::detach() { blink::Platform::current()->currentThread()->removeTaskObserver(this); // Prevent controller from sending messages to the frontend. InspectorController* ic = inspectorController(); ic->disconnectFrontend(); ic->hideHighlight(); m_attached = false; } void WebDevToolsAgentImpl::didNavigate() { ClientMessageLoopAdapter::didNavigate(); } void WebDevToolsAgentImpl::didBeginFrame(int frameId) { if (InspectorController* ic = inspectorController()) ic->didBeginFrame(frameId); } void WebDevToolsAgentImpl::didCancelFrame() { if (InspectorController* ic = inspectorController()) ic->didCancelFrame(); } void WebDevToolsAgentImpl::willComposite() { if (InspectorController* ic = inspectorController()) ic->willComposite(); } void WebDevToolsAgentImpl::didComposite() { if (InspectorController* ic = inspectorController()) ic->didComposite(); } void WebDevToolsAgentImpl::didCreateScriptContext(WebFrameImpl* webframe, int worldId) { // Skip non main world contexts. if (worldId) return; if (WebCore::Frame* frame = webframe->frame()) frame->script().setContextDebugId(m_hostId); } void WebDevToolsAgentImpl::webViewResized(const WebSize& size) { if (InspectorController* ic = inspectorController()) ic->webViewResized(IntSize()); } bool WebDevToolsAgentImpl::handleInputEvent(WebCore::Page* page, const WebInputEvent& inputEvent) { if (!m_attached && !m_generatingEvent) return false; InspectorController* ic = inspectorController(); if (!ic) return false; if (WebInputEvent::isGestureEventType(inputEvent.type) && inputEvent.type == WebInputEvent::GestureTap) { // Only let GestureTab in (we only need it and we know PlatformGestureEventBuilder supports it). PlatformGestureEvent gestureEvent = PlatformGestureEventBuilder(page->mainFrame()->view(), *static_cast<const WebGestureEvent*>(&inputEvent)); return ic->handleGestureEvent(page->mainFrame(), gestureEvent); } if (WebInputEvent::isMouseEventType(inputEvent.type) && inputEvent.type != WebInputEvent::MouseEnter) { // PlatformMouseEventBuilder does not work with MouseEnter type, so we filter it out manually. PlatformMouseEvent mouseEvent = PlatformMouseEventBuilder(page->mainFrame()->view(), *static_cast<const WebMouseEvent*>(&inputEvent)); return ic->handleMouseEvent(page->mainFrame(), mouseEvent); } if (WebInputEvent::isTouchEventType(inputEvent.type)) { PlatformTouchEvent touchEvent = PlatformTouchEventBuilder(page->mainFrame()->view(), *static_cast<const WebTouchEvent*>(&inputEvent)); return ic->handleTouchEvent(page->mainFrame(), touchEvent); } if (WebInputEvent::isKeyboardEventType(inputEvent.type)) { PlatformKeyboardEvent keyboardEvent = PlatformKeyboardEventBuilder(*static_cast<const WebKeyboardEvent*>(&inputEvent)); return ic->handleKeyboardEvent(page->mainFrame(), keyboardEvent); } return false; } void WebDevToolsAgentImpl::overrideDeviceMetrics(int width, int height, float deviceScaleFactor, bool emulateViewport, bool fitWindow) { if (!width && !height) { if (m_deviceMetricsEnabled) { m_deviceMetricsEnabled = false; m_webViewImpl->setBackgroundColorOverride(Color::transparent); RuntimeEnabledFeatures::setOverlayScrollbarsEnabled(m_isOverlayScrollbarsEnabled); disableViewportEmulation(); m_client->disableDeviceEmulation(); } } else { if (!m_deviceMetricsEnabled) { m_deviceMetricsEnabled = true; m_webViewImpl->setBackgroundColorOverride(Color::darkGray); m_isOverlayScrollbarsEnabled = RuntimeEnabledFeatures::overlayScrollbarsEnabled(); RuntimeEnabledFeatures::setOverlayScrollbarsEnabled(true); } if (emulateViewport) enableViewportEmulation(); else disableViewportEmulation(); m_client->enableDeviceEmulation(IntRect(10, 10, width, height), IntRect(0, 0, width, height), deviceScaleFactor, fitWindow); } } void WebDevToolsAgentImpl::enableViewportEmulation() { if (m_emulateViewportEnabled) return; m_emulateViewportEnabled = true; m_originalViewportEnabled = RuntimeEnabledFeatures::cssViewportEnabled(); RuntimeEnabledFeatures::setCSSViewportEnabled(true); m_webViewImpl->settings()->setViewportEnabled(true); m_webViewImpl->settings()->setViewportMetaEnabled(true); m_webViewImpl->setIgnoreViewportTagScaleLimits(true); m_webViewImpl->setPageScaleFactorLimits(-1, -1); m_webViewImpl->setZoomFactorOverride(1); } void WebDevToolsAgentImpl::disableViewportEmulation() { if (!m_emulateViewportEnabled) return; RuntimeEnabledFeatures::setCSSViewportEnabled(m_originalViewportEnabled); m_webViewImpl->settings()->setViewportEnabled(false); m_webViewImpl->settings()->setViewportMetaEnabled(false); m_webViewImpl->setIgnoreViewportTagScaleLimits(false); m_webViewImpl->setPageScaleFactorLimits(1, 1); m_webViewImpl->setZoomFactorOverride(0); m_emulateViewportEnabled = false; } void WebDevToolsAgentImpl::getAllocatedObjects(HashSet<const void*>& set) { class CountingVisitor : public WebDevToolsAgentClient::AllocatedObjectVisitor { public: CountingVisitor() : m_totalObjectsCount(0) { } virtual bool visitObject(const void* ptr) { ++m_totalObjectsCount; return true; } size_t totalObjectsCount() const { return m_totalObjectsCount; } private: size_t m_totalObjectsCount; }; CountingVisitor counter; m_client->visitAllocatedObjects(&counter); class PointerCollector : public WebDevToolsAgentClient::AllocatedObjectVisitor { public: explicit PointerCollector(size_t maxObjectsCount) : m_maxObjectsCount(maxObjectsCount) , m_index(0) , m_success(true) , m_pointers(new const void*[maxObjectsCount]) { } virtual ~PointerCollector() { delete[] m_pointers; } virtual bool visitObject(const void* ptr) { if (m_index == m_maxObjectsCount) { m_success = false; return false; } m_pointers[m_index++] = ptr; return true; } bool success() const { return m_success; } void copyTo(HashSet<const void*>& set) { for (size_t i = 0; i < m_index; i++) set.add(m_pointers[i]); } private: const size_t m_maxObjectsCount; size_t m_index; bool m_success; const void** m_pointers; }; // Double size to allow room for all objects that may have been allocated // since we counted them. size_t estimatedMaxObjectsCount = counter.totalObjectsCount() * 2; while (true) { PointerCollector collector(estimatedMaxObjectsCount); m_client->visitAllocatedObjects(&collector); if (collector.success()) { collector.copyTo(set); break; } estimatedMaxObjectsCount *= 2; } } void WebDevToolsAgentImpl::dumpUncountedAllocatedObjects(const HashMap<const void*, size_t>& map) { class InstrumentedObjectSizeProvider : public WebDevToolsAgentClient::InstrumentedObjectSizeProvider { public: InstrumentedObjectSizeProvider(const HashMap<const void*, size_t>& map) : m_map(map) { } virtual size_t objectSize(const void* ptr) const { HashMap<const void*, size_t>::const_iterator i = m_map.find(ptr); return i == m_map.end() ? 0 : i->value; } private: const HashMap<const void*, size_t>& m_map; }; InstrumentedObjectSizeProvider provider(map); m_client->dumpUncountedAllocatedObjects(&provider); } void WebDevToolsAgentImpl::setTraceEventCallback(TraceEventCallback callback) { m_client->setTraceEventCallback(callback); } void WebDevToolsAgentImpl::startGPUEventsRecording() { m_client->startGPUEventsRecording(); } void WebDevToolsAgentImpl::stopGPUEventsRecording() { m_client->stopGPUEventsRecording(); } void WebDevToolsAgentImpl::processGPUEvent(double timestamp, int phase, bool foreign) { if (InspectorController* ic = inspectorController()) ic->processGPUEvent(timestamp, phase, foreign, 0); } void WebDevToolsAgentImpl::processGPUEvent(const GPUEvent& event) { if (InspectorController* ic = inspectorController()) ic->processGPUEvent(event.timestamp, event.phase, event.foreign, event.usedGPUMemoryBytes); } void WebDevToolsAgentImpl::dispatchKeyEvent(const PlatformKeyboardEvent& event) { m_generatingEvent = true; WebKeyboardEvent webEvent = WebKeyboardEventBuilder(event); if (!webEvent.keyIdentifier[0] && webEvent.type != WebInputEvent::Char) webEvent.setKeyIdentifierFromWindowsKeyCode(); m_webViewImpl->handleInputEvent(webEvent); m_generatingEvent = false; } void WebDevToolsAgentImpl::dispatchMouseEvent(const PlatformMouseEvent& event) { m_generatingEvent = true; WebMouseEvent webEvent = WebMouseEventBuilder(m_webViewImpl->mainFrameImpl()->frameView(), event); m_webViewImpl->handleInputEvent(webEvent); m_generatingEvent = false; } void WebDevToolsAgentImpl::dispatchOnInspectorBackend(const WebString& message) { inspectorController()->dispatchMessageFromFrontend(message); } void WebDevToolsAgentImpl::inspectElementAt(const WebPoint& point) { m_webViewImpl->inspectElementAt(point); } InspectorController* WebDevToolsAgentImpl::inspectorController() { if (Page* page = m_webViewImpl->page()) return &page->inspectorController(); return 0; } Frame* WebDevToolsAgentImpl::mainFrame() { if (Page* page = m_webViewImpl->page()) return page->mainFrame(); return 0; } // WebPageOverlay void WebDevToolsAgentImpl::paintPageOverlay(WebCanvas* canvas) { InspectorController* ic = inspectorController(); if (ic) { GraphicsContext context(canvas); context.setCertainlyOpaque(false); ic->drawHighlight(context); } } void WebDevToolsAgentImpl::highlight() { m_webViewImpl->addPageOverlay(this, OverlayZOrders::highlight); } void WebDevToolsAgentImpl::hideHighlight() { m_webViewImpl->removePageOverlay(this); } bool WebDevToolsAgentImpl::sendMessageToFrontend(const String& message) { WebDevToolsAgentImpl* devToolsAgent = static_cast<WebDevToolsAgentImpl*>(m_webViewImpl->devToolsAgent()); if (!devToolsAgent) return false; m_client->sendMessageToInspectorFrontend(message); return true; } void WebDevToolsAgentImpl::updateInspectorStateCookie(const String& state) { m_client->saveAgentRuntimeState(state); } void WebDevToolsAgentImpl::clearBrowserCache() { m_client->clearBrowserCache(); } void WebDevToolsAgentImpl::clearBrowserCookies() { m_client->clearBrowserCookies(); } void WebDevToolsAgentImpl::setProcessId(long processId) { inspectorController()->setProcessId(processId); } void WebDevToolsAgentImpl::setLayerTreeId(int layerTreeId) { inspectorController()->setLayerTreeId(layerTreeId); } void WebDevToolsAgentImpl::evaluateInWebInspector(long callId, const WebString& script) { InspectorController* ic = inspectorController(); ic->evaluateForTestInFrontend(callId, script); } void WebDevToolsAgentImpl::willProcessTask() { if (InspectorController* ic = inspectorController()) ic->willProcessTask(); } void WebDevToolsAgentImpl::didProcessTask() { if (InspectorController* ic = inspectorController()) ic->didProcessTask(); } WebString WebDevToolsAgent::inspectorProtocolVersion() { return WebCore::inspectorProtocolVersion(); } bool WebDevToolsAgent::supportsInspectorProtocolVersion(const WebString& version) { return WebCore::supportsInspectorProtocolVersion(version); } void WebDevToolsAgent::interruptAndDispatch(MessageDescriptor* rawDescriptor) { // rawDescriptor can't be a PassOwnPtr because interruptAndDispatch is a WebKit API function. OwnPtr<MessageDescriptor> descriptor = adoptPtr(rawDescriptor); OwnPtr<DebuggerTask> task = adoptPtr(new DebuggerTask(descriptor.release())); PageScriptDebugServer::interruptAndRun(task.release(), v8::Isolate::GetCurrent()); } bool WebDevToolsAgent::shouldInterruptForMessage(const WebString& message) { String commandName; if (!InspectorBackendDispatcher::getCommandName(message, &commandName)) return false; return commandName == InspectorBackendDispatcher::commandName(InspectorBackendDispatcher::kDebugger_pauseCmd) || commandName == InspectorBackendDispatcher::commandName(InspectorBackendDispatcher::kDebugger_setBreakpointCmd) || commandName == InspectorBackendDispatcher::commandName(InspectorBackendDispatcher::kDebugger_setBreakpointByUrlCmd) || commandName == InspectorBackendDispatcher::commandName(InspectorBackendDispatcher::kDebugger_removeBreakpointCmd) || commandName == InspectorBackendDispatcher::commandName(InspectorBackendDispatcher::kDebugger_setBreakpointsActiveCmd) || commandName == InspectorBackendDispatcher::commandName(InspectorBackendDispatcher::kProfiler_startCmd) || commandName == InspectorBackendDispatcher::commandName(InspectorBackendDispatcher::kProfiler_stopCmd) || commandName == InspectorBackendDispatcher::commandName(InspectorBackendDispatcher::kHeapProfiler_getHeapSnapshotCmd); } void WebDevToolsAgent::processPendingMessages() { PageScriptDebugServer::shared().runPendingTasks(); } } // namespace blink