Web Inspector: Formatter: Pretty Print HTML resources (including inline <script>...
[WebKit-https.git] / Source / WebInspectorUI / Tools / SourceMaps / index.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4     <meta charset="utf-8">
5     <title>SourceMaps 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
20     <script src="../../UserInterface/Base/WebInspector.js"></script>
21     <script src="../../UserInterface/Base/LinkedList.js"></script>
22     <script src="../../UserInterface/Base/ListMultimap.js"></script>
23     <script src="../../UserInterface/Base/Object.js"></script>
24     <script src="../../UserInterface/Base/Utilities.js"></script>
25     <script src="../../UserInterface/Controllers/FormatterSourceMap.js"></script>
26     <script src="../../UserInterface/Proxies/FormatterWorkerProxy.js"></script>
27 </head>
28 <body>
29     <h1>Debug SourceMaps</h1>
30
31     <!-- Controls -->
32     <select id="mode">
33         <option>html</option>
34         <option>javascript</option>
35         <option>css</option>
36     </select>
37     <button id="format">Format</button>
38     <button id="save-as-url">Save URL</button>
39     <br><br>
40
41     <!-- Editor -->
42     <div class="editors-container">
43         <textarea id="input" name="code"></textarea>
44         <textarea id="output" name="code"></textarea>
45     </div>
46
47     <!-- Output -->
48     <h3>Debug</h3>
49     <pre id="debug"></pre>
50     <h3>Source Mapping</h3>
51     <pre id="debug-mapping"></pre>
52
53     <script>
54     // Elements.
55     const modePicker = document.getElementById("mode");
56     const debugPre = document.getElementById("debug");
57     const debugMappingPre = document.getElementById("debug-mapping");
58
59     // Input Editor.
60     let inputCM = CodeMirror.fromTextArea(document.getElementById("input"), {lineNumbers: true});
61     inputCM.setOption("mode", "text/html");
62
63     // Output Editor
64     let outputCM = CodeMirror.fromTextArea(document.getElementById("output"), {lineNumbers: true, readOnly: true});
65     outputCM.setOption("mode", "text/html");
66
67     // Global.
68     let formatterSourceMap = null;
69
70     // Markers.
71     let inputMarker = null;
72     let outputMarker = null;
73     function clearMarkers() {
74         if (inputMarker)
75             inputMarker.clear();
76         if (outputMarker)
77             outputMarker.clear();
78     }
79
80     // Refresh after changes after a short delay.
81     let timer = null;
82     inputCM.on("change", function(codeMirror, change) {
83         if (timer)
84             clearTimeout(timer)
85         timer = setTimeout(function() {
86             clearTimeout(timer);
87             timer = null;
88             refresh();
89         }, 100);
90     });
91
92     // Input has changed, update Output.
93
94     let originalLocation = {lineNumber: 0, columnNumber: 0};
95     let formattedLocation = {lineNumber: 0, columnNumber: 0};
96
97     inputCM.on("cursorActivity", () => {
98         updateFromInput();
99     });
100
101     function updateFromInput() {
102         updateFormattedLocationFromInput();
103         updateDebugTextFromInput();
104         updateOutputCursorFromInput();
105     }
106
107     function updateFormattedLocationFromInput() {
108         if (!formatterSourceMap)
109             return;
110
111         let codeMirrorPosition = inputCM.getCursor();
112         let codeMirrorIndex = inputCM.getDoc().indexFromPos(codeMirrorPosition);
113         originalLocation = {lineNumber: codeMirrorPosition.line || 0, columnNumber: codeMirrorPosition.ch || 0, position: codeMirrorIndex};
114         formattedLocation = formatterSourceMap.originalToFormatted(originalLocation.lineNumber, originalLocation.columnNumber);
115         formattedLocation.position = formatterSourceMap.originalPositionToFormattedPosition(codeMirrorIndex);
116     }
117
118     function updateDebugTextFromInput() {
119         let originalDisplay = `${originalLocation.position} (${originalLocation.lineNumber}, ${originalLocation.columnNumber})`;
120         let formattedDisplay = `${formattedLocation.position} (${formattedLocation.lineNumber}, ${formattedLocation.columnNumber})`;
121         let debugText = "";
122         debugText = "";
123         debugText += "Original Location:\n";
124         debugText += originalDisplay + "\n";
125         debugText += "\n";
126         debugText += "Formatted Location:\n";
127         debugText += formattedDisplay + "\n";
128         debugPre.textContent = debugText;
129     }
130
131     let outputCursorElem = document.createElement("div");
132     outputCursorElem.style.display = "inline-block";
133     outputCursorElem.style.backgroundColor = "red";
134     outputCursorElem.style.width = "2px";
135     outputCursorElem.textContent = " ";
136
137     function updateOutputCursorFromInput() {
138         clearMarkers();
139         let codeMirrorPosition = {line: formattedLocation.lineNumber, ch: formattedLocation.columnNumber};
140         outputMarker = outputCM.setBookmark(codeMirrorPosition, outputCursorElem);
141     }
142
143     // Output has changed, update Input.
144
145     let reverseOriginalLocation = {lineNumber: 0, columnNumber: 0};
146     let reverseFormattedLocation = {lineNumber: 0, columnNumber: 0};
147
148     outputCM.on("cursorActivity", () => {
149         updateFromOutput();
150     });
151
152     function updateFromOutput() {
153         updateFormattedLocationFromOutput();
154         updateInputCursorFromOutput();
155     }
156
157     function updateFormattedLocationFromOutput() {
158         if (!formatterSourceMap)
159             return;
160
161         let codeMirrorPosition = outputCM.getCursor();
162         let codeMirrorIndex = outputCM.getDoc().indexFromPos(codeMirrorPosition);
163         reverseFormattedLocation = {lineNumber: codeMirrorPosition.line || 0, columnNumber: codeMirrorPosition.ch || 0, position: codeMirrorIndex};
164         reverseOriginalLocation = formatterSourceMap.formattedToOriginal(reverseFormattedLocation.lineNumber, reverseFormattedLocation.columnNumber);
165         reverseOriginalLocation.position = formatterSourceMap.formattedPositionToOriginalPosition(codeMirrorIndex);
166     }
167
168     let inputCursorElem = document.createElement("div");
169     inputCursorElem.style.display = "inline-block";
170     inputCursorElem.style.backgroundColor = "blue";
171     inputCursorElem.style.width = "2px";
172     inputCursorElem.textContent = " ";
173
174     function updateInputCursorFromOutput() {
175         clearMarkers();
176         let codeMirrorPosition = {line: reverseOriginalLocation.lineNumber, ch: reverseOriginalLocation.columnNumber};
177         inputMarker = inputCM.setBookmark(codeMirrorPosition, inputCursorElem);
178     }
179
180     // --------
181
182     function refresh() {
183         if (timer)
184             clearTimeout(timer);
185
186         const indentString = "    ";
187         const includeSourceMapData = true;
188         let workerProxy = WI.FormatterWorkerProxy.singleton();
189
190         switch (modePicker.value) {
191         case "html":
192             workerProxy.formatHTML(inputCM.getValue(), indentString, includeSourceMapData, formatResult);
193             break;
194         case "javascript":
195             workerProxy.formatJavaScript(inputCM.getValue(), false, indentString, includeSourceMapData, formatResult);
196             break;
197         case "css":
198             workerProxy.formatCSS(inputCM.getValue(), indentString, includeSourceMapData, formatResult);
199             break;
200         }
201
202         function formatResult({formattedText, sourceMapData}) {
203             outputCM.setValue(formattedText || "");
204             formatterSourceMap = WI.FormatterSourceMap.fromSourceMapData(sourceMapData);
205             updateFromInput();
206
207             debugMappingPre.textContent = JSON.stringify(sourceMapData, (key, value) => {
208                 if (Array.isArray(value))
209                     return `[${value.join()}]`;
210                 return value;
211             }, 2);
212         }
213     }
214
215     setTimeout(refresh);
216
217     // Format button.
218     document.getElementById("format").addEventListener("click", (event) => {
219         refresh();
220     });
221
222     // Save as URL button.
223     document.getElementById("save-as-url").addEventListener("click", (event) => {
224         let content = inputCM.getValue();
225         let mode = modePicker.value;
226         window.location.search = `?content=${encodeURIComponent(content)}&mode=${encodeURIComponent(mode)}`;
227     });
228
229     const simpleHTML = `<!DOCTYPE html>
230 <html><head><title>Test</title>
231 <script src="js/script.js"></`+`script></head>
232 <body><!-- Comment --><div class="foo">
233 <style>body,div,.foo{color:red}p{color:blue}</style>
234 <script>(function(a,b,c){let sum=a;sum+=b;sum+=c;return sum;})()</`+`script>
235 <input type=text><br><p>Test</p></div><p><![CDATA[ Test ]]></p></body></html>`;
236     const simpleJS = `(function(){let a=1;return a+1;})();`;
237     const simpleCSS = `body{color:red;background:blue}*{color:green}`;
238
239     // Populate picker
240     function updateContentFromPicker() {
241         let mode, content;
242         switch (modePicker.value) {
243         case "html":
244             mode = "text/html";
245             content = simpleHTML;
246             break;
247         case "javascript":
248             mode = "text/javascript";
249             content = simpleJS;
250             break;
251         case "css":
252             mode = "text/css";
253             content = simpleCSS;
254             break;
255         default:
256             console.assert();
257             break;
258         }
259         inputCM.setOption("mode", mode);
260         outputCM.setOption("mode", mode);
261         inputCM.setValue(content);
262         refresh();
263     }
264
265     modePicker.addEventListener("change", (event) => {
266         updateContentFromPicker();
267     });
268
269     // Restore better initial value from query string.
270     (function() {
271         let queryParams = {};
272         if (window.location.search.length > 0) {
273             let searchString = window.location.search.substring(1);
274             let groups = searchString.split("&");
275             for (let i = 0; i < groups.length; ++i) {
276                 let pair = groups[i].split("=");
277                 queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
278             }
279         }
280         if (queryParams.mode) {
281             modePicker.value = queryParams.mode;
282             updateContentFromPicker();
283         }
284         if (queryParams.content)
285             inputCM.setValue(queryParams.content);
286     })();
287
288     if (!inputCM.getValue())
289         inputCM.setValue(simpleHTML);
290     </script>
291 </body>
292 </html>