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