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