blob: 4168389e45490c213ecd9b6a93654b24a34698e3 [file] [log] [blame]
/*
* Copyright (C) 2007-2019 Apple Inc. All rights reserved.
* Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
*
* 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.
* 3. Neither the name of Apple Inc. ("Apple") 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 APPLE 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 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 "InspectorFrontendHost.h"
#include "CertificateInfo.h"
#include "ContextMenu.h"
#include "ContextMenuController.h"
#include "ContextMenuItem.h"
#include "ContextMenuProvider.h"
#include "DOMWrapperWorld.h"
#include "Document.h"
#include "Editor.h"
#include "Event.h"
#include "FloatRect.h"
#include "FocusController.h"
#include "Frame.h"
#include "HitTestResult.h"
#include "InspectorController.h"
#include "InspectorDebuggableType.h"
#include "InspectorFrontendClient.h"
#include "JSDOMConvertInterface.h"
#include "JSDOMExceptionHandling.h"
#include "JSExecState.h"
#include "JSInspectorFrontendHost.h"
#include "MouseEvent.h"
#include "Node.h"
#include "Page.h"
#include "Pasteboard.h"
#include "ScriptState.h"
#include "UserGestureIndicator.h"
#include <JavaScriptCore/ScriptFunctionCall.h>
#include <pal/system/Sound.h>
#include <wtf/JSONValues.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/Base64.h>
namespace WebCore {
using namespace Inspector;
#if ENABLE(CONTEXT_MENUS)
class FrontendMenuProvider : public ContextMenuProvider {
public:
static Ref<FrontendMenuProvider> create(InspectorFrontendHost* frontendHost, Deprecated::ScriptObject frontendApiObject, const Vector<ContextMenuItem>& items)
{
return adoptRef(*new FrontendMenuProvider(frontendHost, frontendApiObject, items));
}
void disconnect()
{
m_frontendApiObject = { };
m_frontendHost = nullptr;
}
private:
FrontendMenuProvider(InspectorFrontendHost* frontendHost, Deprecated::ScriptObject frontendApiObject, const Vector<ContextMenuItem>& items)
: m_frontendHost(frontendHost)
, m_frontendApiObject(frontendApiObject)
, m_items(items)
{
}
~FrontendMenuProvider() override
{
contextMenuCleared();
}
void populateContextMenu(ContextMenu* menu) override
{
for (auto& item : m_items)
menu->appendItem(item);
}
void contextMenuItemSelected(ContextMenuAction action, const String&) override
{
if (m_frontendHost) {
UserGestureIndicator gestureIndicator(ProcessingUserGesture);
int itemNumber = action - ContextMenuItemBaseCustomTag;
Deprecated::ScriptFunctionCall function(m_frontendApiObject, "contextMenuItemSelected", WebCore::functionCallHandlerFromAnyThread);
function.appendArgument(itemNumber);
function.call();
}
}
void contextMenuCleared() override
{
if (m_frontendHost) {
Deprecated::ScriptFunctionCall function(m_frontendApiObject, "contextMenuCleared", WebCore::functionCallHandlerFromAnyThread);
function.call();
m_frontendHost->m_menuProvider = nullptr;
}
m_items.clear();
}
InspectorFrontendHost* m_frontendHost;
Deprecated::ScriptObject m_frontendApiObject;
Vector<ContextMenuItem> m_items;
};
#endif
InspectorFrontendHost::InspectorFrontendHost(InspectorFrontendClient* client, Page* frontendPage)
: m_client(client)
, m_frontendPage(frontendPage)
#if ENABLE(CONTEXT_MENUS)
, m_menuProvider(nullptr)
#endif
{
}
InspectorFrontendHost::~InspectorFrontendHost()
{
ASSERT(!m_client);
}
void InspectorFrontendHost::disconnectClient()
{
m_client = nullptr;
#if ENABLE(CONTEXT_MENUS)
if (m_menuProvider)
m_menuProvider->disconnect();
#endif
m_frontendPage = nullptr;
}
void InspectorFrontendHost::addSelfToGlobalObjectInWorld(DOMWrapperWorld& world)
{
auto& lexicalGlobalObject = *execStateFromPage(world, m_frontendPage);
auto& vm = lexicalGlobalObject.vm();
JSC::JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(&lexicalGlobalObject);
globalObject.putDirect(vm, JSC::Identifier::fromString(vm, "InspectorFrontendHost"), toJS<IDLInterface<InspectorFrontendHost>>(lexicalGlobalObject, globalObject, *this));
if (UNLIKELY(scope.exception()))
reportException(&lexicalGlobalObject, scope.exception());
}
void InspectorFrontendHost::loaded()
{
if (m_client)
m_client->frontendLoaded();
}
void InspectorFrontendHost::requestSetDockSide(const String& side)
{
if (!m_client)
return;
if (side == "undocked")
m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Undocked);
else if (side == "right")
m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Right);
else if (side == "left")
m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Left);
else if (side == "bottom")
m_client->requestSetDockSide(InspectorFrontendClient::DockSide::Bottom);
}
void InspectorFrontendHost::closeWindow()
{
if (m_client) {
m_client->closeWindow();
disconnectClient(); // Disconnect from client.
}
}
void InspectorFrontendHost::reopen()
{
if (m_client)
m_client->reopen();
}
void InspectorFrontendHost::reset()
{
if (m_client)
m_client->resetState();
reopen();
}
void InspectorFrontendHost::bringToFront()
{
if (m_client)
m_client->bringToFront();
}
void InspectorFrontendHost::inspectedURLChanged(const String& newURL)
{
if (m_client)
m_client->inspectedURLChanged(newURL);
}
void InspectorFrontendHost::setZoomFactor(float zoom)
{
if (m_frontendPage)
m_frontendPage->mainFrame().setPageAndTextZoomFactors(zoom, 1);
}
float InspectorFrontendHost::zoomFactor()
{
if (m_frontendPage)
return m_frontendPage->mainFrame().pageZoomFactor();
return 1.0;
}
String InspectorFrontendHost::userInterfaceLayoutDirection()
{
if (m_client && m_client->userInterfaceLayoutDirection() == UserInterfaceLayoutDirection::RTL)
return "rtl"_s;
return "ltr"_s;
}
void InspectorFrontendHost::setAttachedWindowHeight(unsigned height)
{
if (m_client)
m_client->changeAttachedWindowHeight(height);
}
void InspectorFrontendHost::setAttachedWindowWidth(unsigned width)
{
if (m_client)
m_client->changeAttachedWindowWidth(width);
}
void InspectorFrontendHost::setSheetRect(float x, float y, unsigned width, unsigned height)
{
if (m_client)
m_client->changeSheetRect(FloatRect(x, y, width, height));
}
void InspectorFrontendHost::startWindowDrag()
{
if (m_client)
m_client->startWindowDrag();
}
void InspectorFrontendHost::moveWindowBy(float x, float y) const
{
if (m_client)
m_client->moveWindowBy(x, y);
}
bool InspectorFrontendHost::isRemote() const
{
return m_client && m_client->isRemote();
}
String InspectorFrontendHost::localizedStringsURL() const
{
return m_client ? m_client->localizedStringsURL() : String();
}
String InspectorFrontendHost::backendCommandsURL() const
{
return m_client ? m_client->backendCommandsURL() : String();
}
static String debuggableTypeToString(DebuggableType debuggableType)
{
switch (debuggableType) {
case DebuggableType::JavaScript:
return "javascript"_s;
case DebuggableType::Page:
return "page"_s;
case DebuggableType::ServiceWorker:
return "service-worker"_s;
case DebuggableType::WebPage:
return "web-page"_s;
}
ASSERT_NOT_REACHED();
return String();
}
InspectorFrontendHost::DebuggableInfo InspectorFrontendHost::debuggableInfo() const
{
if (!m_client)
return {debuggableTypeToString(DebuggableType::JavaScript), "Unknown"_s, "Unknown"_s, "Unknown"_s, false};
return {
debuggableTypeToString(m_client->debuggableType()),
m_client->targetPlatformName(),
m_client->targetBuildVersion(),
m_client->targetProductVersion(),
m_client->targetIsSimulator(),
};
}
unsigned InspectorFrontendHost::inspectionLevel() const
{
return m_client ? m_client->inspectionLevel() : 1;
}
String InspectorFrontendHost::platform() const
{
#if PLATFORM(COCOA)
return "mac"_s;
#elif OS(WINDOWS)
return "windows"_s;
#elif OS(LINUX)
return "linux"_s;
#elif OS(FREEBSD)
return "freebsd"_s;
#elif OS(OPENBSD)
return "openbsd"_s;
#else
return "unknown"_s;
#endif
}
String InspectorFrontendHost::port() const
{
#if PLATFORM(GTK)
return "gtk"_s;
#else
return "unknown"_s;
#endif
}
void InspectorFrontendHost::copyText(const String& text)
{
Pasteboard::createForCopyAndPaste()->writePlainText(text, Pasteboard::CannotSmartReplace);
}
void InspectorFrontendHost::killText(const String& text, bool shouldPrependToKillRing, bool shouldStartNewSequence)
{
if (!m_frontendPage)
return;
Editor& editor = m_frontendPage->focusController().focusedOrMainFrame().editor();
editor.setStartNewKillRingSequence(shouldStartNewSequence);
Editor::KillRingInsertionMode insertionMode = shouldPrependToKillRing ? Editor::KillRingInsertionMode::PrependText : Editor::KillRingInsertionMode::AppendText;
editor.addTextToKillRing(text, insertionMode);
}
void InspectorFrontendHost::openInNewTab(const String& url)
{
if (WTF::protocolIsJavaScript(url))
return;
if (m_client)
m_client->openInNewTab(url);
}
bool InspectorFrontendHost::canSave()
{
if (m_client)
return m_client->canSave();
return false;
}
void InspectorFrontendHost::save(const String& url, const String& content, bool base64Encoded, bool forceSaveAs)
{
if (m_client)
m_client->save(url, content, base64Encoded, forceSaveAs);
}
void InspectorFrontendHost::append(const String& url, const String& content)
{
if (m_client)
m_client->append(url, content);
}
void InspectorFrontendHost::close(const String&)
{
}
void InspectorFrontendHost::sendMessageToBackend(const String& message)
{
if (m_client)
m_client->sendMessageToBackend(message);
}
#if ENABLE(CONTEXT_MENUS)
static void populateContextMenu(Vector<InspectorFrontendHost::ContextMenuItem>&& items, ContextMenu& menu)
{
for (auto& item : items) {
if (item.type == "separator") {
menu.appendItem({ SeparatorType, ContextMenuItemTagNoAction, { } });
continue;
}
if (item.type == "subMenu" && item.subItems) {
ContextMenu subMenu;
populateContextMenu(WTFMove(*item.subItems), subMenu);
menu.appendItem({ SubmenuType, ContextMenuItemTagNoAction, item.label, &subMenu });
continue;
}
auto type = item.type == "checkbox" ? CheckableActionType : ActionType;
auto action = static_cast<ContextMenuAction>(ContextMenuItemBaseCustomTag + item.id.valueOr(0));
ContextMenuItem menuItem = { type, action, item.label };
if (item.enabled)
menuItem.setEnabled(*item.enabled);
if (item.checked)
menuItem.setChecked(*item.checked);
menu.appendItem(menuItem);
}
}
#endif
void InspectorFrontendHost::showContextMenu(Event& event, Vector<ContextMenuItem>&& items)
{
#if ENABLE(CONTEXT_MENUS)
ASSERT(m_frontendPage);
auto& lexicalGlobalObject = *execStateFromPage(debuggerWorld(), m_frontendPage);
auto& vm = lexicalGlobalObject.vm();
auto value = lexicalGlobalObject.get(&lexicalGlobalObject, JSC::Identifier::fromString(vm, "InspectorFrontendAPI"));
ASSERT(value);
ASSERT(value.isObject());
auto* frontendAPIObject = asObject(value);
ContextMenu menu;
populateContextMenu(WTFMove(items), menu);
auto menuProvider = FrontendMenuProvider::create(this, { &lexicalGlobalObject, frontendAPIObject }, menu.items());
m_menuProvider = menuProvider.ptr();
m_frontendPage->contextMenuController().showContextMenu(event, menuProvider);
#else
UNUSED_PARAM(event);
UNUSED_PARAM(items);
#endif
}
void InspectorFrontendHost::dispatchEventAsContextMenuEvent(Event& event)
{
#if ENABLE(CONTEXT_MENUS) && USE(ACCESSIBILITY_CONTEXT_MENUS)
if (!is<MouseEvent>(event))
return;
auto& mouseEvent = downcast<MouseEvent>(event);
auto& frame = *downcast<Node>(mouseEvent.target())->document().frame();
m_frontendPage->contextMenuController().showContextMenuAt(frame, roundedIntPoint(mouseEvent.absoluteLocation()));
#else
UNUSED_PARAM(event);
#endif
}
bool InspectorFrontendHost::isUnderTest()
{
return m_client && m_client->isUnderTest();
}
bool InspectorFrontendHost::isExperimentalBuild()
{
#if ENABLE(EXPERIMENTAL_FEATURES)
return true;
#else
return false;
#endif
}
void InspectorFrontendHost::unbufferedLog(const String& message)
{
// This is used only for debugging inspector tests.
WTFLogAlways("%s", message.utf8().data());
}
void InspectorFrontendHost::beep()
{
PAL::systemBeep();
}
void InspectorFrontendHost::inspectInspector()
{
if (m_frontendPage)
m_frontendPage->inspectorController().show();
}
bool InspectorFrontendHost::isBeingInspected()
{
if (!m_frontendPage)
return false;
InspectorController& inspectorController = m_frontendPage->inspectorController();
return inspectorController.hasLocalFrontend() || inspectorController.hasRemoteFrontend();
}
bool InspectorFrontendHost::supportsShowCertificate() const
{
#if PLATFORM(COCOA)
return true;
#else
return false;
#endif
}
bool InspectorFrontendHost::showCertificate(const String& serializedCertificate)
{
if (!m_client)
return false;
Vector<uint8_t> data;
if (!base64Decode(serializedCertificate, data))
return false;
CertificateInfo certificateInfo;
WTF::Persistence::Decoder decoder(data.data(), data.size());
if (!decoder.decode(certificateInfo))
return false;
if (certificateInfo.isEmpty())
return false;
m_client->showCertificate(certificateInfo);
return true;
}
bool InspectorFrontendHost::supportsDiagnosticLogging()
{
#if ENABLE(INSPECTOR_TELEMETRY)
return m_client && m_client->supportsDiagnosticLogging();
#else
return false;
#endif
}
#if ENABLE(INSPECTOR_TELEMETRY)
bool InspectorFrontendHost::diagnosticLoggingAvailable()
{
return m_client && m_client->diagnosticLoggingAvailable();
}
static Optional<DiagnosticLoggingClient::ValuePayload> valuePayloadFromJSONValue(const RefPtr<JSON::Value>& value)
{
switch (value->type()) {
case JSON::Value::Type::Array:
case JSON::Value::Type::Null:
case JSON::Value::Type::Object:
ASSERT_NOT_REACHED();
return WTF::nullopt;
case JSON::Value::Type::Boolean:
bool boolValue;
value->asBoolean(boolValue);
return DiagnosticLoggingClient::ValuePayload(boolValue);
case JSON::Value::Type::Double:
double doubleValue;
value->asDouble(doubleValue);
return DiagnosticLoggingClient::ValuePayload(doubleValue);
case JSON::Value::Type::Integer:
long long intValue;
value->asInteger(intValue);
return DiagnosticLoggingClient::ValuePayload(intValue);
case JSON::Value::Type::String:
String stringValue;
value->asString(stringValue);
return DiagnosticLoggingClient::ValuePayload(stringValue);
}
ASSERT_NOT_REACHED();
return WTF::nullopt;
}
void InspectorFrontendHost::logDiagnosticEvent(const String& eventName, const String& payloadString)
{
if (!supportsDiagnosticLogging())
return;
RefPtr<JSON::Value> payloadValue;
if (!JSON::Value::parseJSON(payloadString, payloadValue))
return;
RefPtr<JSON::Object> payloadObject;
if (!payloadValue->asObject(payloadObject))
return;
DiagnosticLoggingClient::ValueDictionary dictionary;
for (const auto& [key, value] : *payloadObject) {
if (auto valuePayload = valuePayloadFromJSONValue(value))
dictionary.set(key, WTFMove(valuePayload.value()));
}
m_client->logDiagnosticEvent(makeString("WebInspector."_s, eventName), dictionary);
}
#endif // ENABLE(INSPECTOR_TELEMETRY)
} // namespace WebCore