Skip to content

Commit e63d8e0

Browse files
authored
refactor(bindings): migrate N-API dom bindings to v8-based implementation to address class inheritance (#336)
1 parent 2e851d5 commit e63d8e0

File tree

590 files changed

+33053
-23318
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

590 files changed

+33053
-23318
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@
9898
"__availability": "cpp",
9999
"span": "cpp",
100100
"execution": "cpp",
101-
"cassert": "cpp"
101+
"cassert": "cpp",
102+
"print": "cpp"
102103
},
103104
"cmake.buildDirectory": "${workspaceFolder}/build/targets/darwin",
104105
"C_Cpp.default.cppStandard": "c++17",

build/build-jsbundle.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const shell = (cmd, options) => execSync(cmd, { stdio: 'inherit', ...options });
1212
const jsDir = path.join(__dirname, '../');
1313

1414
function installDeps() {
15-
shell('npm install', { cwd: jsDir });
15+
shell('npm ci', { cwd: jsDir });
1616
}
1717

1818
if (clean === 'yes' || !fs.existsSync(path.join(jsDir, 'node_modules'))) {

cmake/TransmuteClient.cmake

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ file(GLOB_RECURSE TR_CLIENT_LIBRARY_SOURCE
66
"src/client/canvas/*.cpp"
77
"src/client/cssom/*.cpp"
88
"src/client/dom/*.cpp"
9+
"src/client/fetch/*.cpp"
10+
"src/client/fileapi/*.cpp"
11+
"src/client/frame/*.cpp"
912
"src/client/graphics/*.cpp"
1013
"src/client/html/*.cpp"
1114
"src/client/layout/*.cpp"
@@ -14,6 +17,8 @@ file(GLOB_RECURSE TR_CLIENT_LIBRARY_SOURCE
1417
"src/client/script_bindings/*.cpp"
1518
"src/client/scripting_base/*.cpp"
1619
"src/client/scroll/*.cpp"
20+
"src/client/url/*.cpp"
21+
"src/client/workers/*.cpp"
1722
"src/client/xr/*.cpp"
1823
"src/client/logger.cpp"
1924
"src/client/per_process.cpp"

crates/jsbindings/bindings.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,12 @@ namespace crates
144144
* Parse a URL string into a WHATWGUrl struct.
145145
*
146146
* @param url The URL string to parse.
147+
* @param base A string representing the base URL to use in cases where url is a relative reference.
147148
* @returns The parsed WHATWGUrl struct.
148149
*/
149-
static inline Url Parse(const std::string &url)
150+
static inline Url Parse(const std::string &url, const std::string &base = "")
150151
{
151-
return Url(holocron::parseWHATWGUrl(url));
152+
return Url(holocron::parseWHATWGUrl(url, base));
152153
}
153154

154155
private:

crates/jsbindings/lib.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ mod ffi {
7272

7373
extern "Rust" {
7474
#[cxx_name = "parseWHATWGUrl"]
75-
fn parse_whatwg_url(input: &CxxString) -> Result<WHATWGUrl>;
75+
fn parse_whatwg_url(input: &CxxString, base: &CxxString) -> Result<WHATWGUrl>;
7676

7777
#[cxx_name = "parseURLToModuleExtension"]
7878
fn parse_url_to_module_extension(url: &str) -> ModuleExtensionIndex;
@@ -109,8 +109,13 @@ impl std::fmt::Debug for ffi::ModuleExtensionIndex {
109109
}
110110
}
111111

112-
fn parse_whatwg_url_impl(input_str: &str) -> anyhow::Result<ffi::WHATWGUrl> {
113-
let url = Url::parse(input_str)?;
112+
fn parse_whatwg_url_impl(input_str: &str, base_str: &str) -> anyhow::Result<ffi::WHATWGUrl> {
113+
let url: Url;
114+
if base_str != "" {
115+
url = Url::parse(base_str)?.join(input_str)?;
116+
} else {
117+
url = Url::parse(input_str)?;
118+
}
114119

115120
let hostname = url.host_str().unwrap_or("").to_string();
116121
let port = url.port().unwrap_or(0);
@@ -133,8 +138,8 @@ fn parse_whatwg_url_impl(input_str: &str) -> anyhow::Result<ffi::WHATWGUrl> {
133138
})
134139
}
135140

136-
fn parse_whatwg_url(input: &CxxString) -> anyhow::Result<ffi::WHATWGUrl> {
137-
parse_whatwg_url_impl(input.to_str()?)
141+
fn parse_whatwg_url(input: &CxxString, base: &CxxString) -> anyhow::Result<ffi::WHATWGUrl> {
142+
parse_whatwg_url_impl(input.to_str()?, base.to_str()?)
138143
}
139144

140145
fn parse_url_to_module_extension(url_str: &str) -> ffi::ModuleExtensionIndex {
@@ -201,7 +206,7 @@ mod tests {
201206
#[test]
202207
fn test_parse_whatwg_url() {
203208
let input_str = "https://example.com:8080/path?query#fragment";
204-
let url = parse_whatwg_url_impl(input_str).unwrap();
209+
let url = parse_whatwg_url_impl(input_str, "").unwrap();
205210
assert_eq!(url.host, "example.com:8080");
206211
assert_eq!(url.hostname, "example.com");
207212
assert_eq!(url.port, 8080);
@@ -215,6 +220,24 @@ mod tests {
215220
assert_eq!(url.hash, "fragment");
216221
}
217222

223+
#[test]
224+
fn test_parse_whatwg_url_with_base() {
225+
let input_str = "/newpath";
226+
let base_str = "https://example.com:8080/oldpath";
227+
let url = parse_whatwg_url_impl(input_str, base_str).unwrap();
228+
assert_eq!(url.host, "example.com:8080");
229+
assert_eq!(url.hostname, "example.com");
230+
assert_eq!(url.port, 8080);
231+
assert_eq!(url.href, "https://example.com:8080/newpath");
232+
assert_eq!(url.origin, "https://example.com:8080");
233+
assert_eq!(url.password, "");
234+
assert_eq!(url.pathname, "/newpath");
235+
assert_eq!(url.protocol, "https:");
236+
assert_eq!(url.search, "");
237+
assert_eq!(url.username, "");
238+
assert_eq!(url.hash, "");
239+
}
240+
218241
#[test]
219242
fn test_parse_url_to_module_extension() {
220243
let extension = parse_url_to_module_extension("https://example.com/index.js");

docs/internals/V8_DOM_BINDINGS.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# V8 DOM Bindings Architecture
2+
3+
This document explains the new V8-based DOM binding implementation in `src/client/script_bindings/dom/` that replaces the N-API implementation.
4+
5+
## Overview
6+
7+
The V8 DOM bindings provide a direct interface between JavaScript and the native DOM implementation, using V8 APIs instead of N-API for better performance and integration.
8+
9+
## Architecture
10+
11+
### Base Class Pattern
12+
13+
All DOM classes inherit from `scripting_base::ObjectWrap<T, D, B>` where:
14+
- `T`: The wrapper class (e.g., `Node`, `Element`)
15+
- `D`: The native DOM class (e.g., `dom::Node`, `dom::Element`)
16+
- `B`: The base wrapper class for inheritance (e.g., `Element` extends `Node`)
17+
18+
### Inheritance Hierarchy
19+
20+
```
21+
Node (wraps dom::Node)
22+
├── Element (wraps dom::Element, extends Node)
23+
│ ├── HTMLElement (wraps dom::HTMLElement, extends Element)
24+
│ │ ├── HTMLDivElement (wraps dom::HTMLDivElement, extends HTMLElement)
25+
│ │ ├── HTMLSpanElement (wraps dom::HTMLSpanElement, extends HTMLElement)
26+
│ │ └── ... (other HTML elements)
27+
│ └── Text (wraps dom::Text, extends Node)
28+
├── Document (wraps dom::Document, extends Node)
29+
└── Console (utility class, no DOM equivalent)
30+
```
31+
32+
## Key Classes
33+
34+
### Node (`node.hpp`, `node.cpp`)
35+
- Base class for all DOM nodes
36+
- Implements EventTarget interface (addEventListener, removeEventListener, dispatchEvent)
37+
- Properties: nodeName, nodeType, nodeValue, parentNode, etc.
38+
- Methods: appendChild, removeChild, insertBefore, etc.
39+
40+
### Element (`element.hpp`, `element.cpp`)
41+
- Base class for all DOM elements
42+
- Properties: tagName, id, className, innerHTML, outerHTML
43+
- Methods: getAttribute, setAttribute, hasAttribute, querySelector, etc.
44+
45+
### HTMLElement (`html_element.hpp`, `html_element.cpp`)
46+
- Base class for all HTML elements
47+
- Properties: innerText, hidden
48+
- Methods: click, focus, blur
49+
50+
### Document (`document.hpp`, `document.cpp`)
51+
- Document root node
52+
- Properties: documentElement, body, title
53+
- Methods: createElement, createTextNode, getElementById, querySelector, etc.
54+
55+
### Text (`text.hpp`, `text.cpp`)
56+
- Text node implementation
57+
- Properties: data, length, wholeText
58+
- Methods: substringData, appendData, insertData, deleteData, etc.
59+
60+
### Console (`console.hpp`, `console.cpp`)
61+
- JavaScript console implementation
62+
- Methods: log, info, warn, error, debug, trace, assert, clear
63+
- Creates global `console` object
64+
65+
## Implementation Patterns
66+
67+
### Property Accessors
68+
69+
```cpp
70+
// Read-only property
71+
instanceTemplate->SetAccessor(
72+
String::NewFromUtf8(isolate, "nodeName").ToLocalChecked(),
73+
NodeNameGetter,
74+
nullptr, // no setter
75+
Local<Value>(),
76+
AccessControl::DEFAULT,
77+
PropertyAttribute::ReadOnly);
78+
79+
// Read-write property
80+
instanceTemplate->SetAccessor(
81+
String::NewFromUtf8(isolate, "nodeValue").ToLocalChecked(),
82+
NodeValueGetter,
83+
NodeValueSetter);
84+
```
85+
86+
### Method Binding
87+
88+
```cpp
89+
instanceTemplate->Set(
90+
String::NewFromUtf8(isolate, "appendChild").ToLocalChecked(),
91+
FunctionTemplate::New(isolate, AppendChild));
92+
```
93+
94+
### Class Initialization
95+
96+
```cpp
97+
static v8::Local<v8::Function> Initialize(v8::Isolate *isolate)
98+
{
99+
return scripting_base::ObjectWrap<T, D, B>::Initialize(isolate);
100+
}
101+
```
102+
103+
### Instance Creation
104+
105+
```cpp
106+
static v8::Local<v8::Object> NewInstance(v8::Isolate *isolate, std::shared_ptr<D> native)
107+
{
108+
return scripting_base::ObjectWrap<T, D, B>::NewInstance(isolate, native);
109+
}
110+
```
111+
112+
## Binding Registration
113+
114+
The `binding.cpp` file initializes all DOM classes and registers them with V8:
115+
116+
```cpp
117+
void Initialize(v8::Isolate *isolate, v8::Local<v8::Object> global)
118+
{
119+
// Initialize constructors
120+
auto nodeConstructor = Node::Initialize(isolate);
121+
auto elementConstructor = Element::Initialize(isolate);
122+
// ... other classes
123+
124+
// Register as globals
125+
global->Set(context, String::NewFromUtf8(isolate, "Node").ToLocalChecked(),
126+
nodeConstructor).Check();
127+
// ... other registrations
128+
129+
// Create global console object
130+
auto consoleObject = Console::CreateConsoleObject(isolate);
131+
global->Set(context, String::NewFromUtf8(isolate, "console").ToLocalChecked(),
132+
consoleObject).Check();
133+
}
134+
```
135+
136+
## Key Differences from N-API Implementation
137+
138+
### No N-API Dependencies
139+
- Uses V8 APIs directly (`v8::Object`, `v8::Function`, etc.)
140+
- No `Napi::` namespace usage
141+
- No NAPI environment (`Napi::Env`) dependencies
142+
143+
### Direct V8 Integration
144+
- Uses `v8::FunctionTemplate` and `v8::ObjectTemplate`
145+
- Direct V8 property accessors and method callbacks
146+
- Better performance and integration with V8 engine
147+
148+
### Simplified Inheritance
149+
- Uses template parameters for inheritance instead of complex NAPI patterns
150+
- More type-safe with compile-time inheritance checking
151+
152+
## Usage
153+
154+
### Initialization
155+
```cpp
156+
#include <client/script_bindings/dom/binding.hpp>
157+
158+
// In V8 context setup
159+
script_bindings::dom::Initialize(isolate, global);
160+
```
161+
162+
### Creating DOM Objects
163+
```cpp
164+
// From native DOM objects
165+
auto element = document->createElement("div");
166+
auto jsElement = Element::NewInstance(isolate, element);
167+
168+
// Or directly use existing JavaScript constructors
169+
// new Element() will work in JavaScript after initialization
170+
```
171+
172+
## Future Extensions
173+
174+
To add new DOM classes:
175+
176+
1. Create header/implementation files following the pattern
177+
2. Inherit from appropriate base class using ObjectWrap
178+
3. Implement ConfigureFunctionTemplate, Initialize, and NewInstance
179+
4. Add to binding.cpp initialization
180+
181+
## Testing
182+
183+
The bindings can be tested by:
184+
1. Creating V8 contexts with the bindings initialized
185+
2. Executing JavaScript that uses DOM APIs
186+
3. Verifying proper integration with native DOM backend
187+
188+
Example JavaScript test:
189+
```javascript
190+
const div = document.createElement('div');
191+
div.id = 'test';
192+
div.innerHTML = '<span>Hello World</span>';
193+
document.body.appendChild(div);
194+
console.log('Element created:', div.tagName);
195+
```

fixtures/html/babylon-bounding-box.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
engine = new BABYLON.Engine(navigator.gl, true, {
6363
xrCompatible: true
6464
});
65+
console.info(engine);
6566
}
6667
engine.disableUniformBuffers = true;
6768

@@ -90,7 +91,7 @@
9091
</script>
9192
</head>
9293

93-
<body>
94+
<body style="background: transparent;">
9495
</body>
9596

9697
</html>

fixtures/html/babylon-lod.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<script type="module" src="./babylon/lod-and-instances.js"></script>
1414
</head>
1515

16-
<body>
16+
<body style="background-color: transparent;">
1717
</body>
1818

1919
</html>

fixtures/html/babylon-sun-particles.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
}
1111
}
1212
</script>
13+
<style>
14+
body {
15+
background-color: transparent;
16+
}
17+
</style>
1318
<script type="module">
1419
import 'babylonjs';
1520

@@ -59,6 +64,7 @@
5964
engine.disableUniformBuffers = true;
6065

6166
createScene(engine).then(scene => {
67+
6268
var stars = BABYLON.Mesh.CreateBox("emitter", 0.01, scene);
6369

6470
// Create particle systems
@@ -245,7 +251,8 @@
245251
flareParticles.start();
246252
coronaParticles.start();
247253
starsParticles.start();
248-
}, err => {
254+
console.info("scene is ready");
255+
}).catch(err => {
249256
console.error(err);
250257
});
251258
</script>

fixtures/html/babylon/lod-and-instances.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ createScene(engine).then(scene => {
9191
}
9292
}
9393
}
94+
console.log("Instances created");
95+
9496
}).catch(err => {
9597
console.error(err);
9698
});

0 commit comments

Comments
 (0)