blob: eeb5fc4198b656bb5166c749d6255900982e664d [file] [log] [blame]
/*
* Copyright (C) 2019 Sony Interactive Entertainment Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "RemoteInspector.h"
#if ENABLE(REMOTE_INSPECTOR)
#include "RemoteAutomationTarget.h"
#include "RemoteConnectionToTarget.h"
#include "RemoteInspectionTarget.h"
#include <wtf/FileSystem.h>
#include <wtf/JSONValues.h>
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RunLoop.h>
namespace Inspector {
RemoteInspector& RemoteInspector::singleton()
{
static NeverDestroyed<RemoteInspector> shared;
return shared;
}
RemoteInspector::RemoteInspector()
{
Socket::init();
start();
}
void RemoteInspector::connect(ConnectionID id)
{
ASSERT(!isConnected());
m_clientConnection = id;
start();
}
void RemoteInspector::didClose(ConnectionID)
{
ASSERT(isConnected());
m_clientConnection = WTF::nullopt;
RunLoop::current().dispatch([=] {
LockHolder lock(m_mutex);
stopInternal(StopSource::API);
});
}
void RemoteInspector::sendWebInspectorEvent(const String& event)
{
if (!m_clientConnection)
return;
const CString message = event.utf8();
send(m_clientConnection.value(), reinterpret_cast<const uint8_t*>(message.data()), message.length());
}
void RemoteInspector::start()
{
LockHolder lock(m_mutex);
if (m_enabled)
return;
m_enabled = true;
}
void RemoteInspector::stopInternal(StopSource)
{
if (!m_enabled)
return;
m_enabled = false;
m_pushScheduled = false;
m_readyToPushListings = false;
for (auto targetConnection : m_targetConnectionMap.values())
targetConnection->close();
m_targetConnectionMap.clear();
updateHasActiveDebugSession();
m_automaticInspectionPaused = false;
}
TargetListing RemoteInspector::listingForInspectionTarget(const RemoteInspectionTarget& target) const
{
if (!target.remoteDebuggingAllowed())
return nullptr;
// FIXME: Support remote debugging of a ServiceWorker.
if (target.type() == RemoteInspectionTarget::Type::ServiceWorker)
return nullptr;
TargetListing targetListing = JSON::Object::create();
targetListing->setString("name"_s, target.name());
targetListing->setString("url"_s, target.url());
targetListing->setInteger("targetID"_s, target.targetIdentifier());
targetListing->setBoolean("hasLocalDebugger"_s, target.hasLocalDebugger());
if (target.type() == RemoteInspectionTarget::Type::Web)
targetListing->setString("type"_s, "web"_s);
else if (target.type() == RemoteInspectionTarget::Type::JavaScript)
targetListing->setString("type"_s, "javascript"_s);
else if (target.type() == RemoteInspectionTarget::Type::ServiceWorker)
targetListing->setString("type"_s, "service-worker"_s);
return targetListing;
}
TargetListing RemoteInspector::listingForAutomationTarget(const RemoteAutomationTarget&) const
{
return nullptr;
}
void RemoteInspector::pushListingsNow()
{
if (!isConnected() || !m_readyToPushListings)
return;
m_pushScheduled = false;
auto targetListJSON = JSON::Array::create();
for (auto listing : m_targetListingMap.values())
targetListJSON->pushObject(listing);
auto jsonEvent = JSON::Object::create();
jsonEvent->setString("event"_s, "SetTargetList"_s);
jsonEvent->setString("message"_s, targetListJSON->toJSONString());
jsonEvent->setInteger("connectionID"_s, m_clientConnection.value());
jsonEvent->setBoolean("remoteAutomationAllowed"_s, m_clientCapabilities && m_clientCapabilities->remoteAutomationAllowed);
sendWebInspectorEvent(jsonEvent->toJSONString());
}
void RemoteInspector::pushListingsSoon()
{
if (!isConnected())
return;
if (m_pushScheduled)
return;
m_pushScheduled = true;
RunLoop::current().dispatch([=] {
LockHolder lock(m_mutex);
if (m_pushScheduled)
pushListingsNow();
});
}
void RemoteInspector::sendAutomaticInspectionCandidateMessage()
{
}
void RemoteInspector::sendMessageToRemote(TargetID targetIdentifier, const String& message)
{
if (!m_clientConnection)
return;
auto sendMessageEvent = JSON::Object::create();
sendMessageEvent->setInteger("targetID"_s, targetIdentifier);
sendMessageEvent->setString("event"_s, "SendMessageToFrontend"_s);
sendMessageEvent->setInteger("connectionID"_s, m_clientConnection.value());
sendMessageEvent->setString("message"_s, message);
sendWebInspectorEvent(sendMessageEvent->toJSONString());
}
void RemoteInspector::setup(TargetID targetIdentifier)
{
RemoteControllableTarget* target;
{
LockHolder lock(m_mutex);
target = m_targetMap.get(targetIdentifier);
if (!target)
return;
}
auto connectionToTarget = adoptRef(*new RemoteConnectionToTarget(*target));
ASSERT(is<RemoteInspectionTarget>(target) || is<RemoteAutomationTarget>(target));
if (!connectionToTarget->setup()) {
connectionToTarget->close();
return;
}
LockHolder lock(m_mutex);
m_targetConnectionMap.set(targetIdentifier, WTFMove(connectionToTarget));
updateHasActiveDebugSession();
}
void RemoteInspector::sendMessageToTarget(TargetID targetIdentifier, const char* message)
{
if (auto connectionToTarget = m_targetConnectionMap.get(targetIdentifier))
connectionToTarget->sendMessageToTarget(String::fromUTF8(message));
}
String RemoteInspector::backendCommands() const
{
if (m_backendCommandsPath.isEmpty())
return { };
auto handle = FileSystem::openFile(m_backendCommandsPath, FileSystem::FileOpenMode::Read);
if (!FileSystem::isHandleValid(handle))
return { };
String result;
long long size;
if (FileSystem::getFileSize(handle, size)) {
Vector<LChar> buffer(size);
if (FileSystem::readFromFile(handle, reinterpret_cast<char*>(buffer.data()), size) == size)
result = String::adopt(WTFMove(buffer));
}
FileSystem::closeFile(handle);
return result;
}
// RemoteInspectorConnectionClient handlers
HashMap<String, RemoteInspectorConnectionClient::CallHandler>& RemoteInspector::dispatchMap()
{
static NeverDestroyed<HashMap<String, CallHandler>> methods = HashMap<String, CallHandler>({
{ "SetupInspectorClient"_s, static_cast<CallHandler>(&RemoteInspector::setupInspectorClient) },
{ "Setup"_s, static_cast<CallHandler>(&RemoteInspector::setupTarget) },
{ "FrontendDidClose"_s, static_cast<CallHandler>(&RemoteInspector::frontendDidClose) },
{ "SendMessageToBackend"_s, static_cast<CallHandler>(&RemoteInspector::sendMessageToBackend) },
});
return methods;
}
void RemoteInspector::setupInspectorClient(const Event&)
{
ASSERT(isMainThread());
auto backendCommandsEvent = JSON::Object::create();
backendCommandsEvent->setString("event"_s, "BackendCommands"_s);
backendCommandsEvent->setString("message"_s, backendCommands());
sendWebInspectorEvent(backendCommandsEvent->toJSONString());
m_readyToPushListings = true;
LockHolder lock(m_mutex);
pushListingsNow();
}
void RemoteInspector::setupTarget(const Event& event)
{
ASSERT(isMainThread());
if (!event.targetID || !event.connectionID)
return;
setup(event.targetID.value());
}
void RemoteInspector::frontendDidClose(const Event& event)
{
ASSERT(isMainThread());
if (!event.targetID)
return;
RefPtr<RemoteConnectionToTarget> connectionToTarget;
{
LockHolder lock(m_mutex);
RemoteControllableTarget* target = m_targetMap.get(event.targetID.value());
if (!target)
return;
connectionToTarget = m_targetConnectionMap.take(event.targetID.value());
updateHasActiveDebugSession();
}
if (connectionToTarget)
connectionToTarget->close();
}
void RemoteInspector::sendMessageToBackend(const Event& event)
{
ASSERT(isMainThread());
if (!event.connectionID || !event.targetID || !event.message)
return;
RefPtr<RemoteConnectionToTarget> connectionToTarget;
{
LockHolder lock(m_mutex);
connectionToTarget = m_targetConnectionMap.get(event.targetID.value());
if (!connectionToTarget)
return;
}
connectionToTarget->sendMessageToTarget(event.message.value());
}
} // namespace Inspector
#endif // ENABLE(REMOTE_INSPECTOR)