1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @constructor
7 * @implements {WebInspector.TargetManager.Observer}
8 * @param {!WebInspector.TargetManager} targetManager
9 * @param {!WebInspector.Workspace} workspace
10 * @param {!WebInspector.NetworkWorkspaceBinding} networkWorkspaceBinding
11 */
12WebInspector.DebuggerWorkspaceBinding = function(targetManager, workspace, networkWorkspaceBinding)
13{
14    this._workspace = workspace;
15    this._networkWorkspaceBinding = networkWorkspaceBinding;
16
17    /** @type {!Map.<!WebInspector.Target, !WebInspector.DebuggerWorkspaceBinding.TargetData>} */
18    this._targetToData = new Map();
19    targetManager.observeTargets(this);
20
21    targetManager.addModelListener(WebInspector.DebuggerModel, WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._globalObjectCleared, this);
22    targetManager.addModelListener(WebInspector.DebuggerModel, WebInspector.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this);
23    workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
24    workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
25}
26
27WebInspector.DebuggerWorkspaceBinding.prototype = {
28    /**
29     * @param {!WebInspector.Target} target
30     */
31    targetAdded: function(target)
32    {
33        this._targetToData.set(target, new WebInspector.DebuggerWorkspaceBinding.TargetData(target, this));
34    },
35
36    /**
37     * @param {!WebInspector.Target} target
38     */
39    targetRemoved: function(target)
40    {
41        var targetData = this._targetToData.get(target);
42        targetData._dispose();
43        this._targetToData.remove(target);
44    },
45
46    /**
47     * @param {!WebInspector.Event} event
48     */
49    _uiSourceCodeRemoved: function(event)
50    {
51        var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
52        var targetDatas = this._targetToData.values();
53        for (var i = 0; i < targetDatas.length; ++i)
54            targetDatas[i]._uiSourceCodeRemoved(uiSourceCode);
55    },
56
57    /**
58     * @param {!WebInspector.Event} event
59     */
60    _projectRemoved: function(event)
61    {
62        var project = /** @type {!WebInspector.Project} */ (event.data);
63        var targetDatas = this._targetToData.values();
64        var uiSourceCodes = project.uiSourceCodes();
65        for (var i = 0; i < targetDatas.length; ++i) {
66            for (var j = 0; j < uiSourceCodes.length; ++j)
67                targetDatas[i]._uiSourceCodeRemoved(uiSourceCodes[j]);
68        }
69    },
70
71    /**
72     * @param {!WebInspector.Script} script
73     * @param {!WebInspector.DebuggerSourceMapping} sourceMapping
74     */
75    pushSourceMapping: function(script, sourceMapping)
76    {
77        var info = this._ensureInfoForScript(script);
78        info._pushSourceMapping(sourceMapping);
79    },
80
81    /**
82     * @param {!WebInspector.Script} script
83     * @return {!WebInspector.DebuggerSourceMapping}
84     */
85    popSourceMapping: function(script)
86    {
87        var info = this._infoForScript(script.target(), script.scriptId);
88        console.assert(info);
89        return info._popSourceMapping();
90    },
91
92    /**
93     * @param {!WebInspector.Target} target
94     * @param {!WebInspector.UISourceCode} uiSourceCode
95     * @param {?WebInspector.DebuggerSourceMapping} sourceMapping
96     */
97    setSourceMapping: function(target, uiSourceCode, sourceMapping)
98    {
99        var data = this._targetToData.get(target);
100        if (data)
101            data._setSourceMapping(uiSourceCode, sourceMapping);
102    },
103
104    /**
105     * @param {!WebInspector.Script} script
106     */
107    updateLocations: function(script)
108    {
109        var info = this._infoForScript(script.target(), script.scriptId);
110        if (info)
111            info._updateLocations();
112    },
113
114    /**
115     * @param {!WebInspector.DebuggerModel.Location} rawLocation
116     * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
117     * @return {!WebInspector.DebuggerWorkspaceBinding.Location}
118     */
119    createLiveLocation: function(rawLocation, updateDelegate)
120    {
121        var info = this._infoForScript(rawLocation.target(), rawLocation.scriptId);
122        console.assert(info);
123        var location = new WebInspector.DebuggerWorkspaceBinding.Location(info._script, rawLocation, this, updateDelegate);
124        info._addLocation(location);
125        return location;
126    },
127
128    /**
129     * @param {!WebInspector.DebuggerModel.CallFrame} callFrame
130     * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
131     * @return {!WebInspector.DebuggerWorkspaceBinding.Location}
132     */
133    createCallFrameLiveLocation: function(callFrame, updateDelegate)
134    {
135        var target = callFrame.target();
136        this._ensureInfoForScript(callFrame.script)
137        var location = this.createLiveLocation(callFrame.location(), updateDelegate);
138        this._registerCallFrameLiveLocation(target, location);
139        return location;
140    },
141
142    /**
143     * @param {!WebInspector.DebuggerModel.Location} rawLocation
144     * @return {!WebInspector.UILocation}
145     */
146    rawLocationToUILocation: function(rawLocation)
147    {
148        var info = this._infoForScript(rawLocation.target(), rawLocation.scriptId);
149        console.assert(info);
150        return info._rawLocationToUILocation(rawLocation);
151    },
152
153    /**
154     * @param {!WebInspector.Target} target
155     * @param {!WebInspector.UISourceCode} uiSourceCode
156     * @param {number} lineNumber
157     * @param {number} columnNumber
158     * @return {?WebInspector.DebuggerModel.Location}
159     */
160    uiLocationToRawLocation: function(target, uiSourceCode, lineNumber, columnNumber)
161    {
162        var targetData = this._targetToData.get(target);
163        return targetData ? /** @type {?WebInspector.DebuggerModel.Location} */ (targetData._uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber)) : null;
164    },
165
166    /**
167     * @param {!WebInspector.UISourceCode} uiSourceCode
168     * @param {number} lineNumber
169     * @param {number} columnNumber
170     * @return {!Array.<!WebInspector.DebuggerModel.Location>}
171     */
172    uiLocationToRawLocations: function(uiSourceCode, lineNumber, columnNumber)
173    {
174        var result = [];
175        var targetDatas = this._targetToData.values();
176        for (var i = 0; i < targetDatas.length; ++i) {
177            var rawLocation = targetDatas[i]._uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber);
178            if (rawLocation)
179                result.push(rawLocation);
180        }
181        return result;
182    },
183
184    /**
185     * @param {!WebInspector.UISourceCode} uiSourceCode
186     * @param {number} lineNumber
187     * @return {boolean}
188     */
189    uiLineHasMapping: function(uiSourceCode, lineNumber)
190    {
191        var targetDatas = this._targetToData.values();
192        for (var i = 0; i < targetDatas.length; ++i) {
193            if (!targetDatas[i]._uiLineHasMapping(uiSourceCode, lineNumber))
194                return false;
195        }
196        return true;
197    },
198
199    /**
200     * @param {!WebInspector.Target} target
201     * @return {?WebInspector.LiveEditSupport}
202     */
203    liveEditSupport: function(target)
204    {
205        var targetData = this._targetToData.get(target);
206        return targetData ? targetData._liveEditSupport : null;
207    },
208
209    /**
210     * @param {!WebInspector.UISourceCode} uiSourceCode
211     * @param {!WebInspector.Target} target
212     * @return {?WebInspector.ResourceScriptFile}
213     */
214    scriptFile: function(uiSourceCode, target)
215    {
216        var targetData = this._targetToData.get(target);
217        return targetData ? targetData._resourceMapping.scriptFile(uiSourceCode) : null;
218    },
219
220    /**
221     * @param {!WebInspector.Event} event
222     */
223    _globalObjectCleared: function(event)
224    {
225        var debuggerModel = /** @type {!WebInspector.DebuggerModel} */ (event.target);
226        this._reset(debuggerModel.target());
227    },
228
229    /**
230     * @param {!WebInspector.Target} target
231     */
232    _reset: function(target)
233    {
234        var targetData = this._targetToData.get(target);
235        targetData.callFrameLocations.values().forEach(function(location) { location.dispose(); });
236        targetData.callFrameLocations.clear();
237    },
238
239    /**
240     * @param {!WebInspector.Script} script
241     * @return {!WebInspector.DebuggerWorkspaceBinding.ScriptInfo}
242     */
243    _ensureInfoForScript: function(script)
244    {
245        var scriptDataMap = this._targetToData.get(script.target()).scriptDataMap;
246        var info = scriptDataMap.get(script.scriptId);
247        if (!info) {
248            info = new WebInspector.DebuggerWorkspaceBinding.ScriptInfo(script);
249            scriptDataMap.set(script.scriptId, info);
250        }
251        return info;
252    },
253
254
255    /**
256     * @param {!WebInspector.Target} target
257     * @param {string} scriptId
258     * @return {?WebInspector.DebuggerWorkspaceBinding.ScriptInfo}
259     */
260    _infoForScript: function(target, scriptId)
261    {
262        var data = this._targetToData.get(target);
263        if (!data)
264            return null;
265        return data.scriptDataMap.get(scriptId) || null;
266    },
267
268    /**
269     * @param {!WebInspector.Target} target
270     * @param {!WebInspector.DebuggerWorkspaceBinding.Location} location
271     */
272    _registerCallFrameLiveLocation: function(target, location)
273    {
274        var locations = this._targetToData.get(target).callFrameLocations;
275        locations.add(location);
276    },
277
278    /**
279     * @param {!WebInspector.DebuggerWorkspaceBinding.Location} location
280     */
281    _removeLiveLocation: function(location)
282    {
283        var info = this._infoForScript(location._script.target(), location._script.scriptId);
284        if (info)
285            info._removeLocation(location);
286    },
287
288    /**
289     * @param {!WebInspector.Event} event
290     */
291    _debuggerResumed: function(event)
292    {
293        var debuggerModel = /** @type {!WebInspector.DebuggerModel} */ (event.target);
294        this._reset(debuggerModel.target());
295    }
296}
297
298/**
299 * @constructor
300 * @param {!WebInspector.Target} target
301 * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding
302 */
303WebInspector.DebuggerWorkspaceBinding.TargetData = function(target, debuggerWorkspaceBinding)
304{
305    this._target = target;
306
307    /** @type {!StringMap.<!WebInspector.DebuggerWorkspaceBinding.ScriptInfo>} */
308    this.scriptDataMap = new StringMap();
309
310    /** @type {!Set.<!WebInspector.DebuggerWorkspaceBinding.Location>} */
311    this.callFrameLocations = new Set();
312
313    var debuggerModel = target.debuggerModel;
314    var workspace = debuggerWorkspaceBinding._workspace;
315
316    this._liveEditSupport = new WebInspector.LiveEditSupport(target, workspace, debuggerWorkspaceBinding);
317    this._defaultMapping = new WebInspector.DefaultScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding);
318    this._resourceMapping = new WebInspector.ResourceScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding);
319    this._compilerMapping = new WebInspector.CompilerScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding._networkWorkspaceBinding, debuggerWorkspaceBinding);
320
321    /** @type {!WebInspector.LiveEditSupport} */
322    this._liveEditSupport = new WebInspector.LiveEditSupport(target, workspace, debuggerWorkspaceBinding);
323
324    /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.DebuggerSourceMapping>} */
325    this._uiSourceCodeToSourceMapping = new Map();
326
327    debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.ParsedScriptSource, this._parsedScriptSource, this);
328    debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, this._parsedScriptSource, this);
329}
330
331WebInspector.DebuggerWorkspaceBinding.TargetData.prototype = {
332    /**
333     * @param {!WebInspector.Event} event
334     */
335    _parsedScriptSource: function(event)
336    {
337        var script = /** @type {!WebInspector.Script} */ (event.data);
338        this._defaultMapping.addScript(script);
339
340        if (script.isSnippet()) {
341            WebInspector.scriptSnippetModel.addScript(script);
342            return;
343        }
344
345        this._resourceMapping.addScript(script);
346
347        if (WebInspector.settings.jsSourceMapsEnabled.get())
348            this._compilerMapping.addScript(script);
349    },
350
351    /**
352     * @param {!WebInspector.UISourceCode} uiSourceCode
353     * @param {?WebInspector.DebuggerSourceMapping} sourceMapping
354     */
355    _setSourceMapping: function(uiSourceCode, sourceMapping)
356    {
357        if (this._uiSourceCodeToSourceMapping.get(uiSourceCode) === sourceMapping)
358            return;
359
360        if (sourceMapping)
361            this._uiSourceCodeToSourceMapping.set(uiSourceCode, sourceMapping);
362        else
363            this._uiSourceCodeToSourceMapping.remove(uiSourceCode);
364
365        uiSourceCode.dispatchEventToListeners(WebInspector.UISourceCode.Events.SourceMappingChanged, {target: this._target, isIdentity: sourceMapping ? sourceMapping.isIdentity() : false});
366    },
367
368    /**
369     * @param {!WebInspector.UISourceCode} uiSourceCode
370     * @param {number} lineNumber
371     * @param {number} columnNumber
372     * @return {?WebInspector.DebuggerModel.Location}
373     */
374    _uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
375    {
376        var sourceMapping = this._uiSourceCodeToSourceMapping.get(uiSourceCode);
377        return sourceMapping ? sourceMapping.uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber) : null;
378    },
379
380    /**
381     * @param {!WebInspector.UISourceCode} uiSourceCode
382     * @param {number} lineNumber
383     * @return {boolean}
384     */
385    _uiLineHasMapping: function(uiSourceCode, lineNumber)
386    {
387        var sourceMapping = this._uiSourceCodeToSourceMapping.get(uiSourceCode);
388        return sourceMapping ? sourceMapping.uiLineHasMapping(uiSourceCode, lineNumber) : true;
389    },
390
391    /**
392     * @param {!WebInspector.UISourceCode} uiSourceCode
393     */
394    _uiSourceCodeRemoved: function(uiSourceCode)
395    {
396        this._uiSourceCodeToSourceMapping.remove(uiSourceCode);
397    },
398
399    _dispose: function()
400    {
401        this._compilerMapping.dispose();
402        this._resourceMapping.dispose();
403        this._defaultMapping.dispose();
404        this._uiSourceCodeToSourceMapping.clear();
405    }
406}
407
408/**
409 * @constructor
410 * @param {!WebInspector.Script} script
411 */
412WebInspector.DebuggerWorkspaceBinding.ScriptInfo = function(script)
413{
414    this._script = script;
415
416    /** @type {!Array.<!WebInspector.DebuggerSourceMapping>} */
417    this._sourceMappings = [];
418
419    /** @type {!Set.<!WebInspector.LiveLocation>} */
420    this._locations = new Set();
421}
422
423WebInspector.DebuggerWorkspaceBinding.ScriptInfo.prototype = {
424    /**
425     * @param {!WebInspector.DebuggerSourceMapping} sourceMapping
426     */
427    _pushSourceMapping: function(sourceMapping)
428    {
429        this._sourceMappings.push(sourceMapping);
430        this._updateLocations();
431    },
432
433    /**
434     * @return {!WebInspector.DebuggerSourceMapping}
435     */
436    _popSourceMapping: function()
437    {
438        var sourceMapping = this._sourceMappings.pop();
439        this._updateLocations();
440        return sourceMapping;
441    },
442
443    /**
444     * @param {!WebInspector.LiveLocation} location
445     */
446    _addLocation: function(location)
447    {
448        this._locations.add(location);
449        location.update();
450    },
451
452    /**
453     * @param {!WebInspector.LiveLocation} location
454     */
455    _removeLocation: function(location)
456    {
457        this._locations.remove(location);
458    },
459
460    _updateLocations: function()
461    {
462        var items = this._locations.values();
463        for (var i = 0; i < items.length; ++i)
464            items[i].update();
465    },
466
467    /**
468     * @param {!WebInspector.DebuggerModel.Location} rawLocation
469     * @return {!WebInspector.UILocation}
470     */
471    _rawLocationToUILocation: function(rawLocation)
472    {
473        var uiLocation;
474        for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i)
475            uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation);
476        console.assert(uiLocation, "Script raw location cannot be mapped to any UI location.");
477        return /** @type {!WebInspector.UILocation} */ (uiLocation);
478    }
479}
480
481
482/**
483 * @constructor
484 * @extends {WebInspector.LiveLocation}
485 * @param {!WebInspector.Script} script
486 * @param {!WebInspector.DebuggerModel.Location} rawLocation
487 * @param {!WebInspector.DebuggerWorkspaceBinding} binding
488 * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
489 */
490WebInspector.DebuggerWorkspaceBinding.Location = function(script, rawLocation, binding, updateDelegate)
491{
492    WebInspector.LiveLocation.call(this, updateDelegate);
493    this._script = script;
494    this._rawLocation = rawLocation;
495    this._binding = binding;
496}
497
498WebInspector.DebuggerWorkspaceBinding.Location.prototype = {
499    /**
500     * @return {!WebInspector.UILocation}
501     */
502    uiLocation: function()
503    {
504        var debuggerModelLocation = this._rawLocation;
505        return this._binding.rawLocationToUILocation(debuggerModelLocation);
506    },
507
508    dispose: function()
509    {
510        WebInspector.LiveLocation.prototype.dispose.call(this);
511        this._binding._removeLiveLocation(this);
512    },
513
514    __proto__: WebInspector.LiveLocation.prototype
515}
516
517/**
518 * @interface
519 */
520WebInspector.DebuggerSourceMapping = function()
521{
522}
523
524WebInspector.DebuggerSourceMapping.prototype = {
525    /**
526     * @param {!WebInspector.DebuggerModel.Location} rawLocation
527     * @return {?WebInspector.UILocation}
528     */
529    rawLocationToUILocation: function(rawLocation) { },
530
531    /**
532     * @param {!WebInspector.UISourceCode} uiSourceCode
533     * @param {number} lineNumber
534     * @param {number} columnNumber
535     * @return {?WebInspector.DebuggerModel.Location}
536     */
537    uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) { },
538
539    /**
540     * @return {boolean}
541     */
542    isIdentity: function() { },
543
544    /**
545     * @param {!WebInspector.UISourceCode} uiSourceCode
546     * @param {number} lineNumber
547     * @return {boolean}
548     */
549    uiLineHasMapping: function(uiSourceCode, lineNumber) { }
550}
551
552/**
553 * @type {!WebInspector.DebuggerWorkspaceBinding}
554 */
555WebInspector.debuggerWorkspaceBinding;
556