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 * @extends {WebInspector.Object} 34 * @param {!WebInspector.Workspace} workspace 35 */ 36WebInspector.ScriptSnippetModel = function(workspace) 37{ 38 this._workspace = workspace; 39 /** @type {!Object.<string, !WebInspector.UISourceCode>} */ 40 this._uiSourceCodeForScriptId = {}; 41 /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.Script>} */ 42 this._scriptForUISourceCode = new Map(); 43 /** @type {!Object.<string, !WebInspector.UISourceCode>} */ 44 this._uiSourceCodeForSnippetId = {}; 45 /** @type {!Map.<!WebInspector.UISourceCode, string>} */ 46 this._snippetIdForUISourceCode = new Map(); 47 48 this._snippetStorage = new WebInspector.SnippetStorage("script", "Script snippet #"); 49 this._lastSnippetEvaluationIndexSetting = WebInspector.settings.createSetting("lastSnippetEvaluationIndex", 0); 50 this._snippetScriptMapping = new WebInspector.SnippetScriptMapping(this); 51 this._projectDelegate = new WebInspector.SnippetsProjectDelegate(this); 52 this._project = this._workspace.addProject(this._projectDelegate); 53 this.reset(); 54 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this); 55} 56 57WebInspector.ScriptSnippetModel.prototype = { 58 /** 59 * @return {!WebInspector.SnippetScriptMapping} 60 */ 61 get scriptMapping() 62 { 63 return this._snippetScriptMapping; 64 }, 65 66 /** 67 * @return {!WebInspector.Project} 68 */ 69 project: function() 70 { 71 return this._project; 72 }, 73 74 _loadSnippets: function() 75 { 76 var snippets = this._snippetStorage.snippets(); 77 for (var i = 0; i < snippets.length; ++i) 78 this._addScriptSnippet(snippets[i]); 79 }, 80 81 /** 82 * @param {string} content 83 * @return {string} 84 */ 85 createScriptSnippet: function(content) 86 { 87 var snippet = this._snippetStorage.createSnippet(); 88 snippet.content = content; 89 return this._addScriptSnippet(snippet); 90 }, 91 92 /** 93 * @param {!WebInspector.Snippet} snippet 94 * @return {string} 95 */ 96 _addScriptSnippet: function(snippet) 97 { 98 var path = this._projectDelegate.addSnippet(snippet.name, new WebInspector.SnippetContentProvider(snippet)); 99 var uiSourceCode = this._workspace.uiSourceCode(this._projectDelegate.id(), path); 100 if (!uiSourceCode) { 101 console.assert(uiSourceCode); 102 return ""; 103 } 104 var scriptFile = new WebInspector.SnippetScriptFile(this, uiSourceCode); 105 uiSourceCode.setScriptFile(scriptFile); 106 this._snippetIdForUISourceCode.put(uiSourceCode, snippet.id); 107 uiSourceCode.setSourceMapping(this._snippetScriptMapping); 108 this._uiSourceCodeForSnippetId[snippet.id] = uiSourceCode; 109 return path; 110 }, 111 112 /** 113 * @param {string} path 114 */ 115 deleteScriptSnippet: function(path) 116 { 117 var uiSourceCode = this._workspace.uiSourceCode(this._projectDelegate.id(), path); 118 if (!uiSourceCode) 119 return; 120 var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode) || ""; 121 var snippet = this._snippetStorage.snippetForId(snippetId); 122 this._snippetStorage.deleteSnippet(snippet); 123 this._removeBreakpoints(uiSourceCode); 124 this._releaseSnippetScript(uiSourceCode); 125 delete this._uiSourceCodeForSnippetId[snippet.id]; 126 this._snippetIdForUISourceCode.remove(uiSourceCode); 127 this._projectDelegate.removeFile(snippet.name); 128 }, 129 130 /** 131 * @param {string} name 132 * @param {string} newName 133 * @param {function(boolean, string=)} callback 134 */ 135 renameScriptSnippet: function(name, newName, callback) 136 { 137 newName = newName.trim(); 138 if (!newName || newName.indexOf("/") !== -1 || name === newName || this._snippetStorage.snippetForName(newName)) { 139 callback(false); 140 return; 141 } 142 var snippet = this._snippetStorage.snippetForName(name); 143 console.assert(snippet, "Snippet '" + name + "' was not found."); 144 var uiSourceCode = this._uiSourceCodeForSnippetId[snippet.id]; 145 console.assert(uiSourceCode, "No uiSourceCode was found for snippet '" + name + "'."); 146 147 var breakpointLocations = this._removeBreakpoints(uiSourceCode); 148 snippet.name = newName; 149 this._restoreBreakpoints(uiSourceCode, breakpointLocations); 150 callback(true, newName); 151 }, 152 153 /** 154 * @param {string} name 155 * @param {string} newContent 156 */ 157 _setScriptSnippetContent: function(name, newContent) 158 { 159 var snippet = this._snippetStorage.snippetForName(name); 160 snippet.content = newContent; 161 }, 162 163 /** 164 * @param {!WebInspector.UISourceCode} uiSourceCode 165 */ 166 _scriptSnippetEdited: function(uiSourceCode) 167 { 168 var script = this._scriptForUISourceCode.get(uiSourceCode); 169 if (!script) 170 return; 171 172 var breakpointLocations = this._removeBreakpoints(uiSourceCode); 173 this._releaseSnippetScript(uiSourceCode); 174 this._restoreBreakpoints(uiSourceCode, breakpointLocations); 175 var scriptUISourceCode = script.rawLocationToUILocation(0, 0).uiSourceCode; 176 if (scriptUISourceCode) 177 this._restoreBreakpoints(scriptUISourceCode, breakpointLocations); 178 }, 179 180 /** 181 * @param {string} snippetId 182 * @return {number} 183 */ 184 _nextEvaluationIndex: function(snippetId) 185 { 186 var evaluationIndex = this._lastSnippetEvaluationIndexSetting.get() + 1; 187 this._lastSnippetEvaluationIndexSetting.set(evaluationIndex); 188 return evaluationIndex; 189 }, 190 191 /** 192 * @param {!WebInspector.UISourceCode} uiSourceCode 193 */ 194 evaluateScriptSnippet: function(uiSourceCode) 195 { 196 var breakpointLocations = this._removeBreakpoints(uiSourceCode); 197 this._releaseSnippetScript(uiSourceCode); 198 this._restoreBreakpoints(uiSourceCode, breakpointLocations); 199 var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode) || ""; 200 var evaluationIndex = this._nextEvaluationIndex(snippetId); 201 uiSourceCode._evaluationIndex = evaluationIndex; 202 var evaluationUrl = this._evaluationSourceURL(uiSourceCode); 203 204 var expression = uiSourceCode.workingCopy(); 205 206 // In order to stop on the breakpoints during the snippet evaluation we need to compile and run it separately. 207 // If separate compilation and execution is not supported by the port we fall back to evaluation in console. 208 // In case we don't need that since debugger is already paused. 209 // We do the same when we are stopped on the call frame since debugger is already paused and can not stop on breakpoint anymore. 210 if (WebInspector.debuggerModel.selectedCallFrame()) { 211 expression = uiSourceCode.workingCopy() + "\n//# sourceURL=" + evaluationUrl + "\n"; 212 WebInspector.evaluateInConsole(expression, true); 213 return; 214 } 215 216 WebInspector.showConsole(); 217 DebuggerAgent.compileScript(expression, evaluationUrl, compileCallback.bind(this)); 218 219 /** 220 * @param {?string} error 221 * @param {string=} scriptId 222 * @param {string=} syntaxErrorMessage 223 * @this {WebInspector.ScriptSnippetModel} 224 */ 225 function compileCallback(error, scriptId, syntaxErrorMessage) 226 { 227 if (!uiSourceCode || uiSourceCode._evaluationIndex !== evaluationIndex) 228 return; 229 230 if (error) { 231 console.error(error); 232 return; 233 } 234 235 if (!scriptId) { 236 var consoleMessage = WebInspector.ConsoleMessage.create( 237 WebInspector.ConsoleMessage.MessageSource.JS, 238 WebInspector.ConsoleMessage.MessageLevel.Error, 239 syntaxErrorMessage || ""); 240 WebInspector.console.addMessage(consoleMessage); 241 return; 242 } 243 244 var breakpointLocations = this._removeBreakpoints(uiSourceCode); 245 this._restoreBreakpoints(uiSourceCode, breakpointLocations); 246 247 this._runScript(scriptId); 248 } 249 }, 250 251 /** 252 * @param {!DebuggerAgent.ScriptId} scriptId 253 */ 254 _runScript: function(scriptId) 255 { 256 var currentExecutionContext = WebInspector.runtimeModel.currentExecutionContext(); 257 DebuggerAgent.runScript(scriptId, currentExecutionContext ? currentExecutionContext.id : undefined, "console", false, runCallback.bind(this)); 258 259 /** 260 * @param {?string} error 261 * @param {?RuntimeAgent.RemoteObject} result 262 * @param {boolean=} wasThrown 263 * @this {WebInspector.ScriptSnippetModel} 264 */ 265 function runCallback(error, result, wasThrown) 266 { 267 if (error) { 268 console.error(error); 269 return; 270 } 271 272 this._printRunScriptResult(result, wasThrown); 273 } 274 }, 275 276 /** 277 * @param {?RuntimeAgent.RemoteObject} result 278 * @param {boolean=} wasThrown 279 */ 280 _printRunScriptResult: function(result, wasThrown) 281 { 282 var level = (wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log); 283 var message = WebInspector.ConsoleMessage.create(WebInspector.ConsoleMessage.MessageSource.JS, level, "", undefined, undefined, undefined, undefined, undefined, [result]); 284 WebInspector.console.addMessage(message) 285 }, 286 287 /** 288 * @param {!WebInspector.DebuggerModel.Location} rawLocation 289 * @return {?WebInspector.UILocation} 290 */ 291 _rawLocationToUILocation: function(rawLocation) 292 { 293 var uiSourceCode = this._uiSourceCodeForScriptId[rawLocation.scriptId]; 294 if (!uiSourceCode) 295 return null; 296 return new WebInspector.UILocation(uiSourceCode, rawLocation.lineNumber, rawLocation.columnNumber || 0); 297 }, 298 299 /** 300 * @param {!WebInspector.UISourceCode} uiSourceCode 301 * @param {number} lineNumber 302 * @param {number} columnNumber 303 * @return {?WebInspector.DebuggerModel.Location} 304 */ 305 _uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) 306 { 307 var script = this._scriptForUISourceCode.get(uiSourceCode); 308 if (!script) 309 return null; 310 311 return WebInspector.debuggerModel.createRawLocation(script, lineNumber, columnNumber); 312 }, 313 314 /** 315 * @param {!WebInspector.Script} script 316 */ 317 _addScript: function(script) 318 { 319 var snippetId = this._snippetIdForSourceURL(script.sourceURL); 320 if (!snippetId) 321 return; 322 var uiSourceCode = this._uiSourceCodeForSnippetId[snippetId]; 323 324 if (!uiSourceCode || this._evaluationSourceURL(uiSourceCode) !== script.sourceURL) 325 return; 326 327 console.assert(!this._scriptForUISourceCode.get(uiSourceCode)); 328 this._uiSourceCodeForScriptId[script.scriptId] = uiSourceCode; 329 this._scriptForUISourceCode.put(uiSourceCode, script); 330 uiSourceCode.scriptFile().setHasDivergedFromVM(false); 331 script.pushSourceMapping(this._snippetScriptMapping); 332 }, 333 334 /** 335 * @param {!WebInspector.UISourceCode} uiSourceCode 336 * @return {!Array.<!Object>} 337 */ 338 _removeBreakpoints: function(uiSourceCode) 339 { 340 var breakpointLocations = WebInspector.breakpointManager.breakpointLocationsForUISourceCode(uiSourceCode); 341 for (var i = 0; i < breakpointLocations.length; ++i) 342 breakpointLocations[i].breakpoint.remove(); 343 return breakpointLocations; 344 }, 345 346 /** 347 * @param {!WebInspector.UISourceCode} uiSourceCode 348 * @param {!Array.<!Object>} breakpointLocations 349 */ 350 _restoreBreakpoints: function(uiSourceCode, breakpointLocations) 351 { 352 for (var i = 0; i < breakpointLocations.length; ++i) { 353 var uiLocation = breakpointLocations[i].uiLocation; 354 var breakpoint = breakpointLocations[i].breakpoint; 355 WebInspector.breakpointManager.setBreakpoint(uiSourceCode, uiLocation.lineNumber, breakpoint.condition(), breakpoint.enabled()); 356 } 357 }, 358 359 /** 360 * @param {!WebInspector.UISourceCode} uiSourceCode 361 */ 362 _releaseSnippetScript: function(uiSourceCode) 363 { 364 var script = this._scriptForUISourceCode.get(uiSourceCode); 365 if (!script) 366 return null; 367 368 uiSourceCode.scriptFile().setIsDivergingFromVM(true); 369 uiSourceCode.scriptFile().setHasDivergedFromVM(true); 370 delete this._uiSourceCodeForScriptId[script.scriptId]; 371 this._scriptForUISourceCode.remove(uiSourceCode); 372 delete uiSourceCode._evaluationIndex; 373 uiSourceCode.scriptFile().setIsDivergingFromVM(false); 374 }, 375 376 _debuggerReset: function() 377 { 378 for (var snippetId in this._uiSourceCodeForSnippetId) { 379 var uiSourceCode = this._uiSourceCodeForSnippetId[snippetId]; 380 this._releaseSnippetScript(uiSourceCode); 381 } 382 }, 383 384 /** 385 * @param {!WebInspector.UISourceCode} uiSourceCode 386 * @return {string} 387 */ 388 _evaluationSourceURL: function(uiSourceCode) 389 { 390 var evaluationSuffix = "_" + uiSourceCode._evaluationIndex; 391 var snippetId = this._snippetIdForUISourceCode.get(uiSourceCode); 392 return WebInspector.Script.snippetSourceURLPrefix + snippetId + evaluationSuffix; 393 }, 394 395 /** 396 * @param {string} sourceURL 397 * @return {?string} 398 */ 399 _snippetIdForSourceURL: function(sourceURL) 400 { 401 var snippetPrefix = WebInspector.Script.snippetSourceURLPrefix; 402 if (!sourceURL.startsWith(snippetPrefix)) 403 return null; 404 var splitURL = sourceURL.substring(snippetPrefix.length).split("_"); 405 var snippetId = splitURL[0]; 406 return snippetId; 407 }, 408 409 reset: function() 410 { 411 /** @type {!Object.<string, !WebInspector.UISourceCode>} */ 412 this._uiSourceCodeForScriptId = {}; 413 this._scriptForUISourceCode = new Map(); 414 /** @type {!Object.<string, !WebInspector.UISourceCode>} */ 415 this._uiSourceCodeForSnippetId = {}; 416 this._snippetIdForUISourceCode = new Map(); 417 this._projectDelegate.reset(); 418 this._loadSnippets(); 419 }, 420 421 __proto__: WebInspector.Object.prototype 422} 423 424/** 425 * @constructor 426 * @implements {WebInspector.ScriptFile} 427 * @extends {WebInspector.Object} 428 * @param {!WebInspector.ScriptSnippetModel} scriptSnippetModel 429 * @param {!WebInspector.UISourceCode} uiSourceCode 430 */ 431WebInspector.SnippetScriptFile = function(scriptSnippetModel, uiSourceCode) 432{ 433 WebInspector.ScriptFile.call(this); 434 this._scriptSnippetModel = scriptSnippetModel; 435 this._uiSourceCode = uiSourceCode; 436 this._hasDivergedFromVM = true; 437 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 438} 439 440WebInspector.SnippetScriptFile.prototype = { 441 /** 442 * @return {boolean} 443 */ 444 hasDivergedFromVM: function() 445 { 446 return this._hasDivergedFromVM; 447 }, 448 449 /** 450 * @param {boolean} hasDivergedFromVM 451 */ 452 setHasDivergedFromVM: function(hasDivergedFromVM) 453 { 454 this._hasDivergedFromVM = hasDivergedFromVM; 455 }, 456 457 /** 458 * @return {boolean} 459 */ 460 isDivergingFromVM: function() 461 { 462 return this._isDivergingFromVM; 463 }, 464 465 checkMapping: function() 466 { 467 }, 468 469 /** 470 * @return {boolean} 471 */ 472 isMergingToVM: function() 473 { 474 return false; 475 }, 476 477 /** 478 * @param {boolean} isDivergingFromVM 479 */ 480 setIsDivergingFromVM: function(isDivergingFromVM) 481 { 482 this._isDivergingFromVM = isDivergingFromVM; 483 }, 484 485 _workingCopyChanged: function() 486 { 487 this._scriptSnippetModel._scriptSnippetEdited(this._uiSourceCode); 488 }, 489 490 __proto__: WebInspector.Object.prototype 491} 492 493/** 494 * @constructor 495 * @implements {WebInspector.ScriptSourceMapping} 496 * @param {!WebInspector.ScriptSnippetModel} scriptSnippetModel 497 */ 498WebInspector.SnippetScriptMapping = function(scriptSnippetModel) 499{ 500 this._scriptSnippetModel = scriptSnippetModel; 501} 502 503WebInspector.SnippetScriptMapping.prototype = { 504 /** 505 * @param {!WebInspector.RawLocation} rawLocation 506 * @return {?WebInspector.UILocation} 507 */ 508 rawLocationToUILocation: function(rawLocation) 509 { 510 var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */(rawLocation); 511 return this._scriptSnippetModel._rawLocationToUILocation(debuggerModelLocation); 512 }, 513 514 /** 515 * @param {!WebInspector.UISourceCode} uiSourceCode 516 * @param {number} lineNumber 517 * @param {number} columnNumber 518 * @return {?WebInspector.DebuggerModel.Location} 519 */ 520 uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) 521 { 522 return this._scriptSnippetModel._uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber); 523 }, 524 525 /** 526 * @param {string} sourceURL 527 * @return {?string} 528 */ 529 snippetIdForSourceURL: function(sourceURL) 530 { 531 return this._scriptSnippetModel._snippetIdForSourceURL(sourceURL); 532 }, 533 534 /** 535 * @param {!WebInspector.Script} script 536 */ 537 addScript: function(script) 538 { 539 this._scriptSnippetModel._addScript(script); 540 } 541} 542 543/** 544 * @constructor 545 * @implements {WebInspector.ContentProvider} 546 * @param {!WebInspector.Snippet} snippet 547 */ 548WebInspector.SnippetContentProvider = function(snippet) 549{ 550 this._snippet = snippet; 551} 552 553WebInspector.SnippetContentProvider.prototype = { 554 /** 555 * @return {string} 556 */ 557 contentURL: function() 558 { 559 return ""; 560 }, 561 562 /** 563 * @return {!WebInspector.ResourceType} 564 */ 565 contentType: function() 566 { 567 return WebInspector.resourceTypes.Script; 568 }, 569 570 /** 571 * @param {function(?string)} callback 572 */ 573 requestContent: function(callback) 574 { 575 callback(this._snippet.content); 576 }, 577 578 /** 579 * @param {string} query 580 * @param {boolean} caseSensitive 581 * @param {boolean} isRegex 582 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback 583 */ 584 searchInContent: function(query, caseSensitive, isRegex, callback) 585 { 586 /** 587 * @this {WebInspector.SnippetContentProvider} 588 */ 589 function performSearch() 590 { 591 callback(WebInspector.ContentProvider.performSearchInContent(this._snippet.content, query, caseSensitive, isRegex)); 592 } 593 594 // searchInContent should call back later. 595 window.setTimeout(performSearch.bind(this), 0); 596 }, 597 598 __proto__: WebInspector.ContentProvider.prototype 599} 600 601/** 602 * @constructor 603 * @extends {WebInspector.ContentProviderBasedProjectDelegate} 604 * @param {!WebInspector.ScriptSnippetModel} model 605 */ 606WebInspector.SnippetsProjectDelegate = function(model) 607{ 608 WebInspector.ContentProviderBasedProjectDelegate.call(this, WebInspector.projectTypes.Snippets); 609 this._model = model; 610} 611 612WebInspector.SnippetsProjectDelegate.prototype = { 613 /** 614 * @override 615 * @return {string} 616 */ 617 id: function() 618 { 619 return WebInspector.projectTypes.Snippets + ":"; 620 }, 621 622 /** 623 * @param {string} name 624 * @param {!WebInspector.ContentProvider} contentProvider 625 * @return {string} 626 */ 627 addSnippet: function(name, contentProvider) 628 { 629 return this.addContentProvider("", name, name, contentProvider, true, false); 630 }, 631 632 /** 633 * @return {boolean} 634 */ 635 canSetFileContent: function() 636 { 637 return true; 638 }, 639 640 /** 641 * @param {string} path 642 * @param {string} newContent 643 * @param {function(?string)} callback 644 */ 645 setFileContent: function(path, newContent, callback) 646 { 647 this._model._setScriptSnippetContent(path, newContent); 648 callback(""); 649 }, 650 651 /** 652 * @return {boolean} 653 */ 654 canRename: function() 655 { 656 return true; 657 }, 658 659 /** 660 * @param {string} path 661 * @param {string} newName 662 * @param {function(boolean, string=)} callback 663 */ 664 performRename: function(path, newName, callback) 665 { 666 this._model.renameScriptSnippet(path, newName, callback); 667 }, 668 669 /** 670 * @param {string} path 671 * @param {?string} name 672 * @param {string} content 673 * @param {function(?string)} callback 674 */ 675 createFile: function(path, name, content, callback) 676 { 677 var filePath = this._model.createScriptSnippet(content); 678 callback(filePath); 679 }, 680 681 /** 682 * @param {string} path 683 */ 684 deleteFile: function(path) 685 { 686 this._model.deleteScriptSnippet(path); 687 }, 688 689 __proto__: WebInspector.ContentProviderBasedProjectDelegate.prototype 690} 691 692/** 693 * @type {!WebInspector.ScriptSnippetModel} 694 */ 695WebInspector.scriptSnippetModel; 696