2009-10-03 Joseph Pecoraro <joepeck@webkit.org>
[WebKit-https.git] / WebCore / inspector / front-end / SourceFrame.js
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3  * Copyright (C) 2009 Joseph Pecoraro
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 WebInspector.SourceFrame = function(element, addBreakpointDelegate)
28 {
29     this.messages = [];
30     this.breakpoints = [];
31     this._shortcuts = {};
32
33     this.addBreakpointDelegate = addBreakpointDelegate;
34
35     this.element = element || document.createElement("iframe");
36     this.element.addStyleClass("source-view-frame");
37     this.element.setAttribute("viewsource", "true");
38
39     this.element.addEventListener("load", this._loaded.bind(this), false);
40 }
41
42 WebInspector.SourceFrame.prototype = {
43     get executionLine()
44     {
45         return this._executionLine;
46     },
47
48     set executionLine(x)
49     {
50         if (this._executionLine === x)
51             return;
52
53         var previousLine = this._executionLine;
54         this._executionLine = x;
55
56         this._updateExecutionLine(previousLine);
57     },
58
59     get autoSizesToFitContentHeight()
60     {
61         return this._autoSizesToFitContentHeight;
62     },
63
64     set autoSizesToFitContentHeight(x)
65     {
66         if (this._autoSizesToFitContentHeight === x)
67             return;
68
69         this._autoSizesToFitContentHeight = x;
70
71         if (this._autoSizesToFitContentHeight) {
72             this._windowResizeListener = this._windowResized.bind(this);
73             window.addEventListener("resize", this._windowResizeListener, false);
74             this.sizeToFitContentHeight();
75         } else {
76             this.element.style.removeProperty("height");
77             if (this.element.contentDocument)
78                 this.element.contentDocument.body.removeStyleClass("webkit-height-sized-to-fit");
79             window.removeEventListener("resize", this._windowResizeListener, false);
80             delete this._windowResizeListener;
81         }
82     },
83
84     sourceRow: function(lineNumber)
85     {
86         if (!lineNumber || !this.element.contentDocument)
87             return;
88
89         var table = this.element.contentDocument.getElementsByTagName("table")[0];
90         if (!table)
91             return;
92
93         var rows = table.rows;
94
95         // Line numbers are a 1-based index, but the rows collection is 0-based.
96         --lineNumber;
97
98         return rows[lineNumber];
99     },
100
101     lineNumberForSourceRow: function(sourceRow)
102     {
103         // Line numbers are a 1-based index, but the rows collection is 0-based.
104         var lineNumber = 0;
105         while (sourceRow) {
106             ++lineNumber;
107             sourceRow = sourceRow.previousSibling;
108         }
109
110         return lineNumber;
111     },
112
113     revealLine: function(lineNumber)
114     {
115         if (!this._isContentLoaded()) {
116             this._lineNumberToReveal = lineNumber;
117             return;
118         }
119
120         var row = this.sourceRow(lineNumber);
121         if (row)
122             row.scrollIntoViewIfNeeded(true);
123     },
124
125     addBreakpoint: function(breakpoint)
126     {
127         this.breakpoints.push(breakpoint);
128         breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this);
129         breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this);
130         this._addBreakpointToSource(breakpoint);
131     },
132
133     removeBreakpoint: function(breakpoint)
134     {
135         this.breakpoints.remove(breakpoint);
136         breakpoint.removeEventListener("enabled", null, this);
137         breakpoint.removeEventListener("disabled", null, this);
138         this._removeBreakpointFromSource(breakpoint);
139     },
140
141     addMessage: function(msg)
142     {
143         // Don't add the message if there is no message or valid line or if the msg isn't an error or warning.
144         if (!msg.message || msg.line <= 0 || !msg.isErrorOrWarning())
145             return;
146         this.messages.push(msg);
147         this._addMessageToSource(msg);
148     },
149
150     clearMessages: function()
151     {
152         this.messages = [];
153
154         if (!this.element.contentDocument)
155             return;
156
157         var bubbles = this.element.contentDocument.querySelectorAll(".webkit-html-message-bubble");
158         if (!bubbles)
159             return;
160
161         for (var i = 0; i < bubbles.length; ++i) {
162             var bubble = bubbles[i];
163             bubble.parentNode.removeChild(bubble);
164         }
165     },
166
167     sizeToFitContentHeight: function()
168     {
169         if (this.element.contentDocument) {
170             this.element.style.setProperty("height", this.element.contentDocument.body.offsetHeight + "px");
171             this.element.contentDocument.body.addStyleClass("webkit-height-sized-to-fit");
172         }
173     },
174
175     _highlightLineEnds: function(event)
176     {
177         event.target.parentNode.removeStyleClass("webkit-highlighted-line");
178     },
179
180     highlightLine: function(lineNumber)
181     {
182         if (!this._isContentLoaded()) {
183             this._lineNumberToHighlight = lineNumber;
184             return;
185         }
186
187         var sourceRow = this.sourceRow(lineNumber);
188         if (!sourceRow)
189             return;
190         var line = sourceRow.getElementsByClassName('webkit-line-content')[0];
191         // Trick to reset the animation if the user clicks on the same link
192         // Using a timeout to avoid coalesced style updates
193         line.style.setProperty("-webkit-animation-name", "none");
194         setTimeout(function () {
195             line.style.removeProperty("-webkit-animation-name");
196             sourceRow.addStyleClass("webkit-highlighted-line");
197         }, 0);
198     },
199
200     _loaded: function()
201     {
202         WebInspector.addMainEventListeners(this.element.contentDocument);
203         this.element.contentDocument.addEventListener("contextmenu", this._documentContextMenu.bind(this), true);
204         this.element.contentDocument.addEventListener("mousedown", this._documentMouseDown.bind(this), true);
205         this.element.contentDocument.addEventListener("keydown", this._documentKeyDown.bind(this), true);
206         this.element.contentDocument.addEventListener("keyup", WebInspector.documentKeyUp.bind(WebInspector), true);
207         this.element.contentDocument.addEventListener("webkitAnimationEnd", this._highlightLineEnds.bind(this), false);
208
209         // Register 'eval' shortcut.
210         var isMac = InspectorController.platform().indexOf("mac-") === 0;
211         var platformSpecificModifier = isMac ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl;
212         var shortcut = WebInspector.KeyboardShortcut.makeKey(69 /* 'E' */, platformSpecificModifier | WebInspector.KeyboardShortcut.Modifiers.Shift);
213         this._shortcuts[shortcut] = this._evalSelectionInCallFrame.bind(this);
214
215         var headElement = this.element.contentDocument.getElementsByTagName("head")[0];
216         if (!headElement) {
217             headElement = this.element.contentDocument.createElement("head");
218             this.element.contentDocument.documentElement.insertBefore(headElement, this.element.contentDocument.documentElement.firstChild);
219         }
220
221         var styleElement = this.element.contentDocument.createElement("style");
222         headElement.appendChild(styleElement);
223
224         // Add these style rules here since they are specific to the Inspector. They also behave oddly and not
225         // all properties apply if added to view-source.css (becuase it is a user agent sheet.)
226         var styleText = ".webkit-line-number { background-repeat: no-repeat; background-position: right 1px; }\n";
227         styleText += ".webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(program-counter); }\n";
228
229         styleText += ".webkit-breakpoint .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint); }\n";
230         styleText += ".webkit-breakpoint-disabled .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled); }\n";
231         styleText += ".webkit-breakpoint.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-program-counter); }\n";
232         styleText += ".webkit-breakpoint-disabled.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-program-counter); }\n";
233
234         styleText += ".webkit-breakpoint.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-conditional); }\n";
235         styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled-conditional); }\n";
236         styleText += ".webkit-breakpoint.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-conditional-program-counter); }\n";
237         styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-conditional-program-counter); }\n";
238
239         styleText += ".webkit-execution-line .webkit-line-content { background-color: rgb(171, 191, 254); outline: 1px solid rgb(64, 115, 244); }\n";
240         styleText += ".webkit-height-sized-to-fit { overflow-y: hidden }\n";
241         styleText += ".webkit-line-content { background-color: white; }\n";
242         styleText += "@-webkit-keyframes fadeout {from {background-color: rgb(255, 255, 120);} to { background-color: white;}}\n";
243         styleText += ".webkit-highlighted-line .webkit-line-content { background-color: rgb(255, 255, 120); -webkit-animation: 'fadeout' 2s 500ms}\n";
244         styleText += ".webkit-javascript-comment { color: rgb(0, 116, 0); }\n";
245         styleText += ".webkit-javascript-keyword { color: rgb(170, 13, 145); }\n";
246         styleText += ".webkit-javascript-number { color: rgb(28, 0, 207); }\n";
247         styleText += ".webkit-javascript-string, .webkit-javascript-regexp { color: rgb(196, 26, 22); }\n";
248
249         styleText += ".webkit-css-comment { color: rgb(0, 116, 0); }\n";
250         styleText += ".webkit-css-string, .webkit-css-keyword, .webkit-css-unit { color: rgb(7, 144, 154); }\n";
251         styleText += ".webkit-css-number { color: rgb(50, 0, 255); }\n";
252         styleText += ".webkit-css-property, .webkit-css-at-rule { color: rgb(200, 0, 0); }\n";
253         styleText += ".webkit-css-url { color: rgb(0, 0, 0); }\n";
254         styleText += ".webkit-css-selector { color: rgb(0, 0, 0); }\n";
255         styleText += ".webkit-css-pseudo-class { color: rgb(128, 128, 128); }\n";
256
257         // TODO: Move these styles into inspector.css once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed and popup moved into the top frame.
258         styleText += ".popup-content { position: absolute; z-index: 10000; padding: 4px; background-color: rgb(203, 226, 255); -webkit-border-radius: 7px; border: 2px solid rgb(169, 172, 203); }";
259         styleText += ".popup-glasspane { position: absolute; top: 0; left: 0; height: 100%; width: 100%; opacity: 0; z-index: 9900; }";
260         styleText += ".popup-message { background-color: transparent; font-family: Lucida Grande, sans-serif; font-weight: normal; font-size: 11px; text-align: left; text-shadow: none; color: rgb(85, 85, 85); cursor: default; margin: 0 0 2px 0; }";
261         styleText += ".popup-content.breakpoint-condition { width: 90%; }";
262         styleText += ".popup-content input#bp-condition { font-family: monospace; margin: 0; border: 1px inset rgb(190, 190, 190) !important; width: 100%; box-shadow: none !important; outline: none !important; -webkit-user-modify: read-write; }";
263         // This class is already in inspector.css
264         styleText += ".hidden { display: none !important; }";
265
266         styleElement.textContent = styleText;
267
268         this._needsProgramCounterImage = true;
269         this._needsBreakpointImages = true;
270
271         this.element.contentWindow.Element.prototype.addStyleClass = Element.prototype.addStyleClass;
272         this.element.contentWindow.Element.prototype.removeStyleClass = Element.prototype.removeStyleClass;
273         this.element.contentWindow.Element.prototype.positionAt = Element.prototype.positionAt;
274         this.element.contentWindow.Element.prototype.removeMatchingStyleClasses = Element.prototype.removeMatchingStyleClasses;
275         this.element.contentWindow.Element.prototype.hasStyleClass = Element.prototype.hasStyleClass;
276         this.element.contentWindow.Element.prototype.pageOffsetRelativeToWindow = Element.prototype.pageOffsetRelativeToWindow;
277         this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetLeft", Element.prototype.__lookupGetter__("totalOffsetLeft"));
278         this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetTop", Element.prototype.__lookupGetter__("totalOffsetTop"));
279         this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeName = Node.prototype.enclosingNodeOrSelfWithNodeName;
280         this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = Node.prototype.enclosingNodeOrSelfWithNodeNameInArray;
281
282         this._addExistingMessagesToSource();
283         this._addExistingBreakpointsToSource();
284         this._updateExecutionLine();
285         if (this._executionLine)
286             this.revealLine(this._executionLine);
287
288         if (this.autoSizesToFitContentHeight)
289             this.sizeToFitContentHeight();
290
291         if (this._lineNumberToReveal) {
292             this.revealLine(this._lineNumberToReveal);
293             delete this._lineNumberToReveal;
294         }
295
296         if (this._lineNumberToHighlight) {
297             this.highlightLine(this._lineNumberToHighlight);
298             delete this._lineNumberToHighlight;
299         }
300
301         this.dispatchEventToListeners("content loaded");
302     },
303
304     _isContentLoaded: function() {
305         var doc = this.element.contentDocument;
306         return doc && doc.getElementsByTagName("table")[0];
307     },
308
309     _windowResized: function(event)
310     {
311         if (!this._autoSizesToFitContentHeight)
312             return;
313         this.sizeToFitContentHeight();
314     },
315
316     _documentContextMenu: function(event)
317     {
318         if (!event.target.hasStyleClass("webkit-line-number"))
319             return;
320         var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr");
321         if (!sourceRow._breakpointObject && this.addBreakpointDelegate)
322             this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow));
323
324         var breakpoint = sourceRow._breakpointObject;
325         if (!breakpoint)
326             return;
327
328         this._editBreakpointCondition(event.target, sourceRow, breakpoint);
329         event.preventDefault();
330     },
331
332     _documentMouseDown: function(event)
333     {
334         if (!event.target.hasStyleClass("webkit-line-number"))
335             return;
336         if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
337             return;
338         var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr");
339         if (sourceRow._breakpointObject && sourceRow._breakpointObject.enabled)
340             sourceRow._breakpointObject.enabled = false;
341         else if (sourceRow._breakpointObject)
342             WebInspector.panels.scripts.removeBreakpoint(sourceRow._breakpointObject);
343         else if (this.addBreakpointDelegate)
344             this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow));
345     },
346
347     _editBreakpointCondition: function(eventTarget, sourceRow, breakpoint)
348     {
349         // TODO: Migrate the popup to the top-level document and remove the blur listener from conditionElement once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed.
350         var popupDocument = this.element.contentDocument;
351         this._showBreakpointConditionPopup(eventTarget, breakpoint.line, popupDocument);
352
353         function committed(element, newText)
354         {
355             breakpoint.condition = newText;
356             if (breakpoint.condition)
357                 sourceRow.addStyleClass("webkit-breakpoint-conditional");
358             else
359                 sourceRow.removeStyleClass("webkit-breakpoint-conditional");
360             dismissed.call(this);
361         }
362
363         function dismissed()
364         {
365             this._popup.hide();
366             delete this._conditionEditorElement;
367         }
368
369         var dismissedHandler = dismissed.bind(this);
370         this._conditionEditorElement.addEventListener("blur", dismissedHandler, false);
371
372         WebInspector.startEditing(this._conditionEditorElement, committed.bind(this), dismissedHandler);
373         this._conditionEditorElement.value = breakpoint.condition;
374         this._conditionEditorElement.select();
375     },
376
377     _showBreakpointConditionPopup: function(clickedElement, lineNumber, popupDocument)
378     {
379         var popupContentElement = this._createPopupElement(lineNumber, popupDocument);
380         var lineElement = clickedElement.enclosingNodeOrSelfWithNodeName("td").nextSibling;
381         if (this._popup) {
382             this._popup.hide();
383             this._popup.element = popupContentElement;
384         } else {
385             this._popup = new WebInspector.Popup(popupContentElement);
386             this._popup.autoHide = true;
387         }
388         this._popup.anchor = lineElement;
389         this._popup.show();
390     },
391
392     _createPopupElement: function(lineNumber, popupDocument)
393     {
394         var popupContentElement = popupDocument.createElement("div");
395         popupContentElement.className = "popup-content breakpoint-condition";
396
397         var labelElement = document.createElement("label");
398         labelElement.className = "popup-message";
399         labelElement.htmlFor = "bp-condition";
400         labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber)));
401         popupContentElement.appendChild(labelElement);
402
403         var editorElement = document.createElement("input");
404         editorElement.id = "bp-condition";
405         editorElement.type = "text"
406         popupContentElement.appendChild(editorElement);
407         this._conditionEditorElement = editorElement;
408
409         return popupContentElement;
410     },
411
412     _documentKeyDown: function(event)
413     {
414         var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
415         var handler = this._shortcuts[shortcut];
416         if (handler) {
417             handler(event);
418             event.preventDefault();
419         } else {
420             WebInspector.documentKeyDown(event);
421         }
422     },
423
424     _evalSelectionInCallFrame: function(event)
425     {
426         if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
427             return;
428
429         var selection = this.element.contentWindow.getSelection();
430         if (!selection.rangeCount)
431             return;
432
433         var expression = selection.getRangeAt(0).toString().trimWhitespace();
434         WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, function(result, exception) {
435             WebInspector.showConsole();
436             var commandMessage = new WebInspector.ConsoleCommand(expression);
437             WebInspector.console.addMessage(commandMessage);
438             WebInspector.console.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
439         });
440     },
441
442     _breakpointEnableChanged: function(event)
443     {
444         var breakpoint = event.target;
445         var sourceRow = this.sourceRow(breakpoint.line);
446         if (!sourceRow)
447             return;
448
449         sourceRow.addStyleClass("webkit-breakpoint");
450
451         if (breakpoint.enabled)
452             sourceRow.removeStyleClass("webkit-breakpoint-disabled");
453         else
454             sourceRow.addStyleClass("webkit-breakpoint-disabled");
455     },
456
457     _updateExecutionLine: function(previousLine)
458     {
459         if (previousLine) {
460             var sourceRow = this.sourceRow(previousLine);
461             if (sourceRow)
462                 sourceRow.removeStyleClass("webkit-execution-line");
463         }
464
465         if (!this._executionLine)
466             return;
467
468         this._drawProgramCounterImageIfNeeded();
469
470         var sourceRow = this.sourceRow(this._executionLine);
471         if (sourceRow)
472             sourceRow.addStyleClass("webkit-execution-line");
473     },
474
475     _addExistingBreakpointsToSource: function()
476     {
477         var length = this.breakpoints.length;
478         for (var i = 0; i < length; ++i)
479             this._addBreakpointToSource(this.breakpoints[i]);
480     },
481
482     _addBreakpointToSource: function(breakpoint)
483     {
484         var sourceRow = this.sourceRow(breakpoint.line);
485         if (!sourceRow)
486             return;
487
488         breakpoint.sourceText = sourceRow.getElementsByClassName('webkit-line-content')[0].textContent;
489
490         this._drawBreakpointImagesIfNeeded();
491
492         sourceRow._breakpointObject = breakpoint;
493
494         sourceRow.addStyleClass("webkit-breakpoint");
495         if (!breakpoint.enabled)
496             sourceRow.addStyleClass("webkit-breakpoint-disabled");
497         if (breakpoint.condition)
498             sourceRow.addStyleClass("webkit-breakpoint-conditional");
499     },
500
501     _removeBreakpointFromSource: function(breakpoint)
502     {
503         var sourceRow = this.sourceRow(breakpoint.line);
504         if (!sourceRow)
505             return;
506
507         delete sourceRow._breakpointObject;
508
509         sourceRow.removeStyleClass("webkit-breakpoint");
510         sourceRow.removeStyleClass("webkit-breakpoint-disabled");
511         sourceRow.removeStyleClass("webkit-breakpoint-conditional");
512     },
513
514     _incrementMessageRepeatCount: function(msg, repeatDelta)
515     {
516         if (!msg._resourceMessageLineElement)
517             return;
518
519         if (!msg._resourceMessageRepeatCountElement) {
520             var repeatedElement = document.createElement("span");
521             msg._resourceMessageLineElement.appendChild(repeatedElement);
522             msg._resourceMessageRepeatCountElement = repeatedElement;
523         }
524
525         msg.repeatCount += repeatDelta;
526         msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount);
527     },
528
529     _addExistingMessagesToSource: function()
530     {
531         var length = this.messages.length;
532         for (var i = 0; i < length; ++i)
533             this._addMessageToSource(this.messages[i]);
534     },
535
536     _addMessageToSource: function(msg)
537     {
538         var row = this.sourceRow(msg.line);
539         if (!row)
540             return;
541
542         var cell = row.cells[1];
543         if (!cell)
544             return;
545
546         var messageBubbleElement = cell.lastChild;
547         if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) {
548             messageBubbleElement = this.element.contentDocument.createElement("div");
549             messageBubbleElement.className = "webkit-html-message-bubble";
550             cell.appendChild(messageBubbleElement);
551         }
552
553         if (!row.messages)
554             row.messages = [];
555
556         for (var i = 0; i < row.messages.length; ++i) {
557             if (row.messages[i].isEqual(msg, true)) {
558                 this._incrementMessageRepeatCount(row.messages[i], msg.repeatDelta);
559                 return;
560             }
561         }
562
563         row.messages.push(msg);
564
565         var imageURL;
566         switch (msg.level) {
567             case WebInspector.ConsoleMessage.MessageLevel.Error:
568                 messageBubbleElement.addStyleClass("webkit-html-error-message");
569                 imageURL = "Images/errorIcon.png";
570                 break;
571             case WebInspector.ConsoleMessage.MessageLevel.Warning:
572                 messageBubbleElement.addStyleClass("webkit-html-warning-message");
573                 imageURL = "Images/warningIcon.png";
574                 break;
575         }
576
577         var messageLineElement = this.element.contentDocument.createElement("div");
578         messageLineElement.className = "webkit-html-message-line";
579         messageBubbleElement.appendChild(messageLineElement);
580
581         // Create the image element in the Inspector's document so we can use relative image URLs.
582         var image = document.createElement("img");
583         image.src = imageURL;
584         image.className = "webkit-html-message-icon";
585
586         // Adopt the image element since it wasn't created in element's contentDocument.
587         image = this.element.contentDocument.adoptNode(image);
588         messageLineElement.appendChild(image);
589         messageLineElement.appendChild(this.element.contentDocument.createTextNode(msg.message));
590
591         msg._resourceMessageLineElement = messageLineElement;
592     },
593
594     _drawProgramCounterInContext: function(ctx, glow)
595     {
596         if (glow)
597             ctx.save();
598
599         ctx.beginPath();
600         ctx.moveTo(17, 2);
601         ctx.lineTo(19, 2);
602         ctx.lineTo(19, 0);
603         ctx.lineTo(21, 0);
604         ctx.lineTo(26, 5.5);
605         ctx.lineTo(21, 11);
606         ctx.lineTo(19, 11);
607         ctx.lineTo(19, 9);
608         ctx.lineTo(17, 9);
609         ctx.closePath();
610         ctx.fillStyle = "rgb(142, 5, 4)";
611
612         if (glow) {
613             ctx.shadowBlur = 4;
614             ctx.shadowColor = "rgb(255, 255, 255)";
615             ctx.shadowOffsetX = -1;
616             ctx.shadowOffsetY = 0;
617         }
618
619         ctx.fill();
620         ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels.
621
622         if (glow)
623             ctx.restore();
624     },
625
626     _drawProgramCounterImageIfNeeded: function()
627     {
628         if (!this._needsProgramCounterImage || !this.element.contentDocument)
629             return;
630
631         var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "program-counter", 26, 11);
632         ctx.clearRect(0, 0, 26, 11);
633         this._drawProgramCounterInContext(ctx, true);
634
635         delete this._needsProgramCounterImage;
636     },
637
638     _drawBreakpointImagesIfNeeded: function(conditional)
639     {
640         if (!this._needsBreakpointImages || !this.element.contentDocument)
641             return;
642
643         function drawBreakpoint(ctx, disabled, conditional)
644         {
645             ctx.beginPath();
646             ctx.moveTo(0, 2);
647             ctx.lineTo(2, 0);
648             ctx.lineTo(21, 0);
649             ctx.lineTo(26, 5.5);
650             ctx.lineTo(21, 11);
651             ctx.lineTo(2, 11);
652             ctx.lineTo(0, 9);
653             ctx.closePath();
654             ctx.fillStyle = conditional ? "rgb(217, 142, 1)" : "rgb(1, 142, 217)";
655             ctx.strokeStyle = conditional ? "rgb(205, 103, 0)" : "rgb(0, 103, 205)";
656             ctx.lineWidth = 3;
657             ctx.fill();
658             ctx.save();
659             ctx.clip();
660             ctx.stroke();
661             ctx.restore();
662
663             if (!disabled)
664                 return;
665
666             ctx.save();
667             ctx.globalCompositeOperation = "destination-out";
668             ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
669             ctx.fillRect(0, 0, 26, 11);
670             ctx.restore();
671         }
672
673
674         // Unconditional breakpoints.
675
676         var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint", 26, 11);
677         ctx.clearRect(0, 0, 26, 11);
678         drawBreakpoint(ctx);
679
680         var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-program-counter", 26, 11);
681         ctx.clearRect(0, 0, 26, 11);
682         drawBreakpoint(ctx);
683         ctx.clearRect(20, 0, 6, 11);
684         this._drawProgramCounterInContext(ctx, true);
685
686         var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled", 26, 11);
687         ctx.clearRect(0, 0, 26, 11);
688         drawBreakpoint(ctx, true);
689
690         var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-program-counter", 26, 11);
691         ctx.clearRect(0, 0, 26, 11);
692         drawBreakpoint(ctx, true);
693         ctx.clearRect(20, 0, 6, 11);
694         this._drawProgramCounterInContext(ctx, true);
695
696
697         // Conditional breakpoints.
698
699         var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional", 26, 11);
700         ctx.clearRect(0, 0, 26, 11);
701         drawBreakpoint(ctx, false, true);
702
703         var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional-program-counter", 26, 11);
704         ctx.clearRect(0, 0, 26, 11);
705         drawBreakpoint(ctx, false, true);
706         ctx.clearRect(20, 0, 6, 11);
707         this._drawProgramCounterInContext(ctx, true);
708
709         var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional", 26, 11);
710         ctx.clearRect(0, 0, 26, 11);
711         drawBreakpoint(ctx, true, true);
712
713         var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional-program-counter", 26, 11);
714         ctx.clearRect(0, 0, 26, 11);
715         drawBreakpoint(ctx, true, true);
716         ctx.clearRect(20, 0, 6, 11);
717         this._drawProgramCounterInContext(ctx, true);
718
719         delete this._needsBreakpointImages;
720     },
721
722     syntaxHighlightJavascript: function()
723     {
724         var table = this.element.contentDocument.getElementsByTagName("table")[0];
725         if (!table)
726             return;
727
728         var jsSyntaxHighlighter = new WebInspector.JavaScriptSourceSyntaxHighlighter(table, this);
729         jsSyntaxHighlighter.process();
730     },
731
732     syntaxHighlightCSS: function()
733     {
734         var table = this.element.contentDocument.getElementsByTagName("table")[0];
735         if (!table)
736             return;
737
738         var cssSyntaxHighlighter = new WebInspector.CSSSourceSyntaxHighligher(table, this);
739         cssSyntaxHighlighter.process();
740     }
741 }
742
743 WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype;
744
745 WebInspector.SourceSyntaxHighligher = function(table, sourceFrame)
746 {
747     this.table = table;
748     this.sourceFrame = sourceFrame;
749 }
750
751 WebInspector.SourceSyntaxHighligher.prototype = {
752     createSpan: function(content, className)
753     {
754         var span = document.createElement("span");
755         span.className = className;
756         span.appendChild(document.createTextNode(content));
757         return span;
758     },
759
760     generateFinder: function(regex, matchNumber, className)
761     {
762         return function(str) {
763             var match = regex.exec(str);
764             if (!match)
765                 return null;
766             this.previousMatchLength = match[matchNumber].length;
767             return this.createSpan(match[matchNumber], className);
768         };
769     },
770
771     process: function()
772     {
773         // Split up the work into chunks so we don't block the
774         // UI thread while processing.
775
776         var i = 0;
777         var rows = this.table.rows;
778         var rowsLength = rows.length;
779         var previousCell = null;
780         const linesPerChunk = 10;
781
782         function processChunk()
783         {
784             for (var end = Math.min(i + linesPerChunk, rowsLength); i < end; ++i) {
785                 var row = rows[i];
786                 if (!row)
787                     continue;
788                 var cell = row.cells[1];
789                 if (!cell)
790                     continue;
791                 this.syntaxHighlightLine(cell, previousCell);
792                 if (i < (end - 1))
793                     this.deleteContinueFlags(previousCell);
794                 previousCell = cell;
795             }
796
797             if (i >= rowsLength && processChunkInterval) {
798                 this.deleteContinueFlags(previousCell);
799                 delete this.previousMatchLength;
800                 clearInterval(processChunkInterval);
801
802                 this.sourceFrame.dispatchEventToListeners("syntax highlighting complete");
803             }
804         }
805
806         var boundProcessChunk = processChunk.bind(this);
807         var processChunkInterval = setInterval(boundProcessChunk, 25);
808         boundProcessChunk();
809     }
810 }
811
812 WebInspector.CSSSourceSyntaxHighligher = function(table, sourceFrame) {
813     WebInspector.SourceSyntaxHighligher.call(this, table, sourceFrame);
814
815     this.findNumber = this.generateFinder(/^((-?(\d+|\d*\.\d+))|^(#[a-fA-F0-9]{3,6}))(?:\D|$)/, 1, "webkit-css-number");
816     this.findUnits = this.generateFinder(/^(px|em|pt|in|cm|mm|pc|ex)(?:\W|$)/, 1, "webkit-css-unit");
817     this.findKeyword = this.generateFinder(/^(rgba?|hsla?|var)(?:\W|$)/, 1, "webkit-css-keyword");
818     this.findSingleLineString = this.generateFinder(/^"(?:[^"\\]|\\.)*"|^'([^'\\]|\\.)*'/, 0, "webkit-css-string"); // " this quote keeps Xcode happy
819     this.findSingleLineComment = this.generateFinder(/^\/\*.*?\*\//, 0, "webkit-css-comment");
820     this.findMultilineCommentStart = this.generateFinder(/^\/\*.*$/, 0, "webkit-css-comment");
821     this.findMultilineCommentEnd = this.generateFinder(/^.*?\*\//, 0, "webkit-css-comment");
822     this.findSelector = this.generateFinder(/^([#\.]?[_a-zA-Z].*?)(?:\W|$)/, 1, "webkit-css-selector");
823     this.findProperty = this.generateFinder(/^(-?[_a-z0-9][_a-z0-9-]*\s*)(?:\:)/, 1, "webkit-css-property");
824     this.findGenericIdent = this.generateFinder(/^([@-]?[_a-z0-9][_a-z0-9-]*)(?:\W|$)/, 1, "webkit-css-string");
825 }
826
827 WebInspector.CSSSourceSyntaxHighligher.prototype = {
828     deleteContinueFlags: function(cell)
829     {
830         if (!cell)
831             return;
832         delete cell._commentContinues;
833         delete cell._inSelector;
834     },
835
836     findPseudoClass: function(str)
837     {
838         var match = /^(::?)([_a-z0-9][_a-z0-9-]*)/.exec(str);
839         if (!match)
840             return null;
841         this.previousMatchLength = match[0].length;
842         var span = document.createElement("span");
843         span.appendChild(document.createTextNode(match[1]));
844         span.appendChild(this.createSpan(match[2], "webkit-css-pseudo-class"));
845         return span;
846     },
847
848     findURL: function(str)
849     {
850         var match = /^(?:local|url)\(([^\)]*?)\)/.exec(str);
851         if (!match)
852             return null;
853         this.previousMatchLength = match[0].length;
854         var innerUrlSpan = this.createSpan(match[1], "webkit-css-url");
855         var outerSpan = document.createElement("span");
856         outerSpan.appendChild(this.createSpan("url", "webkit-css-keyword"));
857         outerSpan.appendChild(document.createTextNode("("));
858         outerSpan.appendChild(innerUrlSpan);
859         outerSpan.appendChild(document.createTextNode(")"));
860         return outerSpan;
861     },
862
863     findAtRule: function(str)
864     {
865         var match = /^@[_a-z0-9][_a-z0-9-]*(?:\W|$)/.exec(str);
866         if (!match)
867             return null;
868         this.previousMatchLength = match[0].length;
869         return this.createSpan(match[0], "webkit-css-at-rule");
870     },
871
872     syntaxHighlightLine: function(line, prevLine)
873     {
874         var code = line.textContent;
875         while (line.firstChild)
876             line.removeChild(line.firstChild);
877
878         var token;
879         var tmp = 0;
880         var i = 0;
881         this.previousMatchLength = 0;
882
883         if (prevLine) {
884             if (prevLine._commentContinues) {
885                 if (!(token = this.findMultilineCommentEnd(code))) {
886                     token = this.createSpan(code, "webkit-javascript-comment");
887                     line._commentContinues = true;
888                 }
889             }
890             if (token) {
891                 i += this.previousMatchLength ? this.previousMatchLength : code.length;
892                 tmp = i;
893                 line.appendChild(token);
894             }
895         }
896
897         var inSelector = (prevLine && prevLine._inSelector); // inside a selector, we can now parse properties and values
898         var inAtRuleBlock = (prevLine && prevLine._inAtRuleBlock); // inside an @rule block, but not necessarily inside a selector yet
899         var atRuleStarted = (prevLine && prevLine._atRuleStarted); // we received an @rule, we may stop the @rule at a semicolon or open a block and become inAtRuleBlock
900         var atRuleIsSelector = (prevLine && prevLine._atRuleIsSelector); // when this @rule opens a block it immediately goes into parsing properties and values instead of selectors
901
902         for ( ; i < code.length; ++i) {
903             var codeFragment = code.substr(i);
904             var prevChar = code[i - 1];
905             var currChar = codeFragment[0];
906             token = this.findSingleLineComment(codeFragment);
907             if (!token) {
908                 if ((token = this.findMultilineCommentStart(codeFragment)))
909                     line._commentContinues = true;
910                 else if (currChar === ";" && !inAtRuleBlock)
911                     atRuleStarted = false;
912                 else if (currChar === "}") {
913                     if (inSelector && inAtRuleBlock && atRuleIsSelector) {
914                         inSelector = false;
915                         inAtRuleBlock = false;
916                         atRuleStarted = false;
917                     } else if (inSelector) {
918                         inSelector = false;
919                     } else if (inAtRuleBlock) {
920                         inAtRuleBlock = false;
921                         atRuleStarted = false;
922                     }
923                 } else if (currChar === "{") {
924                     if (!atRuleStarted || inAtRuleBlock) {
925                         inSelector = true;
926                     } else if (!inAtRuleBlock && atRuleIsSelector) {
927                         inAtRuleBlock = true;
928                         inSelector = true;
929                     } else if (!inAtRuleBlock) {
930                         inAtRuleBlock = true;
931                         inSelector = false;
932                     }
933                 } else if (inSelector) {
934                     if (!prevChar || /^\d/.test(prevChar)) {
935                         token = this.findUnits(codeFragment);
936                     } else if (!prevChar || /^\W/.test(prevChar)) {
937                         token = this.findNumber(codeFragment) ||
938                                 this.findKeyword(codeFragment) ||
939                                 this.findURL(codeFragment) ||
940                                 this.findProperty(codeFragment) ||
941                                 this.findAtRule(codeFragment) ||
942                                 this.findGenericIdent(codeFragment) ||
943                                 this.findSingleLineString(codeFragment);
944                     }
945                 } else if (!inSelector) {
946                     if (atRuleStarted && !inAtRuleBlock)
947                         token = this.findURL(codeFragment); // for @import
948                     if (!token) {
949                         token = this.findSelector(codeFragment) ||
950                                 this.findPseudoClass(codeFragment) ||
951                                 this.findAtRule(codeFragment);
952                     }
953                 }
954             }
955
956             if (token) {
957                 if (currChar === "@") {
958                     atRuleStarted = true;
959
960                     // The @font-face, @page, and @variables at-rules do not contain selectors like other at-rules
961                     // instead it acts as a selector and contains properties and values.
962                     var text = token.textContent;
963                     atRuleIsSelector = /font-face/.test(text) || /page/.test(text) || /variables/.test(text);
964                 }
965
966                 if (tmp !== i)
967                     line.appendChild(document.createTextNode(code.substring(tmp, i)));
968                 line.appendChild(token);
969                 i += this.previousMatchLength - 1;
970                 tmp = i + 1;
971             }
972         }
973
974         line._inSelector = inSelector;
975         line._inAtRuleBlock = inAtRuleBlock;
976         line._atRuleStarted = atRuleStarted;
977         line._atRuleIsSelector = atRuleIsSelector;
978
979         if (tmp < code.length)
980             line.appendChild(document.createTextNode(code.substring(tmp, i)));
981     }
982 }
983
984 WebInspector.CSSSourceSyntaxHighligher.prototype.__proto__ = WebInspector.SourceSyntaxHighligher.prototype;
985
986 WebInspector.JavaScriptSourceSyntaxHighlighter = function(table, sourceFrame) {
987     WebInspector.SourceSyntaxHighligher.call(this, table, sourceFrame);
988
989     this.findNumber = this.generateFinder(/^(-?(\d+\.?\d*([eE][+-]\d+)?|0[xX]\h+|Infinity)|NaN)(?:\W|$)/, 1, "webkit-javascript-number");
990     this.findKeyword = this.generateFinder(/^(null|true|false|break|case|catch|const|default|finally|for|instanceof|new|var|continue|function|return|void|delete|if|this|do|while|else|in|switch|throw|try|typeof|with|debugger|class|enum|export|extends|import|super|get|set)(?:\W|$)/, 1, "webkit-javascript-keyword");
991     this.findSingleLineString = this.generateFinder(/^"(?:[^"\\]|\\.)*"|^'([^'\\]|\\.)*'/, 0, "webkit-javascript-string"); // " this quote keeps Xcode happy
992     this.findMultilineCommentStart = this.generateFinder(/^\/\*.*$/, 0, "webkit-javascript-comment");
993     this.findMultilineCommentEnd = this.generateFinder(/^.*?\*\//, 0, "webkit-javascript-comment");
994     this.findMultilineSingleQuoteStringStart = this.generateFinder(/^'(?:[^'\\]|\\.)*\\$/, 0, "webkit-javascript-string");
995     this.findMultilineSingleQuoteStringEnd = this.generateFinder(/^(?:[^'\\]|\\.)*?'/, 0, "webkit-javascript-string");
996     this.findMultilineDoubleQuoteStringStart = this.generateFinder(/^"(?:[^"\\]|\\.)*\\$/, 0, "webkit-javascript-string");
997     this.findMultilineDoubleQuoteStringEnd = this.generateFinder(/^(?:[^"\\]|\\.)*?"/, 0, "webkit-javascript-string");
998     this.findMultilineRegExpEnd = this.generateFinder(/^(?:[^\/\\]|\\.)*?\/([gim]{0,3})/, 0, "webkit-javascript-regexp");
999     this.findSingleLineComment = this.generateFinder(/^\/\/.*|^\/\*.*?\*\//, 0, "webkit-javascript-comment");
1000 }
1001
1002 WebInspector.JavaScriptSourceSyntaxHighlighter.prototype = {
1003     deleteContinueFlags: function(cell)
1004     {
1005         if (!cell)
1006             return;
1007         delete cell._commentContinues;
1008         delete cell._singleQuoteStringContinues;
1009         delete cell._doubleQuoteStringContinues;
1010         delete cell._regexpContinues;
1011     },
1012
1013     findMultilineRegExpStart: function(str)
1014     {
1015         var match = /^\/(?:[^\/\\]|\\.)*\\$/.exec(str);
1016         if (!match || !/\\|\$|\.[\?\*\+]|[^\|]\|[^\|]/.test(match[0]))
1017             return null;
1018         this.previousMatchLength = match[0].length;
1019         return this.createSpan(match[0], "webkit-javascript-regexp");
1020     },
1021
1022     findSingleLineRegExp: function(str)
1023     {
1024         var match = /^(\/(?:[^\/\\]|\\.)*\/([gim]{0,3}))(.?)/.exec(str);
1025         if (!match || !(match[2].length > 0 || /\\|\$|\.[\?\*\+]|[^\|]\|[^\|]/.test(match[1]) || /\.|;|,/.test(match[3])))
1026             return null;
1027         this.previousMatchLength = match[1].length;
1028         return this.createSpan(match[1], "webkit-javascript-regexp");
1029     },
1030
1031     syntaxHighlightLine: function(line, prevLine)
1032     {
1033         var messageBubble = line.lastChild;
1034         if (messageBubble && messageBubble.nodeType === Node.ELEMENT_NODE && messageBubble.hasStyleClass("webkit-html-message-bubble"))
1035             line.removeChild(messageBubble);
1036         else
1037             messageBubble = null;
1038
1039         var code = line.textContent;
1040
1041         while (line.firstChild)
1042             line.removeChild(line.firstChild);
1043
1044         var token;
1045         var tmp = 0;
1046         var i = 0;
1047         this.previousMatchLength = 0;
1048
1049         if (prevLine) {
1050             if (prevLine._commentContinues) {
1051                 if (!(token = this.findMultilineCommentEnd(code))) {
1052                     token = this.createSpan(code, "webkit-javascript-comment");
1053                     line._commentContinues = true;
1054                 }
1055             } else if (prevLine._singleQuoteStringContinues) {
1056                 if (!(token = this.findMultilineSingleQuoteStringEnd(code))) {
1057                     token = this.createSpan(code, "webkit-javascript-string");
1058                     line._singleQuoteStringContinues = true;
1059                 }
1060             } else if (prevLine._doubleQuoteStringContinues) {
1061                 if (!(token = this.findMultilineDoubleQuoteStringEnd(code))) {
1062                     token = this.createSpan(code, "webkit-javascript-string");
1063                     line._doubleQuoteStringContinues = true;
1064                 }
1065             } else if (prevLine._regexpContinues) {
1066                 if (!(token = this.findMultilineRegExpEnd(code))) {
1067                     token = this.createSpan(code, "webkit-javascript-regexp");
1068                     line._regexpContinues = true;
1069                 }
1070             }
1071             if (token) {
1072                 i += this.previousMatchLength ? this.previousMatchLength : code.length;
1073                 tmp = i;
1074                 line.appendChild(token);
1075             }
1076         }
1077
1078         for ( ; i < code.length; ++i) {
1079             var codeFragment = code.substr(i);
1080             var prevChar = code[i - 1];
1081             token = this.findSingleLineComment(codeFragment);
1082             if (!token) {
1083                 if ((token = this.findMultilineCommentStart(codeFragment)))
1084                     line._commentContinues = true;
1085                 else if (!prevChar || /^\W/.test(prevChar)) {
1086                     token = this.findNumber(codeFragment) ||
1087                             this.findKeyword(codeFragment) ||
1088                             this.findSingleLineString(codeFragment) ||
1089                             this.findSingleLineRegExp(codeFragment);
1090                     if (!token) {
1091                         if (token = this.findMultilineSingleQuoteStringStart(codeFragment))
1092                             line._singleQuoteStringContinues = true;
1093                         else if (token = this.findMultilineDoubleQuoteStringStart(codeFragment))
1094                             line._doubleQuoteStringContinues = true;
1095                         else if (token = this.findMultilineRegExpStart(codeFragment))
1096                             line._regexpContinues = true;
1097                     }
1098                 }
1099             }
1100
1101             if (token) {
1102                 if (tmp !== i)
1103                     line.appendChild(document.createTextNode(code.substring(tmp, i)));
1104                 line.appendChild(token);
1105                 i += this.previousMatchLength - 1;
1106                 tmp = i + 1;
1107             }
1108         }
1109
1110         if (tmp < code.length)
1111             line.appendChild(document.createTextNode(code.substring(tmp, i)));
1112
1113         if (messageBubble)
1114             line.appendChild(messageBubble);
1115     }
1116 }
1117
1118 WebInspector.JavaScriptSourceSyntaxHighlighter.prototype.__proto__ = WebInspector.SourceSyntaxHighligher.prototype;