Web Inspector: use weak collections for holding event listeners
[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/Multimap.js"></script>
22     <script src="../../UserInterface/Base/Object.js"></script>
23     <script src="../../UserInterface/Base/Utilities.js"></script>
24     <script src="../../UserInterface/Controllers/FormatterSourceMap.js"></script>
25     <script src="../../UserInterface/Proxies/FormatterWorkerProxy.js"></script>
26 </head>
27 <body>
28     <h1>Debug SourceMaps</h1>
29
30     <!-- Controls -->
31     <select id="mode">
32         <option>html</option>
33         <option>javascript</option>
34         <option>css</option>
35         <option>xml</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         case "xml":
201             workerProxy.formatXML(inputCM.getValue(), indentString, includeSourceMapData, formatResult);
202             break;
203         }
204
205         function formatResult({formattedText, sourceMapData}) {
206             outputCM.setValue(formattedText || "");
207             formatterSourceMap = WI.FormatterSourceMap.fromSourceMapData(sourceMapData);
208             updateFromInput();
209
210             debugMappingPre.textContent = JSON.stringify(sourceMapData, (key, value) => {
211                 if (Array.isArray(value))
212                     return `[${value.join()}]`;
213                 return value;
214             }, 2);
215         }
216     }
217
218     setTimeout(refresh);
219
220     // Format button.
221     document.getElementById("format").addEventListener("click", (event) => {
222         refresh();
223     });
224
225     // Save as URL button.
226     document.getElementById("save-as-url").addEventListener("click", (event) => {
227         let content = inputCM.getValue();
228         let mode = modePicker.value;
229         window.location.search = `?content=${encodeURIComponent(content)}&mode=${encodeURIComponent(mode)}`;
230     });
231
232     const simpleHTML = `<!DOCTYPE html>
233 <html><head><title>Test</title>
234 <script src="js/script.js"></`+`script></head>
235 <body><!-- Comment --><div class="foo">
236 <style>body,div,.foo{color:red}p{color:blue}</style>
237 <script>(function(a,b,c){let sum=a;sum+=b;sum+=c;return sum;})()</`+`script>
238 <input type=text><br><p>Test</p></div><p><![CDATA[ Test ]]></p></body></html>`;
239     const simpleJS = `(function(){let a=1;return a+1;})();`;
240     const simpleCSS = `body{color:red;background:blue}*{color:green}`;
241     const simpleXML = `<?xml version="1.0" encoding="iso8859-5"?><outer><inner attr="value">1</inner></outer>`;
242
243     // Populate picker
244     function updateContentFromPicker() {
245         let mode, content;
246         switch (modePicker.value) {
247         case "html":
248             mode = "text/html";
249             content = simpleHTML;
250             break;
251         case "javascript":
252             mode = "text/javascript";
253             content = simpleJS;
254             break;
255         case "css":
256             mode = "text/css";
257             content = simpleCSS;
258             break;
259         case "xml":
260             mode = "text/xml";
261             content = simpleXML;
262             break;
263         default:
264             console.assert();
265             break;
266         }
267         inputCM.setOption("mode", mode);
268         outputCM.setOption("mode", mode);
269         inputCM.setValue(content);
270         refresh();
271     }
272
273     modePicker.addEventListener("change", (event) => {
274         updateContentFromPicker();
275     });
276
277     // Restore better initial value from query string.
278     (function() {
279         let queryParams = {};
280         if (window.location.search.length > 0) {
281             let searchString = window.location.search.substring(1);
282             let groups = searchString.split("&");
283             for (let i = 0; i < groups.length; ++i) {
284                 let pair = groups[i].split("=");
285                 queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
286             }
287         }
288         if (queryParams.mode) {
289             modePicker.value = queryParams.mode;
290             updateContentFromPicker();
291         }
292         if (queryParams.content)
293             inputCM.setValue(queryParams.content);
294     })();
295
296     if (!inputCM.getValue())
297         inputCM.setValue(simpleHTML);
298     </script>
299 </body>
300 </html>