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