Web Inspector: Formatter: Pretty Print HTML resources (including inline <script>...
[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     <button id="format">Format</button>
44     <button id="select-output">Select Output</button>
45     <button id="save-as-url">Save URL</button>
46     <small id="time"></small>
47     <br><br>
48
49     <!-- Editor -->
50     <textarea id="code" name="code"></textarea>
51
52     <!-- Output -->
53     <h3>Formatted</h3>
54     <pre id="pretty"></pre>
55     <h3>Tree</h3>
56     <pre id="debug-tree"></pre>
57     <h3>Tokens</h3>
58     <pre id="debug"></pre>
59
60     <script>
61     // Elements.
62     const populatePicker = document.getElementById("populate");
63     const timeOutput = document.getElementById("time");
64     const prettyPre = document.getElementById("pretty");
65     const debugPre = document.getElementById("debug");
66     const debugTreePre = document.getElementById("debug-tree");
67
68     // Editor.
69     let cm = CodeMirror.fromTextArea(document.getElementById("code"), {lineNumbers: true});
70     cm.setOption("mode", "text/html");
71
72     // Refresh after changes after a short delay.
73     let timer = null;
74     cm.on("change", function(codeMirror, change) {
75         if (timer)
76             clearTimeout(timer)
77         timer = setTimeout(function() {
78             clearTimeout(timer);
79             timer = null;
80             refresh();
81         }, 500);
82     });
83
84     function refresh() {
85         if (timer)
86             clearTimeout(timer);
87
88         // Time the formatter.
89         let startTime = Date.now();
90         let formatter = new HTMLFormatter(cm.getValue());
91         let endTime = Date.now();
92
93         // Show debug parser info.
94         let debugText = "";
95         try {
96             let parser = new HTMLParser;
97             let treeBuilder = new HTMLTreeBuilderDebug;
98             parser.parseDocument(cm.getValue(), treeBuilder);
99             debugText = treeBuilder.debugText;
100         } catch (error) {
101             debugText = "Parse error: " + JSON.stringify(error, null, 2);
102         }
103
104         // Show debug tree info.
105         let debugTreeText = "";
106         try {
107             let parser = new HTMLParser;
108             let treeBuilder = new HTMLTreeBuilderFormatter;
109             parser.parseDocument(cm.getValue(), treeBuilder);
110             console.log("TreeBuilder DOM", treeBuilder.dom);
111
112             let lines = [];
113             let indentString = "  ";
114             function filter(key, value) {
115                 if (key === "children")
116                     return undefined;
117                 return value;
118             }
119             function stringifyNode(node) {
120                 switch (node.type) {
121                 case HTMLTreeBuilderFormatter.NodeType.Text:
122                     return `TEXT: ${JSON.stringify(node.data)}`;
123                 case HTMLTreeBuilderFormatter.NodeType.Comment:
124                     return `COMMENT: (${node.data})`;
125                 case HTMLTreeBuilderFormatter.NodeType.Doctype:
126                     return `DOCTYPE: (${node.data})`;
127                 case HTMLTreeBuilderFormatter.NodeType.CData:
128                     return `CDATA: (${node.data})`;
129                 case HTMLTreeBuilderFormatter.NodeType.Error:
130                     return `ERROR: ${node.raw}`;
131                 case HTMLTreeBuilderFormatter.NodeType.Node: {
132                     let implicitCloseString = node.implicitClose ? " <implicitClose>" : "";
133                     let attributesString = node.attributes ? " " + JSON.stringify(node.attributes) : "";
134                     return `NODE: ${node.name}${attributesString}${implicitCloseString}`;
135                 }
136                 }
137             }
138             function visit(node, indent) {
139                 lines.push(indentString.repeat(indent) + stringifyNode(node));
140                 if (node.children) {
141                     for (let child of node.children)
142                         visit(child, indent + 1);
143                 }
144             }
145             for (let topLevelNode of treeBuilder.dom)
146                 visit(topLevelNode, 0);
147
148             debugTreeText = lines.join("\n");
149         } catch (error) {
150             debugTreeText = "TreeBuilder error: " + JSON.stringify(error, null, 2);
151         }
152
153         // Output the results.
154         timeOutput.innerText = (endTime - startTime) + "ms";
155         prettyPre.innerText = formatter.formattedText;
156         debugPre.innerText = debugText;
157         debugTreePre.innerText = debugTreeText;
158     }
159
160     setTimeout(refresh);
161
162     // Format button.
163     document.getElementById("format").addEventListener("click", (event) => {
164         refresh();
165     });
166
167     // Select output button.
168     document.getElementById("select-output").addEventListener("click", function(event) {
169         let range = document.createRange();
170         range.selectNodeContents(prettyPre);
171         let selection = window.getSelection();
172         selection.removeAllRanges();
173         selection.addRange(range);
174     });
175
176     // Save as URL button.
177     document.getElementById("save-as-url").addEventListener("click", (event) => {
178         let content = cm.getValue();
179         let populate = populatePicker.value;
180         window.location.search = `?content=${encodeURIComponent(content)}&populate=${encodeURIComponent(populate)}`;
181     });
182
183     const simpleHTML = `<!DOCTYPE html>
184 <html>
185 <head>
186 <title>Test</title>
187 <script src="js/script.js"></`+`script>
188 </head>
189 <body>
190 <!-- Comment -->
191 <div class="foo"><input type=text><br><p>Test</p></div>
192 <p><![CDATA[ Test ]]></p>
193 </body>
194 </html>
195 `;
196     const simpleSVG = `<?xml version="1.0"?>
197 <!-- Copyright © 2014 Apple Inc. All rights reserved. -->
198 <svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 13 14">
199 <rect fill="none" stroke="currentColor" x="0.5" y="0.5" width="12" height="13" rx="2"/>
200 <rect fill="none" stroke="currentColor" stroke-width="1" x="3" y="6" width="7" height="2" rx="0.5"/>
201 </svg>
202 `;
203
204     const htmlcssjs = `<!DOCTYPE html>
205 <html>
206 <head>
207 <title>Test</title>
208 <style>body,div,.foo{color:red}p{color:blue}</style>
209 <script>(function(a,b,c){let sum = a; sum += b; sum += c; return sum;})()</`+`script>
210 </head>
211 <body>
212 <!-- Comment -->
213 <div class="foo"><input type=text><br><p>Test</p></div>
214 </body>
215 </html>
216 `;
217
218     // Populate picker
219     function updateContentFromPicker() {
220         let value = populatePicker.value;
221         let content = simpleHTML;
222         switch (value) {
223         case "html-simple":
224             content = simpleHTML;
225             break;
226         case "svg-simple":
227             content = simpleSVG;
228             break;
229         case "html-css-js":
230             content = htmlcssjs;
231             break;
232         case "self":
233             content = document.documentElement.outerHTML;
234             break;
235         }
236         cm.setValue(content);
237     }
238
239     populatePicker.addEventListener("change", (event) => {
240         updateContentFromPicker();
241     });
242
243     // Restore better initial value from query string.
244     (function() {
245         let queryParams = {};
246         if (window.location.search.length > 0) {
247             let searchString = window.location.search.substring(1);
248             let groups = searchString.split("&");
249             for (let i = 0; i < groups.length; ++i) {
250                 let pair = groups[i].split("=");
251                 queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
252             }
253         }
254         if (queryParams.populate) {
255             populatePicker.value = queryParams.populate;
256             updateContentFromPicker();
257         }
258         if (queryParams.content)
259             cm.setValue(queryParams.content);
260     })();
261     
262     if (!cm.getValue())
263         cm.setValue(simpleHTML);
264     </script>
265 </body>
266 </html>