JSLazyEventListener.cpp [plain text]
#include "config.h"
#include "JSLazyEventListener.h"
#include "CachedScriptFetcher.h"
#include "ContentSecurityPolicy.h"
#include "Element.h"
#include "Frame.h"
#include "JSNode.h"
#include "QualifiedName.h"
#include "SVGElement.h"
#include "ScriptController.h"
#include <JavaScriptCore/CatchScope.h>
#include <JavaScriptCore/FunctionConstructor.h>
#include <JavaScriptCore/IdentifierInlines.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/RefCountedLeakCounter.h>
#include <wtf/StdLibExtras.h>
#include <wtf/WeakPtr.h>
namespace WebCore {
using namespace JSC;
DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, eventListenerCounter, ("JSLazyEventListener"));
struct JSLazyEventListener::CreationArguments {
const QualifiedName& attributeName;
const AtomString& attributeValue;
Document& document;
WeakPtr<ContainerNode> node;
JSObject* wrapper;
bool shouldUseSVGEventName;
};
static const String& eventParameterName(bool shouldUseSVGEventName)
{
static NeverDestroyed<const String> eventString(MAKE_STATIC_STRING_IMPL("event"));
static NeverDestroyed<const String> evtString(MAKE_STATIC_STRING_IMPL("evt"));
return shouldUseSVGEventName ? evtString : eventString;
}
static TextPosition convertZeroToOne(const TextPosition& position)
{
if (position == TextPosition::belowRangePosition())
return { };
return position;
}
JSLazyEventListener::JSLazyEventListener(CreationArguments&& arguments, const String& sourceURL, const TextPosition& sourcePosition)
: JSEventListener(nullptr, arguments.wrapper, true, mainThreadNormalWorld())
, m_functionName(arguments.attributeName.localName().string())
, m_eventParameterName(eventParameterName(arguments.shouldUseSVGEventName))
, m_code(arguments.attributeValue)
, m_sourceURL(sourceURL)
, m_sourcePosition(convertZeroToOne(sourcePosition))
, m_originalNode(WTFMove(arguments.node))
{
#ifndef NDEBUG
eventListenerCounter.increment();
#endif
}
#if !ASSERT_DISABLED
static inline bool isCloneInShadowTreeOfSVGUseElement(Node& originalNode, EventTarget& eventTarget)
{
if (!eventTarget.isNode())
return false;
auto& node = downcast<Node>(eventTarget);
if (!is<SVGElement>(node))
return false;
auto& element = downcast<SVGElement>(node);
if (!element.correspondingElement())
return false;
ASSERT(element.isInShadowTree());
return &originalNode == element.correspondingElement();
}
void JSLazyEventListener::checkValidityForEventTarget(EventTarget& eventTarget)
{
if (eventTarget.isNode()) {
ASSERT(m_originalNode);
ASSERT(static_cast<EventTarget*>(m_originalNode.get()) == &eventTarget || isCloneInShadowTreeOfSVGUseElement(*m_originalNode, eventTarget));
} else
ASSERT(!m_originalNode);
}
#endif
JSLazyEventListener::~JSLazyEventListener()
{
#ifndef NDEBUG
eventListenerCounter.decrement();
#endif
}
JSObject* JSLazyEventListener::initializeJSFunction(ScriptExecutionContext& executionContext) const
{
ASSERT(is<Document>(executionContext));
auto& executionContextDocument = downcast<Document>(executionContext);
auto& document = m_originalNode ? m_originalNode->document() : executionContextDocument;
if (!document.frame())
return nullptr;
if (!document.contentSecurityPolicy()->allowInlineEventHandlers(m_sourceURL, m_sourcePosition.m_line))
return nullptr;
auto& script = document.frame()->script();
if (!script.canExecuteScripts(AboutToCreateEventListener) || script.isPaused())
return nullptr;
if (!executionContextDocument.frame())
return nullptr;
auto* globalObject = toJSDOMWindow(*executionContextDocument.frame(), isolatedWorld());
if (!globalObject)
return nullptr;
VM& vm = globalObject->vm();
JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSGlobalObject* lexicalGlobalObject = globalObject;
MarkedArgumentBuffer args;
args.append(jsNontrivialString(vm, m_eventParameterName));
args.append(jsStringWithCache(lexicalGlobalObject, m_code));
ASSERT(!args.hasOverflowed());
int overrideLineNumber = m_sourcePosition.m_line.oneBasedInt();
JSObject* jsFunction = constructFunctionSkippingEvalEnabledCheck(
lexicalGlobalObject, args, Identifier::fromString(vm, m_functionName),
SourceOrigin { m_sourceURL, CachedScriptFetcher::create(document.charset()) },
m_sourceURL, m_sourcePosition, overrideLineNumber);
if (UNLIKELY(scope.exception())) {
reportCurrentException(lexicalGlobalObject);
scope.clearException();
return nullptr;
}
JSFunction* listenerAsFunction = jsCast<JSFunction*>(jsFunction);
if (m_originalNode) {
if (!wrapper()) {
setWrapper(vm, asObject(toJS(lexicalGlobalObject, globalObject, *m_originalNode)));
}
listenerAsFunction->setScope(vm, jsCast<JSNode*>(wrapper())->pushEventHandlerScope(lexicalGlobalObject, listenerAsFunction->scope()));
}
return jsFunction;
}
RefPtr<JSLazyEventListener> JSLazyEventListener::create(CreationArguments&& arguments)
{
if (arguments.attributeValue.isNull())
return nullptr;
TextPosition position;
String sourceURL;
if (Frame* frame = arguments.document.frame()) {
if (!frame->script().canExecuteScripts(AboutToCreateEventListener))
return nullptr;
position = frame->script().eventHandlerPosition();
sourceURL = arguments.document.url().string();
}
return adoptRef(*new JSLazyEventListener(WTFMove(arguments), sourceURL, position));
}
RefPtr<JSLazyEventListener> JSLazyEventListener::create(Element& element, const QualifiedName& attributeName, const AtomString& attributeValue)
{
return create({ attributeName, attributeValue, element.document(), makeWeakPtr(element), nullptr, element.isSVGElement() });
}
RefPtr<JSLazyEventListener> JSLazyEventListener::create(Document& document, const QualifiedName& attributeName, const AtomString& attributeValue)
{
return create({ attributeName, attributeValue, document, makeWeakPtr(document), nullptr, false });
}
RefPtr<JSLazyEventListener> JSLazyEventListener::create(DOMWindow& window, const QualifiedName& attributeName, const AtomString& attributeValue)
{
ASSERT(window.document());
auto& document = *window.document();
ASSERT(document.frame());
return create({ attributeName, attributeValue, document, nullptr, toJSDOMWindow(document.frame(), mainThreadNormalWorld()), document.isSVGDocument() });
}
}