• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26WebInspector.SourceFrame = function(element, addBreakpointDelegate)
27{
28    this.messages = [];
29    this.breakpoints = [];
30    this._shortcuts = {};
31
32    this.addBreakpointDelegate = addBreakpointDelegate;
33
34    this.element = element || document.createElement("iframe");
35    this.element.addStyleClass("source-view-frame");
36    this.element.setAttribute("viewsource", "true");
37
38    this.element.addEventListener("load", this._loaded.bind(this), false);
39}
40
41WebInspector.SourceFrame.prototype = {
42    get executionLine()
43    {
44        return this._executionLine;
45    },
46
47    set executionLine(x)
48    {
49        if (this._executionLine === x)
50            return;
51
52        var previousLine = this._executionLine;
53        this._executionLine = x;
54
55        this._updateExecutionLine(previousLine);
56    },
57
58    get autoSizesToFitContentHeight()
59    {
60        return this._autoSizesToFitContentHeight;
61    },
62
63    set autoSizesToFitContentHeight(x)
64    {
65        if (this._autoSizesToFitContentHeight === x)
66            return;
67
68        this._autoSizesToFitContentHeight = x;
69
70        if (this._autoSizesToFitContentHeight) {
71            this._windowResizeListener = this._windowResized.bind(this);
72            window.addEventListener("resize", this._windowResizeListener, false);
73            this.sizeToFitContentHeight();
74        } else {
75            this.element.style.removeProperty("height");
76            if (this.element.contentDocument)
77                this.element.contentDocument.body.removeStyleClass("webkit-height-sized-to-fit");
78            window.removeEventListener("resize", this._windowResizeListener, false);
79            delete this._windowResizeListener;
80        }
81    },
82
83    sourceRow: function(lineNumber)
84    {
85        if (!lineNumber || !this.element.contentDocument)
86            return;
87
88        var table = this.element.contentDocument.getElementsByTagName("table")[0];
89        if (!table)
90            return;
91
92        var rows = table.rows;
93
94        // Line numbers are a 1-based index, but the rows collection is 0-based.
95        --lineNumber;
96
97        return rows[lineNumber];
98    },
99
100    lineNumberForSourceRow: function(sourceRow)
101    {
102        // Line numbers are a 1-based index, but the rows collection is 0-based.
103        var lineNumber = 0;
104        while (sourceRow) {
105            ++lineNumber;
106            sourceRow = sourceRow.previousSibling;
107        }
108
109        return lineNumber;
110    },
111
112    revealLine: function(lineNumber)
113    {
114        if (!this._isContentLoaded()) {
115            this._lineNumberToReveal = lineNumber;
116            return;
117        }
118
119        var row = this.sourceRow(lineNumber);
120        if (row)
121            row.scrollIntoViewIfNeeded(true);
122    },
123
124    addBreakpoint: function(breakpoint)
125    {
126        this.breakpoints.push(breakpoint);
127        breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this);
128        breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this);
129        this._addBreakpointToSource(breakpoint);
130    },
131
132    removeBreakpoint: function(breakpoint)
133    {
134        this.breakpoints.remove(breakpoint);
135        breakpoint.removeEventListener("enabled", null, this);
136        breakpoint.removeEventListener("disabled", null, this);
137        this._removeBreakpointFromSource(breakpoint);
138    },
139
140    addMessage: function(msg)
141    {
142        // Don't add the message if there is no message or valid line or if the msg isn't an error or warning.
143        if (!msg.message || msg.line <= 0 || !msg.isErrorOrWarning())
144            return;
145        this.messages.push(msg);
146        this._addMessageToSource(msg);
147    },
148
149    clearMessages: function()
150    {
151        this.messages = [];
152
153        if (!this.element.contentDocument)
154            return;
155
156        var bubbles = this.element.contentDocument.querySelectorAll(".webkit-html-message-bubble");
157        if (!bubbles)
158            return;
159
160        for (var i = 0; i < bubbles.length; ++i) {
161            var bubble = bubbles[i];
162            bubble.parentNode.removeChild(bubble);
163        }
164    },
165
166    sizeToFitContentHeight: function()
167    {
168        if (this.element.contentDocument) {
169            this.element.style.setProperty("height", this.element.contentDocument.body.offsetHeight + "px");
170            this.element.contentDocument.body.addStyleClass("webkit-height-sized-to-fit");
171        }
172    },
173
174    _highlightLineEnds: function(event)
175    {
176        event.target.parentNode.removeStyleClass("webkit-highlighted-line");
177    },
178
179    highlightLine: function(lineNumber)
180    {
181        if (!this._isContentLoaded()) {
182            this._lineNumberToHighlight = lineNumber;
183            return;
184        }
185
186        var sourceRow = this.sourceRow(lineNumber);
187        if (!sourceRow)
188            return;
189        var line = sourceRow.getElementsByClassName('webkit-line-content')[0];
190        // Trick to reset the animation if the user clicks on the same link
191        // Using a timeout to avoid coalesced style updates
192        line.style.setProperty("-webkit-animation-name", "none");
193        setTimeout(function () {
194            line.style.removeProperty("-webkit-animation-name");
195            sourceRow.addStyleClass("webkit-highlighted-line");
196        }, 0);
197    },
198
199    _loaded: function()
200    {
201        WebInspector.addMainEventListeners(this.element.contentDocument);
202        this.element.contentDocument.addEventListener("mousedown", this._documentMouseDown.bind(this), true);
203        this.element.contentDocument.addEventListener("keydown", this._documentKeyDown.bind(this), true);
204        this.element.contentDocument.addEventListener("keyup", WebInspector.documentKeyUp.bind(WebInspector), true);
205        this.element.contentDocument.addEventListener("webkitAnimationEnd", this._highlightLineEnds.bind(this), false);
206
207        // Register 'eval' shortcut.
208        var isMac = InspectorController.platform().indexOf("mac-") === 0;
209        var platformSpecificModifier = isMac ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl;
210        var shortcut = WebInspector.KeyboardShortcut.makeKey(69 /* 'E' */, platformSpecificModifier | WebInspector.KeyboardShortcut.Modifiers.Shift);
211        this._shortcuts[shortcut] = this._evalSelectionInCallFrame.bind(this);
212
213        var headElement = this.element.contentDocument.getElementsByTagName("head")[0];
214        if (!headElement) {
215            headElement = this.element.contentDocument.createElement("head");
216            this.element.contentDocument.documentElement.insertBefore(headElement, this.element.contentDocument.documentElement.firstChild);
217        }
218
219        var styleElement = this.element.contentDocument.createElement("style");
220        headElement.appendChild(styleElement);
221
222        // Add these style rules here since they are specific to the Inspector. They also behave oddly and not
223        // all properties apply if added to view-source.css (becuase it is a user agent sheet.)
224        var styleText = ".webkit-line-number { background-repeat: no-repeat; background-position: right 1px; }\n";
225        styleText += ".webkit-breakpoint .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint); }\n";
226        styleText += ".webkit-breakpoint-disabled .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled); }\n";
227        styleText += ".webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(program-counter); }\n";
228        styleText += ".webkit-breakpoint.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-program-counter); }\n";
229        styleText += ".webkit-breakpoint-disabled.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-program-counter); }\n";
230        styleText += ".webkit-execution-line .webkit-line-content { background-color: rgb(171, 191, 254); outline: 1px solid rgb(64, 115, 244); }\n";
231        styleText += ".webkit-height-sized-to-fit { overflow-y: hidden }\n";
232        styleText += ".webkit-line-content { background-color: white; }\n";
233        styleText += "@-webkit-keyframes fadeout {from {background-color: rgb(255, 255, 120);} to { background-color: white;}}\n";
234        styleText += ".webkit-highlighted-line .webkit-line-content { background-color: rgb(255, 255, 120); -webkit-animation: 'fadeout' 2s 500ms}\n";
235        styleText += ".webkit-javascript-comment { color: rgb(0, 116, 0); }\n";
236        styleText += ".webkit-javascript-keyword { color: rgb(170, 13, 145); }\n";
237        styleText += ".webkit-javascript-number { color: rgb(28, 0, 207); }\n";
238        styleText += ".webkit-javascript-string, .webkit-javascript-regexp { color: rgb(196, 26, 22); }\n";
239
240        styleElement.textContent = styleText;
241
242        this._needsProgramCounterImage = true;
243        this._needsBreakpointImages = true;
244
245        this.element.contentWindow.Element.prototype.addStyleClass = Element.prototype.addStyleClass;
246        this.element.contentWindow.Element.prototype.removeStyleClass = Element.prototype.removeStyleClass;
247        this.element.contentWindow.Element.prototype.removeMatchingStyleClasses = Element.prototype.removeMatchingStyleClasses;
248        this.element.contentWindow.Element.prototype.hasStyleClass = Element.prototype.hasStyleClass;
249        this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeName = Node.prototype.enclosingNodeOrSelfWithNodeName;
250        this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = Node.prototype.enclosingNodeOrSelfWithNodeNameInArray;
251
252        this._addExistingMessagesToSource();
253        this._addExistingBreakpointsToSource();
254        this._updateExecutionLine();
255        if (this._executionLine)
256            this.revealLine(this._executionLine);
257
258        if (this.autoSizesToFitContentHeight)
259            this.sizeToFitContentHeight();
260
261        if (this._lineNumberToReveal) {
262            this.revealLine(this._lineNumberToReveal);
263            delete this._lineNumberToReveal;
264        }
265
266        if (this._lineNumberToHighlight) {
267            this.highlightLine(this._lineNumberToHighlight);
268            delete this._lineNumberToHighlight;
269        }
270
271        this.dispatchEventToListeners("content loaded");
272    },
273
274    _isContentLoaded: function() {
275        var doc = this.element.contentDocument;
276        return doc && doc.getElementsByTagName("table")[0];
277    },
278
279    _windowResized: function(event)
280    {
281        if (!this._autoSizesToFitContentHeight)
282            return;
283        this.sizeToFitContentHeight();
284    },
285
286    _documentMouseDown: function(event)
287    {
288        if (!event.target.hasStyleClass("webkit-line-number"))
289            return;
290
291        var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr");
292        if (sourceRow._breakpointObject)
293            sourceRow._breakpointObject.enabled = !sourceRow._breakpointObject.enabled;
294        else if (this.addBreakpointDelegate)
295            this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow));
296    },
297
298    _documentKeyDown: function(event)
299    {
300        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
301        var handler = this._shortcuts[shortcut];
302        if (handler) {
303            handler(event);
304            event.preventDefault();
305        } else {
306            WebInspector.documentKeyDown(event);
307        }
308    },
309
310    _evalSelectionInCallFrame: function(event)
311    {
312        if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused)
313            return;
314
315        var selection = this.element.contentWindow.getSelection();
316        if (!selection.rangeCount)
317            return;
318
319        var expression = selection.getRangeAt(0).toString().trimWhitespace();
320        WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, function(result, exception) {
321            WebInspector.showConsole();
322            var commandMessage = new WebInspector.ConsoleCommand(expression);
323            WebInspector.console.addMessage(commandMessage);
324            WebInspector.console.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
325        });
326    },
327
328    _breakpointEnableChanged: function(event)
329    {
330        var breakpoint = event.target;
331        var sourceRow = this.sourceRow(breakpoint.line);
332        if (!sourceRow)
333            return;
334
335        sourceRow.addStyleClass("webkit-breakpoint");
336
337        if (breakpoint.enabled)
338            sourceRow.removeStyleClass("webkit-breakpoint-disabled");
339        else
340            sourceRow.addStyleClass("webkit-breakpoint-disabled");
341    },
342
343    _updateExecutionLine: function(previousLine)
344    {
345        if (previousLine) {
346            var sourceRow = this.sourceRow(previousLine);
347            if (sourceRow)
348                sourceRow.removeStyleClass("webkit-execution-line");
349        }
350
351        if (!this._executionLine)
352            return;
353
354        this._drawProgramCounterImageIfNeeded();
355
356        var sourceRow = this.sourceRow(this._executionLine);
357        if (sourceRow)
358            sourceRow.addStyleClass("webkit-execution-line");
359    },
360
361    _addExistingBreakpointsToSource: function()
362    {
363        var length = this.breakpoints.length;
364        for (var i = 0; i < length; ++i)
365            this._addBreakpointToSource(this.breakpoints[i]);
366    },
367
368    _addBreakpointToSource: function(breakpoint)
369    {
370        var sourceRow = this.sourceRow(breakpoint.line);
371        if (!sourceRow)
372            return;
373
374        breakpoint.sourceText = sourceRow.getElementsByClassName('webkit-line-content')[0].textContent;
375
376        this._drawBreakpointImagesIfNeeded();
377
378        sourceRow._breakpointObject = breakpoint;
379
380        sourceRow.addStyleClass("webkit-breakpoint");
381        if (!breakpoint.enabled)
382            sourceRow.addStyleClass("webkit-breakpoint-disabled");
383    },
384
385    _removeBreakpointFromSource: function(breakpoint)
386    {
387        var sourceRow = this.sourceRow(breakpoint.line);
388        if (!sourceRow)
389            return;
390
391        delete sourceRow._breakpointObject;
392
393        sourceRow.removeStyleClass("webkit-breakpoint");
394        sourceRow.removeStyleClass("webkit-breakpoint-disabled");
395    },
396
397    _incrementMessageRepeatCount: function(msg, repeatDelta)
398    {
399        if (!msg._resourceMessageLineElement)
400            return;
401
402        if (!msg._resourceMessageRepeatCountElement) {
403            var repeatedElement = document.createElement("span");
404            msg._resourceMessageLineElement.appendChild(repeatedElement);
405            msg._resourceMessageRepeatCountElement = repeatedElement;
406        }
407
408        msg.repeatCount += repeatDelta;
409        msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount);
410    },
411
412    _addExistingMessagesToSource: function()
413    {
414        var length = this.messages.length;
415        for (var i = 0; i < length; ++i)
416            this._addMessageToSource(this.messages[i]);
417    },
418
419    _addMessageToSource: function(msg)
420    {
421        var row = this.sourceRow(msg.line);
422        if (!row)
423            return;
424
425        var cell = row.cells[1];
426        if (!cell)
427            return;
428
429        var messageBubbleElement = cell.lastChild;
430        if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) {
431            messageBubbleElement = this.element.contentDocument.createElement("div");
432            messageBubbleElement.className = "webkit-html-message-bubble";
433            cell.appendChild(messageBubbleElement);
434        }
435
436        if (!row.messages)
437            row.messages = [];
438
439        for (var i = 0; i < row.messages.length; ++i) {
440            if (row.messages[i].isEqual(msg, true)) {
441                this._incrementMessageRepeatCount(row.messages[i], msg.repeatDelta);
442                return;
443            }
444        }
445
446        row.messages.push(msg);
447
448        var imageURL;
449        switch (msg.level) {
450            case WebInspector.ConsoleMessage.MessageLevel.Error:
451                messageBubbleElement.addStyleClass("webkit-html-error-message");
452                imageURL = "Images/errorIcon.png";
453                break;
454            case WebInspector.ConsoleMessage.MessageLevel.Warning:
455                messageBubbleElement.addStyleClass("webkit-html-warning-message");
456                imageURL = "Images/warningIcon.png";
457                break;
458        }
459
460        var messageLineElement = this.element.contentDocument.createElement("div");
461        messageLineElement.className = "webkit-html-message-line";
462        messageBubbleElement.appendChild(messageLineElement);
463
464        // Create the image element in the Inspector's document so we can use relative image URLs.
465        var image = document.createElement("img");
466        image.src = imageURL;
467        image.className = "webkit-html-message-icon";
468
469        // Adopt the image element since it wasn't created in element's contentDocument.
470        image = this.element.contentDocument.adoptNode(image);
471        messageLineElement.appendChild(image);
472        messageLineElement.appendChild(this.element.contentDocument.createTextNode(msg.message));
473
474        msg._resourceMessageLineElement = messageLineElement;
475    },
476
477    _drawProgramCounterInContext: function(ctx, glow)
478    {
479        if (glow)
480            ctx.save();
481
482        ctx.beginPath();
483        ctx.moveTo(17, 2);
484        ctx.lineTo(19, 2);
485        ctx.lineTo(19, 0);
486        ctx.lineTo(21, 0);
487        ctx.lineTo(26, 5.5);
488        ctx.lineTo(21, 11);
489        ctx.lineTo(19, 11);
490        ctx.lineTo(19, 9);
491        ctx.lineTo(17, 9);
492        ctx.closePath();
493        ctx.fillStyle = "rgb(142, 5, 4)";
494
495        if (glow) {
496            ctx.shadowBlur = 4;
497            ctx.shadowColor = "rgb(255, 255, 255)";
498            ctx.shadowOffsetX = -1;
499            ctx.shadowOffsetY = 0;
500        }
501
502        ctx.fill();
503        ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels.
504
505        if (glow)
506            ctx.restore();
507    },
508
509    _drawProgramCounterImageIfNeeded: function()
510    {
511        if (!this._needsProgramCounterImage || !this.element.contentDocument)
512            return;
513
514        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "program-counter", 26, 11);
515        ctx.clearRect(0, 0, 26, 11);
516        this._drawProgramCounterInContext(ctx, true);
517
518        delete this._needsProgramCounterImage;
519    },
520
521    _drawBreakpointImagesIfNeeded: function()
522    {
523        if (!this._needsBreakpointImages || !this.element.contentDocument)
524            return;
525
526        function drawBreakpoint(ctx, disabled)
527        {
528            ctx.beginPath();
529            ctx.moveTo(0, 2);
530            ctx.lineTo(2, 0);
531            ctx.lineTo(21, 0);
532            ctx.lineTo(26, 5.5);
533            ctx.lineTo(21, 11);
534            ctx.lineTo(2, 11);
535            ctx.lineTo(0, 9);
536            ctx.closePath();
537            ctx.fillStyle = "rgb(1, 142, 217)";
538            ctx.strokeStyle = "rgb(0, 103, 205)";
539            ctx.lineWidth = 3;
540            ctx.fill();
541            ctx.save();
542            ctx.clip();
543            ctx.stroke();
544            ctx.restore();
545
546            if (!disabled)
547                return;
548
549            ctx.save();
550            ctx.globalCompositeOperation = "destination-out";
551            ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
552            ctx.fillRect(0, 0, 26, 11);
553            ctx.restore();
554        }
555
556        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint", 26, 11);
557        ctx.clearRect(0, 0, 26, 11);
558        drawBreakpoint(ctx);
559
560        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-program-counter", 26, 11);
561        ctx.clearRect(0, 0, 26, 11);
562        drawBreakpoint(ctx);
563        ctx.clearRect(20, 0, 6, 11);
564        this._drawProgramCounterInContext(ctx, true);
565
566        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled", 26, 11);
567        ctx.clearRect(0, 0, 26, 11);
568        drawBreakpoint(ctx, true);
569
570        var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-program-counter", 26, 11);
571        ctx.clearRect(0, 0, 26, 11);
572        drawBreakpoint(ctx, true);
573        ctx.clearRect(20, 0, 6, 11);
574        this._drawProgramCounterInContext(ctx, true);
575
576        delete this._needsBreakpointImages;
577    },
578
579    syntaxHighlightJavascript: function()
580    {
581        var table = this.element.contentDocument.getElementsByTagName("table")[0];
582        if (!table)
583            return;
584
585        function deleteContinueFlags(cell)
586        {
587            if (!cell)
588                return;
589            delete cell._commentContinues;
590            delete cell._singleQuoteStringContinues;
591            delete cell._doubleQuoteStringContinues;
592            delete cell._regexpContinues;
593        }
594
595        function createSpan(content, className)
596        {
597            var span = document.createElement("span");
598            span.className = className;
599            span.appendChild(document.createTextNode(content));
600            return span;
601        }
602
603        function generateFinder(regex, matchNumber, className)
604        {
605            return function(str) {
606                var match = regex.exec(str);
607                if (!match)
608                    return null;
609                previousMatchLength = match[matchNumber].length;
610                return createSpan(match[matchNumber], className);
611            };
612        }
613
614        var findNumber = generateFinder(/^(-?(\d+\.?\d*([eE][+-]\d+)?|0[xX]\h+|Infinity)|NaN)(?:\W|$)/, 1, "webkit-javascript-number");
615        var findKeyword = 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");
616        var findSingleLineString = generateFinder(/^"(?:[^"\\]|\\.)*"|^'([^'\\]|\\.)*'/, 0, "webkit-javascript-string"); // " this quote keeps Xcode happy
617        var findMultilineCommentStart = generateFinder(/^\/\*.*$/, 0, "webkit-javascript-comment");
618        var findMultilineCommentEnd = generateFinder(/^.*?\*\//, 0, "webkit-javascript-comment");
619        var findMultilineSingleQuoteStringStart = generateFinder(/^'(?:[^'\\]|\\.)*\\$/, 0, "webkit-javascript-string");
620        var findMultilineSingleQuoteStringEnd = generateFinder(/^(?:[^'\\]|\\.)*?'/, 0, "webkit-javascript-string");
621        var findMultilineDoubleQuoteStringStart = generateFinder(/^"(?:[^"\\]|\\.)*\\$/, 0, "webkit-javascript-string");
622        var findMultilineDoubleQuoteStringEnd = generateFinder(/^(?:[^"\\]|\\.)*?"/, 0, "webkit-javascript-string");
623        var findMultilineRegExpEnd = generateFinder(/^(?:[^\/\\]|\\.)*?\/([gim]{0,3})/, 0, "webkit-javascript-regexp");
624        var findSingleLineComment = generateFinder(/^\/\/.*|^\/\*.*?\*\//, 0, "webkit-javascript-comment");
625
626        function findMultilineRegExpStart(str)
627        {
628            var match = /^\/(?:[^\/\\]|\\.)*\\$/.exec(str);
629            if (!match || !/\\|\$|\.[\?\*\+]|[^\|]\|[^\|]/.test(match[0]))
630                return null;
631            var node = createSpan(match[0], "webkit-javascript-regexp");
632            previousMatchLength = match[0].length;
633            return node;
634        }
635
636        function findSingleLineRegExp(str)
637        {
638            var match = /^(\/(?:[^\/\\]|\\.)*\/([gim]{0,3}))(.?)/.exec(str);
639            if (!match || !(match[2].length > 0 || /\\|\$|\.[\?\*\+]|[^\|]\|[^\|]/.test(match[1]) || /\.|;|,/.test(match[3])))
640                return null;
641            var node = createSpan(match[1], "webkit-javascript-regexp");
642            previousMatchLength = match[1].length;
643            return node;
644        }
645
646        function syntaxHighlightJavascriptLine(line, prevLine)
647        {
648            var messageBubble = line.lastChild;
649            if (messageBubble && messageBubble.nodeType === Node.ELEMENT_NODE && messageBubble.hasStyleClass("webkit-html-message-bubble"))
650                line.removeChild(messageBubble);
651            else
652                messageBubble = null;
653
654            var code = line.textContent;
655
656            while (line.firstChild)
657                line.removeChild(line.firstChild);
658
659            var token;
660            var tmp = 0;
661            var i = 0;
662            previousMatchLength = 0;
663
664            if (prevLine) {
665                if (prevLine._commentContinues) {
666                    if (!(token = findMultilineCommentEnd(code))) {
667                        token = createSpan(code, "webkit-javascript-comment");
668                        line._commentContinues = true;
669                    }
670                } else if (prevLine._singleQuoteStringContinues) {
671                    if (!(token = findMultilineSingleQuoteStringEnd(code))) {
672                        token = createSpan(code, "webkit-javascript-string");
673                        line._singleQuoteStringContinues = true;
674                    }
675                } else if (prevLine._doubleQuoteStringContinues) {
676                    if (!(token = findMultilineDoubleQuoteStringEnd(code))) {
677                        token = createSpan(code, "webkit-javascript-string");
678                        line._doubleQuoteStringContinues = true;
679                    }
680                } else if (prevLine._regexpContinues) {
681                    if (!(token = findMultilineRegExpEnd(code))) {
682                        token = createSpan(code, "webkit-javascript-regexp");
683                        line._regexpContinues = true;
684                    }
685                }
686                if (token) {
687                    i += previousMatchLength ? previousMatchLength : code.length;
688                    tmp = i;
689                    line.appendChild(token);
690                }
691            }
692
693            for ( ; i < code.length; ++i) {
694                var codeFragment = code.substr(i);
695                var prevChar = code[i - 1];
696                token = findSingleLineComment(codeFragment);
697                if (!token) {
698                    if ((token = findMultilineCommentStart(codeFragment)))
699                        line._commentContinues = true;
700                    else if (!prevChar || /^\W/.test(prevChar)) {
701                        token = findNumber(codeFragment, code[i - 1]) ||
702                                findKeyword(codeFragment, code[i - 1]) ||
703                                findSingleLineString(codeFragment) ||
704                                findSingleLineRegExp(codeFragment);
705                        if (!token) {
706                            if (token = findMultilineSingleQuoteStringStart(codeFragment))
707                                line._singleQuoteStringContinues = true;
708                            else if (token = findMultilineDoubleQuoteStringStart(codeFragment))
709                                line._doubleQuoteStringContinues = true;
710                            else if (token = findMultilineRegExpStart(codeFragment))
711                                line._regexpContinues = true;
712                        }
713                    }
714                }
715
716                if (token) {
717                    if (tmp !== i)
718                        line.appendChild(document.createTextNode(code.substring(tmp, i)));
719                    line.appendChild(token);
720                    i += previousMatchLength - 1;
721                    tmp = i + 1;
722                }
723            }
724
725            if (tmp < code.length)
726                line.appendChild(document.createTextNode(code.substring(tmp, i)));
727
728            if (messageBubble)
729                line.appendChild(messageBubble);
730        }
731
732        var i = 0;
733        var rows = table.rows;
734        var rowsLength = rows.length;
735        var previousCell = null;
736        var previousMatchLength = 0;
737        var sourceFrame = this;
738
739        // Split up the work into chunks so we don't block the
740        // UI thread while processing.
741
742        function processChunk()
743        {
744            for (var end = Math.min(i + 10, rowsLength); i < end; ++i) {
745                var row = rows[i];
746                if (!row)
747                    continue;
748                var cell = row.cells[1];
749                if (!cell)
750                    continue;
751                syntaxHighlightJavascriptLine(cell, previousCell);
752                if (i < (end - 1))
753                    deleteContinueFlags(previousCell);
754                previousCell = cell;
755            }
756
757            if (i >= rowsLength && processChunkInterval) {
758                deleteContinueFlags(previousCell);
759                clearInterval(processChunkInterval);
760
761                sourceFrame.dispatchEventToListeners("syntax highlighting complete");
762            }
763        }
764
765        processChunk();
766
767        var processChunkInterval = setInterval(processChunk, 25);
768    }
769}
770
771WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype;
772