/*
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 *           (C) 2001 Dirk Mueller (mueller@kde.org)
 * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
 * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
 *           (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
 */

#include "config.h"
#include "EventTarget.h"

#include "Event.h"
#include "EventException.h"
#include "InspectorInstrumentation.h"
#include "JSEventListener.h"
#include "NoEventDispatchAssertion.h"
#include "WebKitAnimationEvent.h"
#include "WebKitTransitionEvent.h"
#include <wtf/MainThread.h>
#include <wtf/StdLibExtras.h>
#include <wtf/TemporaryChange.h>
#include <wtf/Vector.h>

using namespace WTF;

namespace WebCore {

#ifndef NDEBUG
static int gEventDispatchForbidden = 0;

void forbidEventDispatch()
{
    if (!isMainThread())
        return;
    ++gEventDispatchForbidden;
}

void allowEventDispatch()
{
    if (!isMainThread())
        return;
    if (gEventDispatchForbidden > 0)
        --gEventDispatchForbidden;
}

bool eventDispatchForbidden()
{
    if (!isMainThread())
        return false;
    return gEventDispatchForbidden > 0;
}
#endif // NDEBUG

EventTargetData::EventTargetData()
	: isFiringEventListeners(false)
{
}

EventTargetData::~EventTargetData()
{
}

EventTarget::~EventTarget()
{
}

Node* EventTarget::toNode()
{
    return 0;
}

DOMWindow* EventTarget::toDOMWindow()
{
    return 0;
}

bool EventTarget::isMessagePort() const
{
    return false;
}

void EventTarget::addEventListenerForBindings(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture)
{
	addEventListener(eventType, listener, useCapture);
}

void EventTarget::removeEventListenerForBindings(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture)
{
	removeEventListener(eventType, listener.get(), useCapture);
}

void EventTarget::addEventListenerForBindings(const AtomicString& eventType, PassRefPtr<EventListener> listener, const AddEventListenerOptions& options)
{
	addEventListener(eventType, listener, options.capture);
}

void EventTarget::removeEventListenerForBindings(const AtomicString& eventType, PassRefPtr<EventListener> listener, const EventListenerOptions& options)
{
	removeEventListener(eventType, listener.get(), options.capture);
}

bool EventTarget::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture)
{
    EventTargetData* d = ensureEventTargetData();
    return d->eventListenerMap.add(eventType, listener, useCapture);
}

bool EventTarget::removeEventListener(const AtomicString& eventType, EventListener* listener, bool useCapture)
{
    EventTargetData* d = eventTargetData();
    if (!d)
        return false;

    return d->eventListenerMap.remove(eventType, listener, useCapture);
}

bool EventTarget::setAttributeEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener, DOMWrapperWorld* isolatedWorld)
{
    EventListener* existingListener = attributeEventListener(eventType, isolatedWorld);
    if (!listener) {
        if (existingListener)
            removeEventListener(eventType, existingListener, false);
        return false;
    }
    if (existingListener) {
        eventTargetData()->eventListenerMap.replace(eventType, *existingListener, listener, RegisteredEventListener::Options());
        return true;
    }
    return addEventListener(eventType, listener, false);
}

EventListener* EventTarget::attributeEventListener(const AtomicString& eventType, DOMWrapperWorld* isolatedWorld)
{
    const EventListenerVector& entry = getEventListeners(eventType);
    for (size_t i = 0; i < entry.size(); ++i) {
    	EventListener& listener = entry[i]->listener();
        if (!listener.isAttribute())
            continue;

        auto* listenerWorld = static_cast<JSEventListener&>(listener).isolatedWorld();
        if (listenerWorld == isolatedWorld)
            return &listener;
    }

    return 0;
}

bool EventTarget::dispatchEventForBindings(PassRefPtr<Event> event, ExceptionCode& ec)
{
    if (!event) {
        ec = TypeError;
        return false;
    }

    if (!event->isInitialized() || event->isBeingDispatched()) {
        ec = INVALID_STATE_ERR;
        return false;
    }

    event->setUntrusted();

    if (event->isBeingDispatched()) {
        ec = EventException::DISPATCH_REQUEST_ERR;
        return false;
    }

    if (!scriptExecutionContext())
        return false;

    return dispatchEvent(event);
}

bool EventTarget::dispatchEvent(PassRefPtr<Event> event)
{
#if ENABLE(JS_EXECUTION_TIMING)
    double fireTime = WTF::monotonicallyIncreasingTime();
#endif
    event->setTarget(this);
    event->setCurrentTarget(this);
    event->setEventPhase(Event::AT_TARGET);
    bool defaultPrevented = fireEventListeners(event.get());
    event->resetPropagationFlags();
    event->setEventPhase(Event::NONE);
#if ENABLE(JS_EXECUTION_TIMING)
    double now = WTF::monotonicallyIncreasingTime();
    WTF::logMessage(String("EventTarget::dispatchEvent(") + event->type() + String(") takes ") + String::number((int)((now - fireTime) * 1000)) + String("ms"));
#endif
    return defaultPrevented;
}

void EventTarget::uncaughtExceptionInEventHandler()
{
}

static const AtomicString& legacyType(const Event* event)
{
	EventNames& eventNames = WebCore::eventNames();
    if (event->type() == eventNames.animationendEvent)
        return eventNames.webkitAnimationEndEvent;

    if (event->type() == eventNames.animationstartEvent)
        return eventNames.webkitAnimationStartEvent;

    if (event->type() == eventNames.animationiterationEvent)
        return eventNames.webkitAnimationIterationEvent;

    if (event->type() == eventNames.transitionendEvent)
        return eventNames.webkitTransitionEndEvent;

    return emptyAtom;
}

bool EventTarget::fireEventListeners(Event* event)
{
	ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread());
	ASSERT(event && event->isInitialized());

    EventTargetData* data = eventTargetData();
    if (!data)
        return true;

    TemporaryChange<bool> firingEventListenersScope(data->isFiringEventListeners, true);

    if (auto* listenersVector = data->eventListenerMap.find(event->type())) {
        fireEventListeners(*event, *listenersVector);
        return !event->defaultPrevented();
    }

    const AtomicString& legacyTypeName = legacyType(event);
    if (!legacyTypeName.isEmpty()) {
        if (auto* legacyListenersVector = data->eventListenerMap.find(legacyTypeName)) {
			AtomicString typeName = event->type();
			event->setType(legacyTypeName);
			fireEventListeners(*event, *legacyListenersVector);
			event->setType(typeName);
        }
    }
    return !event->defaultPrevented();
}

// Intentionally creates a copy of the listeners vector to avoid event listeners added after this point from being run.
// Note that removal still has an effect due to the removed field in RegisteredEventListener.
void EventTarget::fireEventListeners(Event& event, EventListenerVector listeners)
{
    RefPtr<EventTarget> protect = this;
    ASSERT(!listeners.isEmpty());

    ScriptExecutionContext* context = scriptExecutionContext();

    for (int i = 0; i < listeners.size(); ++i) {
    	auto& registeredListener = listeners[i];
        if (UNLIKELY(registeredListener->wasRemoved()))
            continue;

        if (event.eventPhase() == Event::CAPTURING_PHASE && !registeredListener->useCapture())
            continue;
        if (event.eventPhase() == Event::BUBBLING_PHASE && registeredListener->useCapture())
            continue;

        // If stopImmediatePropagation has been called, we just break out immediately, without
        // handling any more events on this target.
        if (event.immediatePropagationStopped())
            break;

        InspectorInstrumentationCookie cookie = InspectorInstrumentation::willHandleEvent(context, &event);
        // To match Mozilla, the AT_TARGET phase fires both capturing and bubbling
        // event listeners, even though that violates some versions of the DOM spec.
        registeredListener->listener().handleEvent(context, &event);
        InspectorInstrumentation::didHandleEvent(cookie);
    }
}

const EventListenerVector& EventTarget::getEventListeners(const AtomicString& eventType)
{
    DEFINE_STATIC_LOCAL(EventListenerVector, emptyVector, ());

    EventTargetData* d = eventTargetData();
    if (!d)
        return emptyVector;

    EventListenerVector* listenerVector = d->eventListenerMap.find(eventType);
    if (!listenerVector)
        return emptyVector;

    return *listenerVector;
}

void EventTarget::removeAllEventListeners()
{
    EventTargetData* d = eventTargetData();
    if (!d)
        return;
    d->eventListenerMap.clear();
}

} // namespace WebCore
