Repository: denoland/deno
Pull request: fix(node): implement `resolveObjectURL` for `node:buffer`

## Summary
- Implement missing `resolveObjectURL` export from `node:buffer` (#30950)
- Fix `blobFromObjectUrl` to use `new Blob()` instead of `webidl.createBranded(Blob)` so that returned blobs can call `arrayBuffer()`/`text()`/`bytes()` without hitting V8's private field brand check ("Receiver must be an instance of class Blob")
- Add `test-blob-createobjecturl.js` to node_compat config as ignored (separate `URL.createObjectURL` `ERR_INVALID_ARG_TYPE` incompatibility)

## Test plan
- [x] Added unit tests in `tests/unit_node/buffer_test.ts` covering:
  - Resolving a valid blob URL returns a working Blob
  - Revoked URLs return `undefined`
  - Invalid inputs (non-strings, bad URLs) return `undefined`
- [x] `cargo test unit_node::buffer_test` passes

Closes #30950

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changed files:
filename: ext/node/polyfills/buffer.ts
patch: @@ -13,6 +13,7 @@ export {
   isUtf8,
   kMaxLength,
   kStringMaxLength,
+  resolveObjectURL,
   SlowBuffer,
   transcode,
 } from "ext:deno_node/internal/buffer.mjs";

filename: ext/node/polyfills/internal/buffer.mjs
patch: @@ -114,7 +114,7 @@ import {
   forgivingBase64UrlEncode,
 } from "ext:deno_web/00_infra.js";
 import { atob, btoa } from "ext:deno_web/05_base64.js";
-import { Blob, File } from "ext:deno_web/09_file.js";
+import { Blob, blobFromObjectUrl, File } from "ext:deno_web/09_file.js";
 import { untransferableSymbol } from "ext:deno_node/internal_binding/util.ts";
 
 export { atob, Blob, btoa, File };
@@ -3031,6 +3031,17 @@ export function transcode(source, fromEnco, toEnco) {
   }
 }
 
+export function resolveObjectURL(url) {
+  if (typeof url !== "string") {
+    return undefined;
+  }
+  try {
+    return blobFromObjectUrl(url) ?? undefined;
+  } catch {
+    return undefined;
+  }
+}
+
 const mod = {
   atob,
   btoa,
@@ -3049,6 +3060,7 @@ const mod = {
   },
   kMaxLength,
   kStringMaxLength,
+  resolveObjectURL,
   SlowBuffer,
   transcode,
 };

filename: ext/web/09_file.js
patch: @@ -684,7 +684,7 @@ function blobFromObjectUrl(url) {
     totalSize += size;
   }
 
-  const blob = webidl.createBranded(Blob);
+  const blob = new Blob();
   blob[_type] = blobData.media_type;
   blob[_size] = totalSize;
   blob[_parts] = parts;

filename: tests/node_compat/config.jsonc
patch: @@ -93,6 +93,12 @@
       "windows": false,
       "reason": "testing private benchmark utility"
     },
+    "parallel/test-blob-createobjecturl.js": {
+      "darwin": false,
+      "linux": false,
+      "windows": false,
+      "reason": "URL.createObjectURL does not throw ERR_INVALID_ARG_TYPE for non-Blob arguments"
+    },
     "parallel/test-btoa-atob.js": {},
     "parallel/test-buffer-alloc.js": {},
     "parallel/test-buffer-arraybuffer.js": {},

filename: tests/unit_node/buffer_test.ts
patch: @@ -1,5 +1,10 @@
 // Copyright 2018-2026 the Deno authors. MIT license.
-import { Buffer, constants, File as BufferFile } from "node:buffer";
+import {
+  Buffer,
+  constants,
+  File as BufferFile,
+  resolveObjectURL,
+} from "node:buffer";
 import { assertEquals, assertThrows } from "@std/assert";
 import { strictEqual } from "node:assert";
 
@@ -758,3 +763,46 @@ Deno.test({
     assertEquals(buf.hexSlice(1, 3), "0203");
   },
 });
+
+Deno.test({
+  name: "[node/buffer] resolveObjectURL resolves blob URL",
+  async fn() {
+    const blob = new Blob(["hello"]);
+    const url = URL.createObjectURL(blob);
+    try {
+      const resolved = resolveObjectURL(url);
+      assertEquals(resolved instanceof Blob, true);
+      assertEquals(resolved!.size, 5);
+      assertEquals(
+        Buffer.from(await resolved!.arrayBuffer()).toString(),
+        "hello",
+      );
+    } finally {
+      URL.revokeObjectURL(url);
+    }
+  },
+});
+
+Deno.test({
+  name: "[node/buffer] resolveObjectURL returns undefined for revoked URL",
+  fn() {
+    const blob = new Blob(["hello"]);
+    const url = URL.createObjectURL(blob);
+    URL.revokeObjectURL(url);
+    assertEquals(resolveObjectURL(url), undefined);
+  },
+});
+
+Deno.test({
+  name: "[node/buffer] resolveObjectURL returns undefined for invalid inputs",
+  fn() {
+    assertEquals(resolveObjectURL("not a url"), undefined);
+    assertEquals(resolveObjectURL("blob:nodedata:1:wrong"), undefined);
+    // deno-lint-ignore no-explicit-any
+    assertEquals(resolveObjectURL(undefined as any), undefined);
+    // deno-lint-ignore no-explicit-any
+    assertEquals(resolveObjectURL(1 as any), undefined);
+    // deno-lint-ignore no-explicit-any
+    assertEquals(resolveObjectURL({} as any), undefined);
+  },
+});