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
33using namespace JSC;
34
35namespace WebCore {
36
37DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, eventListenerCounter, ("JSLazyEventListener"));
38
39JSLazyEventListener::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
66JSLazyEventListener::~JSLazyEventListener()
67{
68#ifndef NDEBUG
69    eventListenerCounter.decrement();
70#endif
71}
72
73JSObject* JSLazyEventListener::initializeJSFunction(ScriptExecutionContext* executionContext) const
74{
75    ASSERT(executionContext);
76    ASSERT(executionContext->isDocument());
77    if (!executionContext)
78        return 0;
79
80    ASSERT(!m_code.isNull());
81    ASSERT(!m_eventParameterName.isNull());
82    if (m_code.isNull() || m_eventParameterName.isNull())
83        return 0;
84
85    Document* document = toDocument(executionContext);
86
87    if (!document->frame())
88        return 0;
89
90    if (!document->contentSecurityPolicy()->allowInlineEventHandlers(m_sourceURL, m_position.m_line))
91        return 0;
92
93    ScriptController& script = document->frame()->script();
94    if (!script.canExecuteScripts(AboutToExecuteScript) || script.isPaused())
95        return 0;
96
97    JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(executionContext, isolatedWorld());
98    if (!globalObject)
99        return 0;
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 0;
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
130static 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
137PassRefPtr<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
159PassRefPtr<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
174