11/* SPDX-License-Identifier: AGPL-3.0-or-later WITH Palimpsest */
2- /* Adapted from /mnt/eclipse/repos/rescript-ecosystem/packages/web/dom-mounter/src/SafeDOM.res */
2+ /* High-level API over explicit ABI + FFI layers. */
33
4- type mountResult =
5- | Mounted (Dom .element )
6- | MountPointNotFound (string )
7- | InvalidSelector (string )
8- | InvalidHTML (string )
4+ type mountResult = SafeDOMABI .mountResult =
5+ | MountedAt (Dom .element )
6+ | NotFound (string )
7+ | Failed (string )
98
10- module ProvenSelector = {
11- type validated = ValidSelector (string )
12-
13- let hasInvalidSelectorChars : string => bool = %raw (` (selector ) => / [^ \w \-# . \[\] ():>~+= ] / .test (selector)` )
14-
15- let validate = (selector : string ): result <validated , string > => {
16- let len = selector -> Js .String2 .length
17- if len == 0 {
18- Error ("Selector cannot be empty" )
19- } else if len > 255 {
20- Error ("Selector exceeds maximum length (255 characters)" )
21- } else if hasInvalidSelectorChars (selector ) {
22- Error ("Selector contains invalid CSS characters" )
23- } else {
24- Ok (ValidSelector (selector ))
25- }
26- }
27-
28- let toString = (ValidSelector (selector )) => selector
29- }
30-
31- module ProvenHTML = {
32- type validated = ValidHTML (string )
33-
34- let countOpenTags : string => int = %raw (` (html ) => (html .match (/ <[^ \/ ][^ >] * >/ g ) || []).length ` )
35- let countCloseTags : string => int = %raw (` (html ) => (html .match (/ <\/ [^ >] + >/ g ) || []).length ` )
36- let countSelfClosing : string => int = %raw (` (html ) => (html .match (/ <[^ >] + \/ >/ g ) || []).length ` )
37-
38- let validate = (html : string ): result <validated , string > => {
39- let len = html -> Js .String2 .length
40- if len == 0 {
41- Ok (ValidHTML ("" ))
42- } else if len > 1048576 {
43- Error ("HTML content exceeds maximum size (1MB)" )
44- } else {
45- let openTags = countOpenTags (html )
46- let closeTags = countCloseTags (html )
47- let selfClosing = countSelfClosing (html )
48- if openTags - selfClosing != closeTags {
49- Error (
50- "Unbalanced HTML tags: "
51- ++ Belt .Int .toString (openTags - selfClosing )
52- ++ " open, "
53- ++ Belt .Int .toString (closeTags )
54- ++ " close" ,
55- )
56- } else {
57- Ok (ValidHTML (html ))
58- }
59- }
60- }
61-
62- let toString = (ValidHTML (html )) => html
63- }
64-
65- let findMountPoint = (selector : ProvenSelector .validated ): option <Dom .element > =>
66- Webapi .Dom .document -> Webapi .Dom .Document .querySelector (selector -> ProvenSelector .toString )
67-
68- let mount = (selector : ProvenSelector .validated , html : ProvenHTML .validated ): mountResult => {
69- switch findMountPoint (selector ) {
70- | None => MountPointNotFound (selector -> ProvenSelector .toString )
71- | Some (element ) =>
72- element -> Webapi .Dom .Element .setInnerHTML (html -> ProvenHTML .toString )
73- Mounted (element )
74- }
75- }
76-
77- let mountString = (selector : string , html : string ): mountResult => {
78- switch ProvenSelector .validate (selector ) {
79- | Error (error ) => InvalidSelector (error )
80- | Ok (validSelector ) =>
81- switch ProvenHTML .validate (html ) {
82- | Error (error ) => InvalidHTML (error )
83- | Ok (validHtml ) => mount (validSelector , validHtml )
84- }
85- }
86- }
9+ let mountString = (selector : string , html : string ): mountResult => SafeDOMFFI .safeMountHTML (selector , html )
8710
8811let mountSafe = (
8912 selector : string ,
@@ -92,49 +15,18 @@ let mountSafe = (
9215 ~onError : string => unit ,
9316): unit => {
9417 switch mountString (selector , html ) {
95- | Mounted (element ) => onSuccess (element )
96- | MountPointNotFound (value ) => onError ("Mount point not found: " ++ value )
97- | InvalidSelector (value ) => onError ("Invalid selector: " ++ value )
98- | InvalidHTML (value ) => onError ("Invalid HTML: " ++ value )
18+ | MountedAt (element ) => onSuccess (element )
19+ | NotFound (value ) => onError ("Mount point not found: " ++ value )
20+ | Failed (value ) => onError (value )
9921 }
10022}
10123
102- type mountSpec = {
103- selector : string ,
104- html : string ,
105- }
106-
107- let mountBatch = (specs : array <mountSpec >): result <array <Dom .element >, string > => {
108- let mounted = []
109- let rec loop = (index : int ): result <array <Dom .element >, string > => {
110- if index >= Belt .Array .length (specs ) {
111- Ok (mounted )
112- } else {
113- switch specs [index ] {
114- | spec =>
115- switch mountString (spec .selector , spec .html ) {
116- | Mounted (element ) =>
117- mounted -> Belt .Array .push (element )
118- loop (index + 1 )
119- | MountPointNotFound (value ) => Error ("Mount point not found: " ++ value )
120- | InvalidSelector (value ) => Error ("Invalid selector: " ++ value )
121- | InvalidHTML (value ) => Error ("Invalid HTML: " ++ value )
122- }
123- }
124- }
125- }
126- loop (0 )
127- }
128-
129- let domReadyState : unit => string = %raw (` () => document .readyState ` )
130- let onDOMContentLoaded : (unit => unit ) => unit = %raw (` (callback ) => document .addEventListener (" DOMContentLoaded" , callback)` )
131-
13224let onDOMReady = (callback : unit => unit ): unit => {
133- let state = domReadyState ()
25+ let state = SafeDOMFFI . domReadyState ()
13426 if state == "complete" || state == "interactive" {
13527 callback ()
13628 } else {
137- onDOMContentLoaded (callback )
29+ SafeDOMFFI . onDOMContentLoaded (callback )
13830 }
13931}
14032
0 commit comments