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