Home | History | Annotate | Download | only in front_end
      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.ScriptSourceMapping}
     34  * @param {WebInspector.Workspace} workspace
     35  */
     36 WebInspector.ResourceScriptMapping = function(workspace)
     37 {
     38     this._workspace = workspace;
     39     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
     40 
     41     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
     42     this._initialize();
     43 }
     44 
     45 WebInspector.ResourceScriptMapping.prototype = {
     46     /**
     47      * @param {WebInspector.RawLocation} rawLocation
     48      * @return {WebInspector.UILocation}
     49      */
     50     rawLocationToUILocation: function(rawLocation)
     51     {
     52         var debuggerModelLocation = /** @type {WebInspector.DebuggerModel.Location} */ (rawLocation);
     53         var script = WebInspector.debuggerModel.scriptForId(debuggerModelLocation.scriptId);
     54         var uiSourceCode = this._workspaceUISourceCodeForScript(script);
     55         if (!uiSourceCode)
     56             return null;
     57         var scriptFile = uiSourceCode.scriptFile();
     58         if (scriptFile && ((scriptFile.hasDivergedFromVM() && !scriptFile.isMergingToVM()) || scriptFile.isDivergingFromVM()))
     59             return null;
     60         return new WebInspector.UILocation(uiSourceCode, debuggerModelLocation.lineNumber, debuggerModelLocation.columnNumber || 0);
     61     },
     62 
     63     /**
     64      * @param {WebInspector.UISourceCode} uiSourceCode
     65      * @param {number} lineNumber
     66      * @param {number} columnNumber
     67      * @return {WebInspector.DebuggerModel.Location}
     68      */
     69     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
     70     {
     71         var scripts = this._scriptsForUISourceCode(uiSourceCode);
     72         console.assert(scripts.length);
     73         return WebInspector.debuggerModel.createRawLocation(scripts[0], lineNumber, columnNumber);
     74     },
     75 
     76     /**
     77      * @return {boolean}
     78      */
     79     isIdentity: function()
     80     {
     81         return true;
     82     },
     83 
     84     /**
     85      * @param {WebInspector.Script} script
     86      */
     87     addScript: function(script)
     88     {
     89         if (script.isAnonymousScript())
     90             return;
     91         script.pushSourceMapping(this);
     92 
     93         var scriptsForSourceURL = script.isInlineScript() ? this._inlineScriptsForSourceURL : this._nonInlineScriptsForSourceURL;
     94         scriptsForSourceURL[script.sourceURL] = scriptsForSourceURL[script.sourceURL] || [];
     95         scriptsForSourceURL[script.sourceURL].push(script);
     96 
     97         var uiSourceCode = this._workspaceUISourceCodeForScript(script);
     98         if (!uiSourceCode)
     99             return;
    100 
    101         this._bindUISourceCodeToScripts(uiSourceCode, [script]);
    102     },
    103 
    104     _uiSourceCodeAddedToWorkspace: function(event)
    105     {
    106         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data);
    107         if (!uiSourceCode.url)
    108             return;
    109 
    110         var scripts = this._scriptsForUISourceCode(uiSourceCode);
    111         if (!scripts.length)
    112             return;
    113 
    114         this._bindUISourceCodeToScripts(uiSourceCode, scripts);
    115     },
    116 
    117     /**
    118      * @param {WebInspector.UISourceCode} uiSourceCode
    119      */
    120     _hasMergedToVM: function(uiSourceCode)
    121     {
    122         var scripts = this._scriptsForUISourceCode(uiSourceCode);
    123         if (!scripts.length)
    124             return;
    125         for (var i = 0; i < scripts.length; ++i)
    126             scripts[i].updateLocations();
    127     },
    128 
    129     /**
    130      * @param {WebInspector.UISourceCode} uiSourceCode
    131      */
    132     _hasDivergedFromVM: function(uiSourceCode)
    133     {
    134         var scripts = this._scriptsForUISourceCode(uiSourceCode);
    135         if (!scripts.length)
    136             return;
    137         for (var i = 0; i < scripts.length; ++i)
    138             scripts[i].updateLocations();
    139     },
    140 
    141     /**
    142      * @param {WebInspector.Script} script
    143      * @return {WebInspector.UISourceCode}
    144      */
    145     _workspaceUISourceCodeForScript: function(script)
    146     {
    147         if (script.isAnonymousScript())
    148             return null;
    149         return this._workspace.uiSourceCodeForURL(script.sourceURL);
    150     },
    151 
    152     /**
    153      * @param {WebInspector.UISourceCode} uiSourceCode
    154      * @return {Array.<WebInspector.Script>}
    155      */
    156     _scriptsForUISourceCode: function(uiSourceCode)
    157     {
    158         var isInlineScript;
    159         switch (uiSourceCode.contentType()) {
    160         case WebInspector.resourceTypes.Document:
    161             isInlineScript = true;
    162             break;
    163         case WebInspector.resourceTypes.Script:
    164             isInlineScript = false;
    165             break;
    166         default:
    167             return [];
    168         }
    169         if (!uiSourceCode.url)
    170             return [];
    171         var scriptsForSourceURL = isInlineScript ? this._inlineScriptsForSourceURL : this._nonInlineScriptsForSourceURL;
    172         return scriptsForSourceURL[uiSourceCode.url] || [];
    173     },
    174 
    175     /**
    176      * @param {WebInspector.UISourceCode} uiSourceCode
    177      * @param {Array.<WebInspector.Script>} scripts
    178      */
    179     _bindUISourceCodeToScripts: function(uiSourceCode, scripts)
    180     {
    181         console.assert(scripts.length);
    182         var scriptFile = new WebInspector.ResourceScriptFile(this, uiSourceCode, scripts);
    183         uiSourceCode.setScriptFile(scriptFile);
    184         for (var i = 0; i < scripts.length; ++i)
    185             scripts[i].updateLocations();
    186         uiSourceCode.setSourceMapping(this);
    187     },
    188 
    189     /**
    190      * @param {WebInspector.UISourceCode} uiSourceCode
    191      * @param {Array.<WebInspector.Script>} scripts
    192      */
    193     _unbindUISourceCodeFromScripts: function(uiSourceCode, scripts)
    194     {
    195         console.assert(scripts.length);
    196         var scriptFile = /** @type {WebInspector.ResourceScriptFile} */ (uiSourceCode.scriptFile());
    197         scriptFile.dispose();
    198         uiSourceCode.setScriptFile(null);
    199         uiSourceCode.setSourceMapping(null);
    200     },
    201 
    202     _initialize: function()
    203     {
    204         /** @type {!Object.<string, !Array.<!WebInspector.Script>>} */
    205         this._inlineScriptsForSourceURL = {};
    206         /** @type {!Object.<string, !Array.<!WebInspector.Script>>} */
    207         this._nonInlineScriptsForSourceURL = {};
    208     },
    209 
    210     _debuggerReset: function()
    211     {
    212         /**
    213          * @param {!Object.<string, !Array.<!WebInspector.Script>>} scriptsForSourceURL
    214          */
    215         function unbindUISourceCodes(scriptsForSourceURL)
    216         {
    217             for (var sourceURL in scriptsForSourceURL) {
    218                 var scripts = scriptsForSourceURL[sourceURL];
    219                 if (!scripts.length)
    220                     continue;
    221                 var uiSourceCode = this._workspaceUISourceCodeForScript(scripts[0]);
    222                 if (!uiSourceCode)
    223                     continue;
    224                 this._unbindUISourceCodeFromScripts(uiSourceCode, scripts);
    225             }
    226         }
    227 
    228         unbindUISourceCodes.call(this, this._inlineScriptsForSourceURL);
    229         unbindUISourceCodes.call(this, this._nonInlineScriptsForSourceURL);
    230         this._initialize();
    231     },
    232 }
    233 
    234 /**
    235  * @interface
    236  * @extends {WebInspector.EventTarget}
    237  */
    238 WebInspector.ScriptFile = function()
    239 {
    240 }
    241 
    242 WebInspector.ScriptFile.Events = {
    243     DidMergeToVM: "DidMergeToVM",
    244     DidDivergeFromVM: "DidDivergeFromVM",
    245 }
    246 
    247 WebInspector.ScriptFile.prototype = {
    248     /**
    249      * @return {boolean}
    250      */
    251     hasDivergedFromVM: function() { return false; },
    252 
    253     /**
    254      * @return {boolean}
    255      */
    256     isDivergingFromVM: function() { return false; },
    257 
    258     /**
    259      * @return {boolean}
    260      */
    261     isMergingToVM: function() { return false; },
    262 
    263     checkMapping: function() { },
    264 }
    265 
    266 /**
    267  * @constructor
    268  * @implements {WebInspector.ScriptFile}
    269  * @extends {WebInspector.Object}
    270  * @param {WebInspector.ResourceScriptMapping} resourceScriptMapping
    271  * @param {WebInspector.UISourceCode} uiSourceCode
    272  */
    273 WebInspector.ResourceScriptFile = function(resourceScriptMapping, uiSourceCode, scripts)
    274 {
    275     console.assert(scripts.length);
    276 
    277     WebInspector.ScriptFile.call(this);
    278     this._resourceScriptMapping = resourceScriptMapping;
    279     this._uiSourceCode = uiSourceCode;
    280 
    281     if (this._uiSourceCode.contentType() === WebInspector.resourceTypes.Script)
    282         this._script = scripts[0];
    283 
    284     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
    285     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
    286     this._update();
    287 }
    288 
    289 WebInspector.ResourceScriptFile.prototype = {
    290     _workingCopyCommitted: function(event)
    291     {
    292         /**
    293          * @param {?string} error
    294          * @param {DebuggerAgent.SetScriptSourceError=} errorData
    295          */
    296         function innerCallback(error, errorData)
    297         {
    298             if (error) {
    299                 this._update();
    300                 WebInspector.LiveEditSupport.logDetailedError(error, errorData, this._script);
    301                 return;
    302             }
    303 
    304             this._scriptSource = source;
    305             this._update();
    306         }
    307         if (!this._script)
    308             return;
    309         var source = this._uiSourceCode.workingCopy();
    310         if (this._script.hasSourceURL && !this._sourceEndsWithSourceURL(source))
    311             source += "\n //# sourceURL=" + this._script.sourceURL;
    312         WebInspector.debuggerModel.setScriptSource(this._script.scriptId, source, innerCallback.bind(this));
    313     },
    314 
    315     /**
    316      * @return {boolean}
    317      */
    318     _isDiverged: function()
    319     {
    320         if (this._uiSourceCode.formatted())
    321             return false;
    322         if (this._uiSourceCode.isDirty())
    323             return true;
    324         if (!this._script)
    325             return false;
    326         if (typeof this._scriptSource === "undefined")
    327             return false;
    328         return !this._sourceMatchesScriptSource(this._uiSourceCode.workingCopy(), this._scriptSource);
    329     },
    330 
    331     /**
    332      * @param {string} source
    333      * @param {string} scriptSource
    334      * @return {boolean}
    335      */
    336     _sourceMatchesScriptSource: function(source, scriptSource)
    337     {
    338         if (!scriptSource.startsWith(source))
    339             return false;
    340         var scriptSourceTail = scriptSource.substr(source.length).trim();
    341         return !scriptSourceTail || !!scriptSourceTail.match(/^\/\/[@#]\ssourceURL=\s*(\S*?)\s*$/m);
    342     },
    343 
    344     /**
    345      * @param {string} source
    346      * @return {boolean}
    347      */
    348     _sourceEndsWithSourceURL: function(source)
    349     {
    350         return !!source.match(/\/\/[@#]\ssourceURL=\s*(\S*?)\s*$/m);
    351     },
    352 
    353     /**
    354      * @param {WebInspector.Event} event
    355      */
    356     _workingCopyChanged: function(event)
    357     {
    358         this._update();
    359     },
    360 
    361     _update: function()
    362     {
    363         if (this._isDiverged() && !this._hasDivergedFromVM)
    364             this._divergeFromVM();
    365         else if (!this._isDiverged() && this._hasDivergedFromVM)
    366             this._mergeToVM();
    367     },
    368 
    369     _divergeFromVM: function()
    370     {
    371         this._isDivergingFromVM = true;
    372         this._resourceScriptMapping._hasDivergedFromVM(this._uiSourceCode);
    373         delete this._isDivergingFromVM;
    374         this._hasDivergedFromVM = true;
    375         this.dispatchEventToListeners(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._uiSourceCode);
    376     },
    377 
    378     _mergeToVM: function()
    379     {
    380         delete this._hasDivergedFromVM;
    381         this._isMergingToVM = true;
    382         this._resourceScriptMapping._hasMergedToVM(this._uiSourceCode);
    383         delete this._isMergingToVM;
    384         this.dispatchEventToListeners(WebInspector.ScriptFile.Events.DidMergeToVM, this._uiSourceCode);
    385     },
    386 
    387     /**
    388      * @return {boolean}
    389      */
    390     hasDivergedFromVM: function()
    391     {
    392         return this._hasDivergedFromVM;
    393     },
    394 
    395     /**
    396      * @return {boolean}
    397      */
    398     isDivergingFromVM: function()
    399     {
    400         return this._isDivergingFromVM;
    401     },
    402 
    403     /**
    404      * @return {boolean}
    405      */
    406     isMergingToVM: function()
    407     {
    408         return this._isMergingToVM;
    409     },
    410 
    411     checkMapping: function()
    412     {
    413         if (!this._script)
    414             return;
    415         if (typeof this._scriptSource !== "undefined")
    416             return;
    417         this._script.requestContent(callback.bind(this));
    418 
    419         /**
    420          * @param {?string} source
    421          * @param {boolean} encoded
    422          * @param {string} contentType
    423          */
    424         function callback(source, encoded, contentType)
    425         {
    426             this._scriptSource = source;
    427             this._update();
    428         }
    429     },
    430 
    431     dispose: function()
    432     {
    433         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
    434         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
    435     },
    436 
    437     __proto__: WebInspector.Object.prototype
    438 }
    439