Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .vscode/launch.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 30 additions & 20 deletions src/bun.js/bindings/BunProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1390,12 +1390,40 @@ extern "C" void Bun__Process__emitWarning(Zig::GlobalObject* globalObject, Encod
JSValue::decode(ctor));
}

JSValue Process::emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue warning, JSValue type, JSValue code, JSValue ctor)
JSValue Process::emitWarningErrorInstance(JSC::JSGlobalObject* lexicalGlobalObject, JSValue errorInstance)
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto* process = jsCast<Process*>(globalObject->processObject());

auto warningName = errorInstance.get(lexicalGlobalObject, vm.propertyNames->name);
RETURN_IF_EXCEPTION(scope, {});
if (isJSValueEqualToASCIILiteral(globalObject, warningName, "DeprecationWarning"_s)) {
if (Bun__Node__ProcessNoDeprecation) {
return jsUndefined();
}
if (Bun__Node__ProcessThrowDeprecation) {
// // Delay throwing the error to guarantee that all former warnings were properly logged.
// return process.nextTick(() => {
// throw warning;
// });
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_throwValue, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
}
}

// process.nextTick(doEmitWarning, warning);
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_emitWarning, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
}
JSValue Process::emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue warning, JSValue type, JSValue code, JSValue ctor)
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue detail = jsUndefined();

if (Bun__Node__ProcessNoDeprecation && isJSValueEqualToASCIILiteral(globalObject, type, "DeprecationWarning"_s)) {
Expand Down Expand Up @@ -1453,25 +1481,7 @@ JSValue Process::emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue w
if (!detail.isUndefined()) errorInstance->putDirect(vm, vm.propertyNames->detail, detail, JSC::PropertyAttribute::DontEnum | 0);
// ErrorCaptureStackTrace(warning, ctor || process.emitWarning);

if (isJSValueEqualToASCIILiteral(globalObject, type, "DeprecationWarning"_s)) {
if (Bun__Node__ProcessNoDeprecation) {
return jsUndefined();
}
if (Bun__Node__ProcessThrowDeprecation) {
// // Delay throwing the error to guarantee that all former warnings were properly logged.
// return process.nextTick(() => {
// throw warning;
// });
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_throwValue, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
}
}

// process.nextTick(doEmitWarning, warning);
auto func = JSFunction::create(vm, globalObject, 1, ""_s, jsFunction_emitWarning, JSC::ImplementationVisibility::Private);
process->queueNextTick(globalObject, func, errorInstance);
return jsUndefined();
RELEASE_AND_RETURN(scope, emitWarningErrorInstance(lexicalGlobalObject, errorInstance));
}

JSC_DEFINE_HOST_FUNCTION(Process_emitWarning, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/BunProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Process : public WebCore::JSEventEmitter {
// This is equivalent to `process.nextTick(() => process.emit(eventName, event))` from JavaScript.
void emitOnNextTick(Zig::GlobalObject* globalObject, ASCIILiteral eventName, JSValue event);

static JSValue emitWarningErrorInstance(JSC::JSGlobalObject* lexicalGlobalObject, JSValue errorInstance);
static JSValue emitWarning(JSC::JSGlobalObject* lexicalGlobalObject, JSValue warning, JSValue type, JSValue code, JSValue ctor);

JSString* cachedCwd() { return m_cachedCwd.get(); }
Expand Down
1 change: 1 addition & 0 deletions src/bun.js/bindings/ErrorCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const errors: ErrorCodeMapping = [
["ERR_DNS_SET_SERVERS_FAILED", Error],
["ERR_ENCODING_INVALID_ENCODED_DATA", TypeError],
["ERR_ENCODING_NOT_SUPPORTED", RangeError],
["ERR_EVENT_RECURSION", Error],
["ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE", Error],
["ERR_FORMDATA_PARSE_ERROR", TypeError],
["ERR_FS_CP_DIR_TO_NON_DIR", Error],
Expand Down
2 changes: 2 additions & 0 deletions src/bun.js/bindings/ExceptionCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,11 @@ enum ExceptionCode : uint8_t {
// Used to indicate to the bindings that a JS exception was thrown below and it should be propagated.
ExistingExceptionError,

// Node errors
InvalidThisError,
InvalidURLError,
CryptoOperationFailedError,
EVENT_RECURSION,
};

} // namespace WebCore
Expand Down
3 changes: 3 additions & 0 deletions src/bun.js/bindings/JSDOMExceptionHandling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ JSValue createDOMException(JSGlobalObject* lexicalGlobalObject, ExceptionCode ec
case ExceptionCode::CryptoOperationFailedError:
return Bun::createError(lexicalGlobalObject, Bun::ErrorCode::ERR_CRYPTO_OPERATION_FAILED, message.isEmpty() ? "Crypto operation failed"_s : message);

case ExceptionCode::EVENT_RECURSION:
return Bun::createError(lexicalGlobalObject, Bun::ErrorCode::ERR_EVENT_RECURSION, message);

default: {
// FIXME: All callers to createDOMException need to pass in the correct global object.
// For now, we're going to assume the lexicalGlobalObject. Which is wrong in cases like this:
Expand Down
7 changes: 6 additions & 1 deletion src/bun.js/bindings/webcore/EventTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include <wtf/SetForScope.h>
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>
#include "ErrorCode.h"

namespace WebCore {

Expand Down Expand Up @@ -231,9 +232,13 @@ bool EventTarget::hasActiveEventListeners(const AtomString& eventType) const

ExceptionOr<bool> EventTarget::dispatchEventForBindings(Event& event)
{
if (!event.isInitialized() || event.isBeingDispatched())
if (!event.isInitialized())
return Exception { InvalidStateError };

if (event.isBeingDispatched()) {
return Exception { EVENT_RECURSION, makeString("The event \""_s, event.type(), "\" is already being dispatched"_s) };
}

if (!scriptExecutionContext())
return false;

Expand Down
6 changes: 6 additions & 0 deletions src/bun.js/bindings/webcore/JSEvent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <wtf/URL.h>
#include "ErrorCode.h"
#include "NodeValidator.h"

namespace WebCore {
using namespace JSC;
Expand Down Expand Up @@ -158,6 +160,10 @@ template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSEventDOMConstructor::c
auto type = convert<IDLAtomStringAdaptor<IDLDOMString>>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, {});
EnsureStillAliveScope argument1 = callFrame->argument(1);
if (!argument1.value().isUndefinedOrNull() && !argument1.value().isObject() && !argument1.value().isCallable()) {
Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "options"_s, "object"_s, argument1.value());
}
RETURN_IF_EXCEPTION(throwScope, {});
auto eventInitDict = convert<IDLDictionary<EventInit>>(*lexicalGlobalObject, argument1.value());
RETURN_IF_EXCEPTION(throwScope, {});
auto object = Event::create(WTFMove(type), WTFMove(eventInitDict));
Expand Down
43 changes: 43 additions & 0 deletions src/bun.js/bindings/webcore/JSEventListener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "config.h"
#include "JSEventListener.h"

#include "BunProcess.h"
// #include "BeforeUnloadEvent.h"
// #include "ContentSecurityPolicy.h"
#include "EventNames.h"
Expand Down Expand Up @@ -123,6 +124,22 @@ void JSEventListener::visitJSFunction(SlotVisitor& visitor) { visitJSFunctionImp
// event.setReturnValue(returnValue);
// }

JSC_DEFINE_HOST_FUNCTION(jsFunctionEmitUncaughtException, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
auto exception = callFrame->argument(0);
reportException(lexicalGlobalObject, exception);
return JSValue::encode(JSC::jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsFunctionEmitUncaughtExceptionNextTick, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
Bun::Process* process = jsCast<Bun::Process*>(globalObject->processObject());
auto exception = callFrame->argument(0);
auto func = JSFunction::create(globalObject->vm(), globalObject, 1, String(), jsFunctionEmitUncaughtException, JSC::ImplementationVisibility::Private);
process->queueNextTick(lexicalGlobalObject, func, exception);
return JSC::JSValue::encode(JSC::jsUndefined());
}

void JSEventListener::handleEvent(ScriptExecutionContext& scriptExecutionContext, Event& event)
{
if (scriptExecutionContext.isJSExecutionForbidden())
Expand Down Expand Up @@ -234,6 +251,32 @@ void JSEventListener::handleEvent(ScriptExecutionContext& scriptExecutionContext
if (handleExceptionIfNeeded(uncaughtException))
return;

// Node handles promises in the return value and throws an uncaught exception on nextTick if it rejects.
// See event_target.js function addCatch in node
if (retval.isObject()) {
auto then = retval.get(lexicalGlobalObject, vm.propertyNames->then);
if (UNLIKELY(scope.exception())) {
auto* exception = scope.exception();
scope.clearException();
event.target()->uncaughtExceptionInEventHandler();
reportException(lexicalGlobalObject, exception);
return;
}
if (then.isCallable()) {
MarkedArgumentBuffer arglist;
arglist.append(JSValue(JSC::jsUndefined()));
arglist.append(JSValue(JSC::JSFunction::create(vm, lexicalGlobalObject, 1, String(), jsFunctionEmitUncaughtExceptionNextTick, ImplementationVisibility::Public, NoIntrinsic))); // err => process.nextTick(() => throw err)
JSC::call(lexicalGlobalObject, then, retval, arglist, "Promise.then is not callable"_s);
if (UNLIKELY(scope.exception())) {
auto* exception = scope.exception();
scope.clearException();
event.target()->uncaughtExceptionInEventHandler();
reportException(lexicalGlobalObject, exception);
return;
}
}
}

if (!m_isAttribute) {
// This is an EventListener and there is therefore no need for any return value handling.
return;
Expand Down
18 changes: 18 additions & 0 deletions src/bun.js/bindings/webcore/JSEventTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <wtf/URL.h>
#include "BunProcess.h"

namespace WebCore {
using namespace JSC;
Expand Down Expand Up @@ -223,6 +224,23 @@ static inline JSC::EncodedJSValue jsEventTargetPrototypeFunction_addEventListene
EnsureStillAliveScope argument2 = callFrame->argument(2);
auto options = argument2.value().isUndefined() ? false : convert<IDLUnion<IDLDictionary<AddEventListenerOptions>, IDLBoolean>>(*lexicalGlobalObject, argument2.value());
RETURN_IF_EXCEPTION(throwScope, {});
// Emit a warning if listener is null, as it has no effect
if (!listener) {
auto& vm = JSC::getVM(lexicalGlobalObject);
String warningMessage;
if (argument1.value().isNull()) {
warningMessage = makeString("addEventListener called with null listener, which has no effect."_s);
} else {
warningMessage = makeString("addEventListener called with undefined listener, which has no effect."_s);
}
auto errorInstance = JSC::ErrorInstance::create(vm, lexicalGlobalObject->errorStructure(JSC::ErrorType::Error), warningMessage, JSValue(), nullptr, RuntimeType::TypeNothing, JSC::ErrorType::Error);
errorInstance->putDirect(vm, vm.propertyNames->name, jsString(vm, makeString("AddEventListenerArgumentTypeWarning"_s)));
errorInstance->putDirect(vm, vm.propertyNames->target, &static_cast<JSObject&>(*castedThis));
RETURN_IF_EXCEPTION(throwScope, {});
errorInstance->putDirect(vm, vm.propertyNames->type, jsString(vm, type));
Bun::Process::emitWarningErrorInstance(lexicalGlobalObject, errorInstance);
RETURN_IF_EXCEPTION(throwScope, {});
}
auto result = JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.addEventListenerForBindings(WTFMove(type), WTFMove(listener), WTFMove(options)); }));
RETURN_IF_EXCEPTION(throwScope, {});
vm.writeBarrier(&static_cast<JSObject&>(*castedThis), argument1.value());
Expand Down
Loading