blob: 4ba99019405b73e8922d7b6ea48e8b1570c5dd36 [file] [log] [blame]
/*
* Copyright (C) 2014-2022 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:
* 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 "WebUserContentController.h"
#include "ContentWorldShared.h"
#include "DataReference.h"
#include "FrameInfoData.h"
#include "InjectUserScriptImmediately.h"
#include "InjectedBundleScriptWorld.h"
#include "WebCompiledContentRuleList.h"
#include "WebFrame.h"
#include "WebPage.h"
#include "WebProcess.h"
#include "WebUserContentControllerMessages.h"
#include "WebUserContentControllerProxyMessages.h"
#include <WebCore/DOMWrapperWorld.h>
#include <WebCore/Frame.h>
#include <WebCore/SecurityOriginData.h>
#include <WebCore/SerializedScriptValue.h>
#include <WebCore/UserStyleSheet.h>
#include <wtf/NeverDestroyed.h>
#if ENABLE(USER_MESSAGE_HANDLERS)
#include <WebCore/UserMessageHandler.h>
#include <WebCore/UserMessageHandlerDescriptor.h>
#endif
namespace WebKit {
using namespace WebCore;
static HashMap<UserContentControllerIdentifier, WebUserContentController*>& userContentControllers()
{
static NeverDestroyed<HashMap<UserContentControllerIdentifier, WebUserContentController*>> userContentControllers;
return userContentControllers;
}
typedef HashMap<ContentWorldIdentifier, std::pair<RefPtr<InjectedBundleScriptWorld>, unsigned>> WorldMap;
static WorldMap& worldMap()
{
static NeverDestroyed<WorldMap> map(std::initializer_list<WorldMap::KeyValuePairType> { { pageContentWorldIdentifier(), std::make_pair(&InjectedBundleScriptWorld::normalWorld(), 1) } });
return map;
}
Ref<WebUserContentController> WebUserContentController::getOrCreate(UserContentControllerIdentifier identifier)
{
auto& userContentControllerPtr = userContentControllers().add(identifier, nullptr).iterator->value;
if (userContentControllerPtr)
return *userContentControllerPtr;
RefPtr<WebUserContentController> userContentController = adoptRef(new WebUserContentController(identifier));
userContentControllerPtr = userContentController.get();
return userContentController.releaseNonNull();
}
WebUserContentController::WebUserContentController(UserContentControllerIdentifier identifier)
: m_identifier(identifier)
{
WebProcess::singleton().addMessageReceiver(Messages::WebUserContentController::messageReceiverName(), m_identifier, *this);
}
WebUserContentController::~WebUserContentController()
{
ASSERT(userContentControllers().contains(m_identifier));
WebProcess::singleton().removeMessageReceiver(Messages::WebUserContentController::messageReceiverName(), m_identifier);
userContentControllers().remove(m_identifier);
}
InjectedBundleScriptWorld* WebUserContentController::worldForIdentifier(ContentWorldIdentifier identifier)
{
auto iterator = worldMap().find(identifier);
return iterator == worldMap().end() ? nullptr : iterator->value.first.get();
}
InjectedBundleScriptWorld* WebUserContentController::addContentWorld(const std::pair<ContentWorldIdentifier, String>& world)
{
ASSERT(world.first);
if (world.first == pageContentWorldIdentifier())
return nullptr;
auto addResult = worldMap().ensure(world.first, [&] {
#if PLATFORM(GTK) || PLATFORM(WPE)
// The GLib API doesn't allow to create script worlds from the UI process. We need to
// use the existing world created by the web extension if any. The world name is used
// as the identifier.
if (auto* existingWorld = InjectedBundleScriptWorld::find(world.second))
return std::make_pair(Ref<InjectedBundleScriptWorld>(*existingWorld), 1);
#endif
return std::make_pair(InjectedBundleScriptWorld::create(world.second, InjectedBundleScriptWorld::Type::User), 1);
});
if (addResult.isNewEntry)
return addResult.iterator->value.first.get();
return nullptr;
}
void WebUserContentController::addContentWorlds(const Vector<std::pair<ContentWorldIdentifier, String>>& worlds)
{
for (auto& world : worlds)
addContentWorld(world);
}
void WebUserContentController::removeContentWorlds(const Vector<ContentWorldIdentifier>& worldIdentifiers)
{
for (auto& worldIdentifier : worldIdentifiers) {
ASSERT(worldIdentifier);
ASSERT(worldIdentifier != pageContentWorldIdentifier());
auto it = worldMap().find(worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to remove a ContentWorld (id=%" PRIu64 ") that does not exist.", worldIdentifier.toUInt64());
return;
}
it->value.second--;
if (!it->value.second)
worldMap().remove(it);
}
}
void WebUserContentController::addUserScripts(Vector<WebUserScriptData>&& userScripts, InjectUserScriptImmediately immediately)
{
for (const auto& userScriptData : userScripts) {
auto it = worldMap().find(userScriptData.worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to add a UserScript to a ContentWorld (id=%" PRIu64 ") that does not exist.", userScriptData.worldIdentifier.toUInt64());
continue;
}
UserScript script = userScriptData.userScript;
addUserScriptInternal(*it->value.first, userScriptData.identifier, WTFMove(script), immediately);
}
}
void WebUserContentController::removeUserScript(ContentWorldIdentifier worldIdentifier, uint64_t userScriptIdentifier)
{
auto it = worldMap().find(worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to remove a UserScript from a ContentWorld (id=%" PRIu64 ") that does not exist.", worldIdentifier.toUInt64());
return;
}
removeUserScriptInternal(*it->value.first, userScriptIdentifier);
}
void WebUserContentController::removeAllUserScripts(const Vector<ContentWorldIdentifier>& worldIdentifiers)
{
for (auto& worldIdentifier : worldIdentifiers) {
auto it = worldMap().find(worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to remove all UserScripts from a ContentWorld (id=%" PRIu64 ") that does not exist.", worldIdentifier.toUInt64());
return;
}
removeUserScripts(*it->value.first);
}
}
void WebUserContentController::addUserStyleSheets(const Vector<WebUserStyleSheetData>& userStyleSheets)
{
for (const auto& userStyleSheetData : userStyleSheets) {
auto it = worldMap().find(userStyleSheetData.worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to add a UserStyleSheet to a ContentWorld (id=%" PRIu64 ") that does not exist.", userStyleSheetData.worldIdentifier.toUInt64());
continue;
}
UserStyleSheet sheet = userStyleSheetData.userStyleSheet;
addUserStyleSheetInternal(*it->value.first, userStyleSheetData.identifier, WTFMove(sheet));
}
invalidateInjectedStyleSheetCacheInAllFramesInAllPages();
}
void WebUserContentController::removeUserStyleSheet(ContentWorldIdentifier worldIdentifier, uint64_t userStyleSheetIdentifier)
{
auto it = worldMap().find(worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to remove a UserStyleSheet from a ContentWorld (id=%" PRIu64 ") that does not exist.", worldIdentifier.toUInt64());
return;
}
removeUserStyleSheetInternal(*it->value.first, userStyleSheetIdentifier);
}
void WebUserContentController::removeAllUserStyleSheets(const Vector<ContentWorldIdentifier>& worldIdentifiers)
{
bool sheetsChanged = false;
for (auto& worldIdentifier : worldIdentifiers) {
auto it = worldMap().find(worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to remove all UserStyleSheets from a ContentWorld (id=%" PRIu64 ") that does not exist.", worldIdentifier.toUInt64());
return;
}
if (m_userStyleSheets.remove(it->value.first.get()))
sheetsChanged = true;
}
if (sheetsChanged)
invalidateInjectedStyleSheetCacheInAllFramesInAllPages();
}
#if ENABLE(USER_MESSAGE_HANDLERS)
class WebUserMessageHandlerDescriptorProxy : public WebCore::UserMessageHandlerDescriptor {
public:
static Ref<WebUserMessageHandlerDescriptorProxy> create(WebUserContentController* controller, const String& name, InjectedBundleScriptWorld& world, uint64_t identifier)
{
return adoptRef(*new WebUserMessageHandlerDescriptorProxy(controller, name, world, identifier));
}
virtual ~WebUserMessageHandlerDescriptorProxy()
{
}
uint64_t identifier() { return m_identifier; }
private:
WebUserMessageHandlerDescriptorProxy(WebUserContentController* controller, const String& name, InjectedBundleScriptWorld& world, uint64_t identifier)
: WebCore::UserMessageHandlerDescriptor(name, world.coreWorld())
, m_controller(controller)
, m_identifier(identifier)
{
}
// WebCore::UserMessageHandlerDescriptor
void didPostMessage(WebCore::UserMessageHandler& handler, WebCore::SerializedScriptValue* value, WTF::Function<void(SerializedScriptValue*, const String&)>&& completionHandler) override
{
WebCore::Frame* frame = handler.frame();
if (!frame)
return;
WebFrame* webFrame = WebFrame::fromCoreFrame(*frame);
if (!webFrame)
return;
WebPage* webPage = webFrame->page();
if (!webPage)
return;
auto messageReplyHandler = [completionHandler = WTFMove(completionHandler)](const IPC::DataReference& resultValue, const String& errorMessage) {
if (!errorMessage.isNull()) {
completionHandler(nullptr, errorMessage);
return;
}
auto value = SerializedScriptValue::createFromWireBytes({ resultValue });
completionHandler(value.ptr(), { });
};
WebProcess::singleton().parentProcessConnection()->sendWithAsyncReply(Messages::WebUserContentControllerProxy::DidPostMessage(webPage->webPageProxyIdentifier(), webFrame->info(), m_identifier, IPC::DataReference(value->data())), WTFMove(messageReplyHandler), m_controller->identifier());
}
RefPtr<WebUserContentController> m_controller;
uint64_t m_identifier;
};
#endif
void WebUserContentController::addUserScriptMessageHandlers(const Vector<WebScriptMessageHandlerData>& scriptMessageHandlers)
{
#if ENABLE(USER_MESSAGE_HANDLERS)
for (auto& handler : scriptMessageHandlers) {
auto it = worldMap().find(handler.worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to add a UserScriptMessageHandler to a ContentWorld (id=%" PRIu64 ") that does not exist.", handler.worldIdentifier.toUInt64());
continue;
}
addUserScriptMessageHandlerInternal(*it->value.first, handler.identifier, handler.name);
}
#else
UNUSED_PARAM(scriptMessageHandlers);
#endif
}
void WebUserContentController::removeUserScriptMessageHandler(ContentWorldIdentifier worldIdentifier, uint64_t userScriptMessageHandlerIdentifier)
{
#if ENABLE(USER_MESSAGE_HANDLERS)
auto it = worldMap().find(worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to remove a UserScriptMessageHandler from a ContentWorld (id=%" PRIu64 ") that does not exist.", worldIdentifier.toUInt64());
return;
}
removeUserScriptMessageHandlerInternal(*it->value.first, userScriptMessageHandlerIdentifier);
#else
UNUSED_PARAM(worldIdentifier);
UNUSED_PARAM(userScriptMessageHandlerIdentifier);
#endif
}
void WebUserContentController::removeAllUserScriptMessageHandlers()
{
#if ENABLE(USER_MESSAGE_HANDLERS)
if (m_userMessageHandlers.isEmpty())
return;
m_userMessageHandlers.clear();
invalidateAllRegisteredUserMessageHandlerInvalidationClients();
#endif
}
void WebUserContentController::removeAllUserScriptMessageHandlersForWorlds(const Vector<ContentWorldIdentifier>& worldIdentifiers)
{
#if ENABLE(USER_MESSAGE_HANDLERS)
bool userMessageHandlersChanged = false;
for (auto& worldIdentifier : worldIdentifiers) {
auto it = worldMap().find(worldIdentifier);
if (it == worldMap().end()) {
WTFLogAlways("Trying to remove all UserScriptMessageHandler from a ContentWorld (id=%" PRIu64 ") that does not exist.", worldIdentifier.toUInt64());
return;
}
if (m_userMessageHandlers.remove(it->value.first.get()))
userMessageHandlersChanged = true;
}
if (userMessageHandlersChanged)
invalidateAllRegisteredUserMessageHandlerInvalidationClients();
#else
UNUSED_PARAM(worldIdentifiers);
#endif
}
#if ENABLE(USER_MESSAGE_HANDLERS)
void WebUserContentController::addUserScriptMessageHandlerInternal(InjectedBundleScriptWorld& world, uint64_t userScriptMessageHandlerIdentifier, const String& name)
{
auto& messageHandlersInWorld = m_userMessageHandlers.ensure(&world, [] { return Vector<std::pair<uint64_t, RefPtr<WebUserMessageHandlerDescriptorProxy>>> { }; }).iterator->value;
if (messageHandlersInWorld.findIf([&](auto& pair) { return pair.first == userScriptMessageHandlerIdentifier; }) != notFound)
return;
messageHandlersInWorld.append(std::make_pair(userScriptMessageHandlerIdentifier, WebUserMessageHandlerDescriptorProxy::create(this, name, world, userScriptMessageHandlerIdentifier)));
}
void WebUserContentController::removeUserScriptMessageHandlerInternal(InjectedBundleScriptWorld& world, uint64_t userScriptMessageHandlerIdentifier)
{
auto it = m_userMessageHandlers.find(&world);
if (it == m_userMessageHandlers.end())
return;
Ref protectedThis { *this };
auto& userMessageHandlers = it->value;
bool userMessageHandlersChanged = userMessageHandlers.removeFirstMatching([userScriptMessageHandlerIdentifier](auto& pair) {
return pair.first == userScriptMessageHandlerIdentifier;
});
if (!userMessageHandlersChanged)
return;
if (userMessageHandlers.isEmpty())
m_userMessageHandlers.remove(it);
invalidateAllRegisteredUserMessageHandlerInvalidationClients();
}
#endif
#if ENABLE(CONTENT_EXTENSIONS)
void WebUserContentController::addContentRuleLists(Vector<std::pair<WebCompiledContentRuleListData, URL>>&& contentRuleLists)
{
for (auto&& pair : contentRuleLists) {
auto&& contentRuleList = WTFMove(pair.first);
String identifier = contentRuleList.identifier;
auto compiledContentRuleList = WebCompiledContentRuleList::create(WTFMove(contentRuleList));
m_contentExtensionBackend.addContentExtension(identifier, WTFMove(compiledContentRuleList), WTFMove(pair.second));
}
}
void WebUserContentController::removeContentRuleList(const String& name)
{
m_contentExtensionBackend.removeContentExtension(name);
}
void WebUserContentController::removeAllContentRuleLists()
{
m_contentExtensionBackend.removeAllContentExtensions();
}
#endif
void WebUserContentController::addUserScriptInternal(InjectedBundleScriptWorld& world, const std::optional<uint64_t>& userScriptIdentifier, UserScript&& userScript, InjectUserScriptImmediately immediately)
{
if (immediately == InjectUserScriptImmediately::Yes) {
Page::forEachPage([&] (auto& page) {
if (&page.userContentProvider() != this)
return;
auto& mainFrame = page.mainFrame();
if (userScript.injectedFrames() == UserContentInjectedFrames::InjectInTopFrameOnly) {
mainFrame.injectUserScriptImmediately(world.coreWorld(), userScript);
return;
}
for (auto* frame = &mainFrame; frame; frame = frame->tree().traverseNext(&mainFrame))
frame->injectUserScriptImmediately(world.coreWorld(), userScript);
});
}
auto& scriptsInWorld = m_userScripts.ensure(&world, [] { return Vector<std::pair<std::optional<uint64_t>, WebCore::UserScript>>(); }).iterator->value;
if (userScriptIdentifier && scriptsInWorld.findIf([&](auto& pair) { return pair.first == userScriptIdentifier; }) != notFound)
return;
scriptsInWorld.append(std::make_pair(userScriptIdentifier, WTFMove(userScript)));
}
void WebUserContentController::addUserScript(InjectedBundleScriptWorld& world, UserScript&& userScript)
{
addUserScriptInternal(world, std::nullopt, WTFMove(userScript), InjectUserScriptImmediately::No);
}
void WebUserContentController::removeUserScriptWithURL(InjectedBundleScriptWorld& world, const URL& url)
{
auto it = m_userScripts.find(&world);
if (it == m_userScripts.end())
return;
auto& scripts = it->value;
scripts.removeAllMatching([&](auto& pair) {
return pair.second.url() == url;
});
if (scripts.isEmpty())
m_userScripts.remove(it);
}
void WebUserContentController::removeUserScriptInternal(InjectedBundleScriptWorld& world, uint64_t userScriptIdentifier)
{
auto it = m_userScripts.find(&world);
if (it == m_userScripts.end())
return;
auto& scripts = it->value;
scripts.removeFirstMatching([userScriptIdentifier](auto& pair) {
return pair.first == userScriptIdentifier;
});
if (scripts.isEmpty())
m_userScripts.remove(it);
}
void WebUserContentController::removeUserScripts(InjectedBundleScriptWorld& world)
{
m_userScripts.remove(&world);
}
void WebUserContentController::addUserStyleSheetInternal(InjectedBundleScriptWorld& world, const std::optional<uint64_t>& userStyleSheetIdentifier, UserStyleSheet&& userStyleSheet)
{
auto& styleSheetsInWorld = m_userStyleSheets.ensure(&world, [] { return Vector<std::pair<std::optional<uint64_t>, WebCore::UserStyleSheet>>(); }).iterator->value;
if (userStyleSheetIdentifier && styleSheetsInWorld.findIf([&](auto& pair) { return pair.first == userStyleSheetIdentifier; }) != notFound)
return;
if (auto pageID = userStyleSheet.pageID()) {
if (auto* webPage = WebProcess::singleton().webPage(*pageID)) {
if (auto* page = webPage->corePage())
page->injectUserStyleSheet(userStyleSheet);
}
}
styleSheetsInWorld.append(std::make_pair(userStyleSheetIdentifier, WTFMove(userStyleSheet)));
}
void WebUserContentController::addUserStyleSheet(InjectedBundleScriptWorld& world, UserStyleSheet&& userStyleSheet)
{
addUserStyleSheetInternal(world, std::nullopt, WTFMove(userStyleSheet));
invalidateInjectedStyleSheetCacheInAllFramesInAllPages();
}
void WebUserContentController::removeUserStyleSheetWithURL(InjectedBundleScriptWorld& world, const URL& url)
{
auto it = m_userStyleSheets.find(&world);
if (it == m_userStyleSheets.end())
return;
auto& stylesheets = it->value;
bool sheetsChanged = stylesheets.removeAllMatching([&](auto& pair) {
return pair.second.url() == url;
});
if (!sheetsChanged)
return;
if (stylesheets.isEmpty())
m_userStyleSheets.remove(it);
invalidateInjectedStyleSheetCacheInAllFramesInAllPages();
}
void WebUserContentController::removeUserStyleSheetInternal(InjectedBundleScriptWorld& world, uint64_t userStyleSheetIdentifier)
{
auto it = m_userStyleSheets.find(&world);
if (it == m_userStyleSheets.end())
return;
auto& stylesheets = it->value;
bool sheetsChanged = stylesheets.removeFirstMatching([&](auto& pair) {
if (pair.first != userStyleSheetIdentifier)
return false;
auto& userStyleSheet = pair.second;
if (auto pageID = userStyleSheet.pageID()) {
if (auto* webPage = WebProcess::singleton().webPage(*pageID)) {
if (auto* page = webPage->corePage())
page->removeInjectedUserStyleSheet(userStyleSheet);
}
}
return true;
});
if (!sheetsChanged)
return;
if (stylesheets.isEmpty())
m_userStyleSheets.remove(it);
invalidateInjectedStyleSheetCacheInAllFramesInAllPages();
}
void WebUserContentController::removeUserStyleSheets(InjectedBundleScriptWorld& world)
{
if (!m_userStyleSheets.remove(&world))
return;
invalidateInjectedStyleSheetCacheInAllFramesInAllPages();
}
void WebUserContentController::removeAllUserContent()
{
m_userScripts.clear();
if (!m_userStyleSheets.isEmpty()) {
m_userStyleSheets.clear();
invalidateInjectedStyleSheetCacheInAllFramesInAllPages();
}
}
void WebUserContentController::forEachUserScript(Function<void(WebCore::DOMWrapperWorld&, const WebCore::UserScript&)>&& functor) const
{
for (const auto& worldAndUserScriptVector : m_userScripts) {
auto& world = worldAndUserScriptVector.key->coreWorld();
for (const auto& identifierUserScriptPair : worldAndUserScriptVector.value)
functor(world, identifierUserScriptPair.second);
}
}
void WebUserContentController::forEachUserStyleSheet(Function<void(const WebCore::UserStyleSheet&)>&& functor) const
{
for (auto& styleSheetVector : m_userStyleSheets.values()) {
for (const auto& identifierUserStyleSheetPair : styleSheetVector)
functor(identifierUserStyleSheetPair.second);
}
}
#if ENABLE(USER_MESSAGE_HANDLERS)
void WebUserContentController::forEachUserMessageHandler(Function<void(const WebCore::UserMessageHandlerDescriptor&)>&& functor) const
{
for (auto& userMessageHandlerVector : m_userMessageHandlers.values()) {
for (auto& pair : userMessageHandlerVector)
functor(*pair.second.get());
}
}
#endif
} // namespace WebKit