mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 09:35:59 -04:00
Merge branch 'main' into agent
This commit is contained in:
@@ -57,6 +57,8 @@ Verify the binary before running anything:
|
||||
|
||||
[Linux aarch64 is also available](https://github.com/lightpanda-io/browser/releases/tag/nightly)
|
||||
|
||||
> **Note:** The Linux release binaries are linked against glibc. On musl-based distros (Alpine, etc.) the binary fails with `cannot execute: required file not found` because the glibc dynamic linker is missing. Use a glibc-based base image (e.g., `FROM debian:bookworm-slim` or `FROM ubuntu:24.04`) or [build from sources](#build-from-sources).
|
||||
|
||||
*For MacOS*
|
||||
```console
|
||||
curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-aarch64-macos && \
|
||||
|
||||
@@ -57,8 +57,11 @@ pub const HeaderIterator = http.HeaderIterator;
|
||||
// those other http requests.
|
||||
pub const Client = @This();
|
||||
|
||||
// Count of active requests
|
||||
active: usize = 0,
|
||||
// Count of active ws requests
|
||||
ws_active: usize = 0,
|
||||
|
||||
// Count of active http requests
|
||||
http_active: usize = 0,
|
||||
|
||||
// Count of intercepted requests. This is to help deal with intercepted requests.
|
||||
// The client doesn't track intercepted transfers. If a request is intercepted,
|
||||
@@ -87,6 +90,13 @@ next_request_id: u32 = 0,
|
||||
// When handles has no more available easys, requests get queued.
|
||||
queue: std.DoublyLinkedList = .{},
|
||||
|
||||
// Queue is for Transfers that have no connection. ready_queue is for connections
|
||||
// that were initiated when performing == true and thus need to wait until
|
||||
// performing == false before being added. I'm hoping this is temporary and that
|
||||
// we can unify the two queues. But HTTP is being changed a lot right now, and
|
||||
// I'm trying to minimize the surface area.
|
||||
ready_queue: std.DoublyLinkedList = .{},
|
||||
|
||||
// The main app allocator
|
||||
allocator: Allocator,
|
||||
|
||||
@@ -220,6 +230,13 @@ pub fn setTlsVerify(self: *Client, verify: bool) !void {
|
||||
const conn: *http.Connection = @fieldParentPtr("node", node);
|
||||
try conn.setTlsVerify(verify, self.use_proxy);
|
||||
}
|
||||
|
||||
it = self.ready_queue.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
const conn: *http.Connection = @fieldParentPtr("node", node);
|
||||
try conn.setTlsVerify(verify, self.use_proxy);
|
||||
}
|
||||
|
||||
self.tls_verify = verify;
|
||||
}
|
||||
|
||||
@@ -258,26 +275,8 @@ pub fn abortFrame(self: *Client, frame_id: u32) void {
|
||||
// Written this way so that both abort and abortFrame can share the same code
|
||||
// but abort can avoid the frame_id check at comptime.
|
||||
fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
|
||||
{
|
||||
var n = self.in_use.first;
|
||||
while (n) |node| {
|
||||
n = node.next;
|
||||
const conn: *http.Connection = @fieldParentPtr("node", node);
|
||||
switch (conn.transport) {
|
||||
.http => |transfer| {
|
||||
if ((comptime abort_all) or transfer.req.frame_id == frame_id) {
|
||||
transfer.kill();
|
||||
}
|
||||
},
|
||||
.websocket => |ws| {
|
||||
if ((comptime abort_all) or ws._page._frame_id == frame_id) {
|
||||
ws.kill();
|
||||
}
|
||||
},
|
||||
.none => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
abortConnections(self.in_use, abort_all, frame_id);
|
||||
abortConnections(self.ready_queue, abort_all, frame_id);
|
||||
|
||||
{
|
||||
var q = &self.queue;
|
||||
@@ -296,6 +295,7 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
|
||||
|
||||
if (comptime abort_all) {
|
||||
self.queue = .{};
|
||||
self.ready_queue = .{};
|
||||
}
|
||||
|
||||
if (comptime IS_DEBUG and abort_all) {
|
||||
@@ -312,7 +312,28 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
|
||||
}
|
||||
leftover += 1;
|
||||
}
|
||||
std.debug.assert(self.active == leftover);
|
||||
std.debug.assert(self.http_active == leftover);
|
||||
}
|
||||
}
|
||||
|
||||
fn abortConnections(list: std.DoublyLinkedList, comptime abort_all: bool, frame_id: u32) void {
|
||||
var n = list.first;
|
||||
while (n) |node| {
|
||||
n = node.next;
|
||||
const conn: *http.Connection = @fieldParentPtr("node", node);
|
||||
switch (conn.transport) {
|
||||
.http => |transfer| {
|
||||
if ((comptime abort_all) or transfer.req.frame_id == frame_id) {
|
||||
transfer.kill();
|
||||
}
|
||||
},
|
||||
.websocket => |ws| {
|
||||
if ((comptime abort_all) or ws._page._frame_id == frame_id) {
|
||||
ws.kill();
|
||||
}
|
||||
},
|
||||
.none => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -848,6 +869,11 @@ fn perform(self: *Client, timeout_ms: c_int) anyerror!PerformStatus {
|
||||
self.releaseConn(conn);
|
||||
}
|
||||
|
||||
while (self.ready_queue.popFirst()) |node| {
|
||||
const conn: *http.Connection = @fieldParentPtr("node", node);
|
||||
try self.trackConn(conn);
|
||||
}
|
||||
|
||||
// We're potentially going to block for a while until we get data. Process
|
||||
// whatever messages we have waiting ahead of time.
|
||||
if (try self.processMessages()) {
|
||||
@@ -1034,7 +1060,7 @@ fn processMessages(self: *Client) !bool {
|
||||
// Conn was removed from handles during redirect reconfiguration
|
||||
// but not re-added. Release it directly to avoid double-remove.
|
||||
self.in_use.remove(&c.node);
|
||||
self.active -= 1;
|
||||
self.http_active -= 1;
|
||||
self.releaseConn(c);
|
||||
transfer._detached_conn = null;
|
||||
}
|
||||
@@ -1046,6 +1072,7 @@ fn processMessages(self: *Client) !bool {
|
||||
}
|
||||
},
|
||||
.websocket => |ws| {
|
||||
// ws_active will be decremented through the call to disconnected
|
||||
if (msg.err) |err| switch (err) {
|
||||
error.GotNothing => ws.disconnected(null),
|
||||
else => ws.disconnected(err),
|
||||
@@ -1063,26 +1090,51 @@ fn processMessages(self: *Client) !bool {
|
||||
}
|
||||
|
||||
pub fn trackConn(self: *Client, conn: *http.Connection) !void {
|
||||
if (self.performing) {
|
||||
conn.in_use = false;
|
||||
self.ready_queue.append(&conn.node);
|
||||
return;
|
||||
}
|
||||
|
||||
self.in_use.append(&conn.node);
|
||||
conn.in_use = true;
|
||||
// Set private pointer so readMessage can find the Connection.
|
||||
// Must be done each time since curl_easy_reset clears it when
|
||||
// connections are returned to pool.
|
||||
conn.setPrivate(conn) catch |err| {
|
||||
self.in_use.remove(&conn.node);
|
||||
conn.in_use = false;
|
||||
self.releaseConn(conn);
|
||||
return err;
|
||||
};
|
||||
self.handles.add(conn) catch |err| {
|
||||
self.in_use.remove(&conn.node);
|
||||
conn.in_use = false;
|
||||
self.releaseConn(conn);
|
||||
return err;
|
||||
};
|
||||
self.active += 1;
|
||||
|
||||
switch (conn.transport) {
|
||||
.http => self.http_active += 1,
|
||||
.websocket => self.ws_active += 1,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn removeConn(self: *Client, conn: *http.Connection) void {
|
||||
if (conn.in_use == false) {
|
||||
self.ready_queue.remove(&conn.node);
|
||||
self.releaseConn(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
self.in_use.remove(&conn.node);
|
||||
self.active -= 1;
|
||||
conn.in_use = false;
|
||||
switch (conn.transport) {
|
||||
.http => self.http_active -= 1,
|
||||
.websocket => self.ws_active -= 1,
|
||||
else => unreachable,
|
||||
}
|
||||
if (self.handles.remove(conn)) {
|
||||
self.releaseConn(conn);
|
||||
} else |_| {
|
||||
@@ -1097,7 +1149,7 @@ fn releaseConn(self: *Client, conn: *http.Connection) void {
|
||||
}
|
||||
|
||||
fn ensureNoActiveConnection(self: *const Client) !void {
|
||||
if (self.active > 0) {
|
||||
if (self.http_active > 0 or self.ws_active > 0) {
|
||||
return error.InflightConnection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
|
||||
.pre, .raw, .text, .image => {
|
||||
// The main page hasn't started/finished navigating.
|
||||
// There's no JS to run, and no reason to run the scheduler.
|
||||
if (http_client.active == 0 and (comptime is_cdp) == false) {
|
||||
if (http_client.http_active == 0 and (comptime is_cdp) == false) {
|
||||
// haven't started navigating, I guess.
|
||||
return .done;
|
||||
}
|
||||
@@ -162,14 +162,14 @@ fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
|
||||
// download, or scheduled tasks to execute, or both.
|
||||
|
||||
// scheduler.run could trigger new http transfers, so do not
|
||||
// store http_client.active BEFORE this call and then use
|
||||
// store http_client.http_active BEFORE this call and then use
|
||||
// it AFTER.
|
||||
try browser.runMacrotasks();
|
||||
|
||||
// Each call to this runs scheduled load events.
|
||||
try page.dispatchLoad();
|
||||
|
||||
const http_active = http_client.active;
|
||||
const http_active = http_client.http_active;
|
||||
const total_network_activity = http_active + http_client.intercepted;
|
||||
if (page._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
||||
page.notifyNetworkAlmostIdle();
|
||||
@@ -178,9 +178,22 @@ fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
|
||||
page.notifyNetworkIdle();
|
||||
}
|
||||
|
||||
if (http_active == 0 and (comptime is_cdp == false)) {
|
||||
switch (opts.until) {
|
||||
.done => {},
|
||||
.domcontentloaded => if (page._load_state == .load or page._load_state == .complete) {
|
||||
return .done;
|
||||
},
|
||||
.load => if (page._load_state == .complete) {
|
||||
return .done;
|
||||
},
|
||||
.networkidle => if (page._notified_network_idle == .done) {
|
||||
return .done;
|
||||
},
|
||||
}
|
||||
|
||||
if (http_active == 0 and http_client.ws_active == 0 and (comptime is_cdp == false)) {
|
||||
// we don't need to consider http_client.intercepted here
|
||||
// because is_cdp is true, and that can only be
|
||||
// because is_cdp is false, and that can only be
|
||||
// the case when interception isn't possible.
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(http_client.intercepted == 0);
|
||||
@@ -192,19 +205,6 @@ fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
|
||||
browser.waitForBackgroundTasks();
|
||||
}
|
||||
|
||||
switch (opts.until) {
|
||||
.done => {},
|
||||
.domcontentloaded => if (page._load_state == .load or page._load_state == .complete) {
|
||||
return .done;
|
||||
},
|
||||
.load => if (page._load_state == .complete) {
|
||||
return .done;
|
||||
},
|
||||
.networkidle => if (page._notified_network_idle == .done) {
|
||||
return .done;
|
||||
},
|
||||
}
|
||||
|
||||
// We never advertise a wait time of more than 20, there can
|
||||
// always be new background tasks to run.
|
||||
if (browser.msToNextMacrotask()) |ms_to_next_task| {
|
||||
|
||||
@@ -163,6 +163,53 @@ pub fn isFloat64Array(self: Value) bool {
|
||||
return v8.v8__Value__IsFloat64Array(self.handle);
|
||||
}
|
||||
|
||||
// A few places in the code take various types, but want a string. This is a
|
||||
// type-aware version of toString(). If you do:
|
||||
// (new ArrayBuffer(100)).toString()
|
||||
// You'll get "[object ArrayBuffer]". But this `toStringSmart()` knows about
|
||||
// buffers, and Blobs, etc and will try to return the real underlying string
|
||||
// value. It _does_ ultimately fallback to toString() - callers should check
|
||||
// for types they _don't_ want before calling this. For example, `Response`
|
||||
// checks for null or undefined before calling this to apply specific handling
|
||||
// to those cases.
|
||||
pub fn toStringSmart(self: Value) ![]const u8 {
|
||||
if (self.isString()) |js_str| {
|
||||
return try js_str.toSlice();
|
||||
}
|
||||
|
||||
const Blob = @import("../webapi/Blob.zig");
|
||||
if (self.local.jsValueToZig(*Blob, self)) |blob_obj| {
|
||||
return blob_obj._slice;
|
||||
} else |_| {}
|
||||
|
||||
var byte_offset: usize = 0;
|
||||
var byte_len: usize = undefined;
|
||||
var array_buffer: ?*const v8.ArrayBuffer = null;
|
||||
|
||||
if (self.isTypedArray() or self.isArrayBufferView()) {
|
||||
const buffer_handle: *const v8.ArrayBufferView = @ptrCast(self.handle);
|
||||
byte_len = v8.v8__ArrayBufferView__ByteLength(buffer_handle);
|
||||
byte_offset = v8.v8__ArrayBufferView__ByteOffset(buffer_handle);
|
||||
array_buffer = v8.v8__ArrayBufferView__Buffer(buffer_handle);
|
||||
} else if (self.isArrayBuffer()) {
|
||||
array_buffer = @ptrCast(self.handle);
|
||||
byte_len = v8.v8__ArrayBuffer__ByteLength(array_buffer);
|
||||
} else {
|
||||
return self.toStringSlice();
|
||||
}
|
||||
|
||||
const backing_store_ptr = v8.v8__ArrayBuffer__GetBackingStore(array_buffer orelse return "");
|
||||
if (byte_len == 0) {
|
||||
return &[_]u8{};
|
||||
}
|
||||
|
||||
const backing_store_handle = v8.std__shared_ptr__v8__BackingStore__get(&backing_store_ptr) orelse return "";
|
||||
const data = v8.v8__BackingStore__Data(backing_store_handle) orelse return "";
|
||||
const base = @as([*]const u8, @ptrCast(data)) + byte_offset;
|
||||
|
||||
return base[0..byte_len];
|
||||
}
|
||||
|
||||
pub fn isPromise(self: Value) bool {
|
||||
return v8.v8__Value__IsPromise(self.handle);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id=animation>
|
||||
<script id=animation type=module>
|
||||
const state = await testing.async();
|
||||
|
||||
let a1 = document.createElement('div').animate(null, null);
|
||||
testing.expectEqual('idle', a1.playState);
|
||||
|
||||
@@ -9,13 +11,18 @@
|
||||
a1.finished.then((x) => {
|
||||
cb.push(a1.playState);
|
||||
cb.push(x == a1);
|
||||
state.resolve();
|
||||
});
|
||||
|
||||
a1.ready.then(() => {
|
||||
cb.push(a1.playState);
|
||||
a1.play();
|
||||
cb.push(a1.playState);
|
||||
});
|
||||
testing.onload(() => testing.expectEqual(['idle', 'running', 'finished', true], cb));
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(['idle', 'running', 'finished', true], cb);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- <script id=startTime>
|
||||
|
||||
@@ -79,6 +79,100 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=parts>
|
||||
// Blob as a blob part - contents are copied in, not stringified.
|
||||
{
|
||||
const inner = new Blob(["hello "], { type: "text/plain" });
|
||||
const blob = new Blob([inner, "world"]);
|
||||
testing.expectEqual(11, blob.size);
|
||||
testing.async(async () => {
|
||||
testing.expectEqual("hello world", await blob.text());
|
||||
});
|
||||
}
|
||||
|
||||
// Uint8Array as a blob part.
|
||||
{
|
||||
const bytes = new Uint8Array([104, 101, 108, 108, 111]); // "hello"
|
||||
const blob = new Blob([bytes, " ", "world"]);
|
||||
testing.expectEqual(11, blob.size);
|
||||
testing.async(async () => {
|
||||
testing.expectEqual("hello world", await blob.text());
|
||||
});
|
||||
}
|
||||
|
||||
// Non-byte TypedArrays contribute their raw backing bytes.
|
||||
{
|
||||
// Uint16Array of one element (0x0041 = 'A' little-endian) produces 2 bytes.
|
||||
const u16 = new Uint16Array([0x0041]);
|
||||
const blob = new Blob([u16]);
|
||||
testing.expectEqual(2, blob.size);
|
||||
}
|
||||
|
||||
{
|
||||
// Float32Array: 4 bytes per element.
|
||||
const f32 = new Float32Array([1.0, 2.0, 3.0]);
|
||||
const blob = new Blob([f32]);
|
||||
testing.expectEqual(12, blob.size);
|
||||
}
|
||||
|
||||
// ArrayBuffer as a blob part.
|
||||
{
|
||||
const buf = new Uint8Array([1, 2, 3, 4]).buffer;
|
||||
const blob = new Blob([buf]);
|
||||
testing.expectEqual(4, blob.size);
|
||||
testing.async(async () => {
|
||||
const result = await blob.bytes();
|
||||
testing.expectEqual(new Uint8Array([1, 2, 3, 4]), result);
|
||||
});
|
||||
}
|
||||
|
||||
// DataView (ArrayBufferView) as a blob part.
|
||||
{
|
||||
const buf = new Uint8Array([10, 20, 30, 40, 50]).buffer;
|
||||
const view = new DataView(buf, 1, 3); // bytes [20, 30, 40]
|
||||
const blob = new Blob([view]);
|
||||
testing.expectEqual(3, blob.size);
|
||||
testing.async(async () => {
|
||||
const result = await blob.bytes();
|
||||
testing.expectEqual(new Uint8Array([20, 30, 40]), result);
|
||||
});
|
||||
}
|
||||
|
||||
// Mixed types in a single parts array.
|
||||
{
|
||||
const inner = new Blob(["bb"]);
|
||||
const bytes = new Uint8Array([99, 99]); // "cc"
|
||||
const buf = new Uint8Array([100, 100]).buffer; // "dd"
|
||||
const blob = new Blob(["aa", inner, bytes, buf, "ee"]);
|
||||
testing.expectEqual(10, blob.size);
|
||||
testing.async(async () => {
|
||||
testing.expectEqual("aabbccddee", await blob.text());
|
||||
});
|
||||
}
|
||||
|
||||
// Number coerces to string.
|
||||
{
|
||||
const blob = new Blob([42]);
|
||||
testing.expectEqual(2, blob.size);
|
||||
testing.async(async () => {
|
||||
testing.expectEqual("42", await blob.text());
|
||||
});
|
||||
}
|
||||
|
||||
// Empty parts array.
|
||||
{
|
||||
const blob = new Blob([]);
|
||||
testing.expectEqual(0, blob.size);
|
||||
testing.expectEqual("", blob.type);
|
||||
}
|
||||
|
||||
// No arguments.
|
||||
{
|
||||
const blob = new Blob();
|
||||
testing.expectEqual(0, blob.size);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=stream>
|
||||
{
|
||||
const parts = ["may", "thy", "knife", "chip", "and", "shatter"];
|
||||
|
||||
@@ -121,7 +121,9 @@
|
||||
|
||||
<div id="will_be_removed">This will be removed by document.open()</div>
|
||||
|
||||
<script id=test_open_close_async>
|
||||
<script id=test_open_close_async type=module>
|
||||
const state = await testing.async();
|
||||
|
||||
// Mark that we saw the element before
|
||||
const sawBefore = document.getElementById('will_be_removed') !== null;
|
||||
testing.expectEqual(true, sawBefore);
|
||||
@@ -131,7 +133,15 @@
|
||||
document.open();
|
||||
}, 5);
|
||||
|
||||
testing.onload(() => {
|
||||
// doing this after test_open_close_async used to crash, so we keep it
|
||||
// to make sure it doesn't
|
||||
setTimeout(() => {
|
||||
document.open();
|
||||
document.close();
|
||||
state.resolve();
|
||||
}, 20);
|
||||
|
||||
await state.done(() => {
|
||||
// The element should be gone now
|
||||
const afterOpen = document.getElementById('will_be_removed');
|
||||
testing.expectEqual(null, afterOpen);
|
||||
@@ -149,13 +159,4 @@
|
||||
testing.expectEqual('Replaced', newContent.textContent);
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// doing this after test_open_close_async used to crash, so we keep it
|
||||
// to make sure it doesn't
|
||||
setTimeout(() => {
|
||||
document.open();
|
||||
document.close();
|
||||
}, 20);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -240,13 +240,20 @@
|
||||
testing.expectEqual('AbortError', AbortSignal.abort().reason);
|
||||
</script>
|
||||
|
||||
<script id=abortsignal_timeout>
|
||||
var s3 = AbortSignal.timeout(10);
|
||||
testing.onload(() => {
|
||||
testing.expectEqual(true, s3.aborted);
|
||||
testing.expectEqual('TimeoutError', s3.reason);
|
||||
testing.expectError('Error: TimeoutError', () => {
|
||||
s3.throwIfAborted()
|
||||
<script id=abortsignal_timeout type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
var s3 = AbortSignal.timeout(10);
|
||||
window.setTimeout(() => {
|
||||
state.resolve()
|
||||
}, 11);
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(true, s3.aborted);
|
||||
testing.expectEqual('TimeoutError', s3.reason);
|
||||
testing.expectError('Error: TimeoutError', () => {
|
||||
s3.throwIfAborted()
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
|
||||
<iframe id="receiver"></iframe>
|
||||
|
||||
<script id="messages">
|
||||
<script id="messages" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
let reply = null;
|
||||
window.addEventListener('message', (e) => {
|
||||
reply = e.data;
|
||||
state.resolve();
|
||||
});
|
||||
|
||||
const iframe = $('#receiver');
|
||||
@@ -16,7 +18,7 @@
|
||||
iframe.contentWindow.postMessage('ping', '*');
|
||||
});
|
||||
|
||||
testing.onload(() => {
|
||||
await state.done(() => {
|
||||
testing.expectEqual('pong', reply.data);
|
||||
testing.expectEqual(testing.ORIGIN, reply.origin);
|
||||
});
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
|
||||
<iframe name=f1 id=frame1></iframe>
|
||||
<a id=l1 target=f1 href=support/page.html></a>
|
||||
<script id=anchor>
|
||||
<script id=anchor type=module>
|
||||
const state = await testing.async();
|
||||
$('#l1').click();
|
||||
testing.onload(() => {
|
||||
|
||||
$('#frame1').onload = () => { state.resolve(); }
|
||||
|
||||
state.done(() => {
|
||||
testing.expectEqual('<html><head></head><body>a-page\n</body></html>', $('#frame1').contentDocument.documentElement.outerHTML);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,105 +1,124 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id=fetch_basic>
|
||||
testing.async(async (restore) => {
|
||||
<script id=fetch_basic type=module>
|
||||
{
|
||||
const state = await testing.async()
|
||||
const response = await fetch('http://127.0.0.1:9582/xhr');
|
||||
restore();
|
||||
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual('basic', response.type);
|
||||
testing.expectEqual('http://127.0.0.1:9582/xhr', response.url);
|
||||
testing.expectEqual(false, response.redirected);
|
||||
|
||||
// Check headers
|
||||
const headers = response.headers;
|
||||
testing.expectEqual('text/html; charset=utf-8', headers.get('Content-Type'));
|
||||
testing.expectEqual('100', headers.get('content-length'));
|
||||
|
||||
// Check text response
|
||||
const text = await response.text();
|
||||
testing.expectEqual(100, text.length);
|
||||
});
|
||||
|
||||
state.resolve();
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual('basic', response.type);
|
||||
testing.expectEqual('http://127.0.0.1:9582/xhr', response.url);
|
||||
testing.expectEqual(false, response.redirected);
|
||||
|
||||
// Check headers
|
||||
const headers = response.headers;
|
||||
testing.expectEqual('text/html; charset=utf-8', headers.get('Content-Type'));
|
||||
testing.expectEqual('100', headers.get('content-length'));
|
||||
|
||||
// Check text response
|
||||
|
||||
testing.expectEqual(100, text.length);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=fetch_json>
|
||||
testing.async(async (restore) => {
|
||||
<script id=fetch_json type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const response = await fetch('http://127.0.0.1:9582/xhr/json');
|
||||
restore();
|
||||
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual('basic', response.type);
|
||||
testing.expectEqual(false, response.redirected);
|
||||
|
||||
const json = await response.json();
|
||||
testing.expectEqual('9000!!!', json.over);
|
||||
testing.expectEqual("number", typeof json.updated_at);
|
||||
testing.expectEqual(1765867200000, json.updated_at);
|
||||
testing.expectEqual({over: '9000!!!',updated_at:1765867200000}, json);
|
||||
});
|
||||
state.resolve();
|
||||
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual('basic', response.type);
|
||||
testing.expectEqual(false, response.redirected);
|
||||
|
||||
testing.expectEqual('9000!!!', json.over);
|
||||
testing.expectEqual("number", typeof json.updated_at);
|
||||
testing.expectEqual(1765867200000, json.updated_at);
|
||||
testing.expectEqual({over: '9000!!!',updated_at:1765867200000}, json);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=fetch_post>
|
||||
testing.async(async (restore) => {
|
||||
<script id=fetch_post type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const response = await fetch('http://127.0.0.1:9582/xhr', {
|
||||
method: 'POST',
|
||||
body: 'foo'
|
||||
});
|
||||
restore();
|
||||
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
|
||||
const text = await response.text();
|
||||
testing.expectEqual(true, text.length > 64);
|
||||
});
|
||||
state.resolve();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual(true, text.length > 64);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=fetch_redirect>
|
||||
testing.async(async (restore) => {
|
||||
<script id=fetch_redirect type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const response = await fetch('http://127.0.0.1:9582/xhr/redirect');
|
||||
restore();
|
||||
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual('http://127.0.0.1:9582/xhr', response.url);
|
||||
testing.expectEqual(true, response.redirected);
|
||||
|
||||
const text = await response.text();
|
||||
testing.expectEqual(100, text.length);
|
||||
});
|
||||
state.resolve();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual('http://127.0.0.1:9582/xhr', response.url);
|
||||
testing.expectEqual(true, response.redirected);
|
||||
|
||||
testing.expectEqual(100, text.length);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=fetch_404>
|
||||
testing.async(async (restore) => {
|
||||
<script id=fetch_404 type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const response = await fetch('http://127.0.0.1:9582/xhr/404');
|
||||
restore();
|
||||
|
||||
testing.expectEqual(404, response.status);
|
||||
testing.expectEqual(false, response.ok);
|
||||
|
||||
const text = await response.text();
|
||||
testing.expectEqual('Not Found', text);
|
||||
});
|
||||
state.resolve();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(404, response.status);
|
||||
testing.expectEqual(false, response.ok);
|
||||
|
||||
testing.expectEqual('Not Found', text);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=fetch_500>
|
||||
testing.async(async (restore) => {
|
||||
<script id=fetch_500 type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const response = await fetch('http://127.0.0.1:9582/xhr/500');
|
||||
restore();
|
||||
|
||||
testing.expectEqual(500, response.status);
|
||||
testing.expectEqual(false, response.ok);
|
||||
|
||||
const text = await response.text();
|
||||
testing.expectEqual('Internal Server Error', text);
|
||||
});
|
||||
state.resolve();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(500, response.status);
|
||||
testing.expectEqual(false, response.ok);
|
||||
testing.expectEqual('Internal Server Error', text);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=fetch_request_object>
|
||||
testing.async(async (restore) => {
|
||||
<script id=fetch_request_object type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const request = new Request('http://127.0.0.1:9582/xhr', {
|
||||
method: 'GET'
|
||||
});
|
||||
@@ -108,29 +127,33 @@
|
||||
testing.expectEqual('GET', request.method);
|
||||
|
||||
const response = await fetch(request);
|
||||
restore();
|
||||
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
|
||||
const text = await response.text();
|
||||
testing.expectEqual(100, text.length);
|
||||
});
|
||||
state.resolve();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual(100, text.length);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=fetch_request_with_headers>
|
||||
testing.async(async (restore) => {
|
||||
<script id=fetch_request_with_headers type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const response = await fetch('http://127.0.0.1:9582/xhr', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Custom-Header': 'test-value'
|
||||
}
|
||||
});
|
||||
restore();
|
||||
state.resolve();
|
||||
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
});
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=fetch_request_with_method_variations>
|
||||
@@ -154,8 +177,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=response_constructor>
|
||||
testing.async(async (restore) => {
|
||||
<script id=response_constructor type=module>
|
||||
{
|
||||
const response1 = new Response(null);
|
||||
testing.expectEqual(200, response1.status);
|
||||
testing.expectEqual(true, response1.ok);
|
||||
@@ -163,79 +186,97 @@
|
||||
const response2 = new Response('Hello', {
|
||||
status: 201,
|
||||
});
|
||||
testing.expectEqual(201, response2.status);
|
||||
testing.expectEqual(true, response2.ok);
|
||||
|
||||
const state = await testing.async();
|
||||
const text = await response2.text();
|
||||
restore();
|
||||
state.resolve();
|
||||
|
||||
testing.expectEqual('Hello', text);
|
||||
});
|
||||
await state.done(() => {
|
||||
testing.expectEqual(201, response2.status);
|
||||
testing.expectEqual(true, response2.ok);
|
||||
testing.expectEqual('Hello', text);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=response_body_stream>
|
||||
testing.async(async (restore) => {
|
||||
<script id=response_body_stream type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const response = await fetch('http://127.0.0.1:9582/xhr');
|
||||
restore();
|
||||
|
||||
testing.expectEqual(true, response.body !== null);
|
||||
testing.expectEqual(true, response.body instanceof ReadableStream);
|
||||
state.resolve();
|
||||
|
||||
const buf = await response.arrayBuffer()
|
||||
restore();
|
||||
|
||||
const uint8array = new Uint8Array(buf);
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
testing.expectEqual('1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890', decoder.decode(uint8array));
|
||||
});
|
||||
await state.done(() => {
|
||||
testing.expectEqual(true, response.body !== null);
|
||||
testing.expectEqual(true, response.body instanceof ReadableStream);
|
||||
|
||||
|
||||
const uint8array = new Uint8Array(buf);
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
testing.expectEqual('1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890', decoder.decode(uint8array));
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<script id=response_empty_body>
|
||||
testing.async(async (restore) => {
|
||||
<script id=response_empty_body type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const response = new Response('');
|
||||
testing.expectEqual(200, response.status);
|
||||
|
||||
const text = await response.text();
|
||||
restore();
|
||||
state.resolve();
|
||||
|
||||
testing.expectEqual('', text);
|
||||
// Empty body should still create a valid stream
|
||||
testing.expectEqual(true, response.body !== null);
|
||||
});
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, response.status);
|
||||
|
||||
testing.expectEqual('', text);
|
||||
// Empty body should still create a valid stream
|
||||
testing.expectEqual(true, response.body !== null);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=fetch_blob_url>
|
||||
testing.async(async (restore) => {
|
||||
<script id=fetch_blob_url type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
// Create a blob and get its URL
|
||||
const blob = new Blob(['Hello from blob!'], { type: 'text/plain' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
const response = await fetch(blobUrl);
|
||||
restore();
|
||||
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual(blobUrl, response.url);
|
||||
testing.expectEqual('text/plain', response.headers.get('Content-Type'));
|
||||
|
||||
const text = await response.text();
|
||||
testing.expectEqual('Hello from blob!', text);
|
||||
|
||||
// Clean up
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
});
|
||||
state.resolve();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, response.status);
|
||||
testing.expectEqual(true, response.ok);
|
||||
testing.expectEqual(blobUrl, response.url);
|
||||
testing.expectEqual('text/plain', response.headers.get('Content-Type'));
|
||||
|
||||
testing.expectEqual('Hello from blob!', text);
|
||||
|
||||
// Clean up
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=abort>
|
||||
testing.async(async (restore) => {
|
||||
<script id=abort type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
try {
|
||||
await fetch('http://127.0.0.1:9582/xhr', { signal: controller.signal });
|
||||
testain.fail('fetch should have been aborted');
|
||||
state.resolve();
|
||||
await state.done(() => {
|
||||
testing.fail('fetch should have been aborted');
|
||||
});
|
||||
} catch (e) {
|
||||
restore();
|
||||
testing.expectEqual("AbortError", e.name);
|
||||
state.resolve();
|
||||
await state.done(() => {
|
||||
testing.expectEqual("AbortError", e.name);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
const req = new Request('/path');
|
||||
testing.expectEqual(true, req.url.includes('/path'));
|
||||
}
|
||||
|
||||
const req = new Request('https://example.com/api/hello world');
|
||||
testing.expectEqual('https://example.com/api/hello%20world', req.url);
|
||||
</script>
|
||||
|
||||
<script id=headers>
|
||||
|
||||
@@ -561,3 +561,21 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=ws_within_callback type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
let nested = false;
|
||||
let ws1 = new WebSocket('ws://127.0.0.1:9584/');
|
||||
|
||||
ws1.addEventListener('open', () => {
|
||||
let ws2 = new WebSocket('ws://127.0.0.1:9584/');
|
||||
ws2.addEventListener('open', state.resolve);
|
||||
});
|
||||
|
||||
await state.done(() => {
|
||||
// this case used to fail, so just reaching here is a success!
|
||||
testing.expectTrue(true);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,332 +1,335 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id=xhr>
|
||||
<script id=xhr type=module>
|
||||
testing.expectEqual(0, XMLHttpRequest.UNSENT);
|
||||
testing.expectEqual(1, XMLHttpRequest.OPENED);
|
||||
testing.expectEqual(2, XMLHttpRequest.HEADERS_RECEIVED);
|
||||
testing.expectEqual(3, XMLHttpRequest.LOADING);
|
||||
testing.expectEqual(4, XMLHttpRequest.DONE);
|
||||
|
||||
testing.async(async (restore) => {
|
||||
{
|
||||
const state = await testing.async();
|
||||
const req = new XMLHttpRequest();
|
||||
const event = await new Promise((resolve) => {
|
||||
function cbk(event) {
|
||||
resolve(event)
|
||||
}
|
||||
|
||||
req.onload = cbk;
|
||||
testing.expectEqual(cbk, req.onload);
|
||||
req.onload = cbk;
|
||||
function cbk(event) {
|
||||
state.resolve(event)
|
||||
}
|
||||
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
testing.expectEqual(0, req.status);
|
||||
testing.expectEqual('', req.statusText);
|
||||
testing.expectEqual('', req.getAllResponseHeaders());
|
||||
testing.expectEqual(null, req.getResponseHeader('Content-Type'));
|
||||
testing.expectEqual('', req.responseText);
|
||||
testing.expectEqual('', req.responseURL);
|
||||
req.send();
|
||||
req.onload = cbk;
|
||||
testing.expectEqual(cbk, req.onload);
|
||||
req.onload = cbk;
|
||||
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
testing.expectEqual(0, req.status);
|
||||
testing.expectEqual('', req.statusText);
|
||||
testing.expectEqual('', req.getAllResponseHeaders());
|
||||
testing.expectEqual(null, req.getResponseHeader('Content-Type'));
|
||||
testing.expectEqual('', req.responseText);
|
||||
testing.expectEqual('', req.responseURL);
|
||||
req.send();
|
||||
|
||||
|
||||
await state.done((event) => {
|
||||
testing.expectEqual('load', event.type);
|
||||
testing.expectEqual(true, event.loaded > 0);
|
||||
testing.expectEqual(true, event instanceof ProgressEvent);
|
||||
testing.expectEqual(200, req.status);
|
||||
testing.expectEqual('OK', req.statusText);
|
||||
testing.expectEqual('text/html; charset=utf-8', req.getResponseHeader('Content-Type'));
|
||||
testing.expectEqual('content-length: 100\r\nContent-Type: text/html; charset=utf-8\r\n', req.getAllResponseHeaders());
|
||||
testing.expectEqual(100, req.responseText.length);
|
||||
testing.expectEqual(req.responseText.length, req.response.length);
|
||||
testing.expectEqual('http://127.0.0.1:9582/xhr', req.responseURL);
|
||||
});
|
||||
|
||||
restore();
|
||||
testing.expectEqual('load', event.type);
|
||||
testing.expectEqual(true, event.loaded > 0);
|
||||
testing.expectEqual(true, event instanceof ProgressEvent);
|
||||
testing.expectEqual(200, req.status);
|
||||
testing.expectEqual('OK', req.statusText);
|
||||
testing.expectEqual('text/html; charset=utf-8', req.getResponseHeader('Content-Type'));
|
||||
testing.expectEqual('content-length: 100\r\nContent-Type: text/html; charset=utf-8\r\n', req.getAllResponseHeaders());
|
||||
testing.expectEqual(100, req.responseText.length);
|
||||
testing.expectEqual(req.responseText.length, req.response.length);
|
||||
testing.expectEqual('http://127.0.0.1:9582/xhr', req.responseURL);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr2>
|
||||
const req2 = new XMLHttpRequest()
|
||||
testing.async(async (restore) => {
|
||||
await new Promise((resolve) => {
|
||||
req2.onload = resolve;
|
||||
req2.open('GET', 'http://127.0.0.1:9582/xhr')
|
||||
req2.responseType = 'document';
|
||||
req2.send()
|
||||
});
|
||||
<script id=xhr2 type=module type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
restore();
|
||||
testing.expectEqual(200, req2.status);
|
||||
testing.expectEqual('OK', req2.statusText);
|
||||
testing.expectEqual(true, req2.response instanceof Document);
|
||||
testing.expectEqual(true, req2.responseXML instanceof Document);
|
||||
});
|
||||
const req2 = new XMLHttpRequest()
|
||||
req2.onload = () => { state.resolve() };
|
||||
req2.open('GET', 'http://127.0.0.1:9582/xhr')
|
||||
req2.responseType = 'document';
|
||||
req2.send()
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, req2.status);
|
||||
testing.expectEqual('OK', req2.statusText);
|
||||
testing.expectEqual(true, req2.response instanceof Document);
|
||||
testing.expectEqual(true, req2.responseXML instanceof Document);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr3>
|
||||
const req3 = new XMLHttpRequest()
|
||||
testing.async(async (restore) => {
|
||||
await new Promise((resolve) => {
|
||||
req3.onload = resolve;
|
||||
req3.open('GET', 'http://127.0.0.1:9582/xhr/json')
|
||||
req3.responseType = 'json';
|
||||
req3.send()
|
||||
});
|
||||
<script id=xhr3 type=module type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
restore();
|
||||
testing.expectEqual(200, req3.status);
|
||||
testing.expectEqual('OK', req3.statusText);
|
||||
testing.expectEqual('9000!!!', req3.response.over);
|
||||
testing.expectEqual("number", typeof json.updated_at);
|
||||
testing.expectEqual(1765867200000, json.updated_at);
|
||||
testing.expectEqual({over: '9000!!!',updated_at:1765867200000}, json);
|
||||
});
|
||||
const req3 = new XMLHttpRequest()
|
||||
req3.onload = () => { state.resolve() };
|
||||
req3.open('GET', 'http://127.0.0.1:9582/xhr/json')
|
||||
req3.responseType = 'json';
|
||||
req3.send();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, req3.status);
|
||||
testing.expectEqual('OK', req3.statusText);
|
||||
testing.expectEqual('9000!!!', req3.response.over);
|
||||
testing.expectEqual("number", typeof json.updated_at);
|
||||
testing.expectEqual(1765867200000, json.updated_at);
|
||||
testing.expectEqual({over: '9000!!!',updated_at:1765867200000}, json);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr4>
|
||||
const req4 = new XMLHttpRequest()
|
||||
testing.async(async (restore) => {
|
||||
await new Promise((resolve) => {
|
||||
req4.onload = resolve;
|
||||
req4.open('POST', 'http://127.0.0.1:9582/xhr')
|
||||
req4.send('foo')
|
||||
});
|
||||
<script id=xhr4 type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
restore();
|
||||
testing.expectEqual(200, req4.status);
|
||||
testing.expectEqual('OK', req4.statusText);
|
||||
testing.expectEqual(true, req4.responseText.length > 64);
|
||||
});
|
||||
const req4 = new XMLHttpRequest();
|
||||
req4.onload = () => { state.resolve() };
|
||||
req4.open('POST', 'http://127.0.0.1:9582/xhr')
|
||||
req4.send('foo');
|
||||
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, req4.status);
|
||||
testing.expectEqual('OK', req4.statusText);
|
||||
testing.expectEqual(true, req4.responseText.length > 64);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr5>
|
||||
testing.async(async (restore) => {
|
||||
let state = [];
|
||||
<script id=xhr5 type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
let records = [];
|
||||
const req5 = new XMLHttpRequest();
|
||||
|
||||
const result = await new Promise((resolve) => {
|
||||
req5.onreadystatechange = (e) => {
|
||||
state.push(req5.readyState);
|
||||
if (req5.readyState === XMLHttpRequest.DONE) {
|
||||
resolve({states: state, target: e.currentTarget});
|
||||
}
|
||||
req5.onreadystatechange = (e) => {
|
||||
records.push(req5.readyState);
|
||||
if (req5.readyState === XMLHttpRequest.DONE) {
|
||||
state.resolve({records: records, target: e.currentTarget});
|
||||
}
|
||||
}
|
||||
req5.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
req5.send();
|
||||
|
||||
req5.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
req5.send();
|
||||
await state.done((result) => {
|
||||
const {records: records, target: target} = result;
|
||||
testing.expectEqual(4, records.length)
|
||||
testing.expectEqual(XMLHttpRequest.OPENED, records[0]);
|
||||
testing.expectEqual(XMLHttpRequest.HEADERS_RECEIVED, records[1]);
|
||||
testing.expectEqual(XMLHttpRequest.LOADING, records[2]);
|
||||
testing.expectEqual(XMLHttpRequest.DONE, records[3]);
|
||||
testing.expectEqual(req5, target);
|
||||
});
|
||||
|
||||
restore();
|
||||
const {states: states, target: target} = result;
|
||||
testing.expectEqual(4, states.length)
|
||||
testing.expectEqual(XMLHttpRequest.OPENED, states[0]);
|
||||
testing.expectEqual(XMLHttpRequest.HEADERS_RECEIVED, states[1]);
|
||||
testing.expectEqual(XMLHttpRequest.LOADING, states[2]);
|
||||
testing.expectEqual(XMLHttpRequest.DONE, states[3]);
|
||||
testing.expectEqual(req5, target);
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr6>
|
||||
const req6 = new XMLHttpRequest()
|
||||
testing.async(async (restore) => {
|
||||
await new Promise((resolve) => {
|
||||
req6.onload = resolve;
|
||||
req6.open('GET', 'http://127.0.0.1:9582/xhr/binary')
|
||||
req6.responseType ='arraybuffer'
|
||||
req6.send()
|
||||
});
|
||||
<script id=xhr6 type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
restore();
|
||||
testing.expectEqual(200, req6.status);
|
||||
testing.expectEqual('OK', req6.statusText);
|
||||
testing.expectEqual(7, req6.response.byteLength);
|
||||
testing.expectEqual([0, 0, 1, 2, 0, 0, 9], new Int32Array(req6.response));
|
||||
testing.expectEqual('', typeof req6.response);
|
||||
testing.expectEqual('arraybuffer', req6.responseType);
|
||||
});
|
||||
const req6 = new XMLHttpRequest();
|
||||
req6.onload = () => { state.resolve() };
|
||||
req6.open('GET', 'http://127.0.0.1:9582/xhr/binary')
|
||||
req6.responseType ='arraybuffer';
|
||||
req6.send();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, req6.status);
|
||||
testing.expectEqual('OK', req6.statusText);
|
||||
testing.expectEqual(7, req6.response.byteLength);
|
||||
testing.expectEqual([0, 0, 1, 2, 0, 0, 9], new Int32Array(req6.response));
|
||||
testing.expectEqual('', typeof req6.response);
|
||||
testing.expectEqual('arraybuffer', req6.responseType);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_redirect>
|
||||
testing.async(async (restore) => {
|
||||
<script id=xhr_redirect type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const req = new XMLHttpRequest();
|
||||
await new Promise((resolve) => {
|
||||
req.onload = resolve;
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr/redirect');
|
||||
req.send();
|
||||
});
|
||||
req.onload = () => { state.resolve() };
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr/redirect');
|
||||
req.send();
|
||||
|
||||
restore();
|
||||
testing.expectEqual(200, req.status);
|
||||
testing.expectEqual('OK', req.statusText);
|
||||
testing.expectEqual('http://127.0.0.1:9582/xhr', req.responseURL);
|
||||
testing.expectEqual(100, req.responseText.length);
|
||||
});
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, req.status);
|
||||
testing.expectEqual('OK', req.statusText);
|
||||
testing.expectEqual('http://127.0.0.1:9582/xhr', req.responseURL);
|
||||
testing.expectEqual(100, req.responseText.length);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_404>
|
||||
testing.async(async (restore) => {
|
||||
|
||||
<script id=xhr_404 type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const req = new XMLHttpRequest();
|
||||
await new Promise((resolve) => {
|
||||
req.onload = resolve;
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr/404');
|
||||
req.send();
|
||||
});
|
||||
req.onload = () => { state.resolve() };
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr/404');
|
||||
req.send();
|
||||
|
||||
restore();
|
||||
testing.expectEqual(404, req.status);
|
||||
testing.expectEqual('Not Found', req.statusText);
|
||||
testing.expectEqual('Not Found', req.responseText);
|
||||
});
|
||||
await state.done(() => {
|
||||
testing.expectEqual(404, req.status);
|
||||
testing.expectEqual('Not Found', req.statusText);
|
||||
testing.expectEqual('Not Found', req.responseText);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_500>
|
||||
testing.async(async (restore) => {
|
||||
<script id=xhr_500 type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const req = new XMLHttpRequest();
|
||||
await new Promise((resolve) => {
|
||||
req.onload = resolve;
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr/500');
|
||||
req.send();
|
||||
});
|
||||
req.onload = () => { state.resolve() };
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr/500');
|
||||
req.send();
|
||||
|
||||
restore();
|
||||
testing.expectEqual(500, req.status);
|
||||
testing.expectEqual('Internal Server Error', req.statusText);
|
||||
testing.expectEqual('Internal Server Error', req.responseText);
|
||||
});
|
||||
await state.done(() => {
|
||||
testing.expectEqual(500, req.status);
|
||||
testing.expectEqual('Internal Server Error', req.statusText);
|
||||
testing.expectEqual('Internal Server Error', req.responseText);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_abort>
|
||||
testing.async(async (restore) => {
|
||||
<script id=xhr_abort type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const req = new XMLHttpRequest();
|
||||
let abortFired = false;
|
||||
let errorFired = false;
|
||||
let loadEndFired = false;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
req.onabort = () => { abortFired = true; };
|
||||
req.onerror = () => { errorFired = true; };
|
||||
req.onloadend = () => {
|
||||
loadEndFired = true;
|
||||
resolve();
|
||||
};
|
||||
req.onabort = () => { abortFired = true; };
|
||||
req.onerror = () => { errorFired = true; };
|
||||
req.onloadend = () => {
|
||||
loadEndFired = true;
|
||||
state.resolve();
|
||||
};
|
||||
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
req.send();
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
req.send();
|
||||
req.abort();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(true, abortFired);
|
||||
testing.expectEqual(true, errorFired);
|
||||
testing.expectEqual(true, loadEndFired);
|
||||
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_abort_callback type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const req = new XMLHttpRequest();
|
||||
let abortFired = false;
|
||||
let errorFired = false;
|
||||
let loadEndFired = false;
|
||||
|
||||
req.onabort = () => { abortFired = true; };
|
||||
req.onerror = () => { errorFired = true; };
|
||||
req.onloadend = () => {
|
||||
loadEndFired = true;
|
||||
state.resolve();
|
||||
};
|
||||
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
req.onreadystatechange = (e) => {
|
||||
req.abort();
|
||||
});
|
||||
}
|
||||
req.send();
|
||||
|
||||
restore();
|
||||
testing.expectEqual(true, abortFired);
|
||||
testing.expectEqual(true, errorFired);
|
||||
testing.expectEqual(true, loadEndFired);
|
||||
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
||||
});
|
||||
await state.done(() => {
|
||||
testing.expectEqual(true, abortFired);
|
||||
testing.expectEqual(true, errorFired);
|
||||
testing.expectEqual(true, loadEndFired);
|
||||
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_abort_callback>
|
||||
testing.async(async (restore) => {
|
||||
<script id=xhr_abort_callback_nobody type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const req = new XMLHttpRequest();
|
||||
let abortFired = false;
|
||||
let errorFired = false;
|
||||
let loadEndFired = false;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
req.onabort = () => { abortFired = true; };
|
||||
req.onerror = () => { errorFired = true; };
|
||||
req.onloadend = () => {
|
||||
loadEndFired = true;
|
||||
resolve();
|
||||
};
|
||||
req.onabort = () => { abortFired = true; };
|
||||
req.onerror = () => { errorFired = true; };
|
||||
req.onloadend = () => {
|
||||
loadEndFired = true;
|
||||
state.resolve();
|
||||
};
|
||||
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
req.onreadystatechange = (e) => {
|
||||
req.abort();
|
||||
}
|
||||
req.send();
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr_empty');
|
||||
req.onreadystatechange = (e) => {
|
||||
req.abort();
|
||||
}
|
||||
req.send();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(true, abortFired);
|
||||
testing.expectEqual(true, errorFired);
|
||||
testing.expectEqual(true, loadEndFired);
|
||||
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
||||
});
|
||||
|
||||
restore();
|
||||
testing.expectEqual(true, abortFired);
|
||||
testing.expectEqual(true, errorFired);
|
||||
testing.expectEqual(true, loadEndFired);
|
||||
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_blob_url type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
<script id=xhr_abort_callback_nobody>
|
||||
testing.async(async (restore) => {
|
||||
const req = new XMLHttpRequest();
|
||||
let abortFired = false;
|
||||
let errorFired = false;
|
||||
let loadEndFired = false;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
req.onabort = () => { abortFired = true; };
|
||||
req.onerror = () => { errorFired = true; };
|
||||
req.onloadend = () => {
|
||||
loadEndFired = true;
|
||||
resolve();
|
||||
};
|
||||
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr_empty');
|
||||
req.onreadystatechange = (e) => {
|
||||
req.abort();
|
||||
}
|
||||
req.send();
|
||||
});
|
||||
|
||||
restore();
|
||||
testing.expectEqual(true, abortFired);
|
||||
testing.expectEqual(true, errorFired);
|
||||
testing.expectEqual(true, loadEndFired);
|
||||
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
||||
});
|
||||
</script>
|
||||
|
||||
<script id=xhr_blob_url>
|
||||
testing.async(async (restore) => {
|
||||
// Create a blob and get its URL
|
||||
const blob = new Blob(['Hello from blob!'], { type: 'text/plain' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
const req = new XMLHttpRequest();
|
||||
await new Promise((resolve) => {
|
||||
req.onload = resolve;
|
||||
req.open('GET', blobUrl);
|
||||
req.send();
|
||||
req.onload = () => { state.resolve() };
|
||||
req.open('GET', blobUrl);
|
||||
req.send();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(200, req.status);
|
||||
testing.expectEqual('Hello from blob!', req.responseText);
|
||||
testing.expectEqual(blobUrl, req.responseURL);
|
||||
|
||||
// Clean up
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
});
|
||||
|
||||
restore();
|
||||
testing.expectEqual(200, req.status);
|
||||
testing.expectEqual('Hello from blob!', req.responseText);
|
||||
testing.expectEqual(blobUrl, req.responseURL);
|
||||
|
||||
// Clean up
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_timeout>
|
||||
// timeout property: default is 0
|
||||
const req = new XMLHttpRequest();
|
||||
testing.expectEqual(0, req.timeout);
|
||||
<script id=xhr_timeout type=module>
|
||||
{
|
||||
// timeout property: default is 0
|
||||
const req = new XMLHttpRequest();
|
||||
testing.expectEqual(0, req.timeout);
|
||||
|
||||
// timeout can be set and read back
|
||||
req.timeout = 5000;
|
||||
testing.expectEqual(5000, req.timeout);
|
||||
|
||||
// request with timeout set succeeds normally when server responds in time
|
||||
testing.async(async (restore) => {
|
||||
const event = await new Promise((resolve) => {
|
||||
req.onload = resolve;
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
req.send();
|
||||
});
|
||||
|
||||
restore();
|
||||
testing.expectEqual('load', event.type);
|
||||
testing.expectEqual(200, req.status);
|
||||
// timeout can be set and read back
|
||||
req.timeout = 5000;
|
||||
testing.expectEqual(5000, req.timeout);
|
||||
});
|
||||
|
||||
// request with timeout set succeeds normally when server responds in time
|
||||
const state = await testing.async();
|
||||
req.onload = (e) => { state.resolve(e) };
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr');
|
||||
req.send();
|
||||
|
||||
await state.done((event) => {
|
||||
testing.expectEqual('load', event.type);
|
||||
testing.expectEqual(200, req.status);
|
||||
testing.expectEqual(5000, req.timeout);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
<body></body>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id="gbk_encoding">
|
||||
<script id="gbk_encoding" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
iframe.src = 'encoding/gbk.html';
|
||||
iframe.onload = () => { state.resolve(); }
|
||||
|
||||
testing.onload(() => {
|
||||
await state.done(() => {
|
||||
// GBK-encoded "中文" should be decoded to UTF-8
|
||||
testing.expectEqual('中文', iframe.contentDocument.getElementById('test').textContent);
|
||||
// document.characterSet should return canonical encoding name
|
||||
@@ -19,57 +21,67 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="shift_jis_encoding">
|
||||
<script id="shift_jis_encoding" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
iframe.src = 'encoding/shift_jis.html';
|
||||
iframe.onload = () => { state.resolve(); }
|
||||
|
||||
testing.onload(() => {
|
||||
await state.done(() => {
|
||||
// Shift_JIS-encoded "日本語" should be decoded to UTF-8
|
||||
testing.expectEqual('日本語', iframe.contentDocument.getElementById('test').textContent);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="latin1_encoding">
|
||||
<script id="latin1_encoding" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
iframe.src = 'encoding/latin1.html';
|
||||
iframe.onload = () => { state.resolve(); }
|
||||
|
||||
testing.onload(() => {
|
||||
await state.done(() => {
|
||||
// ISO-8859-1-encoded "Café" should be decoded to UTF-8
|
||||
testing.expectEqual('Café', iframe.contentDocument.getElementById('test').textContent);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="content_type_header_charset">
|
||||
<script id="content_type_header_charset" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
// Test charset from Content-Type HTTP header (no meta charset in file)
|
||||
// TestHTTPServer returns "text/html; charset=GB2312" for *.GB2312.html files
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
iframe.src = testing.BASE_URL + 'page/encoding/content_type.GB2312.html';
|
||||
iframe.onload = () => { state.resolve(); }
|
||||
|
||||
testing.onload(() => {
|
||||
await state.done(() => {
|
||||
// GB2312-encoded "中文" should be decoded to UTF-8 via Content-Type header charset
|
||||
testing.expectEqual('中文', iframe.contentDocument.getElementById('test').textContent);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="no_charset_fallback">
|
||||
<script id="no_charset_fallback" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
// Test file with non-UTF-8 bytes but NO charset declaration anywhere.
|
||||
// Without charset info, the bytes are parsed as UTF-8, producing replacement characters.
|
||||
// This documents the "broken" behavior for files without proper encoding declaration.
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
iframe.src = 'encoding/no_charset.html';
|
||||
iframe.onload = () => { state.resolve(); }
|
||||
|
||||
testing.onload(() => {
|
||||
await state.done(() => {
|
||||
// The GBK bytes D6 D0 CE C4 are invalid UTF-8, each becomes U+FFFD
|
||||
const text = iframe.contentDocument.getElementById('test').textContent;
|
||||
// Should contain replacement characters (the exact count depends on how invalid bytes are handled)
|
||||
@@ -78,16 +90,19 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="anchor_href_encoding_with_ncr">
|
||||
<script id="anchor_href_encoding_with_ncr" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
// Test that anchor.href encodes unmappable characters as NCRs in non-UTF-8 documents.
|
||||
// When a character can't be represented in the document's encoding, it should become &#nnnnn;
|
||||
// Per WHATWG URL Standard, query strings use document encoding with NCR fallback.
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
iframe.src = 'encoding/gbk.html';
|
||||
iframe.onload = () => { state.resolve(); }
|
||||
|
||||
testing.onload(() => {
|
||||
await state.done(() => {
|
||||
testing.expectEqual('GBK', iframe.contentDocument.characterSet);
|
||||
|
||||
// Test 1: U+3D34 (㴴) - a Han character NOT in GBK, should become NCR 㴴
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
let eventuallies = [];
|
||||
let async_capture = null;
|
||||
let current_script_id = null;
|
||||
let async_pending = 0;
|
||||
let async_pending = new Set();
|
||||
|
||||
function expectTrue(actual) {
|
||||
expectEqual(true, actual);
|
||||
@@ -71,26 +71,29 @@
|
||||
}
|
||||
|
||||
async function async(cb) {
|
||||
const script_id = document.currentScript.id;
|
||||
|
||||
if (cb == undefined) {
|
||||
let resolve = null
|
||||
const promise = new Promise((r) => { resolve = r});
|
||||
async_pending += 1;
|
||||
async_pending.add(script_id);
|
||||
|
||||
|
||||
return {
|
||||
promise: promise,
|
||||
resolve: resolve,
|
||||
capture: {script_id: document.currentScript.id, stack: new Error().stack},
|
||||
capture: {script_id: script_id, stack: new Error().stack},
|
||||
done: async function(cb) {
|
||||
await this.promise;
|
||||
async_pending -= 1;
|
||||
const res = await this.promise;
|
||||
async_pending.delete(script_id);
|
||||
async_capture = this.capture;
|
||||
cb();
|
||||
cb(res);
|
||||
async_capture = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let capture = {script_id: document.currentScript.id, stack: new Error().stack};
|
||||
let capture = {script_id: script_id, stack: new Error().stack};
|
||||
await cb(() => { async_capture = capture; });
|
||||
async_capture = null;
|
||||
}
|
||||
@@ -100,7 +103,7 @@
|
||||
throw new Error('Failed');
|
||||
}
|
||||
|
||||
if (async_pending > 0) {
|
||||
if (async_pending.size > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -131,6 +134,10 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
function printTimeoutState() {
|
||||
console.warn('Pending count:', Array.from(async_pending));
|
||||
}
|
||||
|
||||
const IS_TEST_RUNNER = window.navigator.userAgent.startsWith("Lightpanda/");
|
||||
|
||||
window.testing = {
|
||||
@@ -142,6 +149,7 @@
|
||||
expectEqual: expectEqual,
|
||||
expectError: expectError,
|
||||
withError: withError,
|
||||
printTimeoutState: printTimeoutState,
|
||||
onload: onload,
|
||||
IS_TEST_RUNNER: IS_TEST_RUNNER,
|
||||
HOST: '127.0.0.1',
|
||||
|
||||
@@ -1,35 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id=setInterval>
|
||||
let set_interval1 = false
|
||||
let timer1 = window.setInterval(function() {
|
||||
set_interval1 = true;
|
||||
testing.expectEqual(window, this);
|
||||
}, 1);
|
||||
<script id=setInterval type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
let set_interval2 = false
|
||||
let timer2 = window.setInterval(function() {
|
||||
set_interval2 = true;
|
||||
}, 1)
|
||||
window.clearInterval(timer2);
|
||||
let set_interval1 = false
|
||||
let timer1 = window.setInterval(function() {
|
||||
set_interval1 = true;
|
||||
testing.expectEqual(window, this);
|
||||
}, 1);
|
||||
|
||||
testing.expectEqual(true, timer1 != timer2);
|
||||
let set_interval2 = false
|
||||
let timer2 = window.setInterval(function() {
|
||||
set_interval2 = true;
|
||||
}, 1)
|
||||
window.clearInterval(timer2);
|
||||
|
||||
testing.expectEqual(true, timer1 != timer2);
|
||||
|
||||
testing.onload(() => {
|
||||
testing.expectEqual(true, set_interval1);
|
||||
testing.expectEqual(false, set_interval2);
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
state.resolve()
|
||||
}, 5);
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(true, set_interval1);
|
||||
testing.expectEqual(false, set_interval2);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=setTimeout>
|
||||
<script id=setTimeout type=module>
|
||||
const state = await testing.async();
|
||||
|
||||
testing.expectEqual(1, window.setTimeout.length);
|
||||
let wst2 = 1;
|
||||
|
||||
window.setTimeout((a, b) => {
|
||||
wst2 = a + b;
|
||||
state.resolve();
|
||||
}, 1, 2, 3);
|
||||
testing.onload(() => testing.expectEqual(5, wst2));
|
||||
|
||||
await state.done(() => testing.expectEqual(5, wst2));
|
||||
</script>
|
||||
|
||||
<script id=invalid-timer-clear>
|
||||
|
||||
@@ -11,48 +11,51 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_message">
|
||||
testing.async(async (capture) => {
|
||||
<script id="worker_message" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const worker = new Worker('./echo-worker.js');
|
||||
|
||||
const response = await new Promise((resolve) => {
|
||||
worker.onmessage = function(event) {
|
||||
resolve(event.data);
|
||||
};
|
||||
// Give the script time to load before posting
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ greeting: 'hello' });
|
||||
}, 100);
|
||||
});
|
||||
worker.onmessage = function(event) {
|
||||
state.resolve(event.data);
|
||||
};
|
||||
|
||||
capture();
|
||||
testing.expectEqual('hello', response.echo.greeting);
|
||||
testing.expectEqual('worker', response.from);
|
||||
});
|
||||
// Give the script time to load before posting
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ greeting: 'hello' });
|
||||
}, 100);
|
||||
|
||||
await state.done((response) => {
|
||||
testing.expectEqual('hello', response.echo.greeting);
|
||||
testing.expectEqual('worker', response.from);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_structured_clone_date">
|
||||
testing.async(async (capture) => {
|
||||
<script id="worker_structured_clone_date" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const worker = new Worker('./echo-worker.js');
|
||||
|
||||
const testDate = new Date('2024-06-15T12:30:00Z');
|
||||
const response = await new Promise((resolve) => {
|
||||
worker.onmessage = function(event) {
|
||||
resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ date: testDate });
|
||||
}, 100);
|
||||
});
|
||||
worker.onmessage = function(event) {
|
||||
state.resolve(event.data);
|
||||
};
|
||||
|
||||
capture();
|
||||
testing.expectTrue(response.echo.date instanceof Date);
|
||||
testing.expectEqual(testDate.getTime(), response.echo.date.getTime());
|
||||
});
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ date: testDate });
|
||||
}, 100);
|
||||
|
||||
await state.done((response) => {
|
||||
testing.expectTrue(response.echo.date instanceof Date);
|
||||
testing.expectEqual(testDate.getTime(), response.echo.date.getTime());
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_structured_clone_arraybuffer">
|
||||
testing.async(async (capture) => {
|
||||
<script id="worker_structured_clone_arraybuffer" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const worker = new Worker('./echo-worker.js');
|
||||
|
||||
const buffer = new ArrayBuffer(8);
|
||||
@@ -60,118 +63,118 @@
|
||||
view[0] = 1; view[1] = 2; view[2] = 3; view[3] = 4;
|
||||
view[4] = 5; view[5] = 6; view[6] = 7; view[7] = 8;
|
||||
|
||||
const response = await new Promise((resolve) => {
|
||||
worker.onmessage = function(event) {
|
||||
resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ buffer: buffer });
|
||||
}, 100);
|
||||
});
|
||||
worker.onmessage = function(event) {
|
||||
state.resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ buffer: buffer });
|
||||
}, 100);
|
||||
|
||||
capture();
|
||||
testing.expectTrue(response.echo.buffer instanceof ArrayBuffer);
|
||||
testing.expectEqual(8, response.echo.buffer.byteLength);
|
||||
const resultView = new Uint8Array(response.echo.buffer);
|
||||
testing.expectEqual(1, resultView[0]);
|
||||
testing.expectEqual(8, resultView[7]);
|
||||
});
|
||||
await state.done((response) => {
|
||||
testing.expectTrue(response.echo.buffer instanceof ArrayBuffer);
|
||||
testing.expectEqual(8, response.echo.buffer.byteLength);
|
||||
const resultView = new Uint8Array(response.echo.buffer);
|
||||
testing.expectEqual(1, resultView[0]);
|
||||
testing.expectEqual(8, resultView[7]);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_structured_clone_typedarray">
|
||||
testing.async(async (capture) => {
|
||||
<script id="worker_structured_clone_typedarray" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const worker = new Worker('./echo-worker.js');
|
||||
|
||||
const arr = new Float64Array([1.5, 2.5, 3.5, 4.5]);
|
||||
|
||||
const response = await new Promise((resolve) => {
|
||||
worker.onmessage = function(event) {
|
||||
resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ arr: arr });
|
||||
}, 100);
|
||||
});
|
||||
worker.onmessage = function(event) {
|
||||
state.resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ arr: arr });
|
||||
}, 100);
|
||||
|
||||
capture();
|
||||
testing.expectTrue(response.echo.arr instanceof Float64Array);
|
||||
testing.expectEqual(4, response.echo.arr.length);
|
||||
testing.expectEqual(1.5, response.echo.arr[0]);
|
||||
testing.expectEqual(4.5, response.echo.arr[3]);
|
||||
});
|
||||
await state.done((response) => {
|
||||
testing.expectTrue(response.echo.arr instanceof Float64Array);
|
||||
testing.expectEqual(4, response.echo.arr.length);
|
||||
testing.expectEqual(1.5, response.echo.arr[0]);
|
||||
testing.expectEqual(4.5, response.echo.arr[3]);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_structured_clone_map">
|
||||
testing.async(async (capture) => {
|
||||
<script id="worker_structured_clone_map" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const worker = new Worker('./echo-worker.js');
|
||||
|
||||
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
|
||||
|
||||
const response = await new Promise((resolve) => {
|
||||
worker.onmessage = function(event) {
|
||||
resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ map: map });
|
||||
}, 100);
|
||||
});
|
||||
worker.onmessage = function(event) {
|
||||
state.resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ map: map });
|
||||
}, 100);
|
||||
|
||||
capture();
|
||||
testing.expectTrue(response.echo.map instanceof Map);
|
||||
testing.expectEqual(3, response.echo.map.size);
|
||||
testing.expectEqual(1, response.echo.map.get('a'));
|
||||
testing.expectEqual(3, response.echo.map.get('c'));
|
||||
});
|
||||
await state.done((response) => {
|
||||
testing.expectTrue(response.echo.map instanceof Map);
|
||||
testing.expectEqual(3, response.echo.map.size);
|
||||
testing.expectEqual(1, response.echo.map.get('a'));
|
||||
testing.expectEqual(3, response.echo.map.get('c'));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_structured_clone_set">
|
||||
testing.async(async (capture) => {
|
||||
<script id="worker_structured_clone_set" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const worker = new Worker('./echo-worker.js');
|
||||
|
||||
const set = new Set([1, 2, 3, 'four', 'five']);
|
||||
|
||||
const response = await new Promise((resolve) => {
|
||||
worker.onmessage = function(event) {
|
||||
resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ set: set });
|
||||
}, 100);
|
||||
});
|
||||
worker.onmessage = function(event) {
|
||||
state.resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ set: set });
|
||||
}, 100);
|
||||
|
||||
capture();
|
||||
testing.expectTrue(response.echo.set instanceof Set);
|
||||
testing.expectEqual(5, response.echo.set.size);
|
||||
testing.expectTrue(response.echo.set.has(1));
|
||||
testing.expectTrue(response.echo.set.has('four'));
|
||||
});
|
||||
await state.done((response) => {
|
||||
testing.expectTrue(response.echo.set instanceof Set);
|
||||
testing.expectEqual(5, response.echo.set.size);
|
||||
testing.expectTrue(response.echo.set.has(1));
|
||||
testing.expectTrue(response.echo.set.has('four'));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_structured_clone_regexp">
|
||||
testing.async(async (capture) => {
|
||||
<script id="worker_structured_clone_regexp" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const worker = new Worker('./echo-worker.js');
|
||||
|
||||
const regex = /hello.*world/gi;
|
||||
|
||||
const response = await new Promise((resolve) => {
|
||||
worker.onmessage = function(event) {
|
||||
resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ regex: regex });
|
||||
}, 100);
|
||||
});
|
||||
worker.onmessage = function(event) {
|
||||
state.resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage({ regex: regex });
|
||||
}, 100);
|
||||
|
||||
capture();
|
||||
testing.expectTrue(response.echo.regex instanceof RegExp);
|
||||
testing.expectEqual('hello.*world', response.echo.regex.source);
|
||||
testing.expectTrue(response.echo.regex.global);
|
||||
testing.expectTrue(response.echo.regex.ignoreCase);
|
||||
});
|
||||
await state.done((response) => {
|
||||
testing.expectTrue(response.echo.regex instanceof RegExp);
|
||||
testing.expectEqual('hello.*world', response.echo.regex.source);
|
||||
testing.expectTrue(response.echo.regex.global);
|
||||
testing.expectTrue(response.echo.regex.ignoreCase);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_structured_clone_nested">
|
||||
testing.async(async (capture) => {
|
||||
<script id="worker_structured_clone_nested" type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const worker = new Worker('./echo-worker.js');
|
||||
|
||||
const complex = {
|
||||
@@ -184,23 +187,22 @@
|
||||
buffer: new Uint8Array([10, 20, 30]).buffer
|
||||
};
|
||||
|
||||
const response = await new Promise((resolve) => {
|
||||
worker.onmessage = function(event) {
|
||||
resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage(complex);
|
||||
}, 100);
|
||||
});
|
||||
worker.onmessage = function(event) {
|
||||
state.resolve(event.data);
|
||||
};
|
||||
setTimeout(() => {
|
||||
worker.postMessage(complex);
|
||||
}, 100);
|
||||
|
||||
capture();
|
||||
testing.expectEqual('hello', response.echo.string);
|
||||
testing.expectEqual(42, response.echo.number);
|
||||
testing.expectEqual(true, response.echo.boolean);
|
||||
testing.expectEqual(null, response.echo.null);
|
||||
testing.expectEqual(3, response.echo.array.length);
|
||||
testing.expectEqual('value', response.echo.array[2].nested);
|
||||
testing.expectTrue(response.echo.date instanceof Date);
|
||||
testing.expectTrue(response.echo.buffer instanceof ArrayBuffer);
|
||||
});
|
||||
await state.done((response) => {
|
||||
testing.expectEqual('hello', response.echo.string);
|
||||
testing.expectEqual(42, response.echo.number);
|
||||
testing.expectEqual(true, response.echo.boolean);
|
||||
testing.expectEqual(null, response.echo.null);
|
||||
testing.expectEqual(3, response.echo.array.length);
|
||||
testing.expectEqual('value', response.echo.array[2].nested);
|
||||
testing.expectTrue(response.echo.date instanceof Date);
|
||||
testing.expectTrue(response.echo.buffer instanceof ArrayBuffer);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -59,69 +59,23 @@ const InitOptions = struct {
|
||||
endings: []const u8 = "transparent",
|
||||
};
|
||||
|
||||
/// Creates a new Blob (JS constructor).
|
||||
pub fn init(
|
||||
maybe_blob_parts: ?[]const []const u8,
|
||||
maybe_options: ?InitOptions,
|
||||
page: *Page,
|
||||
) !*Blob {
|
||||
return initWithMimeValidation(maybe_blob_parts, maybe_options, false, page);
|
||||
}
|
||||
|
||||
/// Creates a new Blob with optional MIME validation.
|
||||
/// When validate_mime is true, uses full MIME parsing (for Response/Request).
|
||||
/// When false, uses simple ASCII validation per FileAPI spec (for Blob constructor).
|
||||
pub fn initWithMimeValidation(
|
||||
maybe_blob_parts: ?[]const []const u8,
|
||||
maybe_options: ?InitOptions,
|
||||
validate_mime: bool,
|
||||
page: *Page,
|
||||
) !*Blob {
|
||||
const data_len = blk: {
|
||||
const parts = maybe_blob_parts orelse break :blk 0;
|
||||
var size: usize = 0;
|
||||
for (parts) |p| {
|
||||
size += p.len;
|
||||
}
|
||||
break :blk size;
|
||||
};
|
||||
const arena = try page.getArena(256 + data_len, "Blob");
|
||||
/// Creates a new Blob from JS values with optional MIME validation.
|
||||
/// This is the JS Constructor
|
||||
pub fn init(parts_: ?[]const js.Value, opts_: ?InitOptions, page: *Page) !*Blob {
|
||||
const arena = try page.getArena(.large, "Blob");
|
||||
errdefer page.releaseArena(arena);
|
||||
|
||||
const options: InitOptions = maybe_options orelse .{};
|
||||
|
||||
const mime: []const u8 = blk: {
|
||||
const t = options.type;
|
||||
if (t.len == 0) {
|
||||
break :blk "";
|
||||
}
|
||||
|
||||
const buf = try arena.dupe(u8, t);
|
||||
|
||||
if (validate_mime) {
|
||||
// Full MIME parsing per MIME sniff spec (for Content-Type headers)
|
||||
_ = Mime.parse(buf) catch break :blk "";
|
||||
} else {
|
||||
// Simple validation per FileAPI spec (for Blob constructor):
|
||||
// - If any char is outside U+0020-U+007E, return empty string
|
||||
// - Otherwise lowercase
|
||||
for (t) |c| {
|
||||
if (c < 0x20 or c > 0x7E) {
|
||||
break :blk "";
|
||||
}
|
||||
}
|
||||
_ = std.ascii.lowerString(buf, buf);
|
||||
}
|
||||
|
||||
break :blk buf;
|
||||
};
|
||||
const opts: InitOptions = opts_ orelse .{};
|
||||
const mime = try validateMimeType(arena, opts.type, false);
|
||||
|
||||
const data = blk: {
|
||||
if (maybe_blob_parts) |blob_parts| {
|
||||
if (parts_) |blob_parts| {
|
||||
const use_native_endings = std.mem.eql(u8, opts.endings, "native");
|
||||
var w: Writer.Allocating = .init(arena);
|
||||
const use_native_endings = std.mem.eql(u8, options.endings, "native");
|
||||
try writeBlobParts(&w.writer, blob_parts, use_native_endings);
|
||||
|
||||
for (blob_parts) |js_val| {
|
||||
const part = try js_val.toStringSmart();
|
||||
try writePartWithEndings(part, use_native_endings, &w.writer);
|
||||
}
|
||||
break :blk w.written();
|
||||
}
|
||||
|
||||
@@ -139,6 +93,50 @@ pub fn initWithMimeValidation(
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Creates a new Blob from raw byte slices (for internal Zig use).
|
||||
pub fn initFromBytes(data: []const u8, content_type: []const u8, validate_mime: bool, page: *Page) !*Blob {
|
||||
const arena = try page.getArena(.large, "Blob");
|
||||
errdefer page.releaseArena(arena);
|
||||
|
||||
const mime = try validateMimeType(arena, content_type, validate_mime);
|
||||
|
||||
const self = try arena.create(Blob);
|
||||
self.* = .{
|
||||
._rc = .{},
|
||||
._arena = arena,
|
||||
._type = .generic,
|
||||
._slice = try arena.dupe(u8, data),
|
||||
._mime = mime,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Validates and normalizes MIME type according to spec.
|
||||
fn validateMimeType(arena: Allocator, mime_type: []const u8, full_validation: bool) ![]const u8 {
|
||||
if (mime_type.len == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const buf = try arena.dupe(u8, mime_type);
|
||||
|
||||
if (full_validation) {
|
||||
// Full MIME parsing per MIME sniff spec (for Content-Type headers)
|
||||
_ = Mime.parse(buf) catch return "";
|
||||
} else {
|
||||
// Simple validation per FileAPI spec (for Blob constructor):
|
||||
// - If any char is outside U+0020-U+007E, return empty string
|
||||
// - Otherwise lowercase
|
||||
for (mime_type) |c| {
|
||||
if (c < 0x20 or c > 0x7E) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
_ = std.ascii.lowerString(buf, buf);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Blob, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
}
|
||||
@@ -171,18 +169,11 @@ const vector_sizes = blk: {
|
||||
break :blk items;
|
||||
};
|
||||
|
||||
/// Writes blob parts to given `Writer` with desired endings.
|
||||
fn writeBlobParts(
|
||||
writer: *Writer,
|
||||
blob_parts: []const []const u8,
|
||||
use_native_endings: bool,
|
||||
) !void {
|
||||
// Transparent.
|
||||
/// Writes a single part with optional line ending normalization.
|
||||
fn writePartWithEndings(part: []const u8, use_native_endings: bool, writer: *Writer) !void {
|
||||
// Transparent - no conversion needed.
|
||||
if (!use_native_endings) {
|
||||
for (blob_parts) |part| {
|
||||
try writer.writeAll(part);
|
||||
}
|
||||
|
||||
try writer.writeAll(part);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -204,68 +195,66 @@ fn writeBlobParts(
|
||||
// ```
|
||||
// "the quick\n\nbrown fox"
|
||||
// ```
|
||||
scan_parts: for (blob_parts) |part| {
|
||||
var end: usize = 0;
|
||||
var end: usize = 0;
|
||||
|
||||
inline for (vector_sizes) |vector_len| {
|
||||
const Vec = @Vector(vector_len, u8);
|
||||
inline for (vector_sizes) |vector_len| {
|
||||
const Vec = @Vector(vector_len, u8);
|
||||
|
||||
while (end + vector_len <= part.len) : (end += vector_len) {
|
||||
const cr: Vec = @splat('\r');
|
||||
// Load chunk as vectors.
|
||||
const data = part[end..][0..vector_len];
|
||||
const chunk: Vec = data.*;
|
||||
// Look for CR.
|
||||
const match = chunk == cr;
|
||||
while (end + vector_len <= part.len) : (end += vector_len) {
|
||||
const cr: Vec = @splat('\r');
|
||||
// Load chunk as vectors.
|
||||
const data = part[end..][0..vector_len];
|
||||
const chunk: Vec = data.*;
|
||||
// Look for CR.
|
||||
const match = chunk == cr;
|
||||
|
||||
// Create a bitset out of match vector.
|
||||
const bitset = std.bit_set.IntegerBitSet(vector_len){
|
||||
.mask = @bitCast(@intFromBool(match)),
|
||||
};
|
||||
// Create a bitset out of match vector.
|
||||
const bitset = std.bit_set.IntegerBitSet(vector_len){
|
||||
.mask = @bitCast(@intFromBool(match)),
|
||||
};
|
||||
|
||||
var iter = bitset.iterator(.{});
|
||||
var relative_start: usize = 0;
|
||||
while (iter.next()) |index| {
|
||||
_ = try writer.writeVec(&.{ data[relative_start..index], "\n" });
|
||||
var iter = bitset.iterator(.{});
|
||||
var relative_start: usize = 0;
|
||||
while (iter.next()) |index| {
|
||||
_ = try writer.writeVec(&.{ data[relative_start..index], "\n" });
|
||||
|
||||
if (index + 1 != data.len and data[index + 1] == '\n') {
|
||||
relative_start = index + 2;
|
||||
} else {
|
||||
relative_start = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
_ = try writer.writeVec(&.{data[relative_start..]});
|
||||
}
|
||||
}
|
||||
|
||||
// Scalar scan fallback.
|
||||
var relative_start: usize = end;
|
||||
while (end < part.len) {
|
||||
if (part[end] == '\r') {
|
||||
_ = try writer.writeVec(&.{ part[relative_start..end], "\n" });
|
||||
|
||||
// Part ends with CR. We can continue to next part.
|
||||
if (end + 1 == part.len) {
|
||||
continue :scan_parts;
|
||||
}
|
||||
|
||||
// If next char is LF, skip it too.
|
||||
if (part[end + 1] == '\n') {
|
||||
relative_start = end + 2;
|
||||
if (index + 1 != data.len and data[index + 1] == '\n') {
|
||||
relative_start = index + 2;
|
||||
} else {
|
||||
relative_start = end + 1;
|
||||
relative_start = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
end += 1;
|
||||
_ = try writer.writeVec(&.{data[relative_start..]});
|
||||
}
|
||||
}
|
||||
|
||||
// Scalar scan fallback.
|
||||
var relative_start: usize = end;
|
||||
while (end < part.len) {
|
||||
if (part[end] == '\r') {
|
||||
_ = try writer.writeVec(&.{ part[relative_start..end], "\n" });
|
||||
|
||||
// Part ends with CR. We need to remember this for next part.
|
||||
if (end + 1 == part.len) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If next char is LF, skip it too.
|
||||
if (part[end + 1] == '\n') {
|
||||
relative_start = end + 2;
|
||||
} else {
|
||||
relative_start = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the remaining. We get this in such situations:
|
||||
// `the quick brown\rfox`
|
||||
// `the quick brown\r\nfox`
|
||||
try writer.writeAll(part[relative_start..end]);
|
||||
end += 1;
|
||||
}
|
||||
|
||||
// Write the remaining. We get this in such situations:
|
||||
// `the quick brown\rfox`
|
||||
// `the quick brown\r\nfox`
|
||||
try writer.writeAll(part[relative_start..end]);
|
||||
}
|
||||
|
||||
/// Returns a Promise that resolves with the contents of the blob
|
||||
@@ -323,7 +312,7 @@ pub fn slice(
|
||||
break :blk @min(data.len, @max(start, @as(u31, @intCast(requested_end))));
|
||||
};
|
||||
|
||||
return Blob.init(&.{data[start..end]}, .{ .type = content_type_ orelse "" }, page);
|
||||
return Blob.initFromBytes(data[start..end], content_type_ orelse "", false, page);
|
||||
}
|
||||
|
||||
/// Returns the size of the Blob in bytes.
|
||||
|
||||
@@ -165,6 +165,10 @@ pub fn getSessionStorage(self: *Window) *storage.Lookup {
|
||||
return &self._storage_bucket.session;
|
||||
}
|
||||
|
||||
pub fn getOrigin(self: *const Window) []const u8 {
|
||||
return self._page.origin orelse "null";
|
||||
}
|
||||
|
||||
pub fn getLocation(self: *const Window) *Location {
|
||||
return self._location;
|
||||
}
|
||||
@@ -827,6 +831,7 @@ pub const JsApi = struct {
|
||||
pub const performance = bridge.accessor(Window.getPerformance, null, .{});
|
||||
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{});
|
||||
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{});
|
||||
pub const origin = bridge.accessor(Window.getOrigin, null, .{});
|
||||
pub const location = bridge.accessor(Window.getLocation, Window.setLocation, .{ .deletable = false });
|
||||
pub const history = bridge.accessor(Window.getHistory, null, .{});
|
||||
pub const navigation = bridge.accessor(Window.getNavigation, null, .{});
|
||||
|
||||
@@ -232,9 +232,16 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||
return ls.toLocal(self._resolver).resolve("fetch done", js_val);
|
||||
}
|
||||
|
||||
fn httpErrorCallback(ctx: *anyopaque, _: anyerror) void {
|
||||
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||
const self: *Fetch = @ptrCast(@alignCast(ctx));
|
||||
|
||||
log.info(.http, "request error", .{
|
||||
.source = "fetch",
|
||||
.url = self._url,
|
||||
.status = self._response._status,
|
||||
.err = err,
|
||||
});
|
||||
|
||||
var response = self._response;
|
||||
response._http_response = null;
|
||||
// the response is only passed on v8 on success, if we're here, it's safe to
|
||||
|
||||
@@ -73,7 +73,7 @@ const Cache = enum {
|
||||
pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request {
|
||||
const arena = page.arena;
|
||||
const url = switch (input) {
|
||||
.url => |u| try URL.resolve(arena, page.base(), u, .{ .always_dupe = true }),
|
||||
.url => |u| try URL.resolve(arena, page.base(), u, .{ .always_dupe = true, .encoding = page.charset }),
|
||||
.request => |r| try arena.dupeZ(u8, r._url),
|
||||
};
|
||||
|
||||
@@ -174,12 +174,7 @@ pub fn blob(self: *Request, page: *Page) !js.Promise {
|
||||
const headers = try self.getHeaders(page);
|
||||
const content_type = try headers.get("content-type", page) orelse "";
|
||||
|
||||
const b = try Blob.initWithMimeValidation(
|
||||
&.{body},
|
||||
.{ .type = content_type },
|
||||
true,
|
||||
page,
|
||||
);
|
||||
const b = try Blob.initFromBytes(body, content_type, true, page);
|
||||
|
||||
return page.js.local.?.resolvePromise(b);
|
||||
}
|
||||
|
||||
@@ -83,29 +83,10 @@ pub fn init(body_: ?BodyInit, opts_: ?InitOpts, page: *Page) !*Response {
|
||||
.bytes => |body_bytes| break :blk .{ .bytes = try arena.dupe(u8, body_bytes) },
|
||||
.stream => |stream| break :blk .{ .stream = stream },
|
||||
.js_val => |js_val| {
|
||||
const local = page.js.local.?;
|
||||
|
||||
if (local.jsValueToZig(*ReadableStream, js_val)) |stream| {
|
||||
break :blk .{ .stream = stream };
|
||||
} else |_| {}
|
||||
|
||||
if (js_val.isString()) |js_str| {
|
||||
break :blk .{ .bytes = try js_str.toSliceWithAlloc(arena) };
|
||||
}
|
||||
|
||||
if (js_val.isArrayBuffer() or js_val.isTypedArray() or js_val.isArrayBufferView()) {
|
||||
if (local.jsValueToZig([]u8, js_val)) |data| {
|
||||
break :blk .{ .bytes = try arena.dupe(u8, data) };
|
||||
} else |_| {}
|
||||
}
|
||||
|
||||
if (local.jsValueToZig(*Blob, js_val)) |blob_obj| {
|
||||
break :blk .{ .bytes = try arena.dupe(u8, blob_obj._slice) };
|
||||
} else |_| {}
|
||||
|
||||
if (js_val.isNullOrUndefined() == false) {
|
||||
break :blk .{ .bytes = try js_val.toStringSliceWithAlloc(arena) };
|
||||
if (js_val.isNullOrUndefined()) {
|
||||
break :blk .empty;
|
||||
}
|
||||
break :blk .{ .bytes = try arena.dupe(u8, try js_val.toStringSmart()) };
|
||||
},
|
||||
}
|
||||
break :blk .empty;
|
||||
@@ -335,14 +316,7 @@ pub fn blob(self: *const Response, page: *Page) !js.Promise {
|
||||
.stream => return local.rejectPromise(.{ .type_error = "Cannot read blob from stream body" }),
|
||||
};
|
||||
const content_type = try self._headers.get("content-type", page) orelse "";
|
||||
|
||||
const b = try Blob.initWithMimeValidation(
|
||||
&.{body},
|
||||
.{ .type = content_type },
|
||||
true,
|
||||
page,
|
||||
);
|
||||
|
||||
const b = try Blob.initFromBytes(body, content_type, true, page);
|
||||
return local.resolvePromise(b);
|
||||
}
|
||||
|
||||
|
||||
@@ -466,7 +466,7 @@ fn dispatchMessageEvent(self: *WebSocket, data: []const u8, frame_type: http.WsF
|
||||
switch (self._binary_type) {
|
||||
.arraybuffer => .{ .arraybuffer = .{ .values = data } },
|
||||
.blob => blk: {
|
||||
const blob = try Blob.init(&.{data}, .{}, page);
|
||||
const blob = try Blob.initFromBytes(data, "", false, page);
|
||||
blob.acquireRef();
|
||||
break :blk .{ .blob = blob };
|
||||
},
|
||||
|
||||
@@ -76,6 +76,13 @@ pub fn parse(allocator: Allocator, url: [:0]const u8, str: []const u8) !Cookie {
|
||||
return error.InvalidNameValue;
|
||||
};
|
||||
|
||||
if (cookie_name.len == 0 and (std.ascii.startsWithIgnoreCase(cookie_value, "__Host-") or std.ascii.startsWithIgnoreCase(cookie_value, "__Secure-"))) {
|
||||
// A nameless cookie whose value begins with __Host- or __Secure-
|
||||
// (case-insensitive) would otherwise impersonate a cookie with that
|
||||
// prefix. Reject per the cookie-name-prefix rules.
|
||||
return error.InvalidNameValue;
|
||||
}
|
||||
|
||||
var scrap: [8]u8 = undefined;
|
||||
|
||||
var path: ?[]const u8 = null;
|
||||
@@ -127,6 +134,34 @@ pub fn parse(allocator: Allocator, url: [:0]const u8, str: []const u8) !Cookie {
|
||||
return error.InsecureSameSite;
|
||||
}
|
||||
|
||||
// Enforce cookie-name-prefix rules. Match is case-insensitive to
|
||||
// cover impersonation attempts (e.g. "__HoSt-").
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#name-cookie-name-prefixes
|
||||
if (std.ascii.startsWithIgnoreCase(cookie_name, "__Host-")) {
|
||||
if (secure == null) {
|
||||
return error.InvalidPrefixedCookie;
|
||||
}
|
||||
|
||||
if (!std.mem.startsWith(u8, url, "https://")) {
|
||||
return error.InvalidPrefixedCookie;
|
||||
}
|
||||
|
||||
if (domain != null and domain.?.len > 0) {
|
||||
return error.InvalidPrefixedCookie;
|
||||
}
|
||||
|
||||
if (path == null or !std.mem.eql(u8, path.?, "/")) {
|
||||
return error.InvalidPrefixedCookie;
|
||||
}
|
||||
} else if (std.ascii.startsWithIgnoreCase(cookie_name, "__Secure-")) {
|
||||
if (secure == null) {
|
||||
return error.InvalidPrefixedCookie;
|
||||
}
|
||||
if (!std.mem.startsWith(u8, url, "https://")) {
|
||||
return error.InvalidPrefixedCookie;
|
||||
}
|
||||
}
|
||||
|
||||
if (cookie_value.len > max_cookie_size) {
|
||||
return error.CookieSizeExceeded;
|
||||
}
|
||||
@@ -183,6 +218,7 @@ const ValidateCookieError = error{ Empty, InvalidByteSequence };
|
||||
|
||||
/// Returns an error if cookie str length is 0
|
||||
/// or contains characters outside of the ascii range 32...126.
|
||||
/// Tab (0x09) is also allowed, matching browser behavior and WPT.
|
||||
fn validateCookieString(str: []const u8) ValidateCookieError!void {
|
||||
if (str.len == 0) {
|
||||
return error.Empty;
|
||||
@@ -195,17 +231,16 @@ fn validateCookieString(str: []const u8) ValidateCookieError!void {
|
||||
if (comptime vec_size_suggestion) |size| {
|
||||
while (str.len - offset >= size) : (offset += size) {
|
||||
const Vec = @Vector(size, u8);
|
||||
const tab: Vec = @splat(9);
|
||||
const space: Vec = @splat(32);
|
||||
const tilde: Vec = @splat(126);
|
||||
const chunk: Vec = str[offset..][0..size].*;
|
||||
|
||||
// This creates a mask where invalid characters represented
|
||||
// as ones and valid characters as zeros. We then bitCast this
|
||||
// into an unsigned integer. If the integer is not equal to 0,
|
||||
// we know that we've invalid characters in this chunk.
|
||||
// @popCount can also be used but using integers are simpler.
|
||||
const mask = (@intFromBool(chunk < space) | @intFromBool(chunk > tilde));
|
||||
const reduced: std.meta.Int(.unsigned, size) = @bitCast(mask);
|
||||
// Invalid if (c < 32 AND c != 9) OR c > 126. Tab is the one
|
||||
// sub-space byte we allow through (per browser/WPT behavior).
|
||||
const below = @intFromBool(chunk < space) & @intFromBool(chunk != tab);
|
||||
const above = @intFromBool(chunk > tilde);
|
||||
const reduced: std.meta.Int(.unsigned, size) = @bitCast(below | above);
|
||||
|
||||
// Got match.
|
||||
if (reduced != 0) {
|
||||
@@ -221,11 +256,10 @@ fn validateCookieString(str: []const u8) ValidateCookieError!void {
|
||||
}
|
||||
|
||||
// Either remaining slice or the original if fast path not taken.
|
||||
const slice = str[offset..];
|
||||
// Slow path.
|
||||
const min, const max = std.mem.minMax(u8, slice);
|
||||
if (min < 32 or max > 126) {
|
||||
return error.InvalidByteSequence;
|
||||
for (str[offset..]) |c| {
|
||||
if ((c < 32 and c != 9) or c > 126) {
|
||||
return error.InvalidByteSequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,6 +906,52 @@ test "Cookie: parse key=value" {
|
||||
try expectError(error.InvalidByteSequence, null, &.{ 'a', '=', 'b', 20 });
|
||||
try expectError(error.InvalidByteSequence, null, &.{ 'a', '=', 'b', 128 });
|
||||
|
||||
// Tab (0x09) is allowed in name and value, matching browser/WPT behavior.
|
||||
try expectAttribute(.{ .name = "a\tb", .value = "c" }, null, "a\tb=c");
|
||||
try expectAttribute(.{ .name = "a", .value = "b\tc" }, null, "a=b\tc");
|
||||
// Other control characters remain rejected.
|
||||
try expectError(error.InvalidByteSequence, null, "a\nb=c");
|
||||
try expectError(error.InvalidByteSequence, null, "a\rb=c");
|
||||
try expectError(error.InvalidByteSequence, null, &.{ 'a', '=', 'b', 0 });
|
||||
|
||||
// Nameless cookies whose value begins with __Host- or __Secure-
|
||||
// (case-insensitive) are rejected so they can't impersonate prefixed cookies.
|
||||
try expectError(error.InvalidNameValue, null, "=__Host-abc=1");
|
||||
try expectError(error.InvalidNameValue, null, "=__Secure-abc=1");
|
||||
try expectError(error.InvalidNameValue, null, "=__HoSt-abc");
|
||||
try expectError(error.InvalidNameValue, null, "__Secure-abc");
|
||||
|
||||
// __Host- cookie-name-prefix rules:
|
||||
// - must be Secure
|
||||
// - must be set from an https origin
|
||||
// - must not have a Domain attribute
|
||||
// - must have Path=/
|
||||
try expectAttribute(.{ .name = "__Host-abc", .value = "1" }, "https://lightpanda.io/", "__Host-abc=1; Secure; Path=/");
|
||||
try expectAttribute(.{ .name = "__HoSt-abc", .value = "1" }, "https://lightpanda.io/", "__HoSt-abc=1; Secure; Path=/");
|
||||
try expectError(error.InvalidPrefixedCookie, "https://lightpanda.io/", "__Host-abc=1; Path=/");
|
||||
try expectError(error.InvalidPrefixedCookie, null, "__Host-abc=1; Secure; Path=/");
|
||||
try expectError(error.InvalidPrefixedCookie, "https://lightpanda.io/", "__Host-abc=1; Secure");
|
||||
try expectError(error.InvalidPrefixedCookie, "https://lightpanda.io/", "__Host-abc=1; Secure; Path=/foo");
|
||||
try expectError(error.InvalidPrefixedCookie, "https://lightpanda.io/", "__Host-abc=1; Secure; Path=/; Domain=lightpanda.io");
|
||||
|
||||
// __Secure- cookie-name-prefix rules: must be Secure and from https.
|
||||
try expectAttribute(.{ .name = "__Secure-abc", .value = "1" }, "https://lightpanda.io/", "__Secure-abc=1; Secure");
|
||||
try expectAttribute(.{ .name = "__SeCuRe-abc", .value = "1" }, "https://lightpanda.io/", "__SeCuRe-abc=1; Secure; Domain=lightpanda.io");
|
||||
try expectError(error.InvalidPrefixedCookie, "https://lightpanda.io/", "__Secure-abc=1");
|
||||
try expectError(error.InvalidPrefixedCookie, null, "__Secure-abc=1; Secure");
|
||||
|
||||
// Empty Domain= is treated as no Domain and accepted on __Host-.
|
||||
try expectAttribute(.{ .name = "__Host-abc", .value = "1" }, "https://lightpanda.io/", "__Host-abc=1; Secure; Path=/; Domain=");
|
||||
|
||||
// __Host- with additional unrelated attributes remains valid.
|
||||
try expectAttribute(.{ .name = "__Host-abc", .value = "1" }, "https://lightpanda.io/", "__Host-abc=1; Secure; Path=/; Max-Age=60; HttpOnly");
|
||||
|
||||
// Near-misses are not subject to the prefix rules.
|
||||
try expectAttribute(.{ .name = "__Host", .value = "1" }, null, "__Host=1");
|
||||
try expectAttribute(.{ .name = "_Host-abc", .value = "1" }, null, "_Host-abc=1");
|
||||
try expectAttribute(.{ .name = "__Hos-abc", .value = "1" }, null, "__Hos-abc=1");
|
||||
try expectAttribute(.{ .name = "__Secure", .value = "1" }, null, "__Secure=1");
|
||||
|
||||
try expectAttribute(.{ .name = "", .value = "a" }, null, "a");
|
||||
try expectAttribute(.{ .name = "", .value = "a" }, null, "a;");
|
||||
try expectAttribute(.{ .name = "", .value = "a b" }, null, "a b");
|
||||
|
||||
@@ -141,7 +141,7 @@ fn setLifecycleEventsEnabled(cmd: *CDP.Command) !void {
|
||||
try sendPageLifecycle(bc, "load", now, frame_id, loader_id);
|
||||
|
||||
const http_client = page._session.browser.http_client;
|
||||
const http_active = http_client.active;
|
||||
const http_active = http_client.http_active;
|
||||
const total_network_activity = http_active + http_client.intercepted;
|
||||
if (page._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
||||
try sendPageLifecycle(bc, "networkAlmostIdle", now, frame_id, loader_id);
|
||||
|
||||
139
src/html5ever/Cargo.lock
generated
139
src/html5ever/Cargo.lock
generated
@@ -39,31 +39,26 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
|
||||
dependencies = [
|
||||
"mac",
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.35.0"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4"
|
||||
checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"markup5ever",
|
||||
"match_token",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -78,7 +73,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"html5ever",
|
||||
"string_cache 0.9.0",
|
||||
"string_cache",
|
||||
"tikv-jemalloc-ctl",
|
||||
"tikv-jemallocator",
|
||||
"typed-arena",
|
||||
@@ -101,34 +96,17 @@ version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.35.0"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3"
|
||||
checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de"
|
||||
dependencies = [
|
||||
"log",
|
||||
"tendril",
|
||||
"web_atoms",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_token"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.6"
|
||||
@@ -166,40 +144,32 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
"phf_shared",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared 0.11.3",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
"fastrand",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -235,21 +205,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.12"
|
||||
@@ -303,19 +258,6 @@ version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"phf_shared 0.11.3",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.9.0"
|
||||
@@ -324,19 +266,19 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"phf_shared 0.13.1",
|
||||
"phf_shared",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.5.4"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
|
||||
checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared 0.11.3",
|
||||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
@@ -354,20 +296,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.4.3"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
|
||||
checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24"
|
||||
dependencies = [
|
||||
"futf",
|
||||
"mac",
|
||||
"new_debug_unreachable",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tikv-jemalloc-ctl"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f21f216790c8df74ce3ab25b534e0718da5a1916719771d3fec23315c99e468b"
|
||||
checksum = "661f1f6a57b3a36dc9174a2c10f19513b4866816e13425d3e418b11cc37bc24c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"paste",
|
||||
@@ -376,9 +317,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tikv-jemalloc-sys"
|
||||
version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
|
||||
version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d"
|
||||
checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -386,9 +327,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tikv-jemallocator"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865"
|
||||
checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"tikv-jemalloc-sys",
|
||||
@@ -414,13 +355,13 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "web_atoms"
|
||||
version = "0.1.3"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414"
|
||||
checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576"
|
||||
dependencies = [
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"string_cache 0.8.9",
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
]
|
||||
|
||||
@@ -490,9 +431,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "xml5ever"
|
||||
version = "0.35.0"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494"
|
||||
checksum = "5ab627f34ff61b80d756180d556f9c68801d836d271b3b8c094504ceca69d221"
|
||||
dependencies = [
|
||||
"log",
|
||||
"markup5ever",
|
||||
|
||||
@@ -9,12 +9,12 @@ path = "lib.rs"
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
html5ever = "0.35.0"
|
||||
html5ever = "0.39.0"
|
||||
string_cache = "0.9.0"
|
||||
typed-arena = "2.0.2"
|
||||
tikv-jemallocator = {version = "0.6.0", features = ["stats"]}
|
||||
tikv-jemalloc-ctl = {version = "0.6.0", features = ["stats"]}
|
||||
xml5ever = "0.35.0"
|
||||
tikv-jemallocator = {version = "0.6.1", features = ["stats"]}
|
||||
tikv-jemalloc-ctl = {version = "0.6.1", features = ["stats"]}
|
||||
xml5ever = "0.39.0"
|
||||
encoding_rs = "0.8"
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -220,7 +220,7 @@ fn dumpWPT(page: *Page, writer: *std.Io.Writer) !void {
|
||||
\\ notrun: cases.filter(c => c.status === 'Not Run').length,
|
||||
\\ unsupported: cases.filter(c => c.status === 'Optional Feature Unsupported').length
|
||||
\\ },
|
||||
\\ cases
|
||||
\\ not_passed: cases.filter(c => c.status !== 'Pass')
|
||||
\\ };
|
||||
\\ })(), null, 2)
|
||||
;
|
||||
|
||||
@@ -262,6 +262,7 @@ fn opensocketCallback(
|
||||
|
||||
pub const Connection = struct {
|
||||
_easy: *libcurl.Curl,
|
||||
in_use: bool,
|
||||
transport: Transport,
|
||||
node: std.DoublyLinkedList.Node = .{},
|
||||
|
||||
@@ -278,7 +279,7 @@ pub const Connection = struct {
|
||||
) !Connection {
|
||||
const easy = libcurl.curl_easy_init() orelse return error.FailedToInitializeEasy;
|
||||
|
||||
var self = Connection{ ._easy = easy, .transport = .none };
|
||||
var self = Connection{ ._easy = easy, .in_use = false, .transport = .none };
|
||||
errdefer self.deinit();
|
||||
|
||||
try self.reset(config, ca_blob, ip_filter);
|
||||
|
||||
@@ -419,7 +419,7 @@ fn runWebApiTest(test_file: [:0]const u8) !void {
|
||||
}
|
||||
|
||||
var runner = try test_session.runner(.{});
|
||||
try runner.wait(.{ .ms = 2000 });
|
||||
try runner.wait(.{ .ms = 2000, .until = .load });
|
||||
|
||||
var wait_ms: u32 = 2000;
|
||||
var timer = try std.time.Timer.start();
|
||||
@@ -443,6 +443,7 @@ fn runWebApiTest(test_file: [:0]const u8) !void {
|
||||
|
||||
const ms_elapsed = timer.lap() / 1_000_000;
|
||||
if (ms_elapsed >= wait_ms) {
|
||||
ls.local.eval("testing.printTimeoutState()", "testing.printTimeoutState()") catch {};
|
||||
return error.TestTimedOut;
|
||||
}
|
||||
wait_ms -= @intCast(ms_elapsed);
|
||||
|
||||
Reference in New Issue
Block a user