/*
 * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved.
 *
 * 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"
#if !HAVE(BINDINGS_ALL_IN_ONE)
#include "JSDOMGlobalObject.h"

#include "Document.h"
#include "Frame.h"
#include "JSAbortAlgorithm.h"
#include "JSAbortSignal.h"
#include "JSDOMPromiseDeferred.h"
#include "JSDOMWindow.h"
#include "JSEventListener.h"
#include "JSMediaStream.h"
#include "JSMediaStreamTrack.h"
#include "JSRTCIceCandidate.h"
#include "JSRTCSessionDescription.h"
#include "JSReadableStream.h"
#include "JSReadableStreamPrivateConstructors.h"
#include "RejectedPromiseTracker.h"
#include "StructuredClone.h"
#include "WebCoreJSClientData.h"
#include <builtins/BuiltinNames.h>
#include <runtime/JSInternalPromise.h>
#include <runtime/JSInternalPromiseDeferred.h>

#if ENABLE(WORKERS)
#include "JSWorkerGlobalScope.h"
#include "WorkerGlobalScope.h"
#endif

using namespace JSC;

namespace WebCore {

EncodedJSValue JSC_HOST_CALL makeThisTypeErrorForBuiltins(ExecState*);
EncodedJSValue JSC_HOST_CALL makeGetterTypeErrorForBuiltins(ExecState*);
EncodedJSValue JSC_HOST_CALL makeDOMExceptionForBuiltins(ExecState*);
EncodedJSValue JSC_HOST_CALL whenSignalAborted(ExecState*);
EncodedJSValue JSC_HOST_CALL isAbortSignal(ExecState*);

const ClassInfo JSDOMGlobalObject::s_info = { "DOMGlobalObject", &JSGlobalObject::s_info, nullptr, CREATE_METHOD_TABLE(JSDOMGlobalObject) };

JSDOMGlobalObject::JSDOMGlobalObject(JSGlobalData& vm, Structure* structure, PassRefPtr<DOMWrapperWorld> world, const GlobalObjectMethodTable* globalObjectMethodTable)
    : JSGlobalObject(vm, structure, globalObjectMethodTable)
    , m_world(world)
	, m_worldIsNormal(m_world->isNormal())
	, m_builtinInternalFunctions(vm)
	, m_builtinFunctions(vm)
{
}

JSDOMGlobalObject::~JSDOMGlobalObject() = default;

void JSDOMGlobalObject::destroy(JSCell* cell)
{
    static_cast<JSDOMGlobalObject*>(cell)->JSDOMGlobalObject::~JSDOMGlobalObject();
}

EncodedJSValue JSC_HOST_CALL makeThisTypeErrorForBuiltins(ExecState* execState)
{
    ASSERT(execState);
    ASSERT(execState->argumentCount() == 2);

    String interfaceName = execState->uncheckedArgument(0).getString(execState);
    ASSERT(!execState->hadException());
    String functionName = execState->uncheckedArgument(1).getString(execState);
    ASSERT(!execState->hadException());
    return JSValue::encode(createTypeError(execState, makeThisTypeErrorMessage(interfaceName.utf8().data(), functionName.utf8().data())));
}

EncodedJSValue JSC_HOST_CALL makeGetterTypeErrorForBuiltins(ExecState* execState)
{
    ASSERT(execState);
    ASSERT(execState->argumentCount() == 2);

    String interfaceName = execState->uncheckedArgument(0).getString(execState);
    ASSERT(!execState->hadException());
    String attributeName = execState->uncheckedArgument(1).getString(execState);
    ASSERT(!execState->hadException());
    return JSValue::encode(createTypeError(execState, makeGetterTypeErrorMessage(interfaceName.utf8().data(), attributeName.utf8().data())));
}

EncodedJSValue JSC_HOST_CALL makeDOMExceptionForBuiltins(ExecState* execState)
{
    ASSERT(execState);
    ASSERT(execState->argumentCount() == 2);

    auto& vm = execState->vm();
    auto scope = DECLARE_CATCH_SCOPE(vm);

    auto codeValue = execState->uncheckedArgument(0).getString(execState);
    scope.assertNoException();

    auto message = execState->uncheckedArgument(1).getString(execState);
    scope.assertNoException();

    ExceptionCode code = TypeError;
    if (codeValue == "AbortError")
        code = ABORT_ERR;
    auto value = createDOMException(execState, code, message);

    EXCEPTION_ASSERT(!scope.exception() || isTerminatedExecutionException(vm, scope.exception()));

    return JSValue::encode(value);
}

EncodedJSValue JSC_HOST_CALL whenSignalAborted(ExecState* execState)
{
    ASSERT(execState);
    ASSERT(execState->argumentCount() == 2);

    auto& vm = execState->vm();
    auto* abortSignal = jsDynamicCast<JSAbortSignal*>(execState->uncheckedArgument(0));
    if (UNLIKELY(!abortSignal))
        return JSValue::encode(JSValue(JSC::JSValue::JSFalse));

    auto* jsDOMGlobalObject = JSC::jsCast<JSDOMGlobalObject*>(execState->lexicalGlobalObject());
    RefPtr<AbortAlgorithm> abortAlgorithm = JSAbortAlgorithm::create(execState->uncheckedArgument(1).getObject(), jsDOMGlobalObject);

    bool result = AbortSignal::whenSignalAborted(abortSignal->wrapped(), abortAlgorithm.release());
    return JSValue::encode(result ? JSValue(JSC::JSValue::JSTrue) : JSValue(JSC::JSValue::JSFalse));
}

EncodedJSValue JSC_HOST_CALL isAbortSignal(ExecState* execState)
{
    ASSERT(execState->argumentCount() == 1);
    return JSValue::encode(jsBoolean(execState->uncheckedArgument(0).inherits<JSAbortSignal>(execState->vm())));
}

void JSDOMGlobalObject::addBuiltinGlobals(VM& vm)
{
    m_builtinInternalFunctions.initialize(*this, vm);

#if ENABLE(STREAMS_API)
	JSObject* privateReadableStreamDefaultControllerConstructor = createReadableStreamDefaultControllerPrivateConstructor(vm, *this);
	JSObject* privateReadableByteStreamControllerConstructor = createReadableByteStreamControllerPrivateConstructor(vm, *this);
	JSObject* privateReadableStreamBYOBRequestConstructor = createReadableStreamBYOBRequestPrivateConstructor(vm, *this);
	JSObject* privateReadableStreamDefaultReaderConstructor = createReadableStreamDefaultReaderPrivateConstructor(vm, *this);
	JSObject* privateReadableStreamBYOBReaderConstructor = createReadableStreamBYOBReaderPrivateConstructor(vm, *this);

	ASSERT(!constructors().get(privateReadableStreamDefaultControllerConstructor->info()).get());
	ASSERT(!constructors().get(privateReadableByteStreamControllerConstructor->info()).get());
	ASSERT(!constructors(NoLockingNecessary).get(privateReadableStreamBYOBRequestConstructor->info()).get());
	ASSERT(!constructors().get(privateReadableStreamDefaultReaderConstructor->info()).get());
	ASSERT(!constructors(NoLockingNecessary).get(privateReadableStreamBYOBReaderConstructor->info()).get());
	JSC::WriteBarrier<JSC::JSObject> temp;
	{
		constructors().add(privateReadableStreamDefaultControllerConstructor->info(), temp).iterator->second.set(vm, this, privateReadableStreamDefaultControllerConstructor);
		constructors().add(privateReadableByteStreamControllerConstructor->info(), temp).iterator->second.set(vm, this, privateReadableByteStreamControllerConstructor);
		constructors().add(privateReadableStreamBYOBRequestConstructor->info(), temp).iterator->second.set(vm, this, privateReadableStreamBYOBRequestConstructor);
		constructors().add(privateReadableStreamDefaultReaderConstructor->info(), temp).iterator->second.set(vm, this, privateReadableStreamDefaultReaderConstructor);
		constructors().add(privateReadableStreamBYOBReaderConstructor->info(), temp).iterator->second.set(vm, this, privateReadableStreamBYOBReaderConstructor);
    }
#endif

    JSVMClientData& clientData = *static_cast<JSVMClientData*>(vm.clientData);
    {
		DeferGC deferGC(vm.heap);
		JSDOMGlobalObject::GlobalPropertyInfo staticGlobals[] = {
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().makeThisTypeErrorPrivateName(),
				JSFunction::create(vm, this, 2, String(), makeThisTypeErrorForBuiltins), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().makeGetterTypeErrorPrivateName(),
				JSFunction::create(vm, this, 2, String(), makeGetterTypeErrorForBuiltins), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().makeDOMExceptionPrivateName(),
				JSFunction::create(vm, this, 2, String(), makeDOMExceptionForBuiltins), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().whenSignalAbortedPrivateName(),
				JSFunction::create(vm, this, 2, String(), whenSignalAborted), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().cloneArrayBufferPrivateName(),
				JSFunction::create(vm, this, 3, String(), cloneArrayBuffer), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().structuredCloneForStreamPrivateName(),
				JSFunction::create(vm, this, 1, String(), structuredCloneForStream), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(vm.propertyNames->builtinNames().ArrayBufferPrivateName(), getDirect(vm, vm.propertyNames->ArrayBuffer), DontEnum | DontDelete | ReadOnly),
#if ENABLE(STREAMS_API)
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().streamClosedPrivateName(), jsNumber(1), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().streamClosingPrivateName(), jsNumber(2), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().streamErroredPrivateName(), jsNumber(3), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().streamReadablePrivateName(), jsNumber(4), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().streamWaitingPrivateName(), jsNumber(5), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().streamWritablePrivateName(), jsNumber(6), DontEnum | DontDelete | ReadOnly),

			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().ReadableStreamDefaultControllerPrivateName(), privateReadableStreamDefaultControllerConstructor, DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().ReadableByteStreamControllerPrivateName(), privateReadableByteStreamControllerConstructor, DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().ReadableStreamBYOBRequestPrivateName(), privateReadableStreamBYOBRequestConstructor, DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().ReadableStreamDefaultReaderPrivateName(), privateReadableStreamDefaultReaderConstructor, DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().ReadableStreamBYOBReaderPrivateName(), privateReadableStreamBYOBReaderConstructor, DontEnum | DontDelete | ReadOnly),
#endif
#if ENABLE(MEDIA_STREAM)
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().MediaStreamPrivateName(), JSMediaStream::getConstructor(vm, this), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().MediaStreamTrackPrivateName(), JSMediaStreamTrack::getConstructor(vm, this), DontEnum | DontDelete | ReadOnly),
#endif
#if ENABLE(WEB_RTC)
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().RTCIceCandidatePrivateName(), JSRTCIceCandidate::getConstructor(vm, this), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().RTCSessionDescriptionPrivateName(), JSRTCSessionDescription::getConstructor(vm, this), DontEnum | DontDelete | ReadOnly),
#endif

			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().AbortSignalPrivateName(), JSAbortSignal::getConstructor(vm, this), DontEnum | DontDelete | ReadOnly),
			JSDOMGlobalObject::GlobalPropertyInfo(clientData.builtinNames().isAbortSignalPrivateName(), JSFunction::create(vm, this, 1, String(), isAbortSignal), PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
		};
		addStaticGlobals(staticGlobals, WTF_ARRAY_LENGTH(staticGlobals));
    }
}

void JSDOMGlobalObject::finishCreation(JSGlobalData& vm)
{
    Base::finishCreation(vm);
    ASSERT(inherits(info()));

    addBuiltinGlobals(vm);
}

void JSDOMGlobalObject::finishCreation(JSGlobalData& vm, JSObject* thisValue)
{
    Base::finishCreation(vm, thisValue);
    ASSERT(inherits(info()));

    addBuiltinGlobals(vm);
}

ScriptExecutionContext* JSDOMGlobalObject::scriptExecutionContext() const
{
    if (inherits(JSDOMWindowBase::info()))
        return jsCast<const JSDOMWindowBase*>(this)->scriptExecutionContext();
#if ENABLE(WORKERS)
    if (inherits(JSWorkerGlobalScopeBase::info()))
        return jsCast<const JSWorkerGlobalScopeBase*>(this)->scriptExecutionContext();
#endif
    ASSERT_NOT_REACHED();
    return nullptr;
}

DLL_PUBLIC ScriptExecutionContext* scriptExecutionContext(JSC::JSGlobalObject* jsGlobalObject)
{
    if (jsGlobalObject->inherits(JSDOMWindowBase::info()))
        return jsCast<const JSDOMWindowBase*>(jsGlobalObject)->scriptExecutionContext();
#if ENABLE(WORKERS)
    if (jsGlobalObject->inherits(JSWorkerGlobalScopeBase::info()))
        return jsCast<const JSWorkerGlobalScopeBase*>(jsGlobalObject)->scriptExecutionContext();
#endif
    ASSERT_NOT_REACHED();
    return nullptr;
}

void JSDOMGlobalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
    JSDOMGlobalObject* thisObject = jsCast<JSDOMGlobalObject*>(cell);
    ASSERT_GC_OBJECT_INHERITS(thisObject, info());
    Base::visitChildren(thisObject, visitor);

    for (auto& structure : thisObject->m_structures.values())
        visitor.append(structure);

    for (auto& constructor : thisObject->m_constructors.values())
        visitor.append(constructor);

    for (auto& guarded : thisObject->m_guardedObjects)
    	guarded->visitAggregate(visitor);

    thisObject->m_builtinInternalFunctions.visit(visitor);
}

void JSDOMGlobalObject::promiseRejectionTracker(JSGlobalObject* jsGlobalObject, ExecState* exec, JSPromise* promise, JSPromiseRejectionOperation operation)
{
    // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation

    VM& vm = exec->vm();
    auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(jsGlobalObject);
    auto* context = globalObject.scriptExecutionContext();
    if (!context)
        return;

    // InternalPromises should not be exposed to user scripts.
    if (JSC::jsDynamicCast<JSC::JSInternalPromise*>(promise))
        return;

    // FIXME: If script has muted errors (cross origin), terminate these steps.
    // <https://webkit.org/b/171415> Implement the `muted-errors` property of Scripts to avoid onerror/onunhandledrejection for cross-origin scripts

    switch (operation) {
    case JSPromiseRejectionOperation::Reject:
        context->ensureRejectedPromiseTracker().promiseRejected(*exec, globalObject, *promise);
        break;
    case JSPromiseRejectionOperation::Handle:
        context->ensureRejectedPromiseTracker().promiseHandled(*exec, globalObject, *promise);
        break;
    }
}

JSDOMGlobalObject* toJSDOMGlobalObject(Document* document, JSC::ExecState* exec)
{
    return toJSDOMWindow(document->frame(), currentWorld(exec));
}

JSDOMGlobalObject* toJSDOMGlobalObject(ScriptExecutionContext* scriptExecutionContext, JSC::ExecState* exec)
{
    if (scriptExecutionContext->isDocument())
        return toJSDOMGlobalObject(static_cast<Document*>(scriptExecutionContext), exec);

#if ENABLE(WORKERS)
    if (scriptExecutionContext->isWorkerGlobalScope())
        return static_cast<WorkerGlobalScope*>(scriptExecutionContext)->script()->workerGlobalScopeWrapper();
#endif

    ASSERT_NOT_REACHED();
    return nullptr;
}

JSDOMGlobalObject* toJSDOMGlobalObject(Document* document, DOMWrapperWorld* world)
{
    return toJSDOMWindow(document->frame(), world);
}

JSDOMGlobalObject* toJSDOMGlobalObject(ScriptExecutionContext* scriptExecutionContext, DOMWrapperWorld* world)
{
    if (scriptExecutionContext->isDocument())
        return toJSDOMGlobalObject(static_cast<Document*>(scriptExecutionContext), world);

#if ENABLE(WORKERS)
    if (scriptExecutionContext->isWorkerGlobalScope())
        return static_cast<WorkerGlobalScope*>(scriptExecutionContext)->script()->workerGlobalScopeWrapper();
#endif

    ASSERT_NOT_REACHED();
    return nullptr;
}

JSDOMGlobalObject& callerGlobalObject(ExecState& state)
{
	class GetCallerGlobalObjectFunctor {
	public:
	    GetCallerGlobalObjectFunctor() = default;

	    StackVisitor::Status operator()(StackVisitor& visitor) const
	    {
	        if (!m_hasSkippedFirstFrame) {
	            m_hasSkippedFirstFrame = true;
	            return StackVisitor::Continue;
	        }

	        if (auto* codeBlock = visitor->codeBlock())
	            m_globalObject = codeBlock->globalObject();
	        else {
	            ASSERT(visitor->callee());
	            m_globalObject = visitor->callee()->globalObject();
	        }
	        return StackVisitor::Done;
	    }

	    JSGlobalObject* globalObject() const { return m_globalObject; }

	private:
	    mutable bool m_hasSkippedFirstFrame { false };
	    mutable JSGlobalObject* m_globalObject { nullptr };
	};
    GetCallerGlobalObjectFunctor iter;
    state.iterate(iter);
    return *jsCast<JSDOMGlobalObject*>(iter.globalObject() ? iter.globalObject() : state.vmEntryGlobalObject());
}

} // namespace WebCore
#endif
