Skip to content

Commit 9a41e3d

Browse files
committed
Bind handlers' this in Rust
1 parent 2613556 commit 9a41e3d

File tree

3 files changed

+37
-60
lines changed

3 files changed

+37
-60
lines changed

README.md

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,9 @@ import { HTMLRewriter } from "html-rewriter-wasm";
1818
const encoder = new TextEncoder();
1919
const decoder = new TextDecoder();
2020

21+
let output = "";
2122
const rewriter = new HTMLRewriter((outputChunk) => {
22-
if (outputChunk.length === 0) {
23-
// Remember to free memory on last chunk
24-
queueMicrotask(() => rewriter.free());
25-
} else {
26-
console.log(decoder.decode(outputChunk)); // <p>new</p>
27-
}
23+
output += decoder.decode(outputChunk);
2824
});
2925

3026
rewriter.on("p", {
@@ -33,8 +29,13 @@ rewriter.on("p", {
3329
},
3430
});
3531

36-
await rewriter.write(encoder.encode("<p>old</p>"));
37-
await rewriter.end();
32+
try {
33+
await rewriter.write(encoder.encode("<p>old</p>"));
34+
await rewriter.end();
35+
console.log(output); // <p>new</p>
36+
} finally {
37+
rewriter.free(); // Remember to free memory
38+
}
3839
```
3940

4041
See [test/index.ts](./test/index.ts) for a more traditional `HTMLRewriter`
@@ -85,30 +86,6 @@ and output to strings.
8586
await rewriter.write(encoder.encode("<p>2</p>"));
8687
```
8788

88-
- If using handler classes, you must bind their methods to the class first:
89-
90-
```js
91-
class Handler {
92-
constructor(value) {
93-
this.value = value;
94-
}
95-
96-
element(element) {
97-
element.setInnerContent(this.value);
98-
}
99-
}
100-
const rewriter = new HTMLRewriter(...);
101-
const handler = new Handler("new");
102-
103-
//
104-
rewriter.on("p", handler);
105-
106-
//
107-
rewriter.on("p", {
108-
element: handler.element.bind(handler)
109-
})
110-
```
111-
11289
## Building
11390

11491
You can build the package by running `npm run build`. You must do this prior to

src/handlers.rs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use lol_html::{
1010
ElementContentHandlers as NativeElementContentHandlers,
1111
};
1212
use std::mem;
13+
use std::rc::Rc;
1314
use thiserror::Error;
1415
use wasm_bindgen::JsCast;
1516

@@ -29,13 +30,12 @@ extern "C" {
2930
}
3031

3132
macro_rules! make_handler {
32-
($handler:ident, $JsArgType:ident, $stack_ptr:ident) => {
33+
($handler:ident, $JsArgType:ident, $this:ident, $stack_ptr:ident) => {
3334
move |arg: &mut _| {
3435
let (js_arg, anchor) = $JsArgType::from_native(arg);
35-
let this = JsValue::NULL;
3636
let js_arg = JsValue::from(js_arg);
3737

38-
let res = match $handler.call1(&this, &js_arg) {
38+
let res = match $handler.call1(&$this, &js_arg) {
3939
Ok(res) => {
4040
if let Some(promise) = res.dyn_ref::<JsPromise>() {
4141
await_promise($stack_ptr, promise);
@@ -72,18 +72,22 @@ extern "C" {
7272

7373
impl IntoNativeHandlers<NativeElementContentHandlers<'static>> for ElementContentHandlers {
7474
fn into_native(self, stack_ptr: *mut u8) -> NativeElementContentHandlers<'static> {
75+
let handlers: Rc<JsValue> = Rc::new((&self).into());
7576
let mut native = NativeElementContentHandlers::default();
7677

7778
if let Some(handler) = self.element() {
78-
native = native.element(make_handler!(handler, Element, stack_ptr));
79+
let this = handlers.clone();
80+
native = native.element(make_handler!(handler, Element, this, stack_ptr));
7981
}
8082

8183
if let Some(handler) = self.comments() {
82-
native = native.comments(make_handler!(handler, Comment, stack_ptr));
84+
let this = handlers.clone();
85+
native = native.comments(make_handler!(handler, Comment, this, stack_ptr));
8386
}
8487

8588
if let Some(handler) = self.text() {
86-
native = native.text(make_handler!(handler, TextChunk, stack_ptr));
89+
let this = handlers.clone();
90+
native = native.text(make_handler!(handler, TextChunk, this, stack_ptr));
8791
}
8892

8993
native
@@ -109,22 +113,27 @@ extern "C" {
109113

110114
impl IntoNativeHandlers<NativeDocumentContentHandlers<'static>> for DocumentContentHandlers {
111115
fn into_native(self, stack_ptr: *mut u8) -> NativeDocumentContentHandlers<'static> {
116+
let handlers: Rc<JsValue> = Rc::new((&self).into());
112117
let mut native = NativeDocumentContentHandlers::default();
113118

114119
if let Some(handler) = self.doctype() {
115-
native = native.doctype(make_handler!(handler, Doctype, stack_ptr));
120+
let this = handlers.clone();
121+
native = native.doctype(make_handler!(handler, Doctype, this, stack_ptr));
116122
}
117123

118124
if let Some(handler) = self.comments() {
119-
native = native.comments(make_handler!(handler, Comment, stack_ptr));
125+
let this = handlers.clone();
126+
native = native.comments(make_handler!(handler, Comment, this, stack_ptr));
120127
}
121128

122129
if let Some(handler) = self.text() {
123-
native = native.text(make_handler!(handler, TextChunk, stack_ptr));
130+
let this = handlers.clone();
131+
native = native.text(make_handler!(handler, TextChunk, this, stack_ptr));
124132
}
125133

126134
if let Some(handler) = self.end() {
127-
native = native.end(make_handler!(handler, DocumentEnd, stack_ptr));
135+
let this = handlers.clone();
136+
native = native.end(make_handler!(handler, DocumentEnd, this, stack_ptr));
128137
}
129138

130139
native

test/index.ts

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,42 +17,33 @@ export class HTMLRewriter {
1717
private documentHandlers: DocumentHandlers[] = [];
1818

1919
on(selector: string, handlers: ElementHandlers): this {
20-
this.elementHandlers.push([
21-
selector,
22-
{
23-
element: handlers.element?.bind(handlers),
24-
comments: handlers.comments?.bind(handlers),
25-
text: handlers.text?.bind(handlers),
26-
},
27-
]);
20+
this.elementHandlers.push([selector, handlers]);
2821
return this;
2922
}
3023

3124
onDocument(handlers: DocumentHandlers): this {
32-
this.documentHandlers.push({
33-
doctype: handlers.doctype?.bind(handlers),
34-
comments: handlers.comments?.bind(handlers),
35-
text: handlers.text?.bind(handlers),
36-
end: handlers.end?.bind(handlers),
37-
});
25+
this.documentHandlers.push(handlers);
3826
return this;
3927
}
4028

4129
async transform(input: string): Promise<string> {
4230
let output = "";
4331
const rewriter = new RawHTMLRewriter((chunk) => {
4432
output += decoder.decode(chunk);
45-
if (chunk.length === 0) queueMicrotask(() => rewriter.free());
4633
});
4734
for (const [selector, handlers] of this.elementHandlers) {
4835
rewriter.on(selector, handlers);
4936
}
5037
for (const handlers of this.documentHandlers) {
5138
rewriter.onDocument(handlers);
5239
}
53-
await rewriter.write(encoder.encode(input));
54-
await rewriter.end();
55-
return output;
40+
try {
41+
await rewriter.write(encoder.encode(input));
42+
await rewriter.end();
43+
return output;
44+
} finally {
45+
rewriter.free();
46+
}
5647
}
5748
}
5849

0 commit comments

Comments
 (0)