Home | History | Annotate | Download | only in sdk
      1 /*
      2  * Copyright (C) 2008 Apple 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
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 /**
     27  * @constructor
     28  * @extends {WebInspector.TargetAwareObject}
     29  * @implements {WebInspector.ContentProvider}
     30  * @param {!WebInspector.Target} target
     31  * @param {string} scriptId
     32  * @param {string} sourceURL
     33  * @param {number} startLine
     34  * @param {number} startColumn
     35  * @param {number} endLine
     36  * @param {number} endColumn
     37  * @param {boolean} isContentScript
     38  * @param {string=} sourceMapURL
     39  * @param {boolean=} hasSourceURL
     40  */
     41 WebInspector.Script = function(target, scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL)
     42 {
     43     WebInspector.TargetAwareObject.call(this, target);
     44     this.scriptId = scriptId;
     45     this.sourceURL = sourceURL;
     46     this.lineOffset = startLine;
     47     this.columnOffset = startColumn;
     48     this.endLine = endLine;
     49     this.endColumn = endColumn;
     50     this._isContentScript = isContentScript;
     51     this.sourceMapURL = sourceMapURL;
     52     this.hasSourceURL = hasSourceURL;
     53     /** @type {!Set.<!WebInspector.Script.Location>} */
     54     this._locations = new Set();
     55     /** @type {!Array.<!WebInspector.SourceMapping>} */
     56     this._sourceMappings = [];
     57 }
     58 
     59 WebInspector.Script.Events = {
     60     ScriptEdited: "ScriptEdited",
     61 }
     62 
     63 WebInspector.Script.snippetSourceURLPrefix = "snippets:///";
     64 
     65 WebInspector.Script.sourceURLRegex = /\n[\040\t]*\/\/[@#]\ssourceURL=\s*(\S*?)\s*$/mg;
     66 
     67 /**
     68  * @param {string} source
     69  * @return {string}
     70  */
     71 WebInspector.Script._trimSourceURLComment = function(source)
     72 {
     73     return source.replace(WebInspector.Script.sourceURLRegex, "");
     74 },
     75 
     76 
     77 WebInspector.Script.prototype = {
     78     /**
     79      * @return {boolean}
     80      */
     81     isContentScript: function()
     82     {
     83         return this._isContentScript;
     84     },
     85 
     86     /**
     87      * @return {string}
     88      */
     89     contentURL: function()
     90     {
     91         return this.sourceURL;
     92     },
     93 
     94     /**
     95      * @return {!WebInspector.ResourceType}
     96      */
     97     contentType: function()
     98     {
     99         return WebInspector.resourceTypes.Script;
    100     },
    101 
    102     /**
    103      * @param {function(?string)} callback
    104      */
    105     requestContent: function(callback)
    106     {
    107         if (this._source) {
    108             callback(this._source);
    109             return;
    110         }
    111 
    112         /**
    113          * @this {WebInspector.Script}
    114          * @param {?Protocol.Error} error
    115          * @param {string} source
    116          */
    117         function didGetScriptSource(error, source)
    118         {
    119             this._source = WebInspector.Script._trimSourceURLComment(error ? "" : source);
    120             callback(this._source);
    121         }
    122         if (this.scriptId) {
    123             // Script failed to parse.
    124             this.target().debuggerAgent().getScriptSource(this.scriptId, didGetScriptSource.bind(this));
    125         } else
    126             callback("");
    127     },
    128 
    129     /**
    130      * @param {string} query
    131      * @param {boolean} caseSensitive
    132      * @param {boolean} isRegex
    133      * @param {function(!Array.<!PageAgent.SearchMatch>)} callback
    134      */
    135     searchInContent: function(query, caseSensitive, isRegex, callback)
    136     {
    137         /**
    138          * @param {?Protocol.Error} error
    139          * @param {!Array.<!PageAgent.SearchMatch>} searchMatches
    140          */
    141         function innerCallback(error, searchMatches)
    142         {
    143             if (error)
    144                 console.error(error);
    145             var result = [];
    146             for (var i = 0; i < searchMatches.length; ++i) {
    147                 var searchMatch = new WebInspector.ContentProvider.SearchMatch(searchMatches[i].lineNumber, searchMatches[i].lineContent);
    148                 result.push(searchMatch);
    149             }
    150             callback(result || []);
    151         }
    152 
    153         if (this.scriptId) {
    154             // Script failed to parse.
    155             this.target().debuggerAgent().searchInContent(this.scriptId, query, caseSensitive, isRegex, innerCallback);
    156         } else
    157             callback([]);
    158     },
    159 
    160     /**
    161      * @param {string} source
    162      * @return {string}
    163      */
    164     _appendSourceURLCommentIfNeeded: function(source)
    165     {
    166         if (!this.hasSourceURL)
    167             return source;
    168         return source + "\n //# sourceURL=" + this.sourceURL;
    169     },
    170 
    171     /**
    172      * @param {string} newSource
    173      * @param {function(?Protocol.Error, !DebuggerAgent.SetScriptSourceError=, !Array.<!DebuggerAgent.CallFrame>=, !DebuggerAgent.StackTrace=, boolean=)} callback
    174      */
    175     editSource: function(newSource, callback)
    176     {
    177         /**
    178          * @this {WebInspector.Script}
    179          * @param {?Protocol.Error} error
    180          * @param {!DebuggerAgent.SetScriptSourceError=} errorData
    181          * @param {!Array.<!DebuggerAgent.CallFrame>=} callFrames
    182          * @param {!Object=} debugData
    183          * @param {!DebuggerAgent.StackTrace=} asyncStackTrace
    184          */
    185         function didEditScriptSource(error, errorData, callFrames, debugData, asyncStackTrace)
    186         {
    187             // FIXME: support debugData.stack_update_needs_step_in flag by calling WebInspector.debugger_model.callStackModified
    188             if (!error)
    189                 this._source = newSource;
    190             var needsStepIn = !!debugData && debugData["stack_update_needs_step_in"] === true;
    191             callback(error, errorData, callFrames, asyncStackTrace, needsStepIn);
    192             if (!error)
    193                 this.dispatchEventToListeners(WebInspector.Script.Events.ScriptEdited, newSource);
    194         }
    195 
    196         newSource = WebInspector.Script._trimSourceURLComment(newSource);
    197         // We append correct sourceURL to script for consistency only. It's not actually needed for things to work correctly.
    198         newSource = this._appendSourceURLCommentIfNeeded(newSource);
    199 
    200         if (this.scriptId)
    201             this.target().debuggerAgent().setScriptSource(this.scriptId, newSource, undefined, didEditScriptSource.bind(this));
    202         else
    203             callback("Script failed to parse");
    204     },
    205 
    206     /**
    207      * @return {boolean}
    208      */
    209     isInlineScript: function()
    210     {
    211         var startsAtZero = !this.lineOffset && !this.columnOffset;
    212         return !!this.sourceURL && !startsAtZero;
    213     },
    214 
    215     /**
    216      * @return {boolean}
    217      */
    218     isAnonymousScript: function()
    219     {
    220         return !this.sourceURL;
    221     },
    222 
    223     /**
    224      * @return {boolean}
    225      */
    226     isSnippet: function()
    227     {
    228         return !!this.sourceURL && this.sourceURL.startsWith(WebInspector.Script.snippetSourceURLPrefix);
    229     },
    230 
    231     /**
    232      * @return {boolean}
    233      */
    234     isFramework: function()
    235     {
    236         if (!WebInspector.experimentsSettings.frameworksDebuggingSupport.isEnabled())
    237             return false;
    238         if (!WebInspector.settings.skipStackFramesSwitch.get())
    239             return false;
    240         var regex = WebInspector.settings.skipStackFramesPattern.asRegExp();
    241         return regex ? regex.test(this.sourceURL) : false;
    242     },
    243 
    244     /**
    245      * @param {number} lineNumber
    246      * @param {number=} columnNumber
    247      * @return {!WebInspector.UILocation}
    248      */
    249     rawLocationToUILocation: function(lineNumber, columnNumber)
    250     {
    251         var uiLocation;
    252         var rawLocation = new WebInspector.DebuggerModel.Location(this.target(), this.scriptId, lineNumber, columnNumber || 0);
    253         for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i)
    254             uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation);
    255         console.assert(uiLocation, "Script raw location can not be mapped to any ui location.");
    256         return /** @type {!WebInspector.UILocation} */ (uiLocation);
    257     },
    258 
    259     /**
    260      * @param {!WebInspector.SourceMapping} sourceMapping
    261      */
    262     pushSourceMapping: function(sourceMapping)
    263     {
    264         this._sourceMappings.push(sourceMapping);
    265         this.updateLocations();
    266     },
    267 
    268     /**
    269      * @return {!WebInspector.SourceMapping}
    270      */
    271     popSourceMapping: function()
    272     {
    273         var sourceMapping = this._sourceMappings.pop();
    274         this.updateLocations();
    275         return sourceMapping;
    276     },
    277 
    278     updateLocations: function()
    279     {
    280         var items = this._locations.values();
    281         for (var i = 0; i < items.length; ++i)
    282             items[i].update();
    283     },
    284 
    285     /**
    286      * @param {!WebInspector.DebuggerModel.Location} rawLocation
    287      * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
    288      * @return {!WebInspector.Script.Location}
    289      */
    290     createLiveLocation: function(rawLocation, updateDelegate)
    291     {
    292         console.assert(rawLocation.scriptId === this.scriptId);
    293         var location = new WebInspector.Script.Location(this, rawLocation, updateDelegate);
    294         this._locations.add(location);
    295         location.update();
    296         return location;
    297     },
    298 
    299     __proto__: WebInspector.TargetAwareObject.prototype
    300 }
    301 
    302 /**
    303  * @constructor
    304  * @extends {WebInspector.LiveLocation}
    305  * @param {!WebInspector.Script} script
    306  * @param {!WebInspector.DebuggerModel.Location} rawLocation
    307  * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
    308  */
    309 WebInspector.Script.Location = function(script, rawLocation, updateDelegate)
    310 {
    311     WebInspector.LiveLocation.call(this, rawLocation, updateDelegate);
    312     this._script = script;
    313 }
    314 
    315 WebInspector.Script.Location.prototype = {
    316     /**
    317      * @return {!WebInspector.UILocation}
    318      */
    319     uiLocation: function()
    320     {
    321         var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (this.rawLocation());
    322         return this._script.rawLocationToUILocation(debuggerModelLocation.lineNumber, debuggerModelLocation.columnNumber);
    323     },
    324 
    325     dispose: function()
    326     {
    327         WebInspector.LiveLocation.prototype.dispose.call(this);
    328         this._script._locations.remove(this);
    329     },
    330 
    331     __proto__: WebInspector.LiveLocation.prototype
    332 }
    333