2d74f29e26a541517baada3d6d721cad01fdfe57
[WebKit-https.git] / Source / WebCore / bindings / js / JSLazyEventListener.cpp
1 /*
2  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
3  *  Copyright (C) 2003-2018 Apple Inc. All Rights Reserved.
4  *
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.
9  *
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.
14  *
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
18  */
19
20 #include "config.h"
21 #include "JSLazyEventListener.h"
22
23 #include "CachedScriptFetcher.h"
24 #include "ContentSecurityPolicy.h"
25 #include "Element.h"
26 #include "Frame.h"
27 #include "JSNode.h"
28 #include "QualifiedName.h"
29 #include "ScriptController.h"
30 #include <JavaScriptCore/CatchScope.h>
31 #include <JavaScriptCore/FunctionConstructor.h>
32 #include <JavaScriptCore/IdentifierInlines.h>
33 #include <wtf/NeverDestroyed.h>
34 #include <wtf/RefCountedLeakCounter.h>
35 #include <wtf/StdLibExtras.h>
36 #include <wtf/WeakPtr.h>
37
38 namespace WebCore {
39 using namespace JSC;
40
41 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, eventListenerCounter, ("JSLazyEventListener"));
42
43 struct JSLazyEventListener::CreationArguments {
44     const QualifiedName& attributeName;
45     const AtomString& attributeValue;
46     Document& document;
47     WeakPtr<ContainerNode> node;
48     JSObject* wrapper;
49     bool shouldUseSVGEventName;
50 };
51
52 static const String& eventParameterName(bool shouldUseSVGEventName)
53 {
54     static NeverDestroyed<const String> eventString(MAKE_STATIC_STRING_IMPL("event"));
55     static NeverDestroyed<const String> evtString(MAKE_STATIC_STRING_IMPL("evt"));
56     return shouldUseSVGEventName ? evtString : eventString;
57 }
58
59 static TextPosition convertZeroToOne(const TextPosition& position)
60 {
61     // A JSLazyEventListener can be created with a line number of zero when it is created with
62     // a setAttribute call from JavaScript, so make the line number 1 in that case.
63     if (position == TextPosition::belowRangePosition())
64         return { };
65     return position;
66 }
67
68 JSLazyEventListener::JSLazyEventListener(CreationArguments&& arguments, const String& sourceURL, const TextPosition& sourcePosition)
69     : JSEventListener(nullptr, arguments.wrapper, true, mainThreadNormalWorld())
70     , m_functionName(arguments.attributeName.localName().string())
71     , m_eventParameterName(eventParameterName(arguments.shouldUseSVGEventName))
72     , m_code(arguments.attributeValue)
73     , m_sourceURL(sourceURL)
74     , m_sourcePosition(convertZeroToOne(sourcePosition))
75     , m_originalNode(WTFMove(arguments.node))
76 {
77 #ifndef NDEBUG
78     eventListenerCounter.increment();
79 #endif
80 }
81
82 #if !ASSERT_DISABLED
83 // This is to help find the underlying cause of <rdar://problem/24314027>.
84 void JSLazyEventListener::checkValidityForEventTarget(EventTarget& eventTarget)
85 {
86     if (eventTarget.isNode()) {
87         ASSERT(m_originalNode);
88         ASSERT(static_cast<EventTarget*>(m_originalNode.get()) == &eventTarget);
89     } else
90         ASSERT(!m_originalNode);
91 }
92 #endif
93
94 JSLazyEventListener::~JSLazyEventListener()
95 {
96 #ifndef NDEBUG
97     eventListenerCounter.decrement();
98 #endif
99 }
100
101 JSObject* JSLazyEventListener::initializeJSFunction(ScriptExecutionContext& executionContext) const
102 {
103     ASSERT(is<Document>(executionContext));
104
105     auto& executionContextDocument = downcast<Document>(executionContext);
106
107     // As per the HTML specification [1], if this is an element's event handler, then document should be the
108     // element's document. The script execution context may be different from the node's document if the
109     // node's document was created by JavaScript.
110     // [1] https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler
111     auto& document = m_originalNode ? m_originalNode->document() : executionContextDocument;
112     if (!document.frame())
113         return nullptr;
114
115     if (!document.contentSecurityPolicy()->allowInlineEventHandlers(m_sourceURL, m_sourcePosition.m_line))
116         return nullptr;
117
118     auto& script = document.frame()->script();
119     if (!script.canExecuteScripts(AboutToCreateEventListener) || script.isPaused())
120         return nullptr;
121
122     if (!executionContextDocument.frame())
123         return nullptr;
124     auto* globalObject = toJSDOMWindow(*executionContextDocument.frame(), isolatedWorld());
125     if (!globalObject)
126         return nullptr;
127
128     VM& vm = globalObject->vm();
129     JSLockHolder lock(vm);
130     auto scope = DECLARE_CATCH_SCOPE(vm);
131     ExecState* exec = globalObject->globalExec();
132
133     MarkedArgumentBuffer args;
134     args.append(jsNontrivialString(exec, m_eventParameterName));
135     args.append(jsStringWithCache(exec, m_code));
136     ASSERT(!args.hasOverflowed());
137
138     // We want all errors to refer back to the line on which our attribute was
139     // declared, regardless of any newlines in our JavaScript source text.
140     int overrideLineNumber = m_sourcePosition.m_line.oneBasedInt();
141
142     JSObject* jsFunction = constructFunctionSkippingEvalEnabledCheck(exec,
143         exec->lexicalGlobalObject(), args, Identifier::fromString(exec, m_functionName),
144         SourceOrigin { m_sourceURL, CachedScriptFetcher::create(document.charset()) },
145         m_sourceURL, m_sourcePosition, overrideLineNumber);
146     if (UNLIKELY(scope.exception())) {
147         reportCurrentException(exec);
148         scope.clearException();
149         return nullptr;
150     }
151
152     JSFunction* listenerAsFunction = jsCast<JSFunction*>(jsFunction);
153
154     if (m_originalNode) {
155         if (!wrapper()) {
156             // Ensure that 'node' has a JavaScript wrapper to mark the event listener we're creating.
157             // FIXME: Should pass the global object associated with the node
158             setWrapper(vm, asObject(toJS(exec, globalObject, *m_originalNode)));
159         }
160
161         // Add the event's home element to the scope
162         // (and the document, and the form - see JSHTMLElement::eventHandlerScope)
163         listenerAsFunction->setScope(vm, jsCast<JSNode*>(wrapper())->pushEventHandlerScope(exec, listenerAsFunction->scope()));
164     }
165
166     return jsFunction;
167 }
168
169 RefPtr<JSLazyEventListener> JSLazyEventListener::create(CreationArguments&& arguments)
170 {
171     if (arguments.attributeValue.isNull())
172         return nullptr;
173
174     // FIXME: We should be able to provide source information for frameless documents too (e.g. for importing nodes from XMLHttpRequest.responseXML).
175     TextPosition position;
176     String sourceURL;
177     if (Frame* frame = arguments.document.frame()) {
178         if (!frame->script().canExecuteScripts(AboutToCreateEventListener))
179             return nullptr;
180         position = frame->script().eventHandlerPosition();
181         sourceURL = arguments.document.url().string();
182     }
183
184     return adoptRef(*new JSLazyEventListener(WTFMove(arguments), sourceURL, position));
185 }
186
187 RefPtr<JSLazyEventListener> JSLazyEventListener::create(Element& element, const QualifiedName& attributeName, const AtomString& attributeValue)
188 {
189     return create({ attributeName, attributeValue, element.document(), makeWeakPtr(element), nullptr, element.isSVGElement() });
190 }
191
192 RefPtr<JSLazyEventListener> JSLazyEventListener::create(Document& document, const QualifiedName& attributeName, const AtomString& attributeValue)
193 {
194     // FIXME: This always passes false for "shouldUseSVGEventName". Is that correct for events dispatched to SVG documents?
195     // This has been this way for a long time, but became more obvious when refactoring to separate the Element and Document code paths.
196     return create({ attributeName, attributeValue, document, makeWeakPtr(document), nullptr, false });
197 }
198
199 RefPtr<JSLazyEventListener> JSLazyEventListener::create(DOMWindow& window, const QualifiedName& attributeName, const AtomString& attributeValue)
200 {
201     ASSERT(window.document());
202     auto& document = *window.document();
203     ASSERT(document.frame());
204     return create({ attributeName, attributeValue, document, nullptr, toJSDOMWindow(document.frame(), mainThreadNormalWorld()), document.isSVGDocument() });
205 }
206
207 } // namespace WebCore