text: /*
   * Copyright (C) 2010 Google Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   *
   *     * Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above
   * copyright notice, this list of conditions and the following disclaimer
   * in the documentation and/or other materials provided with the
   * distribution.
   *     * Neither the name of Google Inc. nor the names of its
   * contributors may be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  WebInspector.DebuggerModel = function()
  {
      this._paused = false;
      this._callFrames = [];
      this._breakpoints = {};
      this._scripts = {};
  
      InspectorBackend.registerDomainDispatcher("Debugger", new WebInspector.DebuggerDispatcher(this));
  }
  
  WebInspector.DebuggerModel.Events = {
      DebuggerPaused: "debugger-paused",
      DebuggerResumed: "debugger-resumed",
      ParsedScriptSource: "parsed-script-source",
      FailedToParseScriptSource: "failed-to-parse-script-source",
      ScriptSourceChanged: "script-source-changed",
      BreakpointAdded: "breakpoint-added",
      BreakpointRemoved: "breakpoint-removed",
      BreakpointResolved: "breakpoint-resolved"
  }
  
  WebInspector.DebuggerModel.prototype = {
      enableDebugger: function()
      {
          InspectorBackend.enableDebugger();
          if (this._breakpointsPushedToBackend)
              return;
          var breakpoints = WebInspector.settings.breakpoints;
          for (var i = 0; i < breakpoints.length; ++i) {
              var breakpoint = breakpoints[i];
              if (typeof breakpoint.url !== "string" || typeof breakpoint.lineNumber !== "number" || typeof breakpoint.columnNumber !== "number" ||
                  typeof breakpoint.condition !== "string" || typeof breakpoint.enabled !== "boolean")
                  continue;
              this.setBreakpoint(breakpoint.url, breakpoint.lineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled);
          }
          this._breakpointsPushedToBackend = true;
      },
  
      disableDebugger: function()
      {
          InspectorBackend.disableDebugger();
      },
  
      continueToLocation: function(sourceID, lineNumber, columnNumber)
      {
          InspectorBackend.continueToLocation(sourceID, lineNumber, columnNumber);
      },
  
      setBreakpoint: function(url, lineNumber, columnNumber, condition, enabled)
      {
          function didSetBreakpoint(breakpointsPushedToBackend, breakpointId, locations)
          {
              if (!breakpointId)
                  return;
              var breakpoint = new WebInspector.Breakpoint(breakpointId, url, "", lineNumber, columnNumber, condition, enabled);
              breakpoint.locations = locations;
              this._breakpoints[breakpointId] = breakpoint;
              if (breakpointsPushedToBackend)
                  this._saveBreakpoints();
              this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointAdded, breakpoint);
          }
          InspectorBackend.setJavaScriptBreakpoint(url, lineNumber, columnNumber, condition, enabled, didSetBreakpoint.bind(this, this._breakpointsPushedToBackend));
      },
  
      setBreakpointBySourceId: function(sourceID, lineNumber, columnNumber, condition, enabled)
      {
          function didSetBreakpoint(breakpointId, actualLineNumber, actualColumnNumber)
          {
              if (!breakpointId)
                  return;
              var breakpoint = new WebInspector.Breakpoint(breakpointId, "", sourceID, lineNumber, columnNumber, condition, enabled);
              breakpoint.addLocation(sourceID, actualLineNumber, actualColumnNumber);
              this._breakpoints[breakpointId] = breakpoint;
              this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointAdded, breakpoint);
          }
          InspectorBackend.setJavaScriptBreakpointBySourceId(sourceID, lineNumber, columnNumber, condition, enabled, didSetBreakpoint.bind(this));
      },
  
      removeBreakpoint: function(breakpointId)
      {
          InspectorBackend.removeJavaScriptBreakpoint(breakpointId);
          var breakpoint = this._breakpoints[breakpointId];
          delete this._breakpoints[breakpointId];
          this._saveBreakpoints();
          this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointRemoved, breakpointId);
      },
  
      updateBreakpoint: function(breakpointId, condition, enabled)
      {
          var breakpoint = this._breakpoints[breakpointId];
          this.removeBreakpoint(breakpointId);
          if (breakpoint.url)
              this.setBreakpoint(breakpoint.url, breakpoint.lineNumber, breakpoint.columnNumber, condition, enabled);
          else
              this.setBreakpointBySourceId(breakpoint.sourceID, breakpoint.lineNumber, breakpoint.columnNumber, condition, enabled);
      },
  
      _breakpointResolved: function(breakpointId, sourceID, lineNumber, columnNumber)
      {
          var breakpoint = this._breakpoints[breakpointId];
          if (!breakpoint)
              return;
          breakpoint.addLocation(sourceID, lineNumber, columnNumber);
          this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointResolved, breakpoint);
      },
  
      _saveBreakpoints: function()
      {
          var serializedBreakpoints = [];
          for (var id in this._breakpoints) {
              var breakpoint = this._breakpoints[id];
              if (!breakpoint.url)
                  continue;
              var serializedBreakpoint = {};
              serializedBreakpoint.url = breakpoint.url;
              serializedBreakpoint.lineNumber = breakpoint.lineNumber;
              serializedBreakpoint.columnNumber = breakpoint.columnNumber;
              serializedBreakpoint.condition = breakpoint.condition;
              serializedBreakpoint.enabled = breakpoint.enabled;
              serializedBreakpoints.push(serializedBreakpoint);
          }
          WebInspector.settings.breakpoints = serializedBreakpoints;
      },
  
      get breakpoints()
      {
          return this._breakpoints;
      },
  
      breakpointForId: function(breakpointId)
      {
          return this._breakpoints[breakpointId];
      },
  
      queryBreakpoints: function(filter)
      {
          var breakpoints = [];
          for (var id in this._breakpoints) {
             var breakpoint = this._breakpoints[id];
             if (filter(breakpoint))
                 breakpoints.push(breakpoint);
          }
          return breakpoints;
      },
  
      reset: function()
      {
          this._paused = false;
          this._callFrames = [];
          for (var id in this._breakpoints) {
              var breakpoint = this._breakpoints[id];
              if (!breakpoint.url)
                  this.removeBreakpoint(id);
              else
                  breakpoint.locations = [];
          }
          this._scripts = {};
      },
  
      scriptForSourceID: function(sourceID)
      {
          return this._scripts[sourceID];
      },
  
      scriptsForURL: function(url)
      {
          return this.queryScripts(function(s) { return s.sourceURL === url; });
      },
  
      queryScripts: function(filter)
      {
          var scripts = [];
          for (var sourceID in this._scripts) {
              var script = this._scripts[sourceID];
              if (filter(script))
                  scripts.push(script);
          }
          return scripts;
      },
  
      editScriptSource: function(sourceID, scriptSource)
      {
          function didEditScriptSource(success, newBodyOrErrorMessage, callFrames)
          {
              if (success) {
                  if (callFrames && callFrames.length)
                      this._callFrames = callFrames;
                  this._updateScriptSource(sourceID, newBodyOrErrorMessage);
              } else
                  WebInspector.log(newBodyOrErrorMessage, WebInspector.ConsoleMessage.MessageLevel.Warning);
          }
          InspectorBackend.editScriptSource(sourceID, scriptSource, didEditScriptSource.bind(this));
      },
  
      _updateScriptSource: function(sourceID, scriptSource)
      {
          var script = this._scripts[sourceID];
          var oldSource = script.source;
          script.source = scriptSource;
  
          // Clear and re-create breakpoints according to text diff.
          var diff = Array.diff(oldSource.split("\n"), script.source.split("\n"));
          for (var id in this._breakpoints) {
              var breakpoint = this._breakpoints[id];
              if (breakpoint.url) {
                  if (breakpoint.url !== script.sourceURL)
                      continue;
              } else {
                  if (breakpoint.sourceID !== sourceID)
                      continue;
              }
              this.removeBreakpoint(breakpoint.id);
              var lineNumber = breakpoint.lineNumber;
              var newLineNumber = diff.left[lineNumber].row;
              if (newLineNumber === undefined) {
                  for (var i = lineNumber - 1; i >= 0; --i) {
                      if (diff.left[i].row === undefined)
                          continue;
                      var shiftedLineNumber = diff.left[i].row + lineNumber - i;
                      if (shiftedLineNumber < diff.right.length) {
                          var originalLineNumber = diff.right[shiftedLineNumber].row;
                          if (originalLineNumber === lineNumber || originalLineNumber === undefined)
                              newLineNumber = shiftedLineNumber;
                      }
                      break;
                  }
              }
              if (newLineNumber === undefined)
                  continue;
              if (breakpoint.url)
                  this.setBreakpoint(breakpoint.url, newLineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled);
              else
                  this.setBreakpointBySourceId(sourceID, newLineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled);
          }
  
          this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ScriptSourceChanged, { sourceID: sourceID, oldSource: oldSource });
      },
  
      get callFrames()
      {
          return this._callFrames;
      },
  
      _pausedScript: function(details)
      {
          this._paused = true;
          this._callFrames = details.callFrames;
          details.breakpoint = this._breakpointForCallFrame(details.callFrames[0]);
          this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerPaused, details);
      },
  
      _resumedScript: function()
      {
          this._paused = false;
          this._callFrames = [];
          this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerResumed);
      },
  
      _breakpointForCallFrame: function(callFrame)
      {
          function match(location)
          {
              if (location.sourceID != callFrame.sourceID)
                  return false;
              return location.lineNumber === callFrame.line && location.columnNumber === callFrame.column;
          }
          for (var id in this._breakpoints) {
              var breakpoint = this._breakpoints[id];
              for (var i = 0; i < breakpoint.locations.length; ++i) {
                  if (match(breakpoint.locations[i]))
                      return breakpoint;
              }
          }
      },
  
      _parsedScriptSource: function(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType)
      {
          var script = new WebInspector.Script(sourceID, sourceURL, "", lineOffset, columnOffset, length, undefined, undefined, scriptWorldType);
          this._scripts[sourceID] = script;
          this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ParsedScriptSource, script);
      },
  
      _failedToParseScriptSource: function(sourceURL, source, startingLine, errorLine, errorMessage)
      {
          var script = new WebInspector.Script(null, sourceURL, source, startingLine, errorLine, errorMessage, undefined);
          this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, script);
      }
  }
  
  WebInspector.DebuggerModel.prototype.__proto__ = WebInspector.Object.prototype;
  
  WebInspector.DebuggerEventTypes = {
      JavaScriptPause: 0,
      JavaScriptBreakpoint: 1,
      NativeBreakpoint: 2
  };
  
  WebInspector.DebuggerDispatcher = function(debuggerModel)
  {
      this._debuggerModel = debuggerModel;
  }
  
  WebInspector.DebuggerDispatcher.prototype = {
      pausedScript: function(details)
      {
          this._debuggerModel._pausedScript(details);
      },
  
      resumedScript: function()
      {
          this._debuggerModel._resumedScript();
      },
  
      debuggerWasEnabled: function()
      {
          WebInspector.panels.scripts.debuggerWasEnabled();
      },
  
      debuggerWasDisabled: function()
      {
          WebInspector.panels.scripts.debuggerWasDisabled();
      },
  
      parsedScriptSource: function(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType)
      {
          this._debuggerModel._parsedScriptSource(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType);
      },
  
      failedToParseScriptSource: function(sourceURL, source, startingLine, errorLine, errorMessage)
      {
          this._debuggerModel._failedToParseScriptSource(sourceURL, source, startingLine, errorLine, errorMessage);
      },
  
      breakpointResolved: function(breakpointId, sourceID, lineNumber, columnNumber)
      {
          this._debuggerModel._breakpointResolved(breakpointId, sourceID, lineNumber, columnNumber);
      },
  
      didCreateWorker: function()
      {
          var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
          workersPane.addWorker.apply(workersPane, arguments);
      },
  
      didDestroyWorker: function()
      {
          var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
          workersPane.removeWorker.apply(workersPane, arguments);
      }
  }
  