/*
 * Copyright (C) 2011 Google Inc. All rights reserved.
 * Copyright (C) 2014 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 "DOMWrapperWorld.h"
#include "GraphicsContext.h"
#include "InspectorApplicationCacheAgent.h"
#include "InspectorCSSAgent.h"
#include "InspectorClient.h"
#include "InspectorDOMAgent.h"
#include "InspectorDOMDebuggerAgent.h"
#include "InspectorDOMStorageAgent.h"
#include "InspectorDatabaseAgent.h"
#include "InspectorFrontendClient.h"
#include "InspectorIndexedDBAgent.h"
#include "InspectorInstrumentation.h"
#include "InspectorLayerTreeAgent.h"
#include "InspectorPageAgent.h"
#include "InspectorReplayAgent.h"
#include "InspectorResourceAgent.h"
#include "InspectorTimelineAgent.h"
#include "InspectorWorkerAgent.h"
#include "InstrumentingAgents.h"
#include "JSDOMWindow.h"
#include "JSDOMWindowCustom.h"
#include "JSMainThreadExecState.h"
#include "MainFrame.h"
#include "Page.h"
#include "PageConsoleAgent.h"
#include "PageDebuggerAgent.h"
#include "PageRuntimeAgent.h"
#include "PageScriptDebugServer.h"
#include "Settings.h"
#include "WebInjectedScriptHost.h"
#include "WebInjectedScriptManager.h"
#include <inspector/IdentifiersFactory.h>
#include <inspector/InspectorBackendDispatcher.h>
#include <inspector/InspectorBackendDispatchers.h>
#include <inspector/InspectorFrontendDispatchers.h>
#include <inspector/agents/InspectorAgent.h>
#include <profiler/LegacyProfiler.h>
#include <runtime/JSLock.h>
#include <wtf/Stopwatch.h>

#if ENABLE(REMOTE_INSPECTOR)
#include "PageDebuggable.h"
#endif

using namespace JSC;
using namespace Inspector;

namespace WebCore {

InspectorController::InspectorController(Page& page, InspectorClient* inspectorClient)
    : m_instrumentingAgents(InstrumentingAgents::create(*this))
    , m_injectedScriptManager(std::make_unique<WebInjectedScriptManager>(*this, WebInjectedScriptHost::create()))
    , m_overlay(std::make_unique<InspectorOverlay>(page, inspectorClient))
    , m_frontendChannel(nullptr)
    , m_executionStopwatch(Stopwatch::create())
    , m_page(page)
    , m_inspectorClient(inspectorClient)
    , m_inspectorFrontendClient(nullptr)
    , m_isUnderTest(false)
    , m_isAutomaticInspection(false)
#if ENABLE(REMOTE_INSPECTOR)
    , m_hasRemoteFrontend(false)
#endif
{
    ASSERT_ARG(inspectorClient, inspectorClient);

    auto inspectorAgentPtr = std::make_unique<InspectorAgent>(*this);
    m_inspectorAgent = inspectorAgentPtr.get();
    m_instrumentingAgents->setInspectorAgent(m_inspectorAgent);
    m_agents.append(WTF::move(inspectorAgentPtr));

    auto pageAgentPtr = std::make_unique<InspectorPageAgent>(m_instrumentingAgents.get(), &page, inspectorClient, m_overlay.get());
    InspectorPageAgent* pageAgent = pageAgentPtr.get();
    m_pageAgent = pageAgentPtr.get();
    m_agents.append(WTF::move(pageAgentPtr));

    auto runtimeAgentPtr = std::make_unique<PageRuntimeAgent>(m_injectedScriptManager.get(), &page, pageAgent);
    PageRuntimeAgent* runtimeAgent = runtimeAgentPtr.get();
    m_instrumentingAgents->setPageRuntimeAgent(runtimeAgent);
    m_agents.append(WTF::move(runtimeAgentPtr));

    auto domAgentPtr = std::make_unique<InspectorDOMAgent>(m_instrumentingAgents.get(), pageAgent, m_injectedScriptManager.get(), m_overlay.get());
    m_domAgent = domAgentPtr.get();
    m_agents.append(WTF::move(domAgentPtr));

    m_agents.append(std::make_unique<InspectorCSSAgent>(m_instrumentingAgents.get(), m_domAgent));

    auto databaseAgentPtr = std::make_unique<InspectorDatabaseAgent>(m_instrumentingAgents.get());
    InspectorDatabaseAgent* databaseAgent = databaseAgentPtr.get();
    m_agents.append(WTF::move(databaseAgentPtr));

#if ENABLE(INDEXED_DATABASE)
    m_agents.append(std::make_unique<InspectorIndexedDBAgent>(m_instrumentingAgents.get(), m_injectedScriptManager.get(), pageAgent));
#endif

#if ENABLE(WEB_REPLAY)
    m_agents.append(std::make_unique<InspectorReplayAgent>(m_instrumentingAgents.get(), pageAgent));
#endif

    auto domStorageAgentPtr = std::make_unique<InspectorDOMStorageAgent>(m_instrumentingAgents.get(), m_pageAgent);
    InspectorDOMStorageAgent* domStorageAgent = domStorageAgentPtr.get();
    m_agents.append(WTF::move(domStorageAgentPtr));

    auto timelineAgentPtr = std::make_unique<InspectorTimelineAgent>(m_instrumentingAgents.get(), pageAgent, InspectorTimelineAgent::PageInspector, inspectorClient);
    m_timelineAgent = timelineAgentPtr.get();
    m_agents.append(WTF::move(timelineAgentPtr));

    auto resourceAgentPtr = std::make_unique<InspectorResourceAgent>(m_instrumentingAgents.get(), pageAgent, inspectorClient);
    m_resourceAgent = resourceAgentPtr.get();
    m_agents.append(WTF::move(resourceAgentPtr));

    auto consoleAgentPtr = std::make_unique<PageConsoleAgent>(m_injectedScriptManager.get(), m_domAgent);
    WebConsoleAgent* consoleAgent = consoleAgentPtr.get();
    m_instrumentingAgents->setWebConsoleAgent(consoleAgentPtr.get());
    m_agents.append(WTF::move(consoleAgentPtr));

    auto debuggerAgentPtr = std::make_unique<PageDebuggerAgent>(m_injectedScriptManager.get(), m_instrumentingAgents.get(), pageAgent, m_overlay.get());
    m_debuggerAgent = debuggerAgentPtr.get();
    m_agents.append(WTF::move(debuggerAgentPtr));

    auto domDebuggerAgentPtr = std::make_unique<InspectorDOMDebuggerAgent>(m_instrumentingAgents.get(), m_domAgent, m_debuggerAgent);
    m_domDebuggerAgent = domDebuggerAgentPtr.get();
    m_agents.append(WTF::move(domDebuggerAgentPtr));

    m_agents.append(std::make_unique<InspectorApplicationCacheAgent>(m_instrumentingAgents.get(), pageAgent));
    m_agents.append(std::make_unique<InspectorWorkerAgent>(m_instrumentingAgents.get()));
    m_agents.append(std::make_unique<InspectorLayerTreeAgent>(m_instrumentingAgents.get()));

    ASSERT(m_injectedScriptManager->commandLineAPIHost());
    if (CommandLineAPIHost* commandLineAPIHost = m_injectedScriptManager->commandLineAPIHost()) {
        commandLineAPIHost->init(m_inspectorAgent
            , consoleAgent
            , m_domAgent
            , domStorageAgent
            , databaseAgent
        );
    }

    runtimeAgent->setScriptDebugServer(&m_debuggerAgent->scriptDebugServer());
    m_timelineAgent->setPageScriptDebugServer(&m_debuggerAgent->scriptDebugServer());
}

InspectorController::~InspectorController()
{
    m_instrumentingAgents->reset();
    m_agents.discardAgents();
    ASSERT(!m_inspectorClient);
}

void InspectorController::inspectedPageDestroyed()
{
    disconnectFrontend(DisconnectReason::InspectedTargetDestroyed);
    m_injectedScriptManager->disconnect();
    m_inspectorClient->inspectorDestroyed();
    m_inspectorClient = nullptr;
}

void InspectorController::setInspectorFrontendClient(InspectorFrontendClient* inspectorFrontendClient)
{
    m_inspectorFrontendClient = inspectorFrontendClient;
}

bool InspectorController::hasLocalFrontend() const
{
#if ENABLE(REMOTE_INSPECTOR)
    return hasFrontend() && !m_hasRemoteFrontend;
#else
    return hasFrontend();
#endif
}

bool InspectorController::hasRemoteFrontend() const
{
#if ENABLE(REMOTE_INSPECTOR)
    return m_hasRemoteFrontend;
#else
    return false;
#endif
}

bool InspectorController::hasInspectorFrontendClient() const
{
    return m_inspectorFrontendClient;
}

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)
{
    ASSERT(frontendChannel);
    ASSERT(m_inspectorClient);
    ASSERT(!m_frontendChannel);
    ASSERT(!m_backendDispatcher);

    m_isAutomaticInspection = isAutomaticInspection;

    m_frontendChannel = frontendChannel;
    m_backendDispatcher = BackendDispatcher::create(frontendChannel);

    m_agents.didCreateFrontendAndBackend(frontendChannel, m_backendDispatcher.get());

    InspectorInstrumentation::registerInstrumentingAgents(*m_instrumentingAgents);
    InspectorInstrumentation::frontendCreated();

#if ENABLE(REMOTE_INSPECTOR)
    if (!m_hasRemoteFrontend)
        m_page.remoteInspectorInformationDidChange();
#endif
}

void InspectorController::disconnectFrontend(DisconnectReason reason)
{
    if (!m_frontendChannel)
        return;

    m_agents.willDestroyFrontendAndBackend(reason);

    m_backendDispatcher->clearFrontend();
    m_backendDispatcher = nullptr;
    m_frontendChannel = nullptr;

    m_isAutomaticInspection = false;

    // Release overlay page resources.
    m_overlay->freePage();
    InspectorInstrumentation::frontendDeleted();
    InspectorInstrumentation::unregisterInstrumentingAgents(*m_instrumentingAgents);

#if ENABLE(REMOTE_INSPECTOR)
    if (!m_hasRemoteFrontend)
        m_page.remoteInspectorInformationDidChange();
#endif
}

void InspectorController::show()
{
    ASSERT(!hasRemoteFrontend());

    if (!enabled())
        return;

    if (m_frontendChannel)
        m_inspectorClient->bringFrontendToFront();
    else {
        if (Inspector::FrontendChannel* frontendChannel = m_inspectorClient->openInspectorFrontend(this)) {
            bool isAutomaticInspection = false;
            connectFrontend(frontendChannel, isAutomaticInspection);
        }
    }
}

void InspectorController::close()
{
    if (!m_frontendChannel)
        return;
    disconnectFrontend(DisconnectReason::InspectorDestroyed);
    m_inspectorClient->closeInspectorFrontend();
}

void InspectorController::setProcessId(long processId)
{
    IdentifiersFactory::setProcessId(processId);
}

void InspectorController::evaluateForTestInFrontend(const String& script)
{
    m_inspectorAgent->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);
}

Ref<Inspector::Protocol::Array<Inspector::Protocol::OverlayTypes::NodeHighlightData>> InspectorController::buildObjectForHighlightedNodes() const
{
    return m_overlay->buildObjectForHighlightedNodes();
}

void InspectorController::inspect(Node* node)
{
    if (!enabled())
        return;

    if (!hasRemoteFrontend())
        show();

    m_domAgent->inspect(node);
}

bool InspectorController::enabled() const
{
    return developerExtrasEnabled();
}

Page& InspectorController::inspectedPage() const
{
    return m_page;
}

void InspectorController::dispatchMessageFromFrontend(const String& message)
{
    if (m_backendDispatcher)
        m_backendDispatcher->dispatch(message);
}

void InspectorController::hideHighlight()
{
    ErrorString unused;
    m_domAgent->hideHighlight(unused);
}

Node* InspectorController::highlightedNode() const
{
    return m_overlay->highlightedNode();
}

void InspectorController::setIndicating(bool indicating)
{
#if !PLATFORM(IOS)
    m_overlay->setIndicating(indicating);
#else
    if (indicating)
        m_inspectorClient->showInspectorIndication();
    else
        m_inspectorClient->hideInspectorIndication();
#endif
}

bool InspectorController::profilerEnabled() const
{
    return m_instrumentingAgents->persistentInspectorTimelineAgent();
}

void InspectorController::setProfilerEnabled(bool enable)
{
    ErrorString unused;

    if (enable) {
        m_instrumentingAgents->setPersistentInspectorTimelineAgent(m_timelineAgent);
        m_timelineAgent->start(unused);
    } else {
        m_instrumentingAgents->setPersistentInspectorTimelineAgent(nullptr);
        m_timelineAgent->stop(unused);
    }
}

void InspectorController::resume()
{
    if (m_debuggerAgent) {
        ErrorString unused;
        m_debuggerAgent->resume(unused);
    }
}

bool InspectorController::developerExtrasEnabled() const
{
    return m_page.settings().developerExtrasEnabled();
}

bool InspectorController::canAccessInspectedScriptState(JSC::ExecState* scriptState) const
{
    JSLockHolder lock(scriptState);
    JSDOMWindow* inspectedWindow = toJSDOMWindow(scriptState->lexicalGlobalObject());
    if (!inspectedWindow)
        return false;

    return BindingSecurity::shouldAllowAccessToDOMWindow(scriptState, inspectedWindow->impl(), DoNotReportSecurityError);
}

InspectorFunctionCallHandler InspectorController::functionCallHandler() const
{
    return WebCore::functionCallHandlerFromAnyThread;
}

InspectorEvaluateHandler InspectorController::evaluateHandler() const
{
    return WebCore::evaluateHandlerFromAnyThread;
}

void InspectorController::willCallInjectedScriptFunction(JSC::ExecState* scriptState, const String&, int)
{
    LegacyProfiler::profiler()->suspendProfiling(scriptState);
}

void InspectorController::didCallInjectedScriptFunction(JSC::ExecState* scriptState)
{
    LegacyProfiler::profiler()->unsuspendProfiling(scriptState);
}

void InspectorController::frontendInitialized()
{
#if ENABLE(REMOTE_INSPECTOR)
    if (m_isAutomaticInspection)
        m_page.inspectorDebuggable().unpauseForInitializedInspector();
#endif
}

Ref<Stopwatch> InspectorController::executionStopwatch()
{
    return m_executionStopwatch.copyRef();
}

void InspectorController::didComposite(Frame& frame)
{
    InspectorInstrumentation::didComposite(frame);
}

} // namespace WebCore
