Skip to content

Commit

Permalink
Implement buffer.resolveObjectURL (oven-sh#12324)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jarred-Sumner authored Jul 4, 2024
1 parent 5a0b935 commit c486a04
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 1 deletion.
9 changes: 8 additions & 1 deletion src/bun.js/modules/NodeBufferModule.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#pragma once

#include "root.h"

#include "../bindings/JSBuffer.h"
#include "_NativeModule.h"
#include "simdutf.h"
Expand Down Expand Up @@ -121,6 +125,8 @@ JSC_DEFINE_HOST_FUNCTION(jsBufferConstructorFunction_isAscii,
JSValue::encode(jsBoolean(simdutf::validate_ascii(ptr, byteLength))));
}

BUN_DECLARE_HOST_FUNCTION(jsFunctionResolveObjectURL);

JSC_DEFINE_HOST_FUNCTION(jsFunctionNotImplemented,
(JSGlobalObject * globalObject,
CallFrame *callFrame)) {
Expand Down Expand Up @@ -192,7 +198,8 @@ DEFINE_NATIVE_MODULE(NodeBuffer) {

auto *resolveObjectURL =
InternalFunction::createFunctionThatMasqueradesAsUndefined(
vm, globalObject, 1, "resolveObjectURL"_s, jsFunctionNotImplemented);
vm, globalObject, 1, "resolveObjectURL"_s,
jsFunctionResolveObjectURL);

put(JSC::Identifier::fromString(vm, "resolveObjectURL"_s), resolveObjectURL);

Expand Down
36 changes: 36 additions & 0 deletions src/bun.js/webcore/ObjectURLRegistry.zig
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ pub fn resolveAndDupe(this: *ObjectURLRegistry, pathname: []const u8) ?JSC.WebCo
return entry.blob.dupeWithContentType(true);
}

pub fn resolveAndDupeToJS(this: *ObjectURLRegistry, pathname: []const u8, globalObject: *JSC.JSGlobalObject) ?JSC.JSValue {
var blob = JSC.WebCore.Blob.new(this.resolveAndDupe(pathname) orelse return null);
blob.allocator = bun.default_allocator;
return blob.toJS(globalObject);
}

pub fn revoke(this: *ObjectURLRegistry, pathname: []const u8) void {
const uuid = uuidFromPathname(pathname) orelse return;
this.lock.lock();
Expand Down Expand Up @@ -126,9 +132,39 @@ export fn Bun__revokeObjectURL(globalObject: *JSC.JSGlobalObject, callframe: *JS
return JSC.JSValue.undefined;
}

export fn jsFunctionResolveObjectURL(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(1);

// Errors are ignored.
// Not thrown.
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/internal/blob.js#L441
if (arguments.len < 1) {
return JSC.JSValue.undefined;
}
const str = arguments.ptr[0].toBunString(globalObject);
defer str.deref();

if (globalObject.hasException()) {
return .zero;
}

if (!str.hasPrefixComptime("blob:") or str.length() < specifier_len) {
return JSC.JSValue.undefined;
}

const slice = str.toUTF8WithoutRef(bun.default_allocator);
defer slice.deinit();
const sliced = slice.slice();

const registry = ObjectURLRegistry.singleton();
const blob = registry.resolveAndDupeToJS(sliced["blob:".len..], globalObject);
return blob orelse JSC.JSValue.undefined;
}

comptime {
_ = &Bun__createObjectURL;
_ = &Bun__revokeObjectURL;
_ = &jsFunctionResolveObjectURL;
}

pub const specifier_len = "blob:".len + UUID.stringLength;
Expand Down
49 changes: 49 additions & 0 deletions test/js/node/buffer-resolveObjectURL.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Blob, resolveObjectURL } from "buffer";
import { URL } from "url";
import { test, expect } from "bun:test";

// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/internal/blob.js#L441
// https://nodejs.org/api/buffer.html#bufferresolveobjecturlid
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/test/parallel/test-blob-createobjecturl.js#L35
test("buffer.resolveObjectURL", async () => {
const blob = new Blob(["hello"]);
const id = URL.createObjectURL(blob);
expect(id).toBeString();
const otherBlob = resolveObjectURL(id)!;
expect(otherBlob).toBeInstanceOf(Blob);
expect(otherBlob.constructor).toStrictEqual(Blob);
expect(otherBlob.size).toStrictEqual(5);
expect(await otherBlob.text()).toStrictEqual("hello");
URL.revokeObjectURL(id);

// should do nothing
URL.revokeObjectURL(id);

expect(resolveObjectURL(id)).toBeUndefined();
});

test("buffer.resolveObjectURL empty blob", async () => {
const blob = new Blob();
const id = URL.createObjectURL(blob);
expect(
resolveObjectURL(
id.slice(0, id.length - 1) + String.fromCharCode(id.slice(id.length - 1, id.length).charCodeAt(0) + 1),
),
).toBeUndefined();
URL.revokeObjectURL(id);
expect(await blob.text()).toBe("");
});

test("buffer.resolveObjectURL args", async () => {
expect(resolveObjectURL()).toBeUndefined();
expect(resolveObjectURL(1)).toBeUndefined();
expect(resolveObjectURL("foo")).toBeUndefined();
const blob = new Blob(["hello"]);
const id = URL.createObjectURL(blob);
expect(
resolveObjectURL(
id.slice(0, id.length - 1) + String.fromCharCode(id.slice(id.length - 1, id.length).charCodeAt(0) + 1),
),
).toBeUndefined();
URL.revokeObjectURL(id);
});

0 comments on commit c486a04

Please sign in to comment.