| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * Copyright (C) 2014, 2015 Apple 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 "InspectorController.h" |
| |
| #include "CommandLineAPIHost.h" |
| #include "CommonVM.h" |
| #include "DOMWindow.h" |
| #include "DOMWrapperWorld.h" |
| #include "Frame.h" |
| #include "GraphicsContext.h" |
| #include "InspectorApplicationCacheAgent.h" |
| #include "InspectorCPUProfilerAgent.h" |
| #include "InspectorCSSAgent.h" |
| #include "InspectorCanvasAgent.h" |
| #include "InspectorClient.h" |
| #include "InspectorDOMAgent.h" |
| #include "InspectorDOMStorageAgent.h" |
| #include "InspectorDatabaseAgent.h" |
| #include "InspectorDatabaseResource.h" |
| #include "InspectorFrontendClient.h" |
| #include "InspectorIndexedDBAgent.h" |
| #include "InspectorInstrumentation.h" |
| #include "InspectorLayerTreeAgent.h" |
| #include "InspectorMemoryAgent.h" |
| #include "InspectorPageAgent.h" |
| #include "InspectorTimelineAgent.h" |
| #include "InspectorWorkerAgent.h" |
| #include "InstrumentingAgents.h" |
| #include "JSDOMBindingSecurity.h" |
| #include "JSDOMWindow.h" |
| #include "JSDOMWindowCustom.h" |
| #include "JSExecState.h" |
| #include "Page.h" |
| #include "PageAuditAgent.h" |
| #include "PageConsoleAgent.h" |
| #include "PageDOMDebuggerAgent.h" |
| #include "PageDebuggerAgent.h" |
| #include "PageHeapAgent.h" |
| #include "PageNetworkAgent.h" |
| #include "PageRuntimeAgent.h" |
| #include "PageScriptDebugServer.h" |
| #include "Settings.h" |
| #include "SharedBuffer.h" |
| #include "WebInjectedScriptHost.h" |
| #include "WebInjectedScriptManager.h" |
| #include <JavaScriptCore/IdentifiersFactory.h> |
| #include <JavaScriptCore/InspectorAgent.h> |
| #include <JavaScriptCore/InspectorBackendDispatcher.h> |
| #include <JavaScriptCore/InspectorBackendDispatchers.h> |
| #include <JavaScriptCore/InspectorFrontendDispatchers.h> |
| #include <JavaScriptCore/InspectorFrontendRouter.h> |
| #include <JavaScriptCore/InspectorScriptProfilerAgent.h> |
| #include <JavaScriptCore/JSLock.h> |
| #include <wtf/Stopwatch.h> |
| |
| #if ENABLE(REMOTE_INSPECTOR) |
| #include "PageDebuggable.h" |
| #endif |
| |
| namespace WebCore { |
| |
| using namespace JSC; |
| using namespace Inspector; |
| |
| InspectorController::InspectorController(Page& page, InspectorClient* inspectorClient) |
| : m_instrumentingAgents(InstrumentingAgents::create(*this)) |
| , m_injectedScriptManager(makeUnique<WebInjectedScriptManager>(*this, WebInjectedScriptHost::create())) |
| , m_frontendRouter(FrontendRouter::create()) |
| , m_backendDispatcher(BackendDispatcher::create(m_frontendRouter.copyRef())) |
| , m_overlay(makeUnique<InspectorOverlay>(page, inspectorClient)) |
| , m_executionStopwatch(Stopwatch::create()) |
| , m_scriptDebugServer(page) |
| , m_page(page) |
| , m_inspectorClient(inspectorClient) |
| { |
| ASSERT_ARG(inspectorClient, inspectorClient); |
| |
| auto pageContext = pageAgentContext(); |
| |
| auto consoleAgent = makeUnique<PageConsoleAgent>(pageContext); |
| m_instrumentingAgents->setWebConsoleAgent(consoleAgent.get()); |
| m_agents.append(WTFMove(consoleAgent)); |
| } |
| |
| InspectorController::~InspectorController() |
| { |
| m_instrumentingAgents->reset(); |
| ASSERT(!m_inspectorClient); |
| } |
| |
| PageAgentContext InspectorController::pageAgentContext() |
| { |
| AgentContext baseContext = { |
| *this, |
| *m_injectedScriptManager, |
| m_frontendRouter.get(), |
| m_backendDispatcher.get() |
| }; |
| |
| WebAgentContext webContext = { |
| baseContext, |
| m_instrumentingAgents.get() |
| }; |
| |
| PageAgentContext pageContext = { |
| webContext, |
| m_page |
| }; |
| |
| return pageContext; |
| } |
| |
| void InspectorController::createLazyAgents() |
| { |
| if (m_didCreateLazyAgents) |
| return; |
| |
| m_didCreateLazyAgents = true; |
| |
| m_injectedScriptManager->connect(); |
| |
| auto pageContext = pageAgentContext(); |
| |
| ensureInspectorAgent(); |
| ensurePageAgent(); |
| |
| m_agents.append(makeUnique<PageRuntimeAgent>(pageContext)); |
| |
| auto debuggerAgent = makeUnique<PageDebuggerAgent>(pageContext); |
| auto debuggerAgentPtr = debuggerAgent.get(); |
| m_agents.append(WTFMove(debuggerAgent)); |
| |
| m_agents.append(makeUnique<PageNetworkAgent>(pageContext)); |
| m_agents.append(makeUnique<InspectorCSSAgent>(pageContext)); |
| ensureDOMAgent(); |
| m_agents.append(makeUnique<PageDOMDebuggerAgent>(pageContext, debuggerAgentPtr)); |
| m_agents.append(makeUnique<InspectorApplicationCacheAgent>(pageContext)); |
| m_agents.append(makeUnique<InspectorLayerTreeAgent>(pageContext)); |
| m_agents.append(makeUnique<InspectorWorkerAgent>(pageContext)); |
| m_agents.append(makeUnique<InspectorDOMStorageAgent>(pageContext)); |
| m_agents.append(makeUnique<InspectorDatabaseAgent>(pageContext)); |
| #if ENABLE(INDEXED_DATABASE) |
| m_agents.append(makeUnique<InspectorIndexedDBAgent>(pageContext)); |
| #endif |
| |
| auto scriptProfilerAgentPtr = makeUnique<InspectorScriptProfilerAgent>(pageContext); |
| m_instrumentingAgents->setInspectorScriptProfilerAgent(scriptProfilerAgentPtr.get()); |
| m_agents.append(WTFMove(scriptProfilerAgentPtr)); |
| |
| #if ENABLE(RESOURCE_USAGE) |
| m_agents.append(makeUnique<InspectorCPUProfilerAgent>(pageContext)); |
| m_agents.append(makeUnique<InspectorMemoryAgent>(pageContext)); |
| #endif |
| m_agents.append(makeUnique<PageHeapAgent>(pageContext)); |
| m_agents.append(makeUnique<PageAuditAgent>(pageContext)); |
| m_agents.append(makeUnique<InspectorCanvasAgent>(pageContext)); |
| m_agents.append(makeUnique<InspectorTimelineAgent>(pageContext)); |
| |
| if (auto& commandLineAPIHost = m_injectedScriptManager->commandLineAPIHost()) |
| commandLineAPIHost->init(m_instrumentingAgents.copyRef()); |
| } |
| |
| void InspectorController::inspectedPageDestroyed() |
| { |
| // Clean up resources and disconnect local and remote frontends. |
| disconnectAllFrontends(); |
| |
| // Disconnect the client. |
| m_inspectorClient->inspectedPageDestroyed(); |
| m_inspectorClient = nullptr; |
| |
| m_agents.discardValues(); |
| } |
| |
| void InspectorController::setInspectorFrontendClient(InspectorFrontendClient* inspectorFrontendClient) |
| { |
| m_inspectorFrontendClient = inspectorFrontendClient; |
| } |
| |
| bool InspectorController::hasLocalFrontend() const |
| { |
| return m_frontendRouter->hasLocalFrontend(); |
| } |
| |
| bool InspectorController::hasRemoteFrontend() const |
| { |
| return m_frontendRouter->hasRemoteFrontend(); |
| } |
| |
| unsigned InspectorController::inspectionLevel() const |
| { |
| return m_inspectorFrontendClient ? m_inspectorFrontendClient->inspectionLevel() : 0; |
| } |
| |
| void InspectorController::didClearWindowObjectInWorld(Frame& frame, DOMWrapperWorld& world) |
| { |
| if (&world != &mainThreadNormalWorld()) |
| return; |
| |
| if (frame.isMainFrame()) |
| m_injectedScriptManager->discardInjectedScripts(); |
| |
| // If the page is supposed to serve as InspectorFrontend notify inspector frontend |
| // client that it's cleared so that the client can expose inspector bindings. |
| if (m_inspectorFrontendClient && frame.isMainFrame()) |
| m_inspectorFrontendClient->windowObjectCleared(); |
| } |
| |
| void InspectorController::connectFrontend(Inspector::FrontendChannel& frontendChannel, bool isAutomaticInspection, bool immediatelyPause) |
| { |
| ASSERT(m_inspectorClient); |
| |
| // If a frontend has connected enable the developer extras and keep them enabled. |
| m_page.settings().setDeveloperExtrasEnabled(true); |
| |
| createLazyAgents(); |
| |
| bool connectedFirstFrontend = !m_frontendRouter->hasFrontends(); |
| m_isAutomaticInspection = isAutomaticInspection; |
| m_pauseAfterInitialization = immediatelyPause; |
| |
| m_frontendRouter->connectFrontend(frontendChannel); |
| |
| InspectorInstrumentation::frontendCreated(); |
| |
| if (connectedFirstFrontend) { |
| InspectorInstrumentation::registerInstrumentingAgents(m_instrumentingAgents.get()); |
| m_agents.didCreateFrontendAndBackend(&m_frontendRouter.get(), &m_backendDispatcher.get()); |
| } |
| |
| m_inspectorClient->frontendCountChanged(m_frontendRouter->frontendCount()); |
| |
| #if ENABLE(REMOTE_INSPECTOR) |
| if (hasLocalFrontend()) |
| m_page.remoteInspectorInformationDidChange(); |
| #endif |
| } |
| |
| void InspectorController::disconnectFrontend(FrontendChannel& frontendChannel) |
| { |
| m_frontendRouter->disconnectFrontend(frontendChannel); |
| |
| m_isAutomaticInspection = false; |
| m_pauseAfterInitialization = false; |
| |
| InspectorInstrumentation::frontendDeleted(); |
| |
| bool disconnectedLastFrontend = !m_frontendRouter->hasFrontends(); |
| if (disconnectedLastFrontend) { |
| // Notify agents first. |
| m_agents.willDestroyFrontendAndBackend(DisconnectReason::InspectorDestroyed); |
| |
| // Clean up inspector resources. |
| m_injectedScriptManager->discardInjectedScripts(); |
| |
| // Unplug all instrumentations since they aren't needed now. |
| InspectorInstrumentation::unregisterInstrumentingAgents(m_instrumentingAgents.get()); |
| } |
| |
| m_inspectorClient->frontendCountChanged(m_frontendRouter->frontendCount()); |
| |
| #if ENABLE(REMOTE_INSPECTOR) |
| if (disconnectedLastFrontend) |
| m_page.remoteInspectorInformationDidChange(); |
| #endif |
| } |
| |
| void InspectorController::disconnectAllFrontends() |
| { |
| // If the local frontend page was destroyed, close the window. |
| if (m_inspectorFrontendClient) |
| m_inspectorFrontendClient->closeWindow(); |
| |
| // The frontend should call setInspectorFrontendClient(nullptr) under closeWindow(). |
| ASSERT(!m_inspectorFrontendClient); |
| |
| if (!m_frontendRouter->hasFrontends()) |
| return; |
| |
| for (unsigned i = 0; i < m_frontendRouter->frontendCount(); ++i) |
| InspectorInstrumentation::frontendDeleted(); |
| |
| // Unplug all instrumentations to prevent further agent callbacks. |
| InspectorInstrumentation::unregisterInstrumentingAgents(m_instrumentingAgents.get()); |
| |
| // Notify agents first, since they may need to use InspectorClient. |
| m_agents.willDestroyFrontendAndBackend(DisconnectReason::InspectedTargetDestroyed); |
| |
| // Clean up inspector resources. |
| m_injectedScriptManager->disconnect(); |
| |
| // Disconnect any remaining remote frontends. |
| m_frontendRouter->disconnectAllFrontends(); |
| m_isAutomaticInspection = false; |
| m_pauseAfterInitialization = false; |
| |
| m_inspectorClient->frontendCountChanged(m_frontendRouter->frontendCount()); |
| |
| #if ENABLE(REMOTE_INSPECTOR) |
| m_page.remoteInspectorInformationDidChange(); |
| #endif |
| } |
| |
| void InspectorController::show() |
| { |
| ASSERT(!hasRemoteFrontend()); |
| |
| if (!enabled()) |
| return; |
| |
| if (m_frontendRouter->hasLocalFrontend()) |
| m_inspectorClient->bringFrontendToFront(); |
| else if (Inspector::FrontendChannel* frontendChannel = m_inspectorClient->openLocalFrontend(this)) |
| connectFrontend(*frontendChannel); |
| } |
| |
| void InspectorController::setIsUnderTest(bool value) |
| { |
| if (value == m_isUnderTest) |
| return; |
| |
| m_isUnderTest = value; |
| |
| // <rdar://problem/26768628> Try to catch suspicious scenarios where we may have a dangling frontend while running tests. |
| RELEASE_ASSERT(!m_isUnderTest || !m_frontendRouter->hasFrontends()); |
| } |
| |
| void InspectorController::evaluateForTestInFrontend(const String& script) |
| { |
| ensureInspectorAgent().evaluateForTestInFrontend(script); |
| } |
| |
| void InspectorController::drawHighlight(GraphicsContext& context) const |
| { |
| m_overlay->paint(context); |
| } |
| |
| void InspectorController::getHighlight(Highlight& highlight, InspectorOverlay::CoordinateSystem coordinateSystem) const |
| { |
| m_overlay->getHighlight(highlight, coordinateSystem); |
| } |
| |
| void InspectorController::inspect(Node* node) |
| { |
| if (!enabled()) |
| return; |
| |
| if (!hasRemoteFrontend()) |
| show(); |
| |
| ensureDOMAgent().inspect(node); |
| } |
| |
| bool InspectorController::enabled() const |
| { |
| return developerExtrasEnabled(); |
| } |
| |
| Page& InspectorController::inspectedPage() const |
| { |
| return m_page; |
| } |
| |
| void InspectorController::dispatchMessageFromFrontend(const String& message) |
| { |
| m_backendDispatcher->dispatch(message); |
| } |
| |
| void InspectorController::hideHighlight() |
| { |
| m_overlay->hideHighlight(); |
| } |
| |
| Node* InspectorController::highlightedNode() const |
| { |
| return m_overlay->highlightedNode(); |
| } |
| |
| void InspectorController::setIndicating(bool indicating) |
| { |
| #if !PLATFORM(IOS_FAMILY) |
| m_overlay->setIndicating(indicating); |
| #else |
| if (indicating) |
| m_inspectorClient->showInspectorIndication(); |
| else |
| m_inspectorClient->hideInspectorIndication(); |
| #endif |
| } |
| |
| InspectorAgent& InspectorController::ensureInspectorAgent() |
| { |
| if (!m_inspectorAgent) { |
| auto pageContext = pageAgentContext(); |
| auto inspectorAgent = makeUnique<InspectorAgent>(pageContext); |
| m_inspectorAgent = inspectorAgent.get(); |
| m_instrumentingAgents->setInspectorAgent(m_inspectorAgent); |
| m_agents.append(WTFMove(inspectorAgent)); |
| } |
| return *m_inspectorAgent; |
| } |
| |
| InspectorDOMAgent& InspectorController::ensureDOMAgent() |
| { |
| if (!m_inspectorDOMAgent) { |
| auto pageContext = pageAgentContext(); |
| auto domAgent = makeUnique<InspectorDOMAgent>(pageContext, m_overlay.get()); |
| m_inspectorDOMAgent = domAgent.get(); |
| m_agents.append(WTFMove(domAgent)); |
| } |
| return *m_inspectorDOMAgent; |
| } |
| |
| InspectorPageAgent& InspectorController::ensurePageAgent() |
| { |
| if (!m_inspectorPageAgent) { |
| auto pageContext = pageAgentContext(); |
| auto pageAgent = makeUnique<InspectorPageAgent>(pageContext, m_inspectorClient, m_overlay.get()); |
| m_inspectorPageAgent = pageAgent.get(); |
| m_agents.append(WTFMove(pageAgent)); |
| } |
| return *m_inspectorPageAgent; |
| } |
| |
| bool InspectorController::developerExtrasEnabled() const |
| { |
| return m_page.settings().developerExtrasEnabled(); |
| } |
| |
| bool InspectorController::canAccessInspectedScriptState(JSC::ExecState* scriptState) const |
| { |
| JSLockHolder lock(scriptState); |
| |
| JSDOMWindow* inspectedWindow = toJSDOMWindow(scriptState->vm(), scriptState->lexicalGlobalObject()); |
| if (!inspectedWindow) |
| return false; |
| |
| return BindingSecurity::shouldAllowAccessToDOMWindow(scriptState, inspectedWindow->wrapped(), DoNotReportSecurityError); |
| } |
| |
| InspectorFunctionCallHandler InspectorController::functionCallHandler() const |
| { |
| return WebCore::functionCallHandlerFromAnyThread; |
| } |
| |
| InspectorEvaluateHandler InspectorController::evaluateHandler() const |
| { |
| return WebCore::evaluateHandlerFromAnyThread; |
| } |
| |
| void InspectorController::frontendInitialized() |
| { |
| if (m_pauseAfterInitialization) { |
| m_pauseAfterInitialization = false; |
| if (PageDebuggerAgent* debuggerAgent = m_instrumentingAgents->pageDebuggerAgent()) { |
| ErrorString ignored; |
| debuggerAgent->pause(ignored); |
| } |
| } |
| |
| #if ENABLE(REMOTE_INSPECTOR) |
| if (m_isAutomaticInspection) |
| m_page.inspectorDebuggable().unpauseForInitializedInspector(); |
| #endif |
| } |
| |
| Ref<Stopwatch> InspectorController::executionStopwatch() |
| { |
| return m_executionStopwatch.copyRef(); |
| } |
| |
| PageScriptDebugServer& InspectorController::scriptDebugServer() |
| { |
| return m_scriptDebugServer; |
| } |
| |
| JSC::VM& InspectorController::vm() |
| { |
| return commonVM(); |
| } |
| |
| void InspectorController::willComposite(Frame& frame) |
| { |
| InspectorInstrumentation::willComposite(frame); |
| } |
| |
| void InspectorController::didComposite(Frame& frame) |
| { |
| InspectorInstrumentation::didComposite(frame); |
| } |
| |
| } // namespace WebCore |