2 * Copyright (C) 2001 Peter Kelly (pmk@post.com)
3 * Copyright (C) 2003-2018 Apple Inc. All Rights Reserved.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #include "JSLazyEventListener.h"
23 #include "CachedScriptFetcher.h"
24 #include "ContentSecurityPolicy.h"
28 #include "QualifiedName.h"
29 #include "SVGElement.h"
30 #include "ScriptController.h"
31 #include <JavaScriptCore/CatchScope.h>
32 #include <JavaScriptCore/FunctionConstructor.h>
33 #include <JavaScriptCore/IdentifierInlines.h>
34 #include <wtf/NeverDestroyed.h>
35 #include <wtf/RefCountedLeakCounter.h>
36 #include <wtf/StdLibExtras.h>
37 #include <wtf/WeakPtr.h>
42 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, eventListenerCounter, ("JSLazyEventListener"));
44 struct JSLazyEventListener::CreationArguments {
45 const QualifiedName& attributeName;
46 const AtomString& attributeValue;
48 WeakPtr<ContainerNode> node;
50 bool shouldUseSVGEventName;
53 static const String& eventParameterName(bool shouldUseSVGEventName)
55 static NeverDestroyed<const String> eventString(MAKE_STATIC_STRING_IMPL("event"));
56 static NeverDestroyed<const String> evtString(MAKE_STATIC_STRING_IMPL("evt"));
57 return shouldUseSVGEventName ? evtString : eventString;
60 static TextPosition convertZeroToOne(const TextPosition& position)
62 // A JSLazyEventListener can be created with a line number of zero when it is created with
63 // a setAttribute call from JavaScript, so make the line number 1 in that case.
64 if (position == TextPosition::belowRangePosition())
69 JSLazyEventListener::JSLazyEventListener(CreationArguments&& arguments, const String& sourceURL, const TextPosition& sourcePosition)
70 : JSEventListener(nullptr, arguments.wrapper, true, mainThreadNormalWorld())
71 , m_functionName(arguments.attributeName.localName().string())
72 , m_eventParameterName(eventParameterName(arguments.shouldUseSVGEventName))
73 , m_code(arguments.attributeValue)
74 , m_sourceURL(sourceURL)
75 , m_sourcePosition(convertZeroToOne(sourcePosition))
76 , m_originalNode(WTFMove(arguments.node))
79 eventListenerCounter.increment();
84 static inline bool isCloneInShadowTreeOfSVGUseElement(Node& originalNode, EventTarget& eventTarget)
86 if (!eventTarget.isNode())
89 auto& node = downcast<Node>(eventTarget);
90 if (!is<SVGElement>(node))
93 auto& element = downcast<SVGElement>(node);
94 if (!element.correspondingElement())
97 ASSERT(element.isInShadowTree());
98 return &originalNode == element.correspondingElement();
101 // This is to help find the underlying cause of <rdar://problem/24314027>.
102 void JSLazyEventListener::checkValidityForEventTarget(EventTarget& eventTarget)
104 if (eventTarget.isNode()) {
105 ASSERT(m_originalNode);
106 ASSERT(static_cast<EventTarget*>(m_originalNode.get()) == &eventTarget || isCloneInShadowTreeOfSVGUseElement(*m_originalNode, eventTarget));
108 ASSERT(!m_originalNode);
112 JSLazyEventListener::~JSLazyEventListener()
115 eventListenerCounter.decrement();
119 JSObject* JSLazyEventListener::initializeJSFunction(ScriptExecutionContext& executionContext) const
121 ASSERT(is<Document>(executionContext));
123 auto& executionContextDocument = downcast<Document>(executionContext);
125 // As per the HTML specification [1], if this is an element's event handler, then document should be the
126 // element's document. The script execution context may be different from the node's document if the
127 // node's document was created by JavaScript.
128 // [1] https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler
129 auto& document = m_originalNode ? m_originalNode->document() : executionContextDocument;
130 if (!document.frame())
133 if (!document.contentSecurityPolicy()->allowInlineEventHandlers(m_sourceURL, m_sourcePosition.m_line))
136 auto& script = document.frame()->script();
137 if (!script.canExecuteScripts(AboutToCreateEventListener) || script.isPaused())
140 if (!executionContextDocument.frame())
142 auto* globalObject = toJSDOMWindow(*executionContextDocument.frame(), isolatedWorld());
146 VM& vm = globalObject->vm();
147 JSLockHolder lock(vm);
148 auto scope = DECLARE_CATCH_SCOPE(vm);
149 ExecState* exec = globalObject->globalExec();
151 MarkedArgumentBuffer args;
152 args.append(jsNontrivialString(exec, m_eventParameterName));
153 args.append(jsStringWithCache(exec, m_code));
154 ASSERT(!args.hasOverflowed());
156 // We want all errors to refer back to the line on which our attribute was
157 // declared, regardless of any newlines in our JavaScript source text.
158 int overrideLineNumber = m_sourcePosition.m_line.oneBasedInt();
160 JSObject* jsFunction = constructFunctionSkippingEvalEnabledCheck(exec,
161 exec->lexicalGlobalObject(), args, Identifier::fromString(exec, m_functionName),
162 SourceOrigin { m_sourceURL, CachedScriptFetcher::create(document.charset()) },
163 m_sourceURL, m_sourcePosition, overrideLineNumber);
164 if (UNLIKELY(scope.exception())) {
165 reportCurrentException(exec);
166 scope.clearException();
170 JSFunction* listenerAsFunction = jsCast<JSFunction*>(jsFunction);
172 if (m_originalNode) {
174 // Ensure that 'node' has a JavaScript wrapper to mark the event listener we're creating.
175 // FIXME: Should pass the global object associated with the node
176 setWrapper(vm, asObject(toJS(exec, globalObject, *m_originalNode)));
179 // Add the event's home element to the scope
180 // (and the document, and the form - see JSHTMLElement::eventHandlerScope)
181 listenerAsFunction->setScope(vm, jsCast<JSNode*>(wrapper())->pushEventHandlerScope(exec, listenerAsFunction->scope()));
187 RefPtr<JSLazyEventListener> JSLazyEventListener::create(CreationArguments&& arguments)
189 if (arguments.attributeValue.isNull())
192 // FIXME: We should be able to provide source information for frameless documents too (e.g. for importing nodes from XMLHttpRequest.responseXML).
193 TextPosition position;
195 if (Frame* frame = arguments.document.frame()) {
196 if (!frame->script().canExecuteScripts(AboutToCreateEventListener))
198 position = frame->script().eventHandlerPosition();
199 sourceURL = arguments.document.url().string();
202 return adoptRef(*new JSLazyEventListener(WTFMove(arguments), sourceURL, position));
205 RefPtr<JSLazyEventListener> JSLazyEventListener::create(Element& element, const QualifiedName& attributeName, const AtomString& attributeValue)
207 return create({ attributeName, attributeValue, element.document(), makeWeakPtr(element), nullptr, element.isSVGElement() });
210 RefPtr<JSLazyEventListener> JSLazyEventListener::create(Document& document, const QualifiedName& attributeName, const AtomString& attributeValue)
212 // FIXME: This always passes false for "shouldUseSVGEventName". Is that correct for events dispatched to SVG documents?
213 // This has been this way for a long time, but became more obvious when refactoring to separate the Element and Document code paths.
214 return create({ attributeName, attributeValue, document, makeWeakPtr(document), nullptr, false });
217 RefPtr<JSLazyEventListener> JSLazyEventListener::create(DOMWindow& window, const QualifiedName& attributeName, const AtomString& attributeValue)
219 ASSERT(window.document());
220 auto& document = *window.document();
221 ASSERT(document.frame());
222 return create({ attributeName, attributeValue, document, nullptr, toJSDOMWindow(document.frame(), mainThreadNormalWorld()), document.isSVGDocument() });
225 } // namespace WebCore