Home | History | Annotate | Download | only in bindings
      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  */
     30 
     31 /**
     32  * @constructor
     33  * @implements {WebInspector.DebuggerSourceMapping}
     34  * @param {!WebInspector.DebuggerModel} debuggerModel
     35  * @param {!WebInspector.Workspace} workspace
     36  * @param {!WebInspector.NetworkWorkspaceBinding} networkWorkspaceBinding
     37  * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding
     38  */
     39 WebInspector.CompilerScriptMapping = function(debuggerModel, workspace, networkWorkspaceBinding, debuggerWorkspaceBinding)
     40 {
     41     this._target = debuggerModel.target();
     42     this._debuggerModel = debuggerModel;
     43     this._workspace = workspace;
     44     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
     45     this._networkWorkspaceBinding = networkWorkspaceBinding;
     46     this._debuggerWorkspaceBinding = debuggerWorkspaceBinding;
     47 
     48     /** @type {!Object.<string, !WebInspector.SourceMap>} */
     49     this._sourceMapForSourceMapURL = {};
     50     /** @type {!Object.<string, !Array.<function(?WebInspector.SourceMap)>>} */
     51     this._pendingSourceMapLoadingCallbacks = {};
     52     /** @type {!Object.<string, !WebInspector.SourceMap>} */
     53     this._sourceMapForScriptId = {};
     54     /** @type {!Map.<!WebInspector.SourceMap, !WebInspector.Script>} */
     55     this._scriptForSourceMap = new Map();
     56     /** @type {!StringMap.<!WebInspector.SourceMap>} */
     57     this._sourceMapForURL = new StringMap();
     58     debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
     59 }
     60 
     61 WebInspector.CompilerScriptMapping.prototype = {
     62     /**
     63      * @param {!WebInspector.DebuggerModel.Location} rawLocation
     64      * @return {?WebInspector.UILocation}
     65      */
     66     rawLocationToUILocation: function(rawLocation)
     67     {
     68         var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (rawLocation);
     69         var sourceMap = this._sourceMapForScriptId[debuggerModelLocation.scriptId];
     70         if (!sourceMap)
     71             return null;
     72         var lineNumber = debuggerModelLocation.lineNumber;
     73         var columnNumber = debuggerModelLocation.columnNumber || 0;
     74         var entry = sourceMap.findEntry(lineNumber, columnNumber);
     75         if (!entry || entry.length === 2)
     76             return null;
     77         var url = /** @type {string} */ (entry[2]);
     78         var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
     79         if (!uiSourceCode)
     80             return null;
     81         return uiSourceCode.uiLocation(/** @type {number} */ (entry[3]), /** @type {number} */ (entry[4]));
     82     },
     83 
     84     /**
     85      * @param {!WebInspector.UISourceCode} uiSourceCode
     86      * @param {number} lineNumber
     87      * @param {number} columnNumber
     88      * @return {?WebInspector.DebuggerModel.Location}
     89      */
     90     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
     91     {
     92         if (!uiSourceCode.url)
     93             return null;
     94         var sourceMap = this._sourceMapForURL.get(uiSourceCode.url);
     95         if (!sourceMap)
     96             return null;
     97         var script = /** @type {!WebInspector.Script} */ (this._scriptForSourceMap.get(sourceMap));
     98         console.assert(script);
     99         var mappingSearchLinesCount = 5;
    100         // We do not require precise (breakpoint) location but limit the number of lines to search or mapping.
    101         var entry = sourceMap.findEntryReversed(uiSourceCode.url, lineNumber, mappingSearchLinesCount);
    102         if (!entry)
    103             return null;
    104         return this._debuggerModel.createRawLocation(script, /** @type {number} */ (entry[0]), /** @type {number} */ (entry[1]));
    105     },
    106 
    107     /**
    108      * @param {!WebInspector.Script} script
    109      */
    110     addScript: function(script)
    111     {
    112         this._debuggerWorkspaceBinding.pushSourceMapping(script, this);
    113         script.addEventListener(WebInspector.Script.Events.SourceMapURLAdded, this._sourceMapURLAdded.bind(this));
    114         this._processScript(script);
    115     },
    116 
    117     /**
    118      * @param {!WebInspector.Event} event
    119      */
    120     _sourceMapURLAdded: function(event)
    121     {
    122         var script = /** @type {!WebInspector.Script} */ (event.target);
    123         this._processScript(script);
    124     },
    125 
    126     /**
    127      * @param {!WebInspector.Script} script
    128      */
    129     _processScript: function(script)
    130     {
    131         this.loadSourceMapForScript(script, sourceMapLoaded.bind(this));
    132 
    133         /**
    134          * @param {?WebInspector.SourceMap} sourceMap
    135          * @this {WebInspector.CompilerScriptMapping}
    136          */
    137         function sourceMapLoaded(sourceMap)
    138         {
    139             if (!sourceMap)
    140                 return;
    141 
    142             if (this._scriptForSourceMap.get(sourceMap)) {
    143                 this._sourceMapForScriptId[script.scriptId] = sourceMap;
    144                 this._debuggerWorkspaceBinding.updateLocations(script);
    145                 return;
    146             }
    147 
    148             this._sourceMapForScriptId[script.scriptId] = sourceMap;
    149             this._scriptForSourceMap.set(sourceMap, script);
    150 
    151             var sourceURLs = sourceMap.sources();
    152             var missingSources = [];
    153             for (var i = 0; i < sourceURLs.length; ++i) {
    154                 var sourceURL = sourceURLs[i];
    155                 if (this._sourceMapForURL.get(sourceURL))
    156                     continue;
    157                 this._sourceMapForURL.set(sourceURL, sourceMap);
    158                 if (!this._workspace.hasMappingForURL(sourceURL) && !this._workspace.uiSourceCodeForURL(sourceURL)) {
    159                     var contentProvider = sourceMap.sourceContentProvider(sourceURL, WebInspector.resourceTypes.Script);
    160                     this._networkWorkspaceBinding.addFileForURL(sourceURL, contentProvider, script.isContentScript());
    161                 }
    162                 var uiSourceCode = this._workspace.uiSourceCodeForURL(sourceURL);
    163                 if (uiSourceCode) {
    164                     this._bindUISourceCode(uiSourceCode);
    165                 } else {
    166                     if (missingSources.length < 3)
    167                         missingSources.push(sourceURL);
    168                     else if (missingSources.peekLast() !== "\u2026")
    169                         missingSources.push("\u2026");
    170                 }
    171             }
    172             if (missingSources.length) {
    173                 WebInspector.console.warn(
    174                     WebInspector.UIString("Source map %s points to the files missing from the workspace: [%s]",
    175                                           sourceMap.url(), missingSources.join(", ")));
    176             }
    177 
    178             this._debuggerWorkspaceBinding.updateLocations(script);
    179         }
    180     },
    181 
    182     /**
    183      * @return {boolean}
    184      */
    185     isIdentity: function()
    186     {
    187         return false;
    188     },
    189 
    190     /**
    191      * @param {!WebInspector.UISourceCode} uiSourceCode
    192      * @param {number} lineNumber
    193      * @return {boolean}
    194      */
    195     uiLineHasMapping: function(uiSourceCode, lineNumber)
    196     {
    197         if (!uiSourceCode.url)
    198             return true;
    199         var sourceMap = this._sourceMapForURL.get(uiSourceCode.url);
    200         if (!sourceMap)
    201             return true;
    202         return !!sourceMap.findEntryReversed(uiSourceCode.url, lineNumber, 0);
    203     },
    204 
    205     /**
    206      * @param {!WebInspector.UISourceCode} uiSourceCode
    207      */
    208     _bindUISourceCode: function(uiSourceCode)
    209     {
    210         this._debuggerWorkspaceBinding.setSourceMapping(this._target, uiSourceCode, this);
    211     },
    212 
    213     /**
    214      * @param {!WebInspector.UISourceCode} uiSourceCode
    215      */
    216     _unbindUISourceCode: function(uiSourceCode)
    217     {
    218         this._debuggerWorkspaceBinding.setSourceMapping(this._target, uiSourceCode, null);
    219     },
    220 
    221     /**
    222      * @param {!WebInspector.Event} event
    223      */
    224     _uiSourceCodeAddedToWorkspace: function(event)
    225     {
    226         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    227         if (!uiSourceCode.url || !this._sourceMapForURL.get(uiSourceCode.url))
    228             return;
    229         this._bindUISourceCode(uiSourceCode);
    230     },
    231 
    232     /**
    233      * @param {!WebInspector.Script} script
    234      * @param {function(?WebInspector.SourceMap)} callback
    235      */
    236     loadSourceMapForScript: function(script, callback)
    237     {
    238         // script.sourceURL can be a random string, but is generally an absolute path -> complete it to inspected page url for
    239         // relative links.
    240         if (!script.sourceMapURL) {
    241             callback(null);
    242             return;
    243         }
    244         var scriptURL = WebInspector.ParsedURL.completeURL(script.target().resourceTreeModel.inspectedPageURL(), script.sourceURL);
    245         if (!scriptURL) {
    246             callback(null);
    247             return;
    248         }
    249         var sourceMapURL = WebInspector.ParsedURL.completeURL(scriptURL, script.sourceMapURL);
    250         if (!sourceMapURL) {
    251             callback(null);
    252             return;
    253         }
    254 
    255         var sourceMap = this._sourceMapForSourceMapURL[sourceMapURL];
    256         if (sourceMap) {
    257             callback(sourceMap);
    258             return;
    259         }
    260 
    261         var pendingCallbacks = this._pendingSourceMapLoadingCallbacks[sourceMapURL];
    262         if (pendingCallbacks) {
    263             pendingCallbacks.push(callback);
    264             return;
    265         }
    266 
    267         pendingCallbacks = [callback];
    268         this._pendingSourceMapLoadingCallbacks[sourceMapURL] = pendingCallbacks;
    269 
    270         WebInspector.SourceMap.load(sourceMapURL, scriptURL, sourceMapLoaded.bind(this));
    271 
    272         /**
    273          * @param {?WebInspector.SourceMap} sourceMap
    274          * @this {WebInspector.CompilerScriptMapping}
    275          */
    276         function sourceMapLoaded(sourceMap)
    277         {
    278             var url = /** @type {string} */ (sourceMapURL);
    279             var callbacks = this._pendingSourceMapLoadingCallbacks[url];
    280             delete this._pendingSourceMapLoadingCallbacks[url];
    281             if (!callbacks)
    282                 return;
    283             if (sourceMap)
    284                 this._sourceMapForSourceMapURL[url] = sourceMap;
    285             for (var i = 0; i < callbacks.length; ++i)
    286                 callbacks[i](sourceMap);
    287         }
    288     },
    289 
    290     _debuggerReset: function()
    291     {
    292         /**
    293          * @param {string} sourceURL
    294          * @this {WebInspector.CompilerScriptMapping}
    295          */
    296         function unbindUISourceCodeForURL(sourceURL)
    297         {
    298             var uiSourceCode = this._workspace.uiSourceCodeForURL(sourceURL);
    299             if (!uiSourceCode)
    300                 return;
    301             this._unbindUISourceCode(uiSourceCode);
    302         }
    303 
    304         this._sourceMapForURL.keys().forEach(unbindUISourceCodeForURL.bind(this));
    305 
    306         this._sourceMapForSourceMapURL = {};
    307         this._pendingSourceMapLoadingCallbacks = {};
    308         this._sourceMapForScriptId = {};
    309         this._scriptForSourceMap.clear();
    310         this._sourceMapForURL.clear();
    311     },
    312 
    313     dispose: function()
    314     {
    315         this._workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
    316     }
    317 }
    318