[LFC][IFC] Compute isFirstBox/isLastBox for text content
[WebKit-https.git] / Source / WebInspectorUI / Tools / HTMLFormatter / index.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4     <meta charset="utf-8">
5     <title>HTMLFormatter Tool</title>
6
7     <style>:root { color-scheme: light dark; }</style>
8     <link rel="stylesheet" href="../../UserInterface/External/CodeMirror/codemirror.css">
9     <link rel="stylesheet" href="../../UserInterface/Views/Variables.css">
10     <link rel="stylesheet" href="../../UserInterface/Views/CodeMirrorOverrides.css">
11     <link rel="stylesheet" href="../../UserInterface/Views/SyntaxHighlightingDefaultTheme.css">
12     <link rel="stylesheet" href="styles.css">
13
14     <script src="../../UserInterface/External/CodeMirror/codemirror.js"></script>
15     <script src="../../UserInterface/External/CodeMirror/css.js"></script>
16     <script src="../../UserInterface/External/CodeMirror/htmlmixed.js"></script>
17     <script src="../../UserInterface/External/CodeMirror/javascript.js"></script>
18     <script src="../../UserInterface/External/CodeMirror/xml.js"></script>
19     <script src="../../UserInterface/Views/CodeMirrorLocalOverrideURLMode.js"></script>
20     <script src="../../UserInterface/Views/CodeMirrorRegexMode.js"></script>
21
22     <script src="HTMLTreeBuilderDebug.js"></script>
23     <script src="../../UserInterface/External/Esprima/esprima.js"></script>
24     <script src="../../UserInterface/Workers/Formatter/CSSFormatter.js"></script>
25     <script src="../../UserInterface/Workers/Formatter/ESTreeWalker.js"></script>
26     <script src="../../UserInterface/Workers/Formatter/FormatterContentBuilder.js"></script>
27     <script src="../../UserInterface/Workers/Formatter/FormatterUtilities.js"></script>
28     <script src="../../UserInterface/Workers/Formatter/HTMLFormatter.js"></script>
29     <script src="../../UserInterface/Workers/Formatter/HTMLParser.js"></script>
30     <script src="../../UserInterface/Workers/Formatter/HTMLTreeBuilderFormatter.js"></script>
31     <script src="../../UserInterface/Workers/Formatter/JSFormatter.js"></script>
32 </head>
33 <body>
34     <h1>Debug HTMLFormatter</h1>
35
36     <!-- Controls -->
37     <select id="populate">
38         <option value="html-simple">Simple HTML Document</option>
39         <option value="svg-simple">Simple SVG Document</option>
40         <option value="html-css-js">HTML with Styles and Script</option>
41         <option value="self">Self</option>
42     </select>
43     <select id="source-type">
44         <option value="text/html">HTML</option>
45         <option value="text/xml">XML</option>
46     </select>
47     <button id="format">Format</button>
48     <button id="select-output">Select Output</button>
49     <button id="save-as-url">Save URL</button>
50     <small id="time"></small>
51     <br><br>
52
53     <!-- Editor -->
54     <textarea id="code" name="code"></textarea>
55
56     <!-- Output -->
57     <h3>Formatted</h3>
58     <pre id="pretty"></pre>
59     <h3>Tree</h3>
60     <pre id="debug-tree"></pre>
61     <h3>Tokens</h3>
62     <pre id="debug"></pre>
63
64     <script>
65     // Elements.
66     const populatePicker = document.getElementById("populate");
67     const sourceTypePicker = document.getElementById("source-type");
68     const timeOutput = document.getElementById("time");
69     const prettyPre = document.getElementById("pretty");
70     const debugPre = document.getElementById("debug");
71     const debugTreePre = document.getElementById("debug-tree");
72
73     // Editor.
74     let cm = CodeMirror.fromTextArea(document.getElementById("code"), {lineNumbers: true});
75     cm.setOption("mode", "text/html");
76
77     // Refresh after changes after a short delay.
78     let timer = null;
79     cm.on("change", function(codeMirror, change) {
80         if (timer)
81             clearTimeout(timer)
82         timer = setTimeout(function() {
83             clearTimeout(timer);
84             timer = null;
85             refresh();
86         }, 500);
87     });
88
89     function refresh() {
90         if (timer)
91             clearTimeout(timer);
92
93         // Time the formatter.
94         let sourceType = sourceTypePicker.value === "text/html" ? HTMLFormatter.SourceType.HTML : HTMLFormatter.SourceType.XML;
95         let startTime = Date.now();
96         let formatter = new HTMLFormatter(cm.getValue(), sourceType);
97         let endTime = Date.now();
98
99         // Show debug parser info.
100         let debugText = "";
101         try {
102             let options = {isXML: sourceType === HTMLFormatter.SourceType.XML};
103             let parser = new HTMLParser;
104             let treeBuilder = new HTMLTreeBuilderDebug;
105             parser.parseDocument(cm.getValue(), treeBuilder, options);
106             debugText = treeBuilder.debugText;
107         } catch (error) {
108             debugText = "Parse error: " + JSON.stringify(error, null, 2);
109         }
110
111         // Show debug tree info.
112         let debugTreeText = "";
113         try {
114             let parser = new HTMLParser;
115             let treeBuilder = new HTMLTreeBuilderFormatter;
116             parser.parseDocument(cm.getValue(), treeBuilder);
117             console.log("TreeBuilder DOM", treeBuilder.dom);
118
119             let lines = [];
120             let indentString = "  ";
121             function filter(key, value) {
122                 if (key === "children")
123                     return undefined;
124                 return value;
125             }
126             function stringifyNode(node) {
127                 switch (node.type) {
128                 case HTMLTreeBuilderFormatter.NodeType.Text:
129                     return `TEXT: ${JSON.stringify(node.data)}`;
130                 case HTMLTreeBuilderFormatter.NodeType.Comment:
131                     return `COMMENT: (${node.data})`;
132                 case HTMLTreeBuilderFormatter.NodeType.Doctype:
133                     return `DOCTYPE: (${node.data})`;
134                 case HTMLTreeBuilderFormatter.NodeType.CData:
135                     return `CDATA: (${node.data})`;
136                 case HTMLTreeBuilderFormatter.NodeType.Error:
137                     return `ERROR: ${node.raw}`;
138                 case HTMLTreeBuilderFormatter.NodeType.Node: {
139                     let implicitCloseString = node.implicitClose ? " <implicitClose>" : "";
140                     let attributesString = node.attributes ? " " + JSON.stringify(node.attributes) : "";
141                     return `NODE: ${node.name}${attributesString}${implicitCloseString}`;
142                 }
143                 }
144             }
145             function visit(node, indent) {
146                 lines.push(indentString.repeat(indent) + stringifyNode(node));
147                 if (node.children) {
148                     for (let child of node.children)
149                         visit(child, indent + 1);
150                 }
151             }
152             for (let topLevelNode of treeBuilder.dom)
153                 visit(topLevelNode, 0);
154
155             debugTreeText = lines.join("\n");
156         } catch (error) {
157             debugTreeText = "TreeBuilder error: " + JSON.stringify(error, null, 2);
158         }
159
160         // Output the results.
161         timeOutput.innerText = (endTime - startTime) + "ms";
162         prettyPre.innerText = formatter.formattedText;
163         debugPre.innerText = debugText;
164         debugTreePre.innerText = debugTreeText;
165     }
166
167     setTimeout(refresh);
168
169     // Format button.
170     document.getElementById("format").addEventListener("click", (event) => {
171         refresh();
172     });
173
174     // Select output button.
175     document.getElementById("select-output").addEventListener("click", function(event) {
176         let range = document.createRange();
177         range.selectNodeContents(prettyPre);
178         let selection = window.getSelection();
179         selection.removeAllRanges();
180         selection.addRange(range);
181     });
182
183     // Save as URL button.
184     document.getElementById("save-as-url").addEventListener("click", (event) => {
185         let content = cm.getValue();
186         let populate = populatePicker.value;
187         let sourceType = sourceTypePicker.value;
188         window.location.search = `?content=${encodeURIComponent(content)}&populate=${encodeURIComponent(populate)}&sourceType=${encodeURIComponent(sourceType)}`;
189     });
190
191     const simpleHTML = `<!DOCTYPE html>
192 <html>
193 <head>
194 <title>Test</title>
195 <script src="js/script.js"></`+`script>
196 </head>
197 <body>
198 <!-- Comment -->
199 <div class="foo"><input type=text><br><p>Test</p></div>
200 <p><![CDATA[ Test ]]></p>
201 </body>
202 </html>
203 `;
204     const simpleSVG = `<?xml version="1.0"?>
205 <!-- Copyright © 2014 Apple Inc. All rights reserved. -->
206 <svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 13 14">
207 <rect fill="none" stroke="currentColor" x="0.5" y="0.5" width="12" height="13" rx="2"/>
208 <rect fill="none" stroke="currentColor" stroke-width="1" x="3" y="6" width="7" height="2" rx="0.5"/>
209 </svg>
210 `;
211
212     const htmlcssjs = `<!DOCTYPE html>
213 <html>
214 <head>
215 <title>Test</title>
216 <style>body,div,.foo{color:red}p{color:blue}</style>
217 <script>(function(a,b,c){let sum = a; sum += b; sum += c; return sum;})()</`+`script>
218 </head>
219 <body>
220 <!-- Comment -->
221 <div class="foo"><input type=text><br><p>Test</p></div>
222 </body>
223 </html>
224 `;
225
226     // Populate picker
227     function updateContentFromPicker() {
228         let value = populatePicker.value;
229         let content = simpleHTML;
230         switch (value) {
231         case "html-simple":
232             content = simpleHTML;
233             break;
234         case "svg-simple":
235             content = simpleSVG;
236             break;
237         case "html-css-js":
238             content = htmlcssjs;
239             break;
240         case "self":
241             content = document.documentElement.outerHTML;
242             break;
243         }
244         cm.setValue(content);
245     }
246
247     populatePicker.addEventListener("change", (event) => {
248         updateContentFromPicker();
249     });
250
251     // Parser mode picker.
252     sourceTypePicker.addEventListener("change", (event) => {
253         cm.setOption("mode", sourceTypePicker.value);
254         refresh();
255     });
256
257     // Restore better initial value from query string.
258     (function() {
259         let queryParams = {};
260         if (window.location.search.length > 0) {
261             let searchString = window.location.search.substring(1);
262             let groups = searchString.split("&");
263             for (let i = 0; i < groups.length; ++i) {
264                 let pair = groups[i].split("=");
265                 queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
266             }
267         }
268         if (queryParams.populate) {
269             populatePicker.value = queryParams.populate;
270             updateContentFromPicker();
271         }
272         if (queryParams.sourceType) {
273             sourceTypePicker.value = queryParams.sourceType;
274             cm.setOption("mode", sourceTypePicker.value);
275         }
276         if (queryParams.content)
277             cm.setValue(queryParams.content);
278     })();
279     
280     if (!cm.getValue())
281         cm.setValue(simpleHTML);
282     </script>
283 </body>
284 </html>