aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Carter Snook <cartersnook04@gmail.com> 2022-07-11 10:11:02 -0500
committerGravatar GitHub <noreply@github.com> 2022-07-11 08:11:02 -0700
commit5fc353797fe51b562322944c9280f750a345f490 (patch)
tree6bff4a615b8152f4ad35f08a4560d76f334700b7 /src
parent61901306266fac82e7121ea93fde6c8ffb5826bb (diff)
downloadbun-5fc353797fe51b562322944c9280f750a345f490.tar.gz
bun-5fc353797fe51b562322944c9280f750a345f490.tar.zst
bun-5fc353797fe51b562322944c9280f750a345f490.zip
feat(core): implement web interaction APIs (#528)
* feat(core): implement web interaction APIs * fix(core): adjust web prompt code and add types
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/javascript.zig4
-rw-r--r--src/bun.js/webcore.zig343
2 files changed, 347 insertions, 0 deletions
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 6300ae32a..771a5aebc 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -107,6 +107,10 @@ pub const GlobalClasses = [_]type{
WebCore.Crypto.Class,
WebCore.Crypto.Prototype,
+ WebCore.Alert.Class,
+ WebCore.Confirm.Class,
+ WebCore.Prompt.Class,
+
// The last item in this array becomes "process.env"
Bun.EnvironmentVariables.Class,
};
diff --git a/src/bun.js/webcore.zig b/src/bun.js/webcore.zig
index 981e15c8f..b134fb1c9 100644
--- a/src/bun.js/webcore.zig
+++ b/src/bun.js/webcore.zig
@@ -4,6 +4,7 @@ pub usingnamespace @import("./webcore/streams.zig");
const JSC = @import("../jsc.zig");
const std = @import("std");
+const bun = @import("../global.zig");
pub const Lifetime = enum {
clone,
@@ -13,6 +14,348 @@ pub const Lifetime = enum {
temporary,
};
+pub const Alert = struct {
+ pub const Class = JSC.NewClass(
+ void,
+ .{ .name = "alert" },
+ .{
+ .@"call" = .{ .rfn = call },
+ },
+ .{},
+ );
+
+ /// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-alert
+ pub fn call(
+ // this
+ _: void,
+ ctx: JSC.C.JSContextRef,
+ // function
+ _: JSC.C.JSObjectRef,
+ // thisObject
+ _: JSC.C.JSObjectRef,
+ arguments: []const JSC.C.JSValueRef,
+ _: JSC.C.ExceptionRef,
+ ) JSC.C.JSValueRef {
+ var output = bun.Output.writer();
+ const has_message = arguments.len != 0;
+
+ // 2. If the method was invoked with no arguments, then let message be the empty string; otherwise, let message be the method's first argument.
+ if (has_message) {
+ const allocator = std.heap.stackFallback(2048, bun.default_allocator).get();
+ const message = arguments[0].?.value().toSlice(ctx.ptr(), allocator);
+ defer message.deinit();
+
+ // 3. Set message to the result of normalizing newlines given message.
+ // * We skip step 3 because they are already done in most terminals by default.
+
+ // 4. Set message to the result of optionally truncating message.
+ // * We just don't do this because it's not necessary.
+
+ // 5. Show message to the user, treating U+000A LF as a line break.
+ output.writeAll(message.slice()) catch {
+ // 1. If we cannot show simple dialogs for this, then return.
+ return JSC.JSValue.jsUndefined().asObjectRef();
+ };
+ }
+
+ output.writeAll(if (has_message) " [Enter] " else "Alert [Enter] ") catch {
+ // 1. If we cannot show simple dialogs for this, then return.
+ return JSC.JSValue.jsUndefined().asObjectRef();
+ };
+
+ // 6. Invoke WebDriver BiDi user prompt opened with this, "alert", and message.
+ // * Not pertinent to use their complex system in a server context.
+ bun.Output.flush();
+
+ // 7. Optionally, pause while waiting for the user to acknowledge the message.
+ var stdin = std.io.getStdIn();
+ var reader = stdin.reader();
+ while (true) {
+ const byte = reader.readByte() catch break;
+ if (byte == '\n') break;
+ }
+
+ // 8. Invoke WebDriver BiDi user prompt closed with this and true.
+ // * Again, not necessary in a server context.
+
+ return JSC.JSValue.jsUndefined().asObjectRef();
+ }
+};
+
+pub const Confirm = struct {
+ pub const Class = JSC.NewClass(
+ void,
+ .{ .name = "confirm" },
+ .{
+ .@"call" = .{ .rfn = call },
+ },
+ .{},
+ );
+
+ /// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-confirm
+ pub fn call(
+ // this
+ _: void,
+ ctx: JSC.C.JSContextRef,
+ // function
+ _: JSC.C.JSObjectRef,
+ // thisObject
+ _: JSC.C.JSObjectRef,
+ arguments: []const JSC.C.JSValueRef,
+ _: JSC.C.ExceptionRef,
+ ) JSC.C.JSValueRef {
+ var output = bun.Output.writer();
+ const has_message = arguments.len != 0;
+
+ if (has_message) {
+ const allocator = std.heap.stackFallback(1024, bun.default_allocator).get();
+ // 2. Set message to the result of normalizing newlines given message.
+ // * Not pertinent to a server runtime so we will just let the terminal handle this.
+
+ // 3. Set message to the result of optionally truncating message.
+ // * Not necessary so we won't do it.
+ const message = arguments[0].?.value().toSlice(ctx.ptr(), allocator);
+ defer message.deinit();
+
+ output.writeAll(message.slice()) catch {
+ // 1. If we cannot show simple dialogs for this, then return false.
+ return JSC.JSValue.jsBoolean(false).asObjectRef();
+ };
+ }
+
+ // 4. Show message to the user, treating U+000A LF as a line break,
+ // and ask the user to respond with a positive or negative
+ // response.
+ output.writeAll(if (has_message) " [y/N] " else "Confirm [y/N] ") catch {
+ // 1. If we cannot show simple dialogs for this, then return false.
+ return JSC.JSValue.jsBoolean(false).asObjectRef();
+ };
+
+ // 5. Invoke WebDriver BiDi user prompt opened with this, "confirm", and message.
+ // * Not relevant in a server context.
+ bun.Output.flush();
+
+ // 6. Pause until the user responds either positively or negatively.
+ var stdin = std.io.getStdIn();
+ var reader = stdin.reader();
+
+ const first_byte = reader.readByte() catch {
+ return JSC.JSValue.jsBoolean(false).asObjectRef();
+ };
+
+ // 7. Invoke WebDriver BiDi user prompt closed with this, and true if
+ // the user responded positively or false otherwise.
+ // * Not relevant in a server context.
+
+ switch (first_byte) {
+ '\n' => return JSC.JSValue.jsBoolean(false).asObjectRef(),
+ 'y', 'Y' => {
+ const next_byte = reader.readByte() catch {
+ // They may have said yes, but the stdin is invalid.
+ return JSC.JSValue.jsBoolean(false).asObjectRef();
+ };
+
+ if (next_byte == '\n') {
+ // 8. If the user responded positively, return true;
+ // otherwise, the user responded negatively: return false.
+ return JSC.JSValue.jsBoolean(true).asObjectRef();
+ }
+ },
+ else => {},
+ }
+
+ while (reader.readByte()) |b| {
+ if (b == '\n') break;
+ } else |_| {}
+
+ // 8. If the user responded positively, return true; otherwise, the user
+ // responded negatively: return false.
+ return JSC.JSValue.jsBoolean(false).asObjectRef();
+ }
+};
+
+pub const Prompt = struct {
+ pub const Class = JSC.NewClass(
+ void,
+ .{ .name = "prompt" },
+ .{
+ .@"call" = .{ .rfn = call },
+ },
+ .{},
+ );
+
+ /// Adapted from `std.io.Reader.readUntilDelimiterArrayList` to only append
+ /// and assume capacity.
+ pub fn readUntilDelimiterArrayListAppendAssumeCapacity(
+ reader: anytype,
+ array_list: *std.ArrayList(u8),
+ delimiter: u8,
+ max_size: usize,
+ ) !void {
+ while (true) {
+ if (array_list.items.len == max_size) {
+ return error.StreamTooLong;
+ }
+
+ var byte: u8 = try reader.readByte();
+
+ if (byte == delimiter) {
+ return;
+ }
+
+ array_list.appendAssumeCapacity(byte);
+ }
+ }
+
+ /// Adapted from `std.io.Reader.readUntilDelimiterArrayList` to always append
+ /// and not resize.
+ fn readUntilDelimiterArrayListInfinity(
+ reader: anytype,
+ array_list: *std.ArrayList(u8),
+ delimiter: u8,
+ ) !void {
+ while (true) {
+ var byte: u8 = try reader.readByte();
+
+ if (byte == delimiter) {
+ return;
+ }
+
+ try array_list.append(byte);
+ }
+ }
+
+ /// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-prompt
+ pub fn call(
+ // this
+ _: void,
+ ctx: JSC.C.JSContextRef,
+ // function
+ _: JSC.C.JSObjectRef,
+ // thisObject
+ _: JSC.C.JSObjectRef,
+ arguments: []const JSC.C.JSValueRef,
+ _: JSC.C.ExceptionRef,
+ ) JSC.C.JSValueRef {
+ var allocator = std.heap.stackFallback(2048, bun.default_allocator).get();
+ var output = bun.Output.writer();
+ const has_message = arguments.len != 0;
+ const has_default = arguments.len >= 2;
+ // 4. Set default to the result of optionally truncating default.
+ // * We don't really need to do this.
+ const default = if (has_default) arguments[1] else JSC.JSValue.jsNull().asObjectRef();
+
+ if (has_message) {
+ // 2. Set message to the result of normalizing newlines given message.
+ // * Not pertinent to a server runtime so we will just let the terminal handle this.
+
+ // 3. Set message to the result of optionally truncating message.
+ // * Not necessary so we won't do it.
+ const message = arguments[0].?.value().toSlice(ctx.ptr(), allocator);
+ defer message.deinit();
+
+ output.writeAll(message.slice()) catch {
+ // 1. If we cannot show simple dialogs for this, then return null.
+ return JSC.JSValue.jsNull().asObjectRef();
+ };
+ }
+
+ // 4. Set default to the result of optionally truncating default.
+
+ // 5. Show message to the user, treating U+000A LF as a line break,
+ // and ask the user to either respond with a string value or
+ // abort. The response must be defaulted to the value given by
+ // default.
+ output.writeAll(if (has_message) " " else "Prompt ") catch {
+ // 1. If we cannot show simple dialogs for this, then return false.
+ return JSC.JSValue.jsBoolean(false).asObjectRef();
+ };
+
+ if (has_default) {
+ const default_string = arguments[1].?.value().toSlice(ctx.ptr(), allocator);
+ defer default_string.deinit();
+
+ output.print("[{s}] ", .{default_string.slice()}) catch {
+ // 1. If we cannot show simple dialogs for this, then return false.
+ return JSC.JSValue.jsBoolean(false).asObjectRef();
+ };
+ }
+
+ // 6. Invoke WebDriver BiDi user prompt opened with this, "prompt" and message.
+ // * Not relevant in a server context.
+ bun.Output.flush();
+
+ // 7. Pause while waiting for the user's response.
+ var stdin = std.io.getStdIn();
+ var reader = stdin.reader();
+
+ const first_byte = reader.readByte() catch {
+ // 8. Let result be null if the user aborts, or otherwise the string
+ // that the user responded with.
+ return JSC.JSValue.jsNull().asObjectRef();
+ };
+
+ if (first_byte == '\n') {
+ // 8. Let result be null if the user aborts, or otherwise the string
+ // that the user responded with.
+ return default;
+ }
+
+ var input = std.ArrayList(u8).initCapacity(allocator, 2048) catch {
+ // 8. Let result be null if the user aborts, or otherwise the string
+ // that the user responded with.
+ return JSC.JSValue.jsNull().asObjectRef();
+ };
+ defer input.deinit();
+
+ input.appendAssumeCapacity(first_byte);
+
+ // All of this code basically just first tries to load the input into a
+ // buffer of size 2048. If that is too small, then increase the buffer
+ // size to 4096. If that is too small, then just dynamically allocate
+ // the rest.
+ readUntilDelimiterArrayListAppendAssumeCapacity(reader, &input, '\n', 2048) catch |e| {
+ if (e != error.StreamTooLong) {
+ // 8. Let result be null if the user aborts, or otherwise the string
+ // that the user responded with.
+ return JSC.JSValue.jsNull().asObjectRef();
+ }
+
+ input.ensureTotalCapacity(4096) catch {
+ // 8. Let result be null if the user aborts, or otherwise the string
+ // that the user responded with.
+ return JSC.JSValue.jsNull().asObjectRef();
+ };
+
+ readUntilDelimiterArrayListAppendAssumeCapacity(reader, &input, '\n', 4096) catch |e2| {
+ if (e2 != error.StreamTooLong) {
+ // 8. Let result be null if the user aborts, or otherwise the string
+ // that the user responded with.
+ return JSC.JSValue.jsNull().asObjectRef();
+ }
+
+ readUntilDelimiterArrayListInfinity(reader, &input, '\n') catch {
+ // 8. Let result be null if the user aborts, or otherwise the string
+ // that the user responded with.
+ return JSC.JSValue.jsNull().asObjectRef();
+ };
+ };
+ };
+
+ // 8. Let result be null if the user aborts, or otherwise the string
+ // that the user responded with.
+ var result = JSC.ZigString.init(input.items);
+ result.markUTF8();
+
+ // 9. Invoke WebDriver BiDi user prompt closed with this, false if
+ // result is null or true otherwise, and result.
+ // * Too complex for server context.
+
+ // 9. Return result.
+ return result.toValueGC(ctx.ptr()).asObjectRef();
+ }
+};
+
pub const Crypto = struct {
const UUID = @import("./uuid.zig");