Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / bindings / ResourceScriptMapping.js
blob53f90cd160a57353c6e9214106dd5fc380725364
1 /*
2  * Copyright (C) 2012 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  */
31 /**
32  * @constructor
33  * @implements {WebInspector.DebuggerSourceMapping}
34  * @param {!WebInspector.DebuggerModel} debuggerModel
35  * @param {!WebInspector.Workspace} workspace
36  * @param {!WebInspector.NetworkMapping} networkMapping
37  * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding
38  */
39 WebInspector.ResourceScriptMapping = function(debuggerModel, workspace, networkMapping, debuggerWorkspaceBinding)
41     this._target = debuggerModel.target();
42     this._debuggerModel = debuggerModel;
43     this._workspace = workspace;
44     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
45     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
46     this._networkMapping = networkMapping;
47     this._debuggerWorkspaceBinding = debuggerWorkspaceBinding;
48     /** @type {!Set.<string>} */
49     this._boundURLs = new Set();
51     /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.ResourceScriptFile>} */
52     this._uiSourceCodeToScriptFile = new Map();
54     debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
57 WebInspector.ResourceScriptMapping.prototype = {
58     /**
59      * @override
60      * @param {!WebInspector.DebuggerModel.Location} rawLocation
61      * @return {?WebInspector.UILocation}
62      */
63     rawLocationToUILocation: function(rawLocation)
64     {
65         var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (rawLocation);
66         var script = debuggerModelLocation.script();
67         var uiSourceCode = this._workspaceUISourceCodeForScript(script);
68         if (!uiSourceCode)
69             return null;
70         var scriptFile = this.scriptFile(uiSourceCode);
71         if (scriptFile && ((scriptFile.hasDivergedFromVM() && !scriptFile.isMergingToVM()) || scriptFile.isDivergingFromVM()))
72             return null;
73         var lineNumber = debuggerModelLocation.lineNumber - (script.isInlineScriptWithSourceURL() ? script.lineOffset : 0);
74         var columnNumber = debuggerModelLocation.columnNumber || 0;
75         if (script.isInlineScriptWithSourceURL() && !lineNumber && columnNumber)
76             columnNumber -= script.columnOffset;
77         return uiSourceCode.uiLocation(lineNumber, columnNumber);
78     },
80     /**
81      * @override
82      * @param {!WebInspector.UISourceCode} uiSourceCode
83      * @param {number} lineNumber
84      * @param {number} columnNumber
85      * @return {?WebInspector.DebuggerModel.Location}
86      */
87     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
88     {
89         var scripts = this._scriptsForUISourceCode(uiSourceCode);
90         console.assert(scripts.length);
91         var script = scripts[0];
92         if (script.isInlineScriptWithSourceURL())
93             return this._debuggerModel.createRawLocation(script, lineNumber + script.lineOffset, lineNumber ? columnNumber : columnNumber + script.columnOffset);
94         return this._debuggerModel.createRawLocation(script, lineNumber, columnNumber);
95     },
97     /**
98      * @param {!WebInspector.Script} script
99      */
100     addScript: function(script)
101     {
102         if (script.isAnonymousScript())
103             return;
104         this._debuggerWorkspaceBinding.pushSourceMapping(script, this);
106         var uiSourceCode = this._workspaceUISourceCodeForScript(script);
107         if (!uiSourceCode)
108             return;
110         this._bindUISourceCodeToScripts(uiSourceCode, [script]);
111     },
113     /**
114      * @override
115      * @return {boolean}
116      */
117     isIdentity: function()
118     {
119         return true;
120     },
122     /**
123      * @override
124      * @param {!WebInspector.UISourceCode} uiSourceCode
125      * @param {number} lineNumber
126      * @return {boolean}
127      */
128     uiLineHasMapping: function(uiSourceCode, lineNumber)
129     {
130         return true;
131     },
133     /**
134      * @param {!WebInspector.UISourceCode} uiSourceCode
135      * @return {?WebInspector.ResourceScriptFile}
136      */
137     scriptFile: function(uiSourceCode)
138     {
139         return this._uiSourceCodeToScriptFile.get(uiSourceCode) || null;
140     },
142     /**
143      * @param {!WebInspector.UISourceCode} uiSourceCode
144      * @param {?WebInspector.ResourceScriptFile} scriptFile
145      */
146     _setScriptFile: function(uiSourceCode, scriptFile)
147     {
148         if (scriptFile)
149             this._uiSourceCodeToScriptFile.set(uiSourceCode, scriptFile);
150         else
151             this._uiSourceCodeToScriptFile.remove(uiSourceCode);
152     },
154     /**
155      * @param {!WebInspector.Event} event
156      */
157     _uiSourceCodeAdded: function(event)
158     {
159         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
160         if (!this._networkMapping.networkURL(uiSourceCode))
161             return;
162         if (uiSourceCode.project().isServiceProject())
163             return;
165         var scripts = this._scriptsForUISourceCode(uiSourceCode);
166         if (!scripts.length)
167             return;
169         this._bindUISourceCodeToScripts(uiSourceCode, scripts);
170     },
172     /**
173      * @param {!WebInspector.Event} event
174      */
175     _uiSourceCodeRemoved: function(event)
176     {
177         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
178         if (!this._networkMapping.networkURL(uiSourceCode))
179             return;
180         if (uiSourceCode.project().isServiceProject())
181             return;
183         this._unbindUISourceCode(uiSourceCode);
184     },
186     /**
187      * @param {!WebInspector.UISourceCode} uiSourceCode
188      */
189     _hasMergedToVM: function(uiSourceCode)
190     {
191         var scripts = this._scriptsForUISourceCode(uiSourceCode);
192         if (!scripts.length)
193             return;
194         for (var i = 0; i < scripts.length; ++i)
195             this._debuggerWorkspaceBinding.updateLocations(scripts[i]);
196     },
198     /**
199      * @param {!WebInspector.UISourceCode} uiSourceCode
200      */
201     _hasDivergedFromVM: function(uiSourceCode)
202     {
203         var scripts = this._scriptsForUISourceCode(uiSourceCode);
204         if (!scripts.length)
205             return;
206         for (var i = 0; i < scripts.length; ++i)
207             this._debuggerWorkspaceBinding.updateLocations(scripts[i]);
208     },
210     /**
211      * @param {!WebInspector.Script} script
212      * @return {?WebInspector.UISourceCode}
213      */
214     _workspaceUISourceCodeForScript: function(script)
215     {
216         if (script.isAnonymousScript())
217             return null;
218         return this._networkMapping.uiSourceCodeForURL(script.sourceURL, this._target);
219     },
221     /**
222      * @param {!WebInspector.UISourceCode} uiSourceCode
223      * @return {!Array.<!WebInspector.Script>}
224      */
225     _scriptsForUISourceCode: function(uiSourceCode)
226     {
227         var target = WebInspector.NetworkProject.targetForUISourceCode(uiSourceCode);
228         if (target && target !== this._debuggerModel.target())
229             return [];
230         if (!this._networkMapping.networkURL(uiSourceCode))
231             return [];
232         return this._debuggerModel.scriptsForSourceURL(this._networkMapping.networkURL(uiSourceCode));
233     },
235     /**
236      * @param {!WebInspector.UISourceCode} uiSourceCode
237      * @param {!Array.<!WebInspector.Script>} scripts
238      */
239     _bindUISourceCodeToScripts: function(uiSourceCode, scripts)
240     {
241         console.assert(scripts.length);
242         // Due to different listeners order, a script file could be created just before uiSourceCode
243         // for the corresponding script was created. Check that we don't create scriptFile twice.
244         var boundScriptFile = this.scriptFile(uiSourceCode);
245         if (boundScriptFile && boundScriptFile.hasScripts(scripts))
246             return;
248         var scriptFile = new WebInspector.ResourceScriptFile(this, uiSourceCode, scripts);
249         this._setScriptFile(uiSourceCode, scriptFile);
250         for (var i = 0; i < scripts.length; ++i)
251             this._debuggerWorkspaceBinding.updateLocations(scripts[i]);
252         this._debuggerWorkspaceBinding.setSourceMapping(this._target, uiSourceCode, this);
253         this._boundURLs.add(this._networkMapping.networkURL(uiSourceCode));
254     },
256     /**
257      * @param {!WebInspector.UISourceCode} uiSourceCode
258      */
259     _unbindUISourceCode: function(uiSourceCode)
260     {
261         var scriptFile = this.scriptFile(uiSourceCode);
262         if (scriptFile) {
263             scriptFile.dispose();
264             this._setScriptFile(uiSourceCode, null);
265         }
266         this._debuggerWorkspaceBinding.setSourceMapping(this._target, uiSourceCode, null);
267     },
269     _debuggerReset: function()
270     {
271         var boundURLs = this._boundURLs.valuesArray();
272         for (var i = 0; i < boundURLs.length; ++i)
273         {
274             var uiSourceCode = this._networkMapping.uiSourceCodeForURL(boundURLs[i], this._target);
275             if (!uiSourceCode)
276                 continue;
277             this._unbindUISourceCode(uiSourceCode);
278         }
279         this._boundURLs.clear();
280     },
282     dispose: function()
283     {
284         this._debuggerReset();
285         this._workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
286         this._workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
287     }
292  * @constructor
293  * @extends {WebInspector.Object}
294  * @param {!WebInspector.ResourceScriptMapping} resourceScriptMapping
295  * @param {!WebInspector.UISourceCode} uiSourceCode
296  * @param {!Array.<!WebInspector.Script>} scripts
297  */
298 WebInspector.ResourceScriptFile = function(resourceScriptMapping, uiSourceCode, scripts)
300     console.assert(scripts.length);
302     this._resourceScriptMapping = resourceScriptMapping;
303     this._uiSourceCode = uiSourceCode;
305     if (this._uiSourceCode.contentType() === WebInspector.resourceTypes.Script)
306         this._script = scripts[0];
308     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
311 WebInspector.ResourceScriptFile.Events = {
312     DidMergeToVM: "DidMergeToVM",
313     DidDivergeFromVM: "DidDivergeFromVM",
316 WebInspector.ResourceScriptFile.prototype = {
317     /**
318      * @param {!Array.<!WebInspector.Script>} scripts
319      * @return {boolean}
320      */
321     hasScripts: function(scripts)
322     {
323         return this._script && this._script === scripts[0];
324     },
326     /**
327      * @param {function(?string,!DebuggerAgent.SetScriptSourceError=,!WebInspector.Script=)=} callback
328      */
329     commitLiveEdit: function(callback)
330     {
331         /**
332          * @param {?string} error
333          * @param {!DebuggerAgent.SetScriptSourceError=} errorData
334          * @this {WebInspector.ResourceScriptFile}
335          */
336         function innerCallback(error, errorData)
337         {
338             if (!error)
339                 this._scriptSource = source;
340             this._update();
341             if (callback)
342                 callback(error, errorData, this._script);
343         }
344         if (!this._script)
345             return;
346         var debuggerModel = this._resourceScriptMapping._debuggerModel;
347         var source = this._uiSourceCode.workingCopy();
348         debuggerModel.setScriptSource(this._script.scriptId, source, innerCallback.bind(this));
349     },
351     /**
352      * @return {boolean}
353      */
354     _isDiverged: function()
355     {
356         if (this._uiSourceCode.isDirty())
357             return true;
358         if (!this._script)
359             return false;
360         if (typeof this._scriptSource === "undefined")
361             return false;
362         if (!this._uiSourceCode.workingCopy().startsWith(this._scriptSource))
363             return true;
364         var suffix = this._uiSourceCode.workingCopy().substr(this._scriptSource.length);
365         return !!suffix.length && !suffix.match(WebInspector.Script.sourceURLRegex);
366     },
368     /**
369      * @param {!WebInspector.Event} event
370      */
371     _workingCopyChanged: function(event)
372     {
373         this._update();
374     },
376     _update: function()
377     {
378         if (this._isDiverged() && !this._hasDivergedFromVM)
379             this._divergeFromVM();
380         else if (!this._isDiverged() && this._hasDivergedFromVM)
381             this._mergeToVM();
382     },
384     _divergeFromVM: function()
385     {
386         this._isDivergingFromVM = true;
387         this._resourceScriptMapping._hasDivergedFromVM(this._uiSourceCode);
388         delete this._isDivergingFromVM;
389         this._hasDivergedFromVM = true;
390         this.dispatchEventToListeners(WebInspector.ResourceScriptFile.Events.DidDivergeFromVM, this._uiSourceCode);
391     },
393     _mergeToVM: function()
394     {
395         delete this._hasDivergedFromVM;
396         this._isMergingToVM = true;
397         this._resourceScriptMapping._hasMergedToVM(this._uiSourceCode);
398         delete this._isMergingToVM;
399         this.dispatchEventToListeners(WebInspector.ResourceScriptFile.Events.DidMergeToVM, this._uiSourceCode);
400     },
402     /**
403      * @return {boolean}
404      */
405     hasDivergedFromVM: function()
406     {
407         return this._hasDivergedFromVM;
408     },
410     /**
411      * @return {boolean}
412      */
413     isDivergingFromVM: function()
414     {
415         return this._isDivergingFromVM;
416     },
418     /**
419      * @return {boolean}
420      */
421     isMergingToVM: function()
422     {
423         return this._isMergingToVM;
424     },
426     checkMapping: function()
427     {
428         if (!this._script)
429             return;
430         if (typeof this._scriptSource !== "undefined")
431             return;
432         this._script.requestContent(callback.bind(this));
434         /**
435          * @param {?string} source
436          * @this {WebInspector.ResourceScriptFile}
437          */
438         function callback(source)
439         {
440             this._scriptSource = source;
441             this._update();
442         }
443     },
445     /**
446      * @return {?WebInspector.Target}
447      */
448     target: function()
449     {
450         if (!this._script)
451             return null;
452         return this._script.target();
453     },
455     dispose: function()
456     {
457         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
458     },
460     /**
461      * @param {string} sourceMapURL
462      */
463     addSourceMapURL: function(sourceMapURL)
464     {
465         if (!this._script)
466             return;
467         this._script.addSourceMapURL(sourceMapURL);
468     },
470     __proto__: WebInspector.Object.prototype