Use is<>() / downcast<>() for Document
[WebKit-https.git] / Source / WebCore / bindings / js / JSLazyEventListener.cpp
1 /*
2  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
3  *  Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2013 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 "ContentSecurityPolicy.h"
24 #include "Frame.h"
25 #include "JSNode.h"
26 #include "ScriptController.h"
27 #include <runtime/FunctionConstructor.h>
28 #include <runtime/IdentifierInlines.h>
29 #include <wtf/NeverDestroyed.h>
30 #include <wtf/RefCountedLeakCounter.h>
31 #include <wtf/StdLibExtras.h>
32
33 using namespace JSC;
34
35 namespace WebCore {
36
37 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, eventListenerCounter, ("JSLazyEventListener"));
38
39 JSLazyEventListener::JSLazyEventListener(const String& functionName, const String& eventParameterName, const String& code, ContainerNode* node, const String& sourceURL, const TextPosition& position, JSObject* wrapper, DOMWrapperWorld& isolatedWorld)
40     : JSEventListener(0, wrapper, true, isolatedWorld)
41     , m_functionName(functionName)
42     , m_eventParameterName(eventParameterName)
43     , m_code(code)
44     , m_sourceURL(sourceURL)
45     , m_position(position)
46     , m_originalNode(node)
47 {
48     // We don't retain the original node because we assume it
49     // will stay alive as long as this handler object is around
50     // and we need to avoid a reference cycle. If JS transfers
51     // this handler to another node, initializeJSFunction will
52     // be called and then originalNode is no longer needed.
53
54     // A JSLazyEventListener can be created with a line number of zero when it is created with
55     // a setAttribute call from JavaScript, so make the line number 1 in that case.
56     if (m_position == TextPosition::belowRangePosition())
57         m_position = TextPosition::minimumPosition();
58
59     ASSERT(m_eventParameterName == "evt" || m_eventParameterName == "event");
60
61 #ifndef NDEBUG
62     eventListenerCounter.increment();
63 #endif
64 }
65
66 JSLazyEventListener::~JSLazyEventListener()
67 {
68 #ifndef NDEBUG
69     eventListenerCounter.decrement();
70 #endif
71 }
72
73 JSObject* JSLazyEventListener::initializeJSFunction(ScriptExecutionContext* executionContext) const
74 {
75     ASSERT(executionContext);
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_position.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     ExecState* exec = globalObject->globalExec();
102
103     MarkedArgumentBuffer args;
104     args.append(jsNontrivialString(exec, m_eventParameterName));
105     args.append(jsStringWithCache(exec, m_code));
106
107     JSObject* jsFunction = constructFunctionSkippingEvalEnabledCheck(exec, exec->lexicalGlobalObject(), args, Identifier(exec, m_functionName), m_sourceURL, m_position); // FIXME: is globalExec ok?
108     if (exec->hadException()) {
109         reportCurrentException(exec);
110         exec->clearException();
111         return nullptr;
112     }
113
114     JSFunction* listenerAsFunction = jsCast<JSFunction*>(jsFunction);
115     if (m_originalNode) {
116         if (!wrapper()) {
117             // Ensure that 'node' has a JavaScript wrapper to mark the event listener we're creating.
118             JSLockHolder lock(exec);
119             // FIXME: Should pass the global object associated with the node
120             setWrapper(exec->vm(), asObject(toJS(exec, globalObject, m_originalNode)));
121         }
122
123         // Add the event's home element to the scope
124         // (and the document, and the form - see JSHTMLElement::eventHandlerScope)
125         listenerAsFunction->setScope(exec->vm(), jsCast<JSNode*>(wrapper())->pushEventHandlerScope(exec, listenerAsFunction->scope()));
126     }
127     return jsFunction;
128 }
129
130 static const String& eventParameterName(bool isSVGEvent)
131 {
132     static NeverDestroyed<const String> eventString(ASCIILiteral("event"));
133     static NeverDestroyed<const String> evtString(ASCIILiteral("evt"));
134     return isSVGEvent ? evtString : eventString;
135 }
136
137 PassRefPtr<JSLazyEventListener> JSLazyEventListener::createForNode(ContainerNode& node, const QualifiedName& attributeName, const AtomicString& attributeValue)
138 {
139     if (attributeValue.isNull())
140         return nullptr;
141
142     TextPosition position = TextPosition::minimumPosition();
143     String sourceURL;
144
145     // FIXME: We should be able to provide source information for frameless documents too (e.g. for importing nodes from XMLHttpRequest.responseXML).
146     if (Frame* frame = node.document().frame()) {
147         if (!frame->script().canExecuteScripts(AboutToExecuteScript))
148             return nullptr;
149
150         position = frame->script().eventHandlerPosition();
151         sourceURL = node.document().url().string();
152     }
153
154     return adoptRef(new JSLazyEventListener(attributeName.localName().string(),
155         eventParameterName(node.isSVGElement()), attributeValue,
156         &node, sourceURL, position, nullptr, mainThreadNormalWorld()));
157 }
158
159 PassRefPtr<JSLazyEventListener> JSLazyEventListener::createForDOMWindow(Frame& frame, const QualifiedName& attributeName, const AtomicString& attributeValue)
160 {
161     if (attributeValue.isNull())
162         return nullptr;
163
164     if (!frame.script().canExecuteScripts(AboutToExecuteScript))
165         return nullptr;
166
167     return adoptRef(new JSLazyEventListener(attributeName.localName().string(),
168         eventParameterName(frame.document()->isSVGDocument()), attributeValue,
169         nullptr, frame.document()->url().string(), frame.script().eventHandlerPosition(),
170         toJSDOMWindow(&frame, mainThreadNormalWorld()), mainThreadNormalWorld()));
171 }
172
173 } // namespace WebCore