Skip to content

Commit 0305f3d

Browse files
Jarred-Sumnerclaudeautofix-ci[bot]
authored
feat(url): implement URLPattern API (#25168)
## Summary Implements the [URLPattern Web API](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) based on WebKit's implementation. URLPattern provides declarative pattern matching for URLs, similar to how regular expressions work for strings. ### Features - **Constructor**: Create patterns from strings or `URLPatternInit` dictionaries - **`test()`**: Check if a URL matches the pattern (returns boolean) - **`exec()`**: Extract matched groups from a URL (returns `URLPatternResult` or null) - **Pattern properties**: `protocol`, `username`, `password`, `hostname`, `port`, `pathname`, `search`, `hash` - **`hasRegExpGroups`**: Detect if the pattern uses custom regular expressions ### Example Usage ```js // Match URLs with a user ID parameter const pattern = new URLPattern({ pathname: '/users/:id' }); pattern.test('https://example.com/users/123'); // true pattern.test('https://example.com/posts/456'); // false const result = pattern.exec('https://example.com/users/123'); console.log(result.pathname.groups.id); // "123" // Wildcard matching const filesPattern = new URLPattern({ pathname: '/files/*' }); const match = filesPattern.exec('https://example.com/files/image.png'); console.log(match.pathname.groups[0]); // "image.png" ``` ## Implementation Notes - Adapted from WebKit's URLPattern implementation - Modified JS bindings to work with Bun's infrastructure (simpler `convertDictionary` patterns, WTF::Variant handling) - Added IsoSubspaces for proper GC integration ## Test Plan - [x] 408 tests from Web Platform Tests pass - [x] Tests fail with system Bun (URLPattern not defined), pass with debug build - [x] Manual testing of basic functionality Fixes #2286 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 1006a4f commit 0305f3d

38 files changed

+7339
-2
lines changed

bench/snippets/urlpattern.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { bench, group, run } from "../runner.mjs";
2+
3+
const patterns = [
4+
{ name: "string pattern", input: "https://(sub.)?example(.com/)foo" },
5+
{ name: "hostname IDN", input: { hostname: "xn--caf-dma.com" } },
6+
{
7+
name: "pathname + search + hash + baseURL",
8+
input: {
9+
pathname: "/foo",
10+
search: "bar",
11+
hash: "baz",
12+
baseURL: "https://example.com:8080",
13+
},
14+
},
15+
{ name: "pathname with regex", input: { pathname: "/([[a-z]--a])" } },
16+
{ name: "named groups", input: { pathname: "/users/:id/posts/:postId" } },
17+
{ name: "wildcard", input: { pathname: "/files/*" } },
18+
];
19+
20+
const testURL = "https://sub.example.com/foo";
21+
22+
group("URLPattern parse (constructor)", () => {
23+
for (const { name, input } of patterns) {
24+
bench(name, () => {
25+
return new URLPattern(input);
26+
});
27+
}
28+
});
29+
30+
group("URLPattern.test()", () => {
31+
for (const { name, input } of patterns) {
32+
const pattern = new URLPattern(input);
33+
bench(name, () => {
34+
return pattern.test(testURL);
35+
});
36+
}
37+
});
38+
39+
group("URLPattern.exec()", () => {
40+
for (const { name, input } of patterns) {
41+
const pattern = new URLPattern(input);
42+
bench(name, () => {
43+
return pattern.exec(testURL);
44+
});
45+
}
46+
});
47+
48+
await run();

src/bun.js/bindings/URLDecomposition.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ String URLDecomposition::port() const
166166
}
167167

168168
// Outer optional is whether we could parse at all. Inner optional is "no port specified".
169-
static std::optional<std::optional<uint16_t>> parsePort(StringView string, StringView protocol)
169+
std::optional<std::optional<uint16_t>> URLDecomposition::parsePort(StringView string, StringView protocol)
170170
{
171171
// https://url.spec.whatwg.org/#port-state with state override given.
172172
uint32_t port { 0 };

src/bun.js/bindings/URLDecomposition.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ namespace WebCore {
3535

3636
class URLDecomposition {
3737
public:
38+
// Parse a port string with optional protocol for default port detection
39+
// Returns nullopt on parse error, or optional<uint16_t> (nullopt means empty/default port)
40+
static std::optional<std::optional<uint16_t>> parsePort(StringView port, StringView protocol);
41+
3842
String origin() const;
3943

4044
WEBCORE_EXPORT String protocol() const;

src/bun.js/bindings/ZigGlobalObject.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
#include "JSTextDecoderStream.h"
131131
#include "JSTransformStream.h"
132132
#include "JSTransformStreamDefaultController.h"
133+
#include "JSURLPattern.h"
133134
#include "JSURLSearchParams.h"
134135
#include "JSWasmStreamingCompiler.h"
135136
#include "JSWebSocket.h"
@@ -1009,6 +1010,7 @@ WEBCORE_GENERATED_CONSTRUCTOR_GETTER(TextEncoderStream);
10091010
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(TextDecoderStream);
10101011
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(TransformStream)
10111012
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(TransformStreamDefaultController)
1013+
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(URLPattern);
10121014
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(URLSearchParams);
10131015
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(WebSocket);
10141016
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(Worker);

src/bun.js/bindings/ZigGlobalObject.lut.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
TransformStream TransformStreamConstructorCallback PropertyCallback
8585
TransformStreamDefaultController TransformStreamDefaultControllerConstructorCallback PropertyCallback
8686
URL DOMURLConstructorCallback DontEnum|PropertyCallback
87+
URLPattern URLPatternConstructorCallback PropertyCallback
8788
URLSearchParams URLSearchParamsConstructorCallback DontEnum|PropertyCallback
8889
WebSocket WebSocketConstructorCallback PropertyCallback
8990
Worker WorkerConstructorCallback PropertyCallback

src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class DOMClientIsoSubspaces {
8181
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMFormData;
8282
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMFormDataIterator;
8383
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMURL;
84+
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForURLPattern;
8485
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForURLSearchParams;
8586
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForURLSearchParamsIterator;
8687

src/bun.js/bindings/webcore/DOMConstructors.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -860,11 +860,12 @@ enum class DOMConstructorID : uint16_t {
860860
Cookie,
861861
CookieMap,
862862
EventEmitter,
863+
URLPattern,
863864
};
864865

865866
static constexpr unsigned numberOfDOMConstructorsBase = 848;
866867

867-
static constexpr unsigned bunExtraConstructors = 3;
868+
static constexpr unsigned bunExtraConstructors = 4;
868869

869870
static constexpr unsigned numberOfDOMConstructors = numberOfDOMConstructorsBase + bunExtraConstructors;
870871

src/bun.js/bindings/webcore/DOMIsoSubspaces.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,7 @@ class DOMIsoSubspaces {
938938
// std::unique_ptr<IsoSubspace> m_subspaceForDOMFormData;
939939
// std::unique_ptr<IsoSubspace> m_subspaceForDOMFormDataIterator;
940940
std::unique_ptr<IsoSubspace> m_subspaceForDOMURL;
941+
std::unique_ptr<IsoSubspace> m_subspaceForURLPattern;
941942
std::unique_ptr<IsoSubspace> m_subspaceForJSSign;
942943
std::unique_ptr<IsoSubspace> m_subspaceForJSVerify;
943944
std::unique_ptr<IsoSubspace> m_subspaceForJSHmac;

0 commit comments

Comments
 (0)