NeverDestroyed<String>(ASCIILiteral(...)) is not thread safe.
[WebKit-https.git] / Source / WebCore / bindings / js / JSLazyEventListener.cpp
index f11bf90..9e051a9 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
- *  Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All Rights Reserved.
+ *  Copyright (C) 2003-2017 Apple Inc. All Rights Reserved.
  *
  *  This library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public
 #include "config.h"
 #include "JSLazyEventListener.h"
 
+#include "CachedScriptFetcher.h"
 #include "ContentSecurityPolicy.h"
 #include "Frame.h"
 #include "JSNode.h"
+#include "ScriptController.h"
 #include <runtime/FunctionConstructor.h>
-#include <runtime/JSFunction.h>
-#include <runtime/JSLock.h>
+#include <runtime/IdentifierInlines.h>
+#include <wtf/NeverDestroyed.h>
 #include <wtf/RefCountedLeakCounter.h>
 #include <wtf/StdLibExtras.h>
-#include <wtf/text/TextPosition.h>
 
 using namespace JSC;
 
@@ -36,13 +37,13 @@ namespace WebCore {
 
 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, eventListenerCounter, ("JSLazyEventListener"));
 
-JSLazyEventListener::JSLazyEventListener(const String& functionName, const String& eventParameterName, const String& code, Node* node, const String& sourceURL, const TextPosition& position, JSObject* wrapper, DOMWrapperWorld* isolatedWorld)
+JSLazyEventListener::JSLazyEventListener(const String& functionName, const String& eventParameterName, const String& code, ContainerNode* node, const String& sourceURL, const TextPosition& sourcePosition, JSObject* wrapper, DOMWrapperWorld& isolatedWorld)
     : JSEventListener(0, wrapper, true, isolatedWorld)
     , m_functionName(functionName)
     , m_eventParameterName(eventParameterName)
     , m_code(code)
     , m_sourceURL(sourceURL)
-    , m_position(position)
+    , m_sourcePosition(sourcePosition)
     , m_originalNode(node)
 {
     // We don't retain the original node because we assume it
@@ -53,8 +54,10 @@ JSLazyEventListener::JSLazyEventListener(const String& functionName, const Strin
 
     // A JSLazyEventListener can be created with a line number of zero when it is created with
     // a setAttribute call from JavaScript, so make the line number 1 in that case.
-    if (m_position == TextPosition::belowRangePosition())
-        m_position = TextPosition::minimumPosition();
+    if (m_sourcePosition == TextPosition::belowRangePosition())
+        m_sourcePosition = TextPosition();
+
+    ASSERT(m_eventParameterName == "evt" || m_eventParameterName == "event");
 
 #ifndef NDEBUG
     eventListenerCounter.increment();
@@ -70,60 +73,124 @@ JSLazyEventListener::~JSLazyEventListener()
 
 JSObject* JSLazyEventListener::initializeJSFunction(ScriptExecutionContext* executionContext) const
 {
-    ASSERT(executionContext);
-    ASSERT(executionContext->isDocument());
+    ASSERT(is<Document>(executionContext));
     if (!executionContext)
-        return 0;
+        return nullptr;
+
+    ASSERT(!m_code.isNull());
+    ASSERT(!m_eventParameterName.isNull());
+    if (m_code.isNull() || m_eventParameterName.isNull())
+        return nullptr;
 
-    Document* document = static_cast<Document*>(executionContext);
+    Document& document = downcast<Document>(*executionContext);
 
-    if (!document->frame())
-        return 0;
+    if (!document.frame())
+        return nullptr;
 
-    if (!document->contentSecurityPolicy()->allowInlineEventHandlers())
-        return 0;
+    if (!document.contentSecurityPolicy()->allowInlineEventHandlers(m_sourceURL, m_sourcePosition.m_line))
+        return nullptr;
 
-    ScriptController* script = document->frame()->script();
-    if (!script->canExecuteScripts(AboutToExecuteScript) || script->isPaused())
-        return 0;
+    ScriptController& script = document.frame()->script();
+    if (!script.canExecuteScripts(AboutToExecuteScript) || script.isPaused())
+        return nullptr;
 
     JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(executionContext, isolatedWorld());
     if (!globalObject)
-        return 0;
+        return nullptr;
 
+    VM& vm = globalObject->vm();
+    JSLockHolder lock(vm);
+    auto scope = DECLARE_CATCH_SCOPE(vm);
     ExecState* exec = globalObject->globalExec();
 
     MarkedArgumentBuffer args;
-    args.append(jsNontrivialString(exec, stringToUString(m_eventParameterName)));
-    args.append(jsString(exec, m_code));
+    args.append(jsNontrivialString(exec, m_eventParameterName));
+    args.append(jsStringWithCache(exec, m_code));
+
+    // We want all errors to refer back to the line on which our attribute was
+    // declared, regardless of any newlines in our JavaScript source text.
+    int overrideLineNumber = m_sourcePosition.m_line.oneBasedInt();
 
-    JSObject* jsFunction = constructFunctionSkippingEvalEnabledCheck(exec, exec->lexicalGlobalObject(), args, Identifier(exec, stringToUString(m_functionName)), stringToUString(m_sourceURL), m_position); // FIXME: is globalExec ok?
-    if (exec->hadException()) {
+    JSObject* jsFunction = constructFunctionSkippingEvalEnabledCheck(
+        exec, exec->lexicalGlobalObject(), args, Identifier::fromString(exec, m_functionName),
+        SourceOrigin { m_sourceURL, CachedScriptFetcher::create(document.charset()) }, m_sourceURL, m_sourcePosition, overrideLineNumber);
+
+    if (UNLIKELY(scope.exception())) {
         reportCurrentException(exec);
-        exec->clearException();
-        return 0;
+        scope.clearException();
+        return nullptr;
     }
 
     JSFunction* listenerAsFunction = jsCast<JSFunction*>(jsFunction);
+
     if (m_originalNode) {
         if (!wrapper()) {
             // Ensure that 'node' has a JavaScript wrapper to mark the event listener we're creating.
-            JSLock lock(SilenceAssertionsOnly);
             // FIXME: Should pass the global object associated with the node
-            setWrapper(exec->globalData(), asObject(toJS(exec, globalObject, m_originalNode)));
+            setWrapper(vm, asObject(toJS(exec, globalObject, *m_originalNode)));
         }
 
         // Add the event's home element to the scope
         // (and the document, and the form - see JSHTMLElement::eventHandlerScope)
-        listenerAsFunction->setScope(exec->globalData(), jsCast<JSNode*>(wrapper())->pushEventHandlerScope(exec, listenerAsFunction->scope()));
+        listenerAsFunction->setScope(vm, jsCast<JSNode*>(wrapper())->pushEventHandlerScope(exec, listenerAsFunction->scope()));
     }
-
-    // Since we only parse once, there's no need to keep data used for parsing around anymore.
-    m_functionName = String();
-    m_code = String();
-    m_eventParameterName = String();
-    m_sourceURL = String();
     return jsFunction;
 }
 
+static const String& eventParameterName(bool isSVGEvent)
+{
+    static NeverDestroyed<const String> eventString(MAKE_STATIC_STRING_IMPL("event"));
+    static NeverDestroyed<const String> evtString(MAKE_STATIC_STRING_IMPL("evt"));
+    return isSVGEvent ? evtString : eventString;
+}
+
+struct JSLazyEventListener::CreationArguments {
+    const QualifiedName& attributeName;
+    const AtomicString& attributeValue;
+    Document& document;
+    ContainerNode* node;
+    JSC::JSObject* wrapper;
+    bool shouldUseSVGEventName;
+};
+
+RefPtr<JSLazyEventListener> JSLazyEventListener::create(const CreationArguments& arguments)
+{
+    if (arguments.attributeValue.isNull())
+        return nullptr;
+
+    // FIXME: We should be able to provide source information for frameless documents too (e.g. for importing nodes from XMLHttpRequest.responseXML).
+    TextPosition position;
+    String sourceURL;
+    if (Frame* frame = arguments.document.frame()) {
+        if (!frame->script().canExecuteScripts(AboutToExecuteScript))
+            return nullptr;
+        position = frame->script().eventHandlerPosition();
+        sourceURL = arguments.document.url().string();
+    }
+
+    return adoptRef(*new JSLazyEventListener(arguments.attributeName.localName().string(),
+        eventParameterName(arguments.shouldUseSVGEventName), arguments.attributeValue,
+        arguments.node, sourceURL, position, arguments.wrapper, mainThreadNormalWorld()));
+}
+
+RefPtr<JSLazyEventListener> JSLazyEventListener::create(Element& element, const QualifiedName& attributeName, const AtomicString& attributeValue)
+{
+    return create({ attributeName, attributeValue, element.document(), &element, nullptr, element.isSVGElement() });
+}
+
+RefPtr<JSLazyEventListener> JSLazyEventListener::create(Document& document, const QualifiedName& attributeName, const AtomicString& attributeValue)
+{
+    // FIXME: This always passes false for "shouldUseSVGEventName". Is that correct for events dispatched to SVG documents?
+    // This has been this way for a long time, but became more obvious when refactoring to separate the Element and Document code paths.
+    return create({ attributeName, attributeValue, document, &document, nullptr, false });
+}
+
+RefPtr<JSLazyEventListener> JSLazyEventListener::create(DOMWindow& window, const QualifiedName& attributeName, const AtomicString& attributeValue)
+{
+    ASSERT(window.document());
+    auto& document = *window.document();
+    ASSERT(document.frame());
+    return create({ attributeName, attributeValue, document, nullptr, toJSDOMWindow(document.frame(), mainThreadNormalWorld()), document.isSVGDocument() });
+}
+
 } // namespace WebCore