389a3ed73acee16c6505f9f90b7bd7dfb61eabe1
[WebKit-https.git] / Source / WebCore / inspector / front-end / DebuggerPresentationModel.js
1 /*
2  * Copyright (C) 2011 Google 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 are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  * @extends {WebInspector.Object}
34  */
35 WebInspector.DebuggerPresentationModel = function()
36 {
37     // FIXME: apply formatter from outside as a generic mapping.
38     this._formatter = new WebInspector.ScriptFormatter();
39     this._rawSourceCode = {};
40     this._presentationCallFrames = [];
41
42     this._breakpointManager = new WebInspector.BreakpointManager(WebInspector.settings.breakpoints, this._breakpointAdded.bind(this), this._breakpointRemoved.bind(this), WebInspector.debuggerModel);
43
44     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.ParsedScriptSource, this._parsedScriptSource, this);
45     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, this._failedToParseScriptSource, this);
46     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPaused, this);
47     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this);
48     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.Reset, this._debuggerReset, this);
49
50     WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this);
51     WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
52
53     new WebInspector.DebuggerPresentationModelResourceBinding(this);
54 }
55
56 WebInspector.DebuggerPresentationModel.Events = {
57     UISourceCodeAdded: "source-file-added",
58     UISourceCodeReplaced: "source-file-replaced",
59     UISourceCodeRemoved: "source-file-removed",
60     ConsoleMessageAdded: "console-message-added",
61     ConsoleMessagesCleared: "console-messages-cleared",
62     BreakpointAdded: "breakpoint-added",
63     BreakpointRemoved: "breakpoint-removed",
64     DebuggerPaused: "debugger-paused",
65     DebuggerResumed: "debugger-resumed",
66     CallFrameSelected: "call-frame-selected",
67     ExecutionLineChanged: "execution-line-changed"
68 }
69
70 WebInspector.DebuggerPresentationModel.prototype = {
71     createLinkifier: function()
72     {
73         return new WebInspector.DebuggerPresentationModel.Linkifier(this);
74     },
75
76     createPlacard: function(callFrame)
77     {
78         var title = callFrame._callFrame.functionName || WebInspector.UIString("(anonymous function)");
79         var placard = new WebInspector.Placard(title, "");
80
81         var rawSourceCode = callFrame._rawSourceCode;
82         function updatePlacard()
83         {
84             var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(callFrame._callFrame.location);
85             placard.subtitle = WebInspector.displayNameForURL(uiLocation.uiSourceCode.url) + ":" + (uiLocation.lineNumber + 1);
86             placard._text = WebInspector.UIString("%s() at %s", placard.title, placard.subtitle);
87         }
88         if (rawSourceCode.sourceMapping)
89             updatePlacard.call(this);
90         rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, updatePlacard, this);
91         return placard;
92     },
93
94     _parsedScriptSource: function(event)
95     {
96         this._addScript(event.data);
97     },
98
99     _failedToParseScriptSource: function(event)
100     {
101         this._addScript(event.data);
102     },
103
104     _addScript: function(script)
105     {
106         var rawSourceCodeId = this._createRawSourceCodeId(script);
107         var rawSourceCode = this._rawSourceCode[rawSourceCodeId];
108         if (rawSourceCode) {
109             rawSourceCode.addScript(script);
110             return;
111         }
112
113         var resource;
114         if (script.sourceURL)
115             resource = WebInspector.networkManager.inflightResourceForURL(script.sourceURL) || WebInspector.resourceForURL(script.sourceURL);
116         rawSourceCode = new WebInspector.RawSourceCode(rawSourceCodeId, script, resource, this._formatter, this._formatSource);
117         this._rawSourceCode[rawSourceCodeId] = rawSourceCode;
118         if (rawSourceCode.sourceMapping)
119             this._updateSourceMapping(rawSourceCode, null);
120         rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._sourceMappingUpdated, this);
121     },
122
123     _sourceMappingUpdated: function(event)
124     {
125         var rawSourceCode = event.target;
126         var oldSourceMapping = event.data.oldSourceMapping;
127         this._updateSourceMapping(rawSourceCode, oldSourceMapping);
128     },
129
130     _updateSourceMapping: function(rawSourceCode, oldSourceMapping)
131     {
132         if (oldSourceMapping) {
133             var oldUISourceCodeList = oldSourceMapping.uiSourceCodeList();
134             for (var i = 0; i < oldUISourceCodeList.length; ++i) {
135                 var breakpoints = this._breakpointManager.breakpointsForUISourceCode(oldUISourceCodeList[i]);
136                 for (var lineNumber in breakpoints) {
137                     var breakpoint = breakpoints[lineNumber];
138                     this._breakpointRemoved(breakpoint);
139                     delete breakpoint.uiSourceCode;
140                 }
141             }
142         }
143
144         this._restoreBreakpoints(rawSourceCode);
145         this._restoreConsoleMessages(rawSourceCode);
146
147         if (!oldSourceMapping) {
148             var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList();
149             for (var i = 0; i < uiSourceCodeList.length; ++i)
150                 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeAdded, uiSourceCodeList[i]);
151         } else {
152             var eventData = { uiSourceCodeList: rawSourceCode.sourceMapping.uiSourceCodeList(), oldUISourceCodeList: oldSourceMapping.uiSourceCodeList() };
153             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeReplaced, eventData);
154         }
155     },
156
157     _restoreBreakpoints: function(rawSourceCode)
158     {
159         var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList();
160         for (var i = 0; i < uiSourceCodeList.length; ++i) {
161             var uiSourceCode = uiSourceCodeList[i];
162             this._breakpointManager.uiSourceCodeAdded(uiSourceCode);
163             var breakpoints = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
164             for (var lineNumber in breakpoints)
165                 this._breakpointAdded(breakpoints[lineNumber]);
166         }
167
168     },
169
170     _restoreConsoleMessages: function(rawSourceCode)
171     {
172         var messages = rawSourceCode.messages;
173         for (var i = 0; i < messages.length; ++i)
174             messages[i]._presentationMessage = this._createPresentationMessage(messages[i], rawSourceCode.sourceMapping);
175     },
176
177     canEditScriptSource: function(uiSourceCode)
178     {
179         if (!Preferences.canEditScriptSource || this._formatSource)
180             return false;
181         var rawSourceCode = uiSourceCode.rawSourceCode;
182         var script = this._scriptForRawSourceCode(rawSourceCode);
183         return script && !script.lineOffset && !script.columnOffset;
184     },
185
186     setScriptSource: function(uiSourceCode, newSource, callback)
187     {
188         var rawSourceCode = uiSourceCode.rawSourceCode;
189         var script = this._scriptForRawSourceCode(rawSourceCode);
190
191         function didEditScriptSource(error)
192         {
193             callback(error);
194             if (error)
195                 return;
196
197             var resource = WebInspector.resourceForURL(rawSourceCode.url);
198             if (resource)
199                 resource.addRevision(newSource);
200
201             uiSourceCode.contentChanged(newSource);
202
203             if (WebInspector.debuggerModel.callFrames)
204                 this._debuggerPaused();
205         }
206         WebInspector.debuggerModel.setScriptSource(script.scriptId, newSource, didEditScriptSource.bind(this));
207     },
208
209     _updateBreakpointsAfterLiveEdit: function(uiSourceCode, oldSource, newSource)
210     {
211         var breakpoints = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
212
213         // Clear and re-create breakpoints according to text diff.
214         var diff = Array.diff(oldSource.split("\n"), newSource.split("\n"));
215         for (var lineNumber in breakpoints) {
216             var breakpoint = breakpoints[lineNumber];
217
218             this.removeBreakpoint(uiSourceCode, lineNumber);
219
220             var newLineNumber = diff.left[lineNumber].row;
221             if (newLineNumber === undefined) {
222                 for (var i = lineNumber - 1; i >= 0; --i) {
223                     if (diff.left[i].row === undefined)
224                         continue;
225                     var shiftedLineNumber = diff.left[i].row + lineNumber - i;
226                     if (shiftedLineNumber < diff.right.length) {
227                         var originalLineNumber = diff.right[shiftedLineNumber].row;
228                         if (originalLineNumber === lineNumber || originalLineNumber === undefined)
229                             newLineNumber = shiftedLineNumber;
230                     }
231                     break;
232                 }
233             }
234             if (newLineNumber !== undefined)
235                 this.setBreakpoint(uiSourceCode, newLineNumber, breakpoint.condition, breakpoint.enabled);
236         }
237     },
238
239     setFormatSource: function(formatSource)
240     {
241         if (this._formatSource === formatSource)
242             return;
243
244         this._formatSource = formatSource;
245         this._breakpointManager.reset();
246         for (var id in this._rawSourceCode)
247             this._rawSourceCode[id].setFormatted(this._formatSource);
248
249         if (WebInspector.debuggerModel.callFrames)
250             this._debuggerPaused();
251     },
252
253     _consoleMessageAdded: function(event)
254     {
255         var message = event.data;
256         if (!message.url || !message.isErrorOrWarning() || !message.message)
257             return;
258
259         var rawSourceCode = this._rawSourceCodeForScriptWithURL(message.url);
260         if (!rawSourceCode)
261             return;
262
263         rawSourceCode.messages.push(message);
264         if (rawSourceCode.sourceMapping) {
265             message._presentationMessage = this._createPresentationMessage(message, rawSourceCode.sourceMapping);
266             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessageAdded, message._presentationMessage);
267         }
268     },
269
270     _createPresentationMessage: function(message, sourceMapping)
271     {
272         // FIXME(62725): stack trace line/column numbers are one-based.
273         var lineNumber = message.stackTrace ? message.stackTrace[0].lineNumber - 1 : message.line - 1;
274         var columnNumber = message.stackTrace ? message.stackTrace[0].columnNumber - 1 : 0;
275         var uiLocation = sourceMapping.rawLocationToUILocation({ lineNumber: lineNumber, columnNumber: columnNumber });
276         var presentationMessage = {};
277         presentationMessage.uiSourceCode = uiLocation.uiSourceCode;
278         presentationMessage.lineNumber = uiLocation.lineNumber;
279         presentationMessage.originalMessage = message;
280         return presentationMessage;
281     },
282
283     _consoleCleared: function()
284     {
285         for (var id in this._rawSourceCode)
286             this._rawSourceCode[id].messages = [];
287         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessagesCleared);
288     },
289
290     continueToLine: function(uiSourceCode, lineNumber)
291     {
292         // FIXME: use RawSourceCode.uiLocationToRawLocation.
293         var rawLocation = uiSourceCode.rawSourceCode.sourceMapping.uiLocationToRawLocation(uiSourceCode, lineNumber);
294         WebInspector.debuggerModel.continueToLocation(rawLocation);
295     },
296
297     breakpointsForUISourceCode: function(uiSourceCode)
298     {
299         var breakpointsMap = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
300         var breakpointsList = [];
301         for (var lineNumber in breakpointsMap)
302             breakpointsList.push(breakpointsMap[lineNumber]);
303         return breakpointsList;
304     },
305
306     messagesForUISourceCode: function(uiSourceCode)
307     {
308         var rawSourceCode = uiSourceCode.rawSourceCode;
309         var messages = [];
310         for (var i = 0; i < rawSourceCode.messages.length; ++i)
311             messages.push(rawSourceCode.messages[i]._presentationMessage);
312         return messages;
313     },
314
315     setBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
316     {
317         this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled);
318     },
319
320     setBreakpointEnabled: function(uiSourceCode, lineNumber, enabled)
321     {
322         var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber);
323         if (!breakpoint)
324             return;
325         this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
326         this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, breakpoint.condition, enabled);
327     },
328
329     updateBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
330     {
331         this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
332         this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled);
333     },
334
335     removeBreakpoint: function(uiSourceCode, lineNumber)
336     {
337         this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
338     },
339
340     findBreakpoint: function(uiSourceCode, lineNumber)
341     {
342         return this._breakpointManager.breakpointsForUISourceCode(uiSourceCode)[lineNumber];
343     },
344
345     _breakpointAdded: function(breakpoint)
346     {
347         if (breakpoint.uiSourceCode)
348             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, breakpoint);
349     },
350
351     _breakpointRemoved: function(breakpoint)
352     {
353         if (breakpoint.uiSourceCode)
354             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointRemoved, breakpoint);
355     },
356
357     _debuggerPaused: function()
358     {
359         var callFrames = WebInspector.debuggerModel.callFrames;
360         this._presentationCallFrames = [];
361         for (var i = 0; i < callFrames.length; ++i) {
362             var callFrame = callFrames[i];
363             var script = WebInspector.debuggerModel.scriptForSourceID(callFrame.location.scriptId);
364             if (!script)
365                 continue;
366             var rawSourceCode = this._rawSourceCodeForScript(script);
367             this._presentationCallFrames.push(new WebInspector.PresentationCallFrame(callFrame, i, this, rawSourceCode));
368         }
369         var details = WebInspector.debuggerModel.debuggerPausedDetails;
370         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerPaused, { callFrames: this._presentationCallFrames, details: details });
371
372         this.selectedCallFrame = this._presentationCallFrames[0];
373     },
374
375     _debuggerResumed: function()
376     {
377         this._presentationCallFrames = [];
378         this.selectedCallFrame = null;
379         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerResumed);
380     },
381
382     set selectedCallFrame(callFrame)
383     {
384         if (this._selectedCallFrame)
385             this._selectedCallFrame.rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._dispatchExecutionLineChanged, this);
386         this._selectedCallFrame = callFrame;
387         if (!this._selectedCallFrame)
388             return;
389
390         this._selectedCallFrame.rawSourceCode.forceUpdateSourceMapping();
391         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.CallFrameSelected, callFrame);
392
393         if (this._selectedCallFrame.rawSourceCode.sourceMapping)
394             this._dispatchExecutionLineChanged(null);
395         this._selectedCallFrame.rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._dispatchExecutionLineChanged, this);
396     },
397
398     get selectedCallFrame()
399     {
400         return this._selectedCallFrame;
401     },
402
403     _dispatchExecutionLineChanged: function(event)
404     {
405         var rawLocation = this._selectedCallFrame._callFrame.location;
406         var uiLocation = this._selectedCallFrame.rawSourceCode.sourceMapping.rawLocationToUILocation(rawLocation);
407         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ExecutionLineChanged, uiLocation);
408     },
409
410     /**
411      * @param {string} sourceURL
412      */
413     _rawSourceCodeForScriptWithURL: function(sourceURL)
414     {
415         return this._rawSourceCode[sourceURL];
416     },
417
418     /**
419      * @param {WebInspector.Script} script
420      */
421     _rawSourceCodeForScript: function(script)
422     {
423         return this._rawSourceCode[this._createRawSourceCodeId(script)];
424     },
425
426     _scriptForRawSourceCode: function(rawSourceCode)
427     {
428         function filter(script)
429         {
430             return this._createRawSourceCodeId(script) === rawSourceCode.id;
431         }
432         return WebInspector.debuggerModel.queryScripts(filter.bind(this))[0];
433     },
434
435     _createRawSourceCodeId: function(script)
436     {
437         return script.sourceURL || script.scriptId;
438     },
439
440     _debuggerReset: function()
441     {
442         for (var id in this._rawSourceCode) {
443             var rawSourceCode = this._rawSourceCode[id];
444             if (rawSourceCode.sourceMapping) {
445                 var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList();
446                 for (var i = 0; i < uiSourceCodeList.length; ++i)
447                     this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeRemoved, uiSourceCodeList[i]);
448             }
449             rawSourceCode.removeAllListeners();
450         }
451         this._rawSourceCode = {};
452         this._presentationCallFrames = [];
453         this._selectedCallFrame = null;
454         this._breakpointManager.debuggerReset();
455     }
456 }
457
458 WebInspector.DebuggerPresentationModel.prototype.__proto__ = WebInspector.Object.prototype;
459
460 /**
461  * @constructor
462  */
463 WebInspector.PresentationCallFrame = function(callFrame, index, model, rawSourceCode)
464 {
465     this._callFrame = callFrame;
466     this._index = index;
467     this._model = model;
468     this._rawSourceCode = rawSourceCode;
469 }
470
471 WebInspector.PresentationCallFrame.prototype = {
472     get type()
473     {
474         return this._callFrame.type;
475     },
476
477     get scopeChain()
478     {
479         return this._callFrame.scopeChain;
480     },
481
482     get this()
483     {
484         return this._callFrame.this;
485     },
486
487     get index()
488     {
489         return this._index;
490     },
491
492     get rawSourceCode()
493     {
494         return this._rawSourceCode;
495     },
496
497     evaluate: function(code, objectGroup, includeCommandLineAPI, returnByValue, callback)
498     {
499         function didEvaluateOnCallFrame(error, result, wasThrown)
500         {
501             if (error) {
502                 console.error(error);
503                 callback(null);
504                 return;
505             }
506
507             if (returnByValue && !wasThrown)
508                 callback(result, wasThrown);
509             else
510                 callback(WebInspector.RemoteObject.fromPayload(result), wasThrown);
511         }
512         DebuggerAgent.evaluateOnCallFrame(this._callFrame.callFrameId, code, objectGroup, includeCommandLineAPI, returnByValue, didEvaluateOnCallFrame.bind(this));
513     },
514
515     uiLocation: function(callback)
516     {
517         function sourceMappingReady()
518         {
519             this._rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingReady, this);
520             callback(this._rawSourceCode.sourceMapping.rawLocationToUILocation(this._callFrame.location));
521         }
522         if (this._rawSourceCode.sourceMapping)
523             sourceMappingReady.call(this);
524         else
525             this._rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingReady, this);
526     }
527 }
528
529 /**
530  * @constructor
531  * @implements {WebInspector.ResourceDomainModelBinding}
532  */
533 WebInspector.DebuggerPresentationModelResourceBinding = function(model)
534 {
535     this._presentationModel = model;
536     WebInspector.Resource.registerDomainModelBinding(WebInspector.Resource.Type.Script, this);
537 }
538
539 WebInspector.DebuggerPresentationModelResourceBinding.prototype = {
540     canSetContent: function(resource)
541     {
542         var rawSourceCode = this._presentationModel._rawSourceCodeForScriptWithURL(resource.url)
543         if (!rawSourceCode)
544             return false;
545         return this._presentationModel.canEditScriptSource(rawSourceCode.sourceMapping.uiSourceCodeList()[0]);
546     },
547
548     setContent: function(resource, content, majorChange, userCallback)
549     {
550         if (!majorChange)
551             return;
552
553         var rawSourceCode = this._presentationModel._rawSourceCodeForScriptWithURL(resource.url);
554         if (!rawSourceCode) {
555             userCallback("Resource is not editable");
556             return;
557         }
558
559         resource.requestContent(this._setContentWithInitialContent.bind(this, rawSourceCode.sourceMapping.uiSourceCodeList()[0], content, userCallback));
560     },
561
562     _setContentWithInitialContent: function(uiSourceCode, content, userCallback, oldContent)
563     {
564         function callback(error)
565         {
566             if (userCallback)
567                 userCallback(error);
568             if (!error)
569                 this._presentationModel._updateBreakpointsAfterLiveEdit(uiSourceCode, oldContent, content);
570         }
571         this._presentationModel.setScriptSource(uiSourceCode, content, callback.bind(this));
572     }
573 }
574
575 /**
576  * @constructor
577  */
578 WebInspector.DebuggerPresentationModel.Linkifier = function(model)
579 {
580     this._model = model;
581     this._anchorsForRawSourceCode = {};
582 }
583
584 WebInspector.DebuggerPresentationModel.Linkifier.prototype = {
585     linkifyLocation: function(sourceURL, lineNumber, columnNumber, classes)
586     {
587         var linkText = WebInspector.formatLinkText(sourceURL, lineNumber);
588         var anchor = WebInspector.linkifyURLAsNode(sourceURL, linkText, classes, false);
589         anchor.rawLocation = { lineNumber: lineNumber, columnNumber: columnNumber };
590
591         var rawSourceCode = this._model._rawSourceCodeForScriptWithURL(sourceURL);
592         if (!rawSourceCode) {
593             anchor.setAttribute("preferred_panel", "resources");
594             anchor.setAttribute("line_number", lineNumber);
595             return anchor;
596         }
597
598         var anchors = this._anchorsForRawSourceCode[rawSourceCode.id];
599         if (!anchors) {
600             anchors = [];
601             this._anchorsForRawSourceCode[rawSourceCode.id] = anchors;
602             rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._updateSourceAnchors, this);
603         }
604
605         if (rawSourceCode.sourceMapping)
606             this._updateAnchor(rawSourceCode, anchor);
607         anchors.push(anchor);
608         return anchor;
609     },
610
611     reset: function()
612     {
613         for (var id in this._anchorsForRawSourceCode) {
614             if (this._model._rawSourceCode[id]) // In case of navigation the list of rawSourceCodes is empty.
615                 this._model._rawSourceCode[id].removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._updateSourceAnchors, this);
616         }
617         this._anchorsForRawSourceCode = {};
618     },
619
620     _updateSourceAnchors: function(event)
621     {
622         var rawSourceCode = event.target;
623         var anchors = this._anchorsForRawSourceCode[rawSourceCode.id];
624         for (var i = 0; i < anchors.length; ++i)
625             this._updateAnchor(rawSourceCode, anchors[i]);
626     },
627
628     _updateAnchor: function(rawSourceCode, anchor)
629     {
630         var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(anchor.rawLocation);
631         anchor.textContent = WebInspector.formatLinkText(uiLocation.uiSourceCode.url, uiLocation.lineNumber);
632         anchor.setAttribute("preferred_panel", "scripts");
633         anchor.uiSourceCode = uiLocation.uiSourceCode;
634         anchor.lineNumber = uiLocation.lineNumber;
635     }
636 }
637
638 WebInspector.DebuggerPresentationModelResourceBinding.prototype.__proto__ = WebInspector.ResourceDomainModelBinding.prototype;
639
640 /**
641  * @type {?WebInspector.DebuggerPresentationModel}
642  */
643 WebInspector.debuggerPresentationModel = null;