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