rusteerio 0.1.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +7 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/{chunk-DKULX6VO.mjs → chunk-DMRD3FGC.mjs} +1 -1
- package/dist/chunk-DMRD3FGC.mjs.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/slim.cjs.map +1 -1
- package/dist/slim.mjs +1 -1
- package/native/src/arena.rs +42 -13
- package/native/src/lib.rs +187 -17
- package/package.json +1 -1
- package/rusteerio.darwin-arm64.node +0 -0
- package/rusteerio.darwin-x64.node +0 -0
- package/rusteerio.linux-arm64-gnu.node +0 -0
- package/rusteerio.linux-arm64-musl.node +0 -0
- package/rusteerio.linux-x64-gnu.node +0 -0
- package/rusteerio.linux-x64-musl.node +0 -0
- package/rusteerio.win32-arm64-msvc.node +0 -0
- package/rusteerio.win32-x64-msvc.node +0 -0
- package/src/router.ts +3 -2
- package/dist/chunk-DKULX6VO.mjs.map +0 -1
package/dist/slim.mjs
CHANGED
package/native/src/arena.rs
CHANGED
|
@@ -51,6 +51,15 @@ impl DomNode {
|
|
|
51
51
|
pub(crate) fn is_element(&self) -> bool {
|
|
52
52
|
matches!(self.kind.as_str(), "tag" | "script" | "style")
|
|
53
53
|
}
|
|
54
|
+
pub(crate) fn content_bytes(&self) -> usize {
|
|
55
|
+
let attributes: usize = self
|
|
56
|
+
.attribs
|
|
57
|
+
.values()
|
|
58
|
+
.filter_map(Value::as_str)
|
|
59
|
+
.map(str::len)
|
|
60
|
+
.sum();
|
|
61
|
+
self.data.as_ref().map_or(0, String::len) + attributes
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
#[derive(Clone, Debug)]
|
|
@@ -353,20 +362,31 @@ impl DomArena {
|
|
|
353
362
|
let name = node.name.as_deref().unwrap_or("");
|
|
354
363
|
output.push('<');
|
|
355
364
|
output.push_str(name);
|
|
365
|
+
// parse5 qualifies foreign attributes; XML and htmlparser2
|
|
366
|
+
// rendering write the stored attribute names unchanged.
|
|
367
|
+
let qualifiers = (!xml
|
|
368
|
+
&& options.get("_useHtmlParser2") != Some(&Value::Bool(true)))
|
|
369
|
+
.then(|| {
|
|
370
|
+
(
|
|
371
|
+
node.extras.get("attribsNamespace"),
|
|
372
|
+
node.extras.get("attribsPrefix"),
|
|
373
|
+
)
|
|
374
|
+
});
|
|
356
375
|
for (name, value) in &node.attribs {
|
|
357
376
|
output.push(' ');
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
377
|
+
match qualifiers {
|
|
378
|
+
Some((namespaces, prefixes)) => super::push_attribute_name(
|
|
379
|
+
&mut output,
|
|
380
|
+
name,
|
|
381
|
+
namespaces
|
|
382
|
+
.and_then(|map| map.get(name))
|
|
383
|
+
.and_then(Value::as_str),
|
|
384
|
+
prefixes
|
|
385
|
+
.and_then(|map| map.get(name))
|
|
386
|
+
.and_then(Value::as_str),
|
|
387
|
+
),
|
|
388
|
+
None => output.push_str(name),
|
|
368
389
|
}
|
|
369
|
-
output.push_str(name);
|
|
370
390
|
let value = value
|
|
371
391
|
.as_str()
|
|
372
392
|
.map(str::to_owned)
|
|
@@ -625,16 +645,25 @@ impl DomArena {
|
|
|
625
645
|
.filter(|id| self.node(*id).is_some_and(DomNode::is_element)),
|
|
626
646
|
),
|
|
627
647
|
"parents" | "ancestors" => {
|
|
648
|
+
// Like Cheerio, stop at the first document node. A template's
|
|
649
|
+
// contents live in a fragment, so they never report the
|
|
650
|
+
// template or its ancestors as their own.
|
|
628
651
|
let mut parent = node.parent;
|
|
629
652
|
let mut seen = HashSet::new();
|
|
630
653
|
while let Some(id) = parent {
|
|
631
654
|
if !seen.insert(id) {
|
|
632
655
|
break;
|
|
633
656
|
}
|
|
634
|
-
|
|
657
|
+
let Some(ancestor) = self.node(id) else {
|
|
658
|
+
break;
|
|
659
|
+
};
|
|
660
|
+
if ancestor.kind == "root" {
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
if ancestor.is_element() {
|
|
635
664
|
result.push(id);
|
|
636
665
|
}
|
|
637
|
-
parent =
|
|
666
|
+
parent = ancestor.parent;
|
|
638
667
|
}
|
|
639
668
|
}
|
|
640
669
|
"next" | "prev" | "nextAll" | "prevAll" => {
|
package/native/src/lib.rs
CHANGED
|
@@ -35,8 +35,40 @@ pub use runtime::{configure_worker_pool, worker_pool_info, WorkerPoolInfo};
|
|
|
35
35
|
pub use stream::NativeStream;
|
|
36
36
|
|
|
37
37
|
const HTML_NS: &str = "http://www.w3.org/1999/xhtml";
|
|
38
|
+
const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
|
|
39
|
+
const XMLNS_NS: &str = "http://www.w3.org/2000/xmlns/";
|
|
40
|
+
const XLINK_NS: &str = "http://www.w3.org/1999/xlink";
|
|
41
|
+
|
|
42
|
+
/// Writes an attribute's serialized name. Like parse5, the namespace rather
|
|
43
|
+
/// than the stored prefix selects the qualifier: HTML5 gives foreign `xmlns`
|
|
44
|
+
/// attributes an empty prefix, which must not become `:xmlns`. Other namespaces
|
|
45
|
+
/// keep a non-empty prefix; an empty or missing one writes the bare name.
|
|
46
|
+
pub(crate) fn push_attribute_name(
|
|
47
|
+
output: &mut String,
|
|
48
|
+
local: &str,
|
|
49
|
+
namespace: Option<&str>,
|
|
50
|
+
prefix: Option<&str>,
|
|
51
|
+
) {
|
|
52
|
+
match namespace.filter(|namespace| !namespace.is_empty()) {
|
|
53
|
+
None => {}
|
|
54
|
+
Some(XML_NS) => output.push_str("xml:"),
|
|
55
|
+
Some(XMLNS_NS) => {
|
|
56
|
+
if local != "xmlns" {
|
|
57
|
+
output.push_str("xmlns:");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
Some(XLINK_NS) => output.push_str("xlink:"),
|
|
61
|
+
Some(_) => {
|
|
62
|
+
if let Some(prefix) = prefix.filter(|prefix| !prefix.is_empty()) {
|
|
63
|
+
output.push_str(prefix);
|
|
64
|
+
output.push(':');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
output.push_str(local);
|
|
69
|
+
}
|
|
38
70
|
|
|
39
|
-
#[napi]
|
|
71
|
+
#[napi(custom_finalize)]
|
|
40
72
|
pub struct NativeDocument {
|
|
41
73
|
html: RefCell<Option<Html>>,
|
|
42
74
|
// Stable public IDs are preorder positions, rather than parser allocation
|
|
@@ -51,10 +83,43 @@ pub struct NativeDocument {
|
|
|
51
83
|
managed: Cell<bool>,
|
|
52
84
|
diagnostics: RefCell<Vec<Value>>,
|
|
53
85
|
locations: RefCell<HashMap<u32, Value>>,
|
|
86
|
+
// V8 cannot see native allocations. Reporting them lets its GC collect
|
|
87
|
+
// unreachable documents under native memory pressure.
|
|
88
|
+
content_bytes: Cell<usize>,
|
|
89
|
+
reported_heap_bytes: Cell<i64>,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl ObjectFinalize for NativeDocument {
|
|
93
|
+
fn finalize(self, mut env: Env) -> Result<()> {
|
|
94
|
+
let reported = self.reported_heap_bytes.get();
|
|
95
|
+
if reported != 0 {
|
|
96
|
+
// Accounting is advisory; a failure must not throw from a finalizer.
|
|
97
|
+
let _ = env.adjust_external_memory(-reported);
|
|
98
|
+
}
|
|
99
|
+
Ok(())
|
|
100
|
+
}
|
|
54
101
|
}
|
|
55
102
|
|
|
103
|
+
fn html_node_content_bytes(node: &Node) -> usize {
|
|
104
|
+
match node {
|
|
105
|
+
Node::Text(text) => text.len(),
|
|
106
|
+
Node::Comment(comment) => comment.len(),
|
|
107
|
+
Node::Element(element) => element.attrs.iter().map(|(_, value)| value.len()).sum(),
|
|
108
|
+
_ => 0,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Approximate retained bytes per node in each representation, excluding the
|
|
113
|
+
/// text and attribute content counted separately. V8 treats the total only as
|
|
114
|
+
/// allocation pressure, so these err on the high side.
|
|
115
|
+
const IMMUTABLE_NODE_BYTES: usize = 256;
|
|
116
|
+
const ARENA_NODE_BYTES: usize = 512;
|
|
117
|
+
/// Smaller changes to an already reported size are skipped to keep operations cheap.
|
|
118
|
+
const HEAP_REPORT_STEP: i64 = 64 * 1024;
|
|
119
|
+
|
|
56
120
|
impl NativeDocument {
|
|
57
121
|
pub(crate) fn from_arena(arena: arena::DomArena, options: Value) -> Self {
|
|
122
|
+
let content_bytes = arena.nodes.iter().map(arena::DomNode::content_bytes).sum();
|
|
58
123
|
Self {
|
|
59
124
|
html: RefCell::new(None),
|
|
60
125
|
ids: RefCell::new(Vec::new()),
|
|
@@ -67,6 +132,8 @@ impl NativeDocument {
|
|
|
67
132
|
managed: Cell::new(false),
|
|
68
133
|
diagnostics: RefCell::new(Vec::new()),
|
|
69
134
|
locations: RefCell::new(HashMap::new()),
|
|
135
|
+
content_bytes: Cell::new(content_bytes),
|
|
136
|
+
reported_heap_bytes: Cell::new(0),
|
|
70
137
|
}
|
|
71
138
|
}
|
|
72
139
|
pub(crate) fn set_diagnostic_records(&self, records: Vec<Value>) {
|
|
@@ -82,6 +149,39 @@ impl NativeDocument {
|
|
|
82
149
|
Self::parse_options(source, is_document, serde_json::json!({}), None)
|
|
83
150
|
}
|
|
84
151
|
|
|
152
|
+
fn estimated_heap_bytes(&self) -> i64 {
|
|
153
|
+
let live_arena_nodes = self.arena.borrow().as_ref().map_or(0, |arena| {
|
|
154
|
+
arena.nodes.len().saturating_sub(arena.free.len())
|
|
155
|
+
});
|
|
156
|
+
let immutable_nodes = self.ids.borrow().len();
|
|
157
|
+
let retained_source = self.source.borrow().as_ref().map_or(0, String::capacity);
|
|
158
|
+
let content = if live_arena_nodes + immutable_nodes == 0 {
|
|
159
|
+
0
|
|
160
|
+
} else {
|
|
161
|
+
self.content_bytes.get()
|
|
162
|
+
};
|
|
163
|
+
let bytes = content
|
|
164
|
+
+ retained_source
|
|
165
|
+
+ immutable_nodes * IMMUTABLE_NODE_BYTES
|
|
166
|
+
+ live_arena_nodes * ARENA_NODE_BYTES;
|
|
167
|
+
i64::try_from(bytes).unwrap_or(i64::MAX)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn report_heap_bytes(&self, mut env: Env) {
|
|
171
|
+
let reported = self.reported_heap_bytes.get();
|
|
172
|
+
let estimated = self.estimated_heap_bytes();
|
|
173
|
+
let change = estimated - reported;
|
|
174
|
+
let is_small_update = reported != 0 && estimated != 0 && change.abs() < HEAP_REPORT_STEP;
|
|
175
|
+
if change != 0 && !is_small_update && env.adjust_external_memory(change).is_ok() {
|
|
176
|
+
self.reported_heap_bytes.set(estimated);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fn reported<T>(&self, env: Env, value: T) -> T {
|
|
181
|
+
self.report_heap_bytes(env);
|
|
182
|
+
value
|
|
183
|
+
}
|
|
184
|
+
|
|
85
185
|
pub(crate) fn parse_options(
|
|
86
186
|
source: &str,
|
|
87
187
|
is_document: bool,
|
|
@@ -262,6 +362,7 @@ impl NativeDocument {
|
|
|
262
362
|
&& value != &&serde_json::json!("")
|
|
263
363
|
})
|
|
264
364
|
.and(source);
|
|
365
|
+
let content_bytes = html.tree.values().map(html_node_content_bytes).sum();
|
|
265
366
|
Self {
|
|
266
367
|
html: RefCell::new(Some(html)),
|
|
267
368
|
ids: RefCell::new(ids),
|
|
@@ -274,6 +375,8 @@ impl NativeDocument {
|
|
|
274
375
|
managed: Cell::new(false),
|
|
275
376
|
diagnostics: RefCell::new(diagnostics),
|
|
276
377
|
locations: RefCell::new(locations),
|
|
378
|
+
content_bytes: Cell::new(content_bytes),
|
|
379
|
+
reported_heap_bytes: Cell::new(0),
|
|
277
380
|
}
|
|
278
381
|
}
|
|
279
382
|
|
|
@@ -583,7 +686,16 @@ impl NativeDocument {
|
|
|
583
686
|
}
|
|
584
687
|
}
|
|
585
688
|
}
|
|
586
|
-
#[napi]
|
|
689
|
+
#[napi(js_name = "collectGarbage")]
|
|
690
|
+
pub fn collect_garbage_and_report(&self, env: Env) -> MemoryStats {
|
|
691
|
+
let stats = self.collect_garbage();
|
|
692
|
+
self.report_heap_bytes(env);
|
|
693
|
+
MemoryStats {
|
|
694
|
+
external_bytes: self.reported_heap_bytes.get(),
|
|
695
|
+
..stats
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
587
699
|
pub fn collect_garbage(&self) -> MemoryStats {
|
|
588
700
|
self.needs_collect.set(false);
|
|
589
701
|
if self.arena.borrow().is_none() && self.managed.get() && self.retained.borrow().is_empty()
|
|
@@ -599,6 +711,7 @@ impl NativeDocument {
|
|
|
599
711
|
retained: 0,
|
|
600
712
|
reclaimed,
|
|
601
713
|
immutable_nodes: 0,
|
|
714
|
+
external_bytes: self.reported_heap_bytes.get(),
|
|
602
715
|
};
|
|
603
716
|
}
|
|
604
717
|
if let Some(arena) = self.arena.borrow_mut().as_mut() {
|
|
@@ -610,6 +723,7 @@ impl NativeDocument {
|
|
|
610
723
|
retained: stats.retained,
|
|
611
724
|
reclaimed: stats.reclaimed,
|
|
612
725
|
immutable_nodes: 0,
|
|
726
|
+
external_bytes: self.reported_heap_bytes.get(),
|
|
613
727
|
}
|
|
614
728
|
} else {
|
|
615
729
|
let count = self.ids.borrow().len() as u32;
|
|
@@ -619,6 +733,7 @@ impl NativeDocument {
|
|
|
619
733
|
retained: self.retained.borrow().len() as u32,
|
|
620
734
|
reclaimed: 0,
|
|
621
735
|
immutable_nodes: count,
|
|
736
|
+
external_bytes: self.reported_heap_bytes.get(),
|
|
622
737
|
}
|
|
623
738
|
}
|
|
624
739
|
}
|
|
@@ -629,19 +744,30 @@ impl NativeDocument {
|
|
|
629
744
|
}
|
|
630
745
|
|
|
631
746
|
#[napi]
|
|
632
|
-
pub fn root_id(&self) -> u32 {
|
|
747
|
+
pub fn root_id(&self, env: Env) -> u32 {
|
|
748
|
+
self.report_heap_bytes(env);
|
|
633
749
|
0
|
|
634
750
|
}
|
|
635
751
|
|
|
636
|
-
#[napi]
|
|
752
|
+
#[napi(js_name = "select")]
|
|
753
|
+
pub fn select_and_report(
|
|
754
|
+
&self,
|
|
755
|
+
env: Env,
|
|
756
|
+
selector: String,
|
|
757
|
+
contexts: Option<Vec<u32>>,
|
|
758
|
+
) -> Result<Vec<u32>> {
|
|
759
|
+
self.reported(env, self.select(selector, contexts))
|
|
760
|
+
}
|
|
761
|
+
|
|
637
762
|
pub fn select(&self, selector: String, contexts: Option<Vec<u32>>) -> Result<Vec<u32>> {
|
|
638
763
|
self.select_ids(&selector, contexts.as_deref())
|
|
639
764
|
}
|
|
640
765
|
|
|
641
766
|
#[napi]
|
|
642
|
-
pub fn matches(&self, selector: String, ids: Vec<u32>) -> Result<Vec<u32>> {
|
|
767
|
+
pub fn matches(&self, env: Env, selector: String, ids: Vec<u32>) -> Result<Vec<u32>> {
|
|
643
768
|
self.ensure_arena()?;
|
|
644
|
-
selector::filter(self.arena.borrow().as_ref().unwrap(), &selector, &ids)
|
|
769
|
+
let matched = selector::filter(self.arena.borrow().as_ref().unwrap(), &selector, &ids);
|
|
770
|
+
self.reported(env, matched)
|
|
645
771
|
}
|
|
646
772
|
|
|
647
773
|
#[napi]
|
|
@@ -671,7 +797,11 @@ impl NativeDocument {
|
|
|
671
797
|
self.text_ids(&ids)
|
|
672
798
|
}
|
|
673
799
|
|
|
674
|
-
#[napi]
|
|
800
|
+
#[napi(js_name = "innerHtml")]
|
|
801
|
+
pub fn inner_html_and_report(&self, env: Env, id: u32) -> String {
|
|
802
|
+
self.reported(env, self.inner_html(id))
|
|
803
|
+
}
|
|
804
|
+
|
|
675
805
|
pub fn inner_html(&self, id: u32) -> String {
|
|
676
806
|
if (self.tag_name(id).as_deref() == Some("template") || self.arena.borrow().is_some())
|
|
677
807
|
&& self.ensure_arena().is_ok()
|
|
@@ -704,7 +834,11 @@ impl NativeDocument {
|
|
|
704
834
|
.flatten()
|
|
705
835
|
}
|
|
706
836
|
|
|
707
|
-
#[napi]
|
|
837
|
+
#[napi(js_name = "exportTree")]
|
|
838
|
+
pub fn export_tree_and_report(&self, env: Env) -> Result<String> {
|
|
839
|
+
self.reported(env, self.export_tree())
|
|
840
|
+
}
|
|
841
|
+
|
|
708
842
|
pub fn export_tree(&self) -> Result<String> {
|
|
709
843
|
if self.source.borrow().is_some() {
|
|
710
844
|
self.ensure_arena()?;
|
|
@@ -718,7 +852,11 @@ impl NativeDocument {
|
|
|
718
852
|
serde_json::to_string(&records).map_err(json_error)
|
|
719
853
|
}
|
|
720
854
|
|
|
721
|
-
#[napi]
|
|
855
|
+
#[napi(js_name = "node")]
|
|
856
|
+
pub fn node_and_report(&self, env: Env, id: u32) -> Result<String> {
|
|
857
|
+
self.reported(env, self.node(id))
|
|
858
|
+
}
|
|
859
|
+
|
|
722
860
|
pub fn node(&self, id: u32) -> Result<String> {
|
|
723
861
|
if self.source.borrow().is_some() {
|
|
724
862
|
self.ensure_arena()?;
|
|
@@ -729,13 +867,26 @@ impl NativeDocument {
|
|
|
729
867
|
serde_json::to_string(&self.arena.borrow().as_ref().unwrap().node(id)).map_err(json_error)
|
|
730
868
|
}
|
|
731
869
|
|
|
732
|
-
#[napi]
|
|
870
|
+
#[napi(js_name = "create")]
|
|
871
|
+
pub fn create_and_report(
|
|
872
|
+
&self,
|
|
873
|
+
env: Env,
|
|
874
|
+
html: String,
|
|
875
|
+
context_id: Option<u32>,
|
|
876
|
+
options_json: Option<String>,
|
|
877
|
+
) -> Result<Vec<u32>> {
|
|
878
|
+
let created = self.create(html, context_id, options_json);
|
|
879
|
+
self.reported(env, created)
|
|
880
|
+
}
|
|
881
|
+
|
|
733
882
|
pub fn create(
|
|
734
883
|
&self,
|
|
735
884
|
html: String,
|
|
736
885
|
context_id: Option<u32>,
|
|
737
886
|
_options_json: Option<String>,
|
|
738
887
|
) -> Result<Vec<u32>> {
|
|
888
|
+
self.content_bytes
|
|
889
|
+
.set(self.content_bytes.get().saturating_add(html.len()));
|
|
739
890
|
self.ensure_arena()?;
|
|
740
891
|
self.arena
|
|
741
892
|
.borrow_mut()
|
|
@@ -746,7 +897,19 @@ impl NativeDocument {
|
|
|
746
897
|
|
|
747
898
|
/// Move a branch between native documents. JavaScript only redirects its
|
|
748
899
|
/// existing stable handles using the returned complete descendant mapping.
|
|
749
|
-
#[napi]
|
|
900
|
+
#[napi(js_name = "adopt")]
|
|
901
|
+
pub fn adopt_and_report(
|
|
902
|
+
&self,
|
|
903
|
+
env: Env,
|
|
904
|
+
source: &NativeDocument,
|
|
905
|
+
ids: Vec<u32>,
|
|
906
|
+
detach: Option<bool>,
|
|
907
|
+
) -> Result<String> {
|
|
908
|
+
let adopted = self.adopt(source, ids, detach);
|
|
909
|
+
source.report_heap_bytes(env);
|
|
910
|
+
self.reported(env, adopted)
|
|
911
|
+
}
|
|
912
|
+
|
|
750
913
|
pub fn adopt(
|
|
751
914
|
&self,
|
|
752
915
|
source: &NativeDocument,
|
|
@@ -840,7 +1003,11 @@ impl NativeDocument {
|
|
|
840
1003
|
.map_err(json_error)
|
|
841
1004
|
}
|
|
842
1005
|
|
|
843
|
-
#[napi]
|
|
1006
|
+
#[napi(js_name = "op")]
|
|
1007
|
+
pub fn op_and_report(&self, env: Env, command: String, args_json: String) -> Result<String> {
|
|
1008
|
+
self.reported(env, self.op(command, args_json))
|
|
1009
|
+
}
|
|
1010
|
+
|
|
844
1011
|
pub fn op(&self, command: String, args_json: String) -> Result<String> {
|
|
845
1012
|
self.maybe_collect();
|
|
846
1013
|
let args: Value = serde_json::from_str(&args_json).map_err(json_error)?;
|
|
@@ -964,6 +1131,8 @@ pub struct MemoryStats {
|
|
|
964
1131
|
pub retained: u32,
|
|
965
1132
|
pub reclaimed: u32,
|
|
966
1133
|
pub immutable_nodes: u32,
|
|
1134
|
+
/// Native bytes currently reported to V8 for this document.
|
|
1135
|
+
pub external_bytes: i64,
|
|
967
1136
|
}
|
|
968
1137
|
|
|
969
1138
|
/// The Cheerio DOM adapter exposes foreign attributes by local name, whereas
|
|
@@ -1156,11 +1325,12 @@ fn serialize_node(node: NodeRef<'_, Node>, output: &mut String) {
|
|
|
1156
1325
|
output.push_str(element.name());
|
|
1157
1326
|
for (name, value) in &element.attrs {
|
|
1158
1327
|
output.push(' ');
|
|
1159
|
-
|
|
1160
|
-
output
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1328
|
+
push_attribute_name(
|
|
1329
|
+
output,
|
|
1330
|
+
&name.local,
|
|
1331
|
+
Some(name.ns.as_ref()),
|
|
1332
|
+
name.prefix.as_deref(),
|
|
1333
|
+
);
|
|
1164
1334
|
output.push_str("=\"");
|
|
1165
1335
|
escape_html(value, true, output);
|
|
1166
1336
|
output.push('"');
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/router.ts
CHANGED
|
@@ -2,10 +2,11 @@ import { requireNative, type NativeDocument, type NativeNodeRecord } from './nat
|
|
|
2
2
|
import { hasParserHooks, parseNativeDocument } from './hooks.js';
|
|
3
3
|
import type { CheerioOptions } from './types.js';
|
|
4
4
|
|
|
5
|
+
interface NativeMemoryStats { live: number; slots: number; retained: number; reclaimed: number; immutableNodes: number; externalBytes: number }
|
|
5
6
|
interface ManagedNativeDocument extends NativeDocument {
|
|
6
7
|
retain?(ids: number[]): void;
|
|
7
8
|
release?(ids: number[]): void;
|
|
8
|
-
collectGarbage?():
|
|
9
|
+
collectGarbage?(): NativeMemoryStats;
|
|
9
10
|
}
|
|
10
11
|
interface PinRecord { global: number; document: WeakRef<ManagedNativeDocument>; local: number }
|
|
11
12
|
interface Handle { global: number; document: ManagedNativeDocument; local: number; data?: object; pin: PinRecord }
|
|
@@ -105,7 +106,7 @@ export class RoutedDocument implements NativeDocument {
|
|
|
105
106
|
retainNode(id: number): void { this.lookup(id); }
|
|
106
107
|
retainNodes(ids: number[]): object { return ids.filter((id) => id !== undefined).map((id) => this.lookup(id)); }
|
|
107
108
|
nodeDocument(id: number): NativeDocument { return new RoutedDocument(this.raw, this.options, this.lookup(id)); }
|
|
108
|
-
collectGarbage():
|
|
109
|
+
collectGarbage(): NativeMemoryStats | undefined { registry.snapshots.delete(this.raw); return this.raw.collectGarbage?.(); }
|
|
109
110
|
retainNodeData(id: number, data: object): void { this.lookup(id).data = data; }
|
|
110
111
|
nodeData(id: number): object | undefined { return this.lookup(id).data; }
|
|
111
112
|
nodeDataOperation(id: number): (command: string, args: any) => any {
|