Initial commit of updated Chrome Java SDK
authorEugene Ostroukhov <eugeneo@symbian.org>
Mon, 07 Jun 2010 16:51:19 -0700
changeset 355 8726e95bcbba
parent 354 0bceeb415e7f
child 356 8e561bc07a21
Initial commit of updated Chrome Java SDK
org.chromium.debug.core/plugin.properties
org.chromium.debug.core/plugin.xml
org.chromium.debug.core/src/org/chromium/debug/core/ChromiumDebugPlugin.java
org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceComputer.java
org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceDirector.java
org.chromium.debug.core/src/org/chromium/debug/core/Messages.java
org.chromium.debug.core/src/org/chromium/debug/core/ReverseSourceLookup.java
org.chromium.debug.core/src/org/chromium/debug/core/SourceNameMapperContainer.java
org.chromium.debug.core/src/org/chromium/debug/core/VProjectSourceContainer.java
org.chromium.debug.core/src/org/chromium/debug/core/messages.properties
org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointAdapterFactory.java
org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointMap.java
org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointRegistry.java
org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointSynchronizer.java
org.chromium.debug.core/src/org/chromium/debug/core/model/ChromiumLineBreakpoint.java
org.chromium.debug.core/src/org/chromium/debug/core/model/DebugTargetImpl.java
org.chromium.debug.core/src/org/chromium/debug/core/model/LineBreakpointAdapter.java
org.chromium.debug.core/src/org/chromium/debug/core/model/Messages.java
org.chromium.debug.core/src/org/chromium/debug/core/model/MockUpResourceWriter.java
org.chromium.debug.core/src/org/chromium/debug/core/model/ResourceManager.java
org.chromium.debug.core/src/org/chromium/debug/core/model/StackFrame.java
org.chromium.debug.core/src/org/chromium/debug/core/model/VProjectWorkspaceBridge.java
org.chromium.debug.core/src/org/chromium/debug/core/model/VmResource.java
org.chromium.debug.core/src/org/chromium/debug/core/model/VmResourceId.java
org.chromium.debug.core/src/org/chromium/debug/core/model/WorkspaceBridge.java
org.chromium.debug.core/src/org/chromium/debug/core/model/messages.properties
org.chromium.debug.core/src/org/chromium/debug/core/util/ChromiumDebugPluginUtil.java
org.chromium.debug.ui/META-INF/MANIFEST.MF
org.chromium.debug.ui/plugin.xml
org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/CompareChangesAction.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/JsBreakpointPropertiesAction.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/JsBreakpointPropertiesRulerAction.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/JsBreakpointPropertiesRulerActionDelegate.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/Messages.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/OpenFunctionAction.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/PushChangesAction.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/SynchronizeBreakpoints.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/V8ScriptAction.java
org.chromium.debug.ui/src/org/chromium/debug/ui/actions/messages.properties
org.chromium.debug.ui/src/org/chromium/debug/ui/editors/JsDocumentProvider.java
org.chromium.debug.ui/src/org/chromium/debug/ui/launcher/LaunchTabGroup.java
org.chromium.debug.ui/src/org/chromium/debug/ui/source/Messages.java
org.chromium.debug.ui/src/org/chromium/debug/ui/source/SourceNameMapperContainerDialog.java
org.chromium.debug.ui/src/org/chromium/debug/ui/source/SourceNameMapperContainerDialogLogic.java
org.chromium.debug.ui/src/org/chromium/debug/ui/source/SourceNameMapperContainerPresentation.java
org.chromium.debug.ui/src/org/chromium/debug/ui/source/messages.properties
org.chromium.sdk/src/org/chromium/sdk/Breakpoint.java
org.chromium.sdk/src/org/chromium/sdk/CallFrame.java
org.chromium.sdk/src/org/chromium/sdk/JavascriptVm.java
org.chromium.sdk/src/org/chromium/sdk/LiveEditDebugEventListener.java
org.chromium.sdk/src/org/chromium/sdk/LiveEditExtension.java
org.chromium.sdk/src/org/chromium/sdk/Script.java
org.chromium.sdk/src/org/chromium/sdk/UpdatableScript.java
org.chromium.sdk/src/org/chromium/sdk/internal/ContextBuilder.java
org.chromium.sdk/src/org/chromium/sdk/internal/DebugSession.java
org.chromium.sdk/src/org/chromium/sdk/internal/JavascriptVmImpl.java
org.chromium.sdk/src/org/chromium/sdk/internal/JsObjectImpl.java
org.chromium.sdk/src/org/chromium/sdk/internal/ScriptImpl.java
org.chromium.sdk/src/org/chromium/sdk/internal/ScriptManager.java
org.chromium.sdk/src/org/chromium/sdk/internal/protocol/ChangeLiveBody.java
org.chromium.sdk/src/org/chromium/sdk/internal/protocol/CommandResponse.java
org.chromium.sdk/src/org/chromium/sdk/internal/protocol/CommandResponseBody.java
org.chromium.sdk/src/org/chromium/sdk/internal/protocol/ListBreakpointsBody.java
org.chromium.sdk/src/org/chromium/sdk/internal/protocol/data/BreakpointInfo.java
org.chromium.sdk/src/org/chromium/sdk/internal/protocolparser/dynamicimpl/JsonProtocolParser.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/BreakpointImpl.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/BreakpointManager.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/DebuggerCommand.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8CommandCallbackBase.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8Helper.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8ProtocolUtil.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/ChangeLiveMessage.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/DebuggerMessageFactory.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/ListBreakpointsMessage.java
org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/SetBreakpointMessage.java
--- a/org.chromium.debug.core/plugin.properties	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/plugin.properties	Mon Jun 07 16:51:19 2010 -0700
@@ -4,4 +4,7 @@
 
 providerName = The Chromium Authors
 
-pluginName = Chromium JavaScript Remote Debugger Core
\ No newline at end of file
+pluginName = Chromium JavaScript Remote Debugger Core
+
+SourceNameMapperContainer.name = JavaScript Source Name Mapper
+SourceNameMapperContainer.description = Transforms name of source and delegates lookup to another source container.
\ No newline at end of file
--- a/org.chromium.debug.core/plugin.xml	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/plugin.xml	Mon Jun 07 16:51:19 2010 -0700
@@ -41,4 +41,40 @@
         <run class="org.chromium.debug.core.efs.ChromiumScriptFileSystem"/>
      </filesystem>
   </extension>
+
+  <extension point="org.eclipse.debug.core.sourceLocators">
+     <sourceLocator 
+        id="org.chromium.debug.core.ChromiumSourceDirector"
+        class="org.chromium.debug.core.ChromiumSourceDirector"
+        name="a ChromiumSourceDirector">
+     </sourceLocator>
+   </extension>
+
+  <extension point="org.eclipse.debug.core.sourcePathComputers">
+     <sourcePathComputer
+        id="org.chromium.debug.core.ChromiumSourceComputer"
+        class="org.chromium.debug.core.ChromiumSourceComputer">
+      </sourcePathComputer>
+  </extension>
+  
+  <extension point="org.eclipse.debug.core.sourceContainerTypes">
+    <sourceContainerType
+      name="JS Server Scripts"
+      class="org.chromium.debug.core.VProjectSourceContainer$TypeDelegate"
+      id="org.chromium.debug.core.VProjectSourceContainer.type"
+      description="Remote V8/Chrome VM JavaScript Scripts">
+    </sourceContainerType>
+   </extension>
+
+  
+  <extension point="org.eclipse.debug.core.sourceContainerTypes">
+    <sourceContainerType
+      name="%SourceNameMapperContainer.name"
+      class="org.chromium.debug.core.SourceNameMapperContainer$TypeDelegate"
+      id="org.chromium.debug.core.SourceNameMapperContainer.type"
+      description="%SourceNameMapperContainer.description">
+    </sourceContainerType>
+   </extension>
+  
+
 </plugin>
--- a/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumDebugPlugin.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumDebugPlugin.java	Mon Jun 07 16:51:19 2010 -0700
@@ -6,6 +6,7 @@
 
 import java.text.MessageFormat;
 
+import org.chromium.debug.core.model.BreakpointMap;
 import org.chromium.debug.core.model.ChromiumBreakpointWBAFactory;
 import org.chromium.debug.core.model.ChromiumLineBreakpoint;
 import org.eclipse.core.runtime.CoreException;
@@ -30,6 +31,8 @@
   /** The shared instance. */
   private static ChromiumDebugPlugin plugin;
 
+  private final BreakpointMap breakpointMap = new BreakpointMap();
+
   private ChromiumBreakpointWBAFactory breakpointWorkbenchAdapterFactory;
 
   public ChromiumDebugPlugin() {
@@ -52,6 +55,10 @@
     super.stop(context);
   }
 
+  public BreakpointMap getBreakpointMap() {
+    return breakpointMap;
+  }
+
   /**
    * @return the shared instance
    */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceComputer.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,22 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourcePathComputerDelegate;
+
+/**
+ * A source path computer implementation that provides {@link VProjectSourceContainer} as
+ * a default source files container for V8/Chrome debug sessions.
+ */
+public class ChromiumSourceComputer implements ISourcePathComputerDelegate {
+  public ISourceContainer[] computeSourceContainers(ILaunchConfiguration configuration,
+      IProgressMonitor monitor) throws CoreException {
+    return new ISourceContainer[] { new VProjectSourceContainer() };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceDirector.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,68 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core;
+
+import org.chromium.debug.core.model.ResourceManager;
+import org.chromium.debug.core.model.StackFrame;
+import org.chromium.debug.core.model.VmResourceId;
+import org.chromium.sdk.Breakpoint;
+import org.chromium.sdk.Script;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupDirector;
+import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupParticipant;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant;
+
+/**
+ * A source lookup director implementation that provides a simple participant and
+ * accepts instance of virtual project once it is created.
+ */
+public class ChromiumSourceDirector extends AbstractSourceLookupDirector {
+  private volatile ResourceManager resourceManager = null;
+  private volatile IProject project = null;
+  private volatile ReverseSourceLookup reverseSourceLookup = null;
+
+
+  public void initializeParticipants() {
+    ISourceLookupParticipant participant = new AbstractSourceLookupParticipant() {
+      public String getSourceName(Object object) throws CoreException {
+        Script script = null;
+        if (object instanceof Script) {
+          script = (Script) object;
+        } else if (object instanceof StackFrame) {
+          StackFrame jsStackFrame = (StackFrame) object;
+          script = jsStackFrame.getCallFrame().getScript();
+        } else if (object instanceof Breakpoint) {
+          Breakpoint breakpoint = (Breakpoint) object;
+          return breakpoint.getScriptName();
+        }
+        if (script == null) {
+          return null;
+        }
+
+        return VmResourceId.forScript(script).getEclipseSourceName();
+      }
+    };
+    addParticipants(new ISourceLookupParticipant[] { participant } );
+  }
+
+  public void initializeVProjectContainers(IProject project, ResourceManager resourceManager) {
+    this.resourceManager = resourceManager;
+    this.project = project;
+    this.reverseSourceLookup = new ReverseSourceLookup(this);
+  }
+
+  public ReverseSourceLookup getReverseSourceLookup() {
+    return reverseSourceLookup;
+  }
+
+  public ResourceManager getResourceManager() {
+    return resourceManager;
+  }
+
+  IProject getProject() {
+    return project;
+  }
+}
--- a/org.chromium.debug.core/src/org/chromium/debug/core/Messages.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/Messages.java	Mon Jun 07 16:51:19 2010 -0700
@@ -15,6 +15,10 @@
 
   public static String ChromiumDebugPlugin_InternalError;
 
+  public static String SourceNameMapperContainer_NAME;
+
+  public static String VProjectSourceContainer_DEFAULT_TYPE_NAME;
+
   static {
     // initialize resource bundle
     NLS.initializeMessages(BUNDLE_NAME, Messages.class);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/ReverseSourceLookup.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,100 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core;
+
+import org.chromium.debug.core.model.VmResourceId;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+import org.eclipse.debug.core.sourcelookup.containers.ContainerSourceContainer;
+import org.eclipse.debug.core.sourcelookup.containers.DefaultSourceContainer;
+
+/**
+ * Eclipse has a standard facility for looking up source file for a debug artifact.
+ * LiveEdit feature has an opposite problem: find script in remote VM for a particular js file.
+ * This class implements some approach to this problem. An instance of this class corresponds
+ * to a particular debug launch.
+ */
+public class ReverseSourceLookup {
+  private final ISourceLookupDirector sourceDirector;
+
+  public ReverseSourceLookup(ISourceLookupDirector sourceDirector) {
+    this.sourceDirector = sourceDirector;
+  }
+
+  /**
+   * Tries to find a corresponding script for a file from a user workspace. The method uses
+   * the file name and current source lookup rules to retrieve a resource id, regardless of
+   * whether the resource has actually been loaded into the VM (you may want to set a breakpoint
+   * on resource before it is actually loaded).
+   */
+  public VmResourceId findVmResource(IFile sourceFile) throws CoreException {
+    for (ISourceContainer container : sourceDirector.getSourceContainers()) {
+      VmResourceId scriptName = tryForContainer(sourceFile, container);
+      if (scriptName != null) {
+        return scriptName;
+      }
+    }
+    return null;
+  }
+
+  private VmResourceId tryForContainer(IFile sourceFile, ISourceContainer container)
+      throws CoreException {
+    if (container.isComposite() && isSupportedCompositeContainer(container)) {
+      ISourceContainer[] subContainers = container.getSourceContainers();
+      for (ISourceContainer subContainer : subContainers) {
+        VmResourceId res = tryForContainer(sourceFile, subContainer);
+        if (res != null) {
+          return res;
+        }
+      }
+      return null;
+    } else if (container instanceof VProjectSourceContainer) {
+      VProjectSourceContainer projectSourceContainer = (VProjectSourceContainer) container;
+      return projectSourceContainer.findScriptId(sourceFile);
+    } else {
+      String name = tryForNonVirtualContainer(sourceFile, container);
+      if (name == null) {
+        return null;
+      }
+      return VmResourceId.forName(name);
+    }
+  }
+
+  /**
+   * We use {@link ISourceContainer#getSourceContainers()} method to unwrap internal containers.
+   * However it doesn't make sense for all composite containers (some of them may return their
+   * subdirectories as containers, which is not what we need).
+   */
+  private boolean isSupportedCompositeContainer(ISourceContainer container) {
+    return container instanceof DefaultSourceContainer;
+  }
+
+  /**
+   * @param container that may not wrap VProjectSourceContainer
+   */
+  private String tryForNonVirtualContainer(IFile resource, ISourceContainer container) {
+    if (container instanceof ContainerSourceContainer) {
+      ContainerSourceContainer containerSourceContainer = (ContainerSourceContainer) container;
+      IContainer resourceContainer = containerSourceContainer.getContainer();
+      if (resourceContainer.getFullPath().isPrefixOf(resource.getFullPath())) {
+        String name = resource.getFullPath().makeRelativeTo(
+            resourceContainer.getFullPath()).toPortableString();
+        return name;
+      }
+    } else if (container instanceof SourceNameMapperContainer) {
+      SourceNameMapperContainer mappingContainer =
+          (SourceNameMapperContainer) container;
+      String subResult = tryForNonVirtualContainer(resource, mappingContainer.getTargetContainer());
+      if (subResult != null) {
+        return mappingContainer.getPrefix() + subResult;
+      }
+    }
+
+    return null;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/SourceNameMapperContainer.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,188 @@
+package org.chromium.debug.core;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerType;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerTypeDelegate;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * A special type of container that translates names of the source and delegates lookup
+ * to another source container.
+ * This could be useful when JS resource name is like "http://localhost/scripts/util.js"; such
+ * source name could be converted into "scripts/util.js".
+ * Currently container supports only prefix-based translation: if source name starts with a prefix,
+ * the prefix is truncated; otherwise the source name is discarded.
+ */
+public class SourceNameMapperContainer implements ISourceContainer {
+
+  private static final String TYPE_ID =
+      "org.chromium.debug.core.SourceNameMapperContainer.type"; //$NON-NLS-1$
+
+  private final ISourceContainer targetContainer;
+  private final String prefix;
+
+  public SourceNameMapperContainer(String prefix, ISourceContainer targetContainer) {
+    this.targetContainer = targetContainer;
+    this.prefix = prefix;
+  }
+
+  public void dispose() {
+  }
+
+  public String getPrefix() {
+    return prefix;
+  }
+
+  public ISourceContainer getTargetContainer() {
+    return targetContainer;
+  }
+
+  public Object[] findSourceElements(String name) throws CoreException {
+    if (!name.startsWith(prefix)) {
+      return new Object[0];
+    }
+    String shortName = name.substring(prefix.length());
+    return targetContainer.findSourceElements(shortName);
+  }
+
+
+  public String getName() {
+    return NLS.bind(Messages.SourceNameMapperContainer_NAME, prefix);
+  }
+
+
+  public ISourceContainer[] getSourceContainers() {
+    return new ISourceContainer[] { targetContainer };
+  }
+
+
+  public ISourceContainerType getType() {
+    return DebugPlugin.getDefault().getLaunchManager().getSourceContainerType(TYPE_ID);
+  }
+
+
+  public void init(ISourceLookupDirector director) {
+  }
+
+
+  public boolean isComposite() {
+    return true;
+  }
+
+  private String getMemento() throws CoreException {
+    StringBuilder builder = new StringBuilder();
+    MementoFormat.encodeComponent(prefix, builder);
+    MementoFormat.encodeComponent(targetContainer.getType().getId(), builder);
+    MementoFormat.encodeComponent(targetContainer.getType().getMemento(targetContainer), builder);
+    return builder.toString();
+  }
+
+
+  public Object getAdapter(Class adapter) {
+    return null;
+  }
+
+  /**
+   * A type delegate that serializes/deserializes container instances into/from memento.
+   */
+  public static class TypeDelegate implements ISourceContainerTypeDelegate {
+    public ISourceContainer createSourceContainer(String memento) throws CoreException {
+      MementoFormat.Parser parser = new MementoFormat.Parser(memento);
+      String prefix;
+      String typeId;
+      String subContainerMemento;
+      try {
+        prefix = parser.nextComponent();
+        typeId = parser.nextComponent();
+        subContainerMemento = parser.nextComponent();
+      } catch (MementoFormat.ParserException e) {
+        throw new CoreException(new Status(IStatus.ERROR,
+            ChromiumDebugPlugin.PLUGIN_ID, "Failed to parse memento", e)); //$NON-NLS-1$
+      }
+      ISourceContainerType subContainerType =
+          DebugPlugin.getDefault().getLaunchManager().getSourceContainerType(typeId);
+      ISourceContainer subContainer = subContainerType.createSourceContainer(subContainerMemento);
+      return new SourceNameMapperContainer(prefix, subContainer);
+    }
+
+    public String getMemento(ISourceContainer container) throws CoreException {
+      SourceNameMapperContainer chromeContainer = (SourceNameMapperContainer) container;
+      return chromeContainer.getMemento();
+    }
+  }
+
+  /**
+   * Handles memento string format. The format is just a sequence of strings that are preceded
+   * with their lengths and decorated with parentheses to make it more human-readable.
+   */
+  private static class MementoFormat {
+
+    static void encodeComponent(String component, StringBuilder output) {
+      output.append(component.length());
+      output.append('(').append(component).append(')');
+    }
+
+    /**
+     * A simple parser that reads char sequence as a sequence of strings.
+     */
+    static class Parser {
+      private final CharSequence charSequence;
+      private int pos = 0;
+      Parser(CharSequence charSequence) {
+        this.charSequence = charSequence;
+      }
+      String nextComponent() throws ParserException {
+        if (pos >= charSequence.length()) {
+          throw new ParserException("Unexpected end of line"); //$NON-NLS-1$
+        }
+        char ch = charSequence.charAt(pos);
+        pos++;
+        int num = Character.digit(ch, 10);
+        if (num == -1) {
+          throw new ParserException("Digit expected"); //$NON-NLS-1$
+        }
+        int len = num;
+        while (true) {
+          if (pos >= charSequence.length()) {
+            throw new ParserException("Unexpected end of line"); //$NON-NLS-1$
+          }
+          ch = charSequence.charAt(pos);
+          if (!Character.isDigit(ch)) {
+            break;
+          }
+          pos++;
+          num = Character.digit(ch, 10);
+          if (num == -1) {
+            throw new ParserException("Digit expected"); //$NON-NLS-1$
+          }
+          len = len * 10 + num;
+        }
+        pos++;
+        if (pos + len + 1 > charSequence.length()) {
+          throw new ParserException("Unexpected end of line"); //$NON-NLS-1$
+        }
+        String result = charSequence.subSequence(pos, pos + len).toString();
+        pos += len + 1;
+        return result;
+      }
+    }
+    private static class ParserException extends Exception {
+      ParserException() {
+      }
+      ParserException(String message, Throwable cause) {
+        super(message, cause);
+      }
+      ParserException(String message) {
+        super(message);
+      }
+      ParserException(Throwable cause) {
+        super(cause);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/VProjectSourceContainer.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,107 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core;
+
+import org.chromium.debug.core.model.ResourceManager;
+import org.chromium.debug.core.model.VmResourceId;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerType;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerTypeDelegate;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+
+/**
+ * A source container implementation that wraps V8 virtual project. Currently virtual project
+ * has a flat file structure, so the container is accordingly one-level.
+ * <p>
+ * Unlike other implementation of {@link ISourceContainer} this class initially gets instantiated
+ * with no data. In this state it serves as an empty container because actual VM scripts are
+ * not available yet. Launch configuration UI will use it in this state more as a symbolic
+ * place-holder in sources tab. Later when VM is connected, method
+ * {@link #init(ISourceLookupDirector)} will be called and the actual content will be set.
+ */
+public class VProjectSourceContainer implements ISourceContainer {
+
+  private static final String TYPE_ID =
+      "org.chromium.debug.core.VProjectSourceContainer.type"; //$NON-NLS-1$
+
+  private ChromiumSourceDirector chromiumSourceDirector = null;
+
+  VProjectSourceContainer() {
+  }
+
+  public void init(ISourceLookupDirector director) {
+    if (director instanceof ChromiumSourceDirector) {
+      chromiumSourceDirector = (ChromiumSourceDirector) director;
+    }
+  }
+
+  public void dispose() {
+  }
+
+  public Object[] findSourceElements(String name) {
+    if (chromiumSourceDirector == null) {
+      return new Object[0];
+    }
+    ResourceManager resourceManager = chromiumSourceDirector.getResourceManager();
+    IFile file = resourceManager.getFile(name);
+    if (file == null) {
+      return new Object[0];
+    }
+    return new Object[] { file };
+  }
+
+  public String getName() {
+    IProject project = null;
+    if (chromiumSourceDirector != null) {
+      project = chromiumSourceDirector.getProject();
+    }
+    if (project == null) {
+      return Messages.VProjectSourceContainer_DEFAULT_TYPE_NAME;
+    } else {
+      return project.getName();
+    }
+  }
+
+  public ISourceContainer[] getSourceContainers() {
+    return null;
+  }
+
+  public ISourceContainerType getType() {
+    return DebugPlugin.getDefault().getLaunchManager().getSourceContainerType(TYPE_ID);
+  }
+
+  public boolean isComposite() {
+    return false;
+  }
+
+  public VmResourceId findScriptId(IFile resource) {
+    if (chromiumSourceDirector == null) {
+      throw new IllegalStateException();
+    }
+    return chromiumSourceDirector.getResourceManager().getResourceId(resource);
+  }
+
+  public Object getAdapter(Class adapter) {
+    return null;
+  }
+
+  /**
+   * A type delegate that implements a trivial memento. We do not save any actual data here,
+   * because it all should be derived from a current VM launch.
+   */
+  public static class TypeDelegate implements ISourceContainerTypeDelegate {
+    public ISourceContainer createSourceContainer(String memento) throws CoreException {
+      return new VProjectSourceContainer();
+    }
+
+    public String getMemento(ISourceContainer container) throws CoreException {
+      return "VProjectSourceContainer.memento.stub"; //$NON-NLS-1$
+    }
+  }
+}
--- a/org.chromium.debug.core/src/org/chromium/debug/core/messages.properties	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/messages.properties	Mon Jun 07 16:51:19 2010 -0700
@@ -3,3 +3,5 @@
 # found in the LICENSE file.
 
 ChromiumDebugPlugin_InternalError=Internal Error
+SourceNameMapperContainer_NAME=Source mapper with prefix {0}
+VProjectSourceContainer_DEFAULT_TYPE_NAME=Remote V8/Chrome Scripts
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointAdapterFactory.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointAdapterFactory.java	Mon Jun 07 16:51:19 2010 -0700
@@ -23,7 +23,7 @@
           (IResource) editorPart.getEditorInput().getAdapter(IResource.class);
       if (resource != null) {
         String extension = resource.getFileExtension();
-        if (extension != null && ChromiumDebugPluginUtil.CHROMIUM_EXTENSION.equals(extension)) {
+        if (extension != null && ChromiumDebugPluginUtil.SUPPORTED_EXTENSIONS.contains(extension)) {
           return new LineBreakpointAdapter.ForVirtualProject();
         }
       }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointMap.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,59 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.chromium.sdk.Breakpoint;
+
+/**
+ * TODO(peter.rybin): this class is obsolete, it only holds useful inner class;
+ * consider removing this class.
+ */
+public class BreakpointMap {
+
+  /**
+   * A one-to-one map between SDK and UI breakpoints inside one debug target.
+   */
+  public static class InTargetMap {
+    private final Map<Breakpoint, ChromiumLineBreakpoint> sdkToUiMap =
+        new HashMap<Breakpoint, ChromiumLineBreakpoint>();
+    private final Map<ChromiumLineBreakpoint, Breakpoint> uiToSdkMap =
+      new HashMap<ChromiumLineBreakpoint, Breakpoint>();
+
+    public InTargetMap() {
+    }
+
+    public synchronized Breakpoint getSdkBreakpoint(ChromiumLineBreakpoint chromiumLineBreakpoint) {
+      return uiToSdkMap.get(chromiumLineBreakpoint);
+    }
+
+    public synchronized ChromiumLineBreakpoint getUiBreakpoint(Breakpoint sdkBreakpoint) {
+      return sdkToUiMap.get(sdkBreakpoint);
+    }
+
+    public synchronized void add(Breakpoint sdkBreakpoint, ChromiumLineBreakpoint uiBreakpoint) {
+      Object conflict1 = uiToSdkMap.put(uiBreakpoint, sdkBreakpoint);
+      Object conflict2 = sdkToUiMap.put(sdkBreakpoint, uiBreakpoint);
+      if (conflict1 != null || conflict2 != null) {
+        throw new RuntimeException();
+      }
+    }
+
+    public synchronized void remove(ChromiumLineBreakpoint lineBreakpoint) {
+      Breakpoint sdkBreakpoint = uiToSdkMap.remove(lineBreakpoint);
+      if (sdkBreakpoint == null) {
+        throw new RuntimeException();
+      }
+      sdkToUiMap.remove(sdkBreakpoint);
+    }
+
+    public synchronized void clear() {
+      sdkToUiMap.clear();
+      uiToSdkMap.clear();
+    }
+  }
+}
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointRegistry.java	Mon Jun 07 16:33:07 2010 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,227 +0,0 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.debug.core.model;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.Map;
-
-import org.chromium.sdk.Breakpoint;
-import org.chromium.sdk.Script;
-
-/**
- * A registry of existing breakpoints associated with their script locations. It
- * is used to restore
- */
-public class BreakpointRegistry {
-
-  /**
-   * Script identifier for a breakpoint location.
-   */
-  public static class ScriptIdentifier {
-    private final String name;
-
-    private final long id;
-
-    private final int startLine;
-
-    private final int endLine;
-
-    public static ScriptIdentifier forScript(Script script) {
-      String name = script.getName();
-      return new ScriptIdentifier(
-          name,
-          name != null ? -1 : script.getId(),
-          script.getStartLine(),
-          script.getEndLine());
-    }
-
-    private ScriptIdentifier(String name, long id, int startLine, int endLine) {
-      this.name = name;
-      this.id = id;
-      this.startLine = startLine;
-      this.endLine = endLine;
-    }
-
-    @Override
-    public int hashCode() {
-      final int prime = 31;
-      int result = 1;
-      result = prime * result + (int) (id ^ (id >>> 32));
-      result = prime * result + ((name == null) ? 0 : name.hashCode());
-      result = prime * result + 17 * startLine + 19 * endLine;
-      return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (!(obj instanceof ScriptIdentifier)) {
-        return false;
-      }
-      ScriptIdentifier that = (ScriptIdentifier) obj;
-      if (this.startLine != that.startLine || this.endLine != that.endLine) {
-        return false;
-      }
-      if (name == null) {
-        // an unnamed script, only id is known
-        return that.name == null && this.id == that.id;
-      }
-      // a named script
-      return this.name.equals(that.name);
-    }
-  }
-
-  static class BreakpointLocation {
-    private final ScriptIdentifier scriptIdentifier;
-
-    private final int line;
-
-    public BreakpointLocation(ScriptIdentifier scriptIdentifier, int line) {
-      this.scriptIdentifier = scriptIdentifier;
-      this.line = line;
-    }
-
-    public ScriptIdentifier getScriptIdentifier() {
-      return scriptIdentifier;
-    }
-
-    public int getLine() {
-      return line;
-    }
-
-    @Override
-    public int hashCode() {
-      final int prime = 31;
-      int result = 1;
-      result = prime * result + line;
-      result = prime * result + ((scriptIdentifier == null)
-          ? 0
-          : scriptIdentifier.hashCode());
-      return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (!(obj instanceof BreakpointLocation)) {
-        return false;
-      }
-      BreakpointLocation that = (BreakpointLocation) obj;
-      return (this.line == that.line && eq(this.scriptIdentifier, that.scriptIdentifier));
-    }
-  }
-
-  /**
-   * A breakpoint accompanied by its line number in the corresponding enclosing
-   * resource.
-   */
-  public static class BreakpointEntry {
-    public final Breakpoint breakpoint;
-
-    public final int line;
-
-    private BreakpointEntry(Breakpoint breakpoint, int line) {
-      this.breakpoint = breakpoint;
-      this.line = line;
-    }
-
-    boolean isWithinScriptRange(Script script) {
-      return line >= script.getStartLine() && line <= script.getEndLine();
-    }
-
-    @Override
-    public int hashCode() {
-      return 31 * line + 17 * breakpoint.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      if (!(obj instanceof BreakpointEntry)) {
-        return false;
-      }
-      BreakpointEntry that = (BreakpointEntry) obj;
-      return this.line == that.line && this.breakpoint.equals(that.breakpoint);
-    }
-  }
-
-  private final Map<ScriptIdentifier, Collection<BreakpointEntry>> scriptIdToBreakpointEntries =
-      new HashMap<ScriptIdentifier, Collection<BreakpointEntry>>();
-
-  /**
-   * Adds the given line breakpoint.
-   *
-   * @param script where the breakpoint is set
-   * @param line (0-based, like in V8) in the script
-   * @param breakpoint
-   */
-  public void add(Script script, int line, Breakpoint breakpoint) {
-    ScriptIdentifier scriptId = ScriptIdentifier.forScript(script);
-    Collection<BreakpointEntry> entries = scriptIdToBreakpointEntries.get(scriptId);
-    if (entries == null) {
-      entries = new HashSet<BreakpointEntry>();
-      scriptIdToBreakpointEntries.put(scriptId, entries);
-    }
-    entries.add(new BreakpointEntry(breakpoint, line));
-  }
-
-  /**
-   * Gets breakpoint entries for the given script. An empty collection for a
-   * {@code null} script.
-   *
-   * @param script to extract the breakpoints for
-   * @return the breakpoints that fall within the given script line range
-   */
-  public Collection<? extends BreakpointEntry> getBreakpointEntries(Script script) {
-    if (script == null) {
-      return Collections.emptySet();
-    }
-    Collection<BreakpointEntry> entries =
-        scriptIdToBreakpointEntries.get(ScriptIdentifier.forScript(script));
-    if (entries == null) {
-      return Collections.emptySet();
-    }
-    Collection<BreakpointEntry> scriptBreakpoints = new LinkedList<BreakpointEntry>();
-    // Linear search should work fairly well for a reasonable number of
-    // breakpoints per script
-    for (BreakpointEntry entry : entries) {
-      if (entry.isWithinScriptRange(script)) {
-        scriptBreakpoints.add(entry);
-      }
-    }
-    return scriptBreakpoints;
-  }
-
-  /**
-   * Removes the given line breakpoint in the given script. Does nothing for a
-   * {@code null} script (which may be the case after a navigation + disconnect
-   * when the resource referenced by the breakpoint marker is absent.)
-   *
-   * @param script where the breakpoint is set
-   * @param line (0-based, like in V8) in the script
-   * @param breakpoint
-   */
-  public void remove(Script script, int line, Breakpoint breakpoint) {
-    if (script == null) {
-      return;
-    }
-    ScriptIdentifier scriptId = ScriptIdentifier.forScript(script);
-    Collection<BreakpointEntry> entries = scriptIdToBreakpointEntries.get(scriptId);
-    if (entries == null) {
-      return;
-    }
-    if (entries.size() == 1) {
-      scriptIdToBreakpointEntries.remove(scriptId);
-    } else {
-      entries.remove(new BreakpointEntry(breakpoint, line));
-    }
-  }
-
-  protected static boolean eq(Object left, Object right) {
-    return left == right || (left != null && left.equals(right));
-  }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointSynchronizer.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,631 @@
+package org.chromium.debug.core.model;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.chromium.debug.core.ChromiumDebugPlugin;
+import org.chromium.debug.core.ChromiumSourceDirector;
+import org.chromium.sdk.Breakpoint;
+import org.chromium.sdk.CallbackSemaphore;
+import org.chromium.sdk.JavascriptVm;
+import org.chromium.sdk.SyncCallback;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.IBreakpointManager;
+import org.eclipse.debug.core.model.IBreakpoint;
+
+/**
+ * A class responsible for comparing breakpoints in workspace and on remote VM and synchronizing
+ * them in both directions. {@link Direction#RESET_REMOTE} allows several synchronization
+ * jobs to different VMs.
+ */
+public class BreakpointSynchronizer {
+  private final JavascriptVm javascriptVm;
+  private final BreakpointMap.InTargetMap breakpointInTargetMap;
+  private final ChromiumSourceDirector sourceDirector;
+  private final BreakpointHelper breakpointHelper;
+  private final String debugModelId;
+
+  public BreakpointSynchronizer(JavascriptVm javascriptVm,
+      BreakpointMap.InTargetMap breakpointInTargetMap,
+      ChromiumSourceDirector sourceDirector, BreakpointHelper breakpointHelper,
+      String debugModelId) {
+    this.javascriptVm = javascriptVm;
+    this.breakpointInTargetMap = breakpointInTargetMap;
+    this.sourceDirector = sourceDirector;
+    this.breakpointHelper = breakpointHelper;
+    this.debugModelId = debugModelId;
+  }
+
+  /**
+   * Describes a direction the breakpoint synchronization should be performed in.
+   */
+  public enum Direction {
+
+    /**
+     * All breakpoints in remote VM/VMs are cleared/updated/created to conform to breakpoints in
+     * Eclipse workspace.
+     */
+    RESET_REMOTE,
+
+    /**
+     * All breakpoints in local workspace are cleared/updated/created to conform to breakpoints in
+     * remote VM (not applicable for multiple VMs).
+     */
+    RESET_LOCAL,
+
+    /**
+     * Breakpoints are created locally or remotely or tied together so that every breakpoint
+     * has a counterpart on other side.
+     */
+    MERGE
+  }
+
+  /**
+   * Additional interface used by {@link BreakpointSynchronizer}.
+   */
+  public interface BreakpointHelper {
+    /**
+     * Create breakpoint on remote VM (asynchronously) and link it to uiBreakpoint.
+     */
+    void createBreakpointOnRemote(ChromiumLineBreakpoint uiBreakpoint,
+        VmResourceId vmResourceId,
+        CreateCallback createCallback, SyncCallback syncCallback);
+
+    interface CreateCallback {
+      void failure(Exception ex);
+      void success();
+    }
+  }
+
+  public interface Callback {
+    void onDone(IStatus status);
+  }
+
+  /**
+   * The main entry method of the class. Asynchronously performs synchronization job.
+   * TODO(peter.rybin): consider some end-of-job notification for this method and possibly locks
+   */
+  public void syncBreakpoints(Direction direction, Callback callback) {
+    ReportBuilder reportBuilder = new ReportBuilder(direction);
+    StatusBuilder statusBuilder = new StatusBuilder(callback, reportBuilder);
+
+    statusBuilder.plan();
+    Exception ex = null;
+    try {
+      syncBreakpointsImpl(direction, statusBuilder);
+    } catch (RuntimeException e) {
+      ex = e;
+    } finally {
+      statusBuilder.done(ex);
+    }
+  }
+
+  private void syncBreakpointsImpl(final Direction direction, final StatusBuilder statusBuilder) {
+    // Collect the remote breakpoints.
+    Collection<? extends Breakpoint> sdkBreakpoints = readSdkBreakpoints(javascriptVm);
+    // Collect all local breakpoints.
+    Set<ChromiumLineBreakpoint> uiBreakpoints = getUiBreakpoints();
+
+    List<Breakpoint> sdkBreakpoints2 = new ArrayList<Breakpoint>(sdkBreakpoints.size());
+
+    if (direction != Direction.MERGE) {
+      breakpointInTargetMap.clear();
+    }
+
+    // Throw away all already linked breakpoints and put remaining into sdkBreakpoints2 list.
+    for (Breakpoint sdkBreakpoint : sdkBreakpoints) {
+      ChromiumLineBreakpoint uiBreakpoint = breakpointInTargetMap.getUiBreakpoint(sdkBreakpoint);
+      if (uiBreakpoint == null) {
+        // No mapping. Schedule for further processing.
+        sdkBreakpoints2.add(sdkBreakpoint);
+      } else {
+        // There is a live mapping. This set should also contain this breakpoint.
+        uiBreakpoints.remove(uiBreakpoint);
+        statusBuilder.getReportBuilder().increment(ReportBuilder.Property.LINKED);
+      }
+    }
+
+    // Sort all breakpoints by (script_name, line_number).
+    SortedBreakpoints<ChromiumLineBreakpoint> sortedUiBreakpoints =
+        sortBreakpoints(uiBreakpoints, uiBreakpointHandler);
+    SortedBreakpoints<Breakpoint> sortedSdkBreakpoints =
+        sortBreakpoints(sdkBreakpoints2, sdkBreakpointHandler);
+
+    BreakpointMerger breakpointMerger = new BreakpointMerger(direction, breakpointInTargetMap);
+
+    // Find all unlinked breakpoints on both sides.
+    mergeBreakpoints(breakpointMerger, sortedUiBreakpoints, sortedSdkBreakpoints);
+
+    List<Breakpoint> sdkBreakpointsToDelete;
+    List<Breakpoint> sdkBreakpointsToCreate;
+    List<ChromiumLineBreakpoint> uiBreakpointsToDelete;
+    List<ChromiumLineBreakpoint> uiBreakpointsToCreate;
+
+    // Plan actions for all breakpoints without pair.
+    if (direction == Direction.RESET_REMOTE) {
+      sdkBreakpointsToDelete = breakpointMerger.getMissingSdk();
+      sdkBreakpointsToCreate = Collections.emptyList();
+    } else {
+      sdkBreakpointsToCreate = breakpointMerger.getMissingSdk();
+      sdkBreakpointsToDelete = Collections.emptyList();
+    }
+
+    if (direction == Direction.RESET_LOCAL) {
+      uiBreakpointsToDelete = breakpointMerger.getMissingUi();
+      uiBreakpointsToCreate = Collections.emptyList();
+    } else {
+      uiBreakpointsToCreate = breakpointMerger.getMissingUi();
+      uiBreakpointsToDelete = Collections.emptyList();
+    }
+
+    // First delete everything, then create (we may need to re-create some breakpoints, so order
+    // is significant).
+    deteleBreakpoints(sdkBreakpointsToDelete, uiBreakpointsToDelete, statusBuilder);
+    createBreakpoints(sdkBreakpointsToCreate, uiBreakpointsToCreate, statusBuilder);
+  }
+
+  private void deteleBreakpoints(List<Breakpoint> sdkBreakpointsToDelete,
+      List<ChromiumLineBreakpoint> uiBreakpointsToDelete, final StatusBuilder statusBuilder) {
+    for (Breakpoint sdkBreakpoint : sdkBreakpointsToDelete) {
+      final PlannedTaskHelper deleteTaskHelper = new PlannedTaskHelper(statusBuilder);
+      JavascriptVm.BreakpointCallback callback = new JavascriptVm.BreakpointCallback() {
+        public void failure(String errorMessage) {
+          deleteTaskHelper.setException(new Exception(errorMessage));
+        }
+        public void success(Breakpoint breakpoint) {
+          statusBuilder.getReportBuilder().increment(ReportBuilder.Property.DELETED_ON_REMOTE);
+        }
+      };
+      sdkBreakpoint.clear(callback, deleteTaskHelper);
+    }
+    for (ChromiumLineBreakpoint uiBreakpoint : uiBreakpointsToDelete) {
+      ChromiumLineBreakpoint.getIgnoreList().add(uiBreakpoint);
+      try {
+        try {
+          uiBreakpoint.delete();
+        } catch (CoreException e) {
+          throw new RuntimeException(e);
+        }
+      } finally {
+        ChromiumLineBreakpoint.getIgnoreList().remove(uiBreakpoint);
+      }
+      statusBuilder.getReportBuilder().increment(ReportBuilder.Property.DELETED_LOCALLY);
+    }
+  }
+
+  private void createBreakpoints(List<Breakpoint> sdkBreakpointsToCreate,
+      List<ChromiumLineBreakpoint> uiBreakpointsToCreate, final StatusBuilder statusBuilder) {
+    IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
+    for (Breakpoint sdkBreakpoint : sdkBreakpointsToCreate) {
+      Object sourceElement = sourceDirector.getSourceElement(sdkBreakpoint);
+      if (sourceElement instanceof IFile == false) {
+        continue;
+      }
+      // We do not actually support working files for scripts with offset.
+      int script_line_offset = 0;
+      IFile resource = (IFile) sourceElement;
+      ChromiumLineBreakpoint uiBreakpoint;
+      try {
+        uiBreakpoint = ChromiumLineBreakpoint.Helper.createLocal(sdkBreakpoint, breakpointManager,
+            resource, script_line_offset, debugModelId);
+        breakpointInTargetMap.add(sdkBreakpoint, uiBreakpoint);
+      } catch (CoreException e) {
+        throw new RuntimeException(e);
+      }
+      statusBuilder.getReportBuilder().increment(ReportBuilder.Property.CREATED_LOCALLY);
+    }
+    for (ChromiumLineBreakpoint uiBreakpoint : uiBreakpointsToCreate) {
+      VmResourceId vmResourceId = uiBreakpointHandler.getVmResourceId(uiBreakpoint);
+      if (vmResourceId == null) {
+        // Actually we should not get here, because getScript call succeeded before.
+        continue;
+      }
+
+      final PlannedTaskHelper createTaskHelper = new PlannedTaskHelper(statusBuilder);
+      BreakpointHelper.CreateCallback createCallback = new BreakpointHelper.CreateCallback() {
+        public void success() {
+          statusBuilder.getReportBuilder().increment(ReportBuilder.Property.CREATED_ON_REMOTE);
+        }
+        public void failure(Exception ex) {
+          createTaskHelper.setException(ex);
+        }
+      };
+      breakpointHelper.createBreakpointOnRemote(uiBreakpoint, vmResourceId, createCallback,
+          createTaskHelper);
+    }
+  }
+
+  private static class BreakpointMerger extends Merger<ChromiumLineBreakpoint, Breakpoint> {
+    private final Direction direction;
+    private final List<ChromiumLineBreakpoint> missingUi = new ArrayList<ChromiumLineBreakpoint>();
+    private final List<Breakpoint> missingSdk = new ArrayList<Breakpoint>();
+    private final BreakpointMap.InTargetMap breakpointMap;
+
+    BreakpointMerger(Direction direction, BreakpointMap.InTargetMap breakpointMap) {
+      this.direction = direction;
+      this.breakpointMap = breakpointMap;
+    }
+    @Override
+    void both(ChromiumLineBreakpoint v1, Breakpoint v2) {
+      if (direction == Direction.MERGE) {
+        breakpointMap.add(v2, v1);
+      } else {
+        onlyFirst(v1);
+        onlySecond(v2);
+      }
+    }
+    @Override
+    void onlyFirst(ChromiumLineBreakpoint v1) {
+      missingUi.add(v1);
+    }
+    @Override
+    void onlySecond(Breakpoint v2) {
+      missingSdk.add(v2);
+    }
+    List<ChromiumLineBreakpoint> getMissingUi() {
+      return missingUi;
+    }
+    List<Breakpoint> getMissingSdk() {
+      return missingSdk;
+    }
+  }
+
+  /**
+   * A class responsible for creating a summary status of synchronization operation. The status
+   * is created once all asynchronous jobs have finished. Each job first registers itself
+   * via {@link #plan()} method and
+   * later reports its result via {@link #done(Exception)} method.
+   * When the last job is reporting its finishing, the status gets built and sent to
+   * {@link #callback}. If no exceptions were registered,
+   * status contains text report from {@link ReportBuilder}.
+   */
+  private static class StatusBuilder {
+    private final Callback callback;
+    private int plannedNumber = 0;
+    private final List<Exception> exceptions = new ArrayList<Exception>(0);
+    private boolean alreadyReported = false;
+    private final ReportBuilder reportBuilder;
+
+    StatusBuilder(Callback callback, ReportBuilder reportBuilder) {
+      this.callback = callback;
+      this.reportBuilder = reportBuilder;
+    }
+
+    ReportBuilder getReportBuilder() {
+      return reportBuilder;
+    }
+
+    public synchronized void plan() {
+      if (alreadyReported) {
+        throw new IllegalStateException();
+      }
+      plannedNumber++;
+    }
+
+    public void done(Exception ex) {
+      boolean timeToReport = doneImpl(ex);
+      if (timeToReport) {
+        reportResult();
+      }
+    }
+
+    private synchronized boolean doneImpl(Exception ex) {
+      if (ex != null) {
+        exceptions.add(ex);
+      }
+      plannedNumber--;
+      if (plannedNumber == 0) {
+        if (!alreadyReported) {
+          alreadyReported = true;
+          return true;
+        }
+      }
+      return false;
+    }
+
+    private void reportResult() {
+      IStatus status;
+      if (exceptions.isEmpty()) {
+        status = new Status(IStatus.OK, ChromiumDebugPlugin.PLUGIN_ID,
+            "Breakpoint synchronization done: " + reportBuilder.build(), null); //$NON-NLS-1$
+      } else {
+        IStatus[] subStatuses = new IStatus[exceptions.size()];
+        for (int i = 0 ; i < subStatuses.length; i++) {
+          subStatuses[i] = new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID,
+              exceptions.get(i).getMessage(), exceptions.get(i));
+        }
+        status = new MultiStatus(ChromiumDebugPlugin.PLUGIN_ID, IStatus.ERROR, subStatuses,
+            "Breakpoint synchronization errors", null); //$NON-NLS-1$
+      }
+      if (callback != null) {
+        callback.onDone(status);
+      }
+    }
+  }
+
+  private static class PlannedTaskHelper implements SyncCallback {
+    private final StatusBuilder statusBuilder;
+    private volatile Exception exception = null;
+    PlannedTaskHelper(StatusBuilder statusBuilder) {
+      this.statusBuilder = statusBuilder;
+      statusBuilder.plan();
+    }
+    public void callbackDone(RuntimeException e) {
+      if (e != null) {
+        exception = e;
+      }
+      statusBuilder.done(exception);
+    }
+    void setException(Exception ex) {
+      exception = ex;
+    }
+  }
+
+  /**
+   * A class that contains several conunters.
+   */
+  private static class ReportBuilder {
+    enum Property {
+      LINKED,
+      CREATED_LOCALLY,
+      DELETED_LOCALLY,
+      CREATED_ON_REMOTE,
+      DELETED_ON_REMOTE;
+      String getVisibleName() {
+        return toString();
+      }
+    }
+
+    private final Direction direction;
+    private final Map<Property, AtomicInteger> counters;
+
+    ReportBuilder(Direction direction) {
+      this.direction = direction;
+      counters = new EnumMap<Property, AtomicInteger>(Property.class);
+      for (Property property : Property.class.getEnumConstants()) {
+        counters.put(property, new AtomicInteger(0));
+      }
+    }
+
+    public void increment(Property property) {
+      counters.get(property).addAndGet(1);
+    }
+
+    public String build() {
+      StringBuilder builder = new StringBuilder();
+      builder.append("direction=").append(direction); //$NON-NLS-1$
+      for (Map.Entry<Property, AtomicInteger> en : counters.entrySet()) {
+        int number = en.getValue().get();
+        if (number == 0) {
+          continue;
+        }
+        builder.append(" ").append(en.getKey().getVisibleName()); //$NON-NLS-1$
+        builder.append("=").append(number); //$NON-NLS-1$
+      }
+      return builder.toString();
+    }
+  }
+
+  /**
+   * A handler for properties of breakpoint type B that helps reading them.
+   */
+  private static abstract class PropertyHandler<B> {
+    /** @return vm resource name or null */
+    abstract VmResourceId getVmResourceId(B breakpoint);
+    /** @return 0-based number */
+    abstract long getLineNumber(B breakpoint);
+  }
+
+  private final PropertyHandler<ChromiumLineBreakpoint> uiBreakpointHandler =
+      new PropertyHandler<ChromiumLineBreakpoint>() {
+      @Override
+    long getLineNumber(ChromiumLineBreakpoint chromiumLineBreakpoint) {
+      int lineNumber;
+      try {
+        // TODO(peter.rybin): Consider supporting inline scripts here.
+        return chromiumLineBreakpoint.getLineNumber() - 1;
+      } catch (CoreException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    VmResourceId getVmResourceId(ChromiumLineBreakpoint chromiumLineBreakpoint) {
+      IMarker marker = chromiumLineBreakpoint.getMarker();
+      if (marker == null) {
+        return null;
+      }
+      IResource resource = marker.getResource();
+      if (resource instanceof IFile == false) {
+        return null;
+      }
+      IFile file = (IFile) resource;
+      try {
+        return sourceDirector.getReverseSourceLookup().findVmResource(file);
+      } catch (CoreException e) {
+        throw new RuntimeException("Failed to read script name from breakpoint", e); //$NON-NLS-1$
+      }
+    }
+  };
+
+  private static final PropertyHandler<Breakpoint> sdkBreakpointHandler =
+      new PropertyHandler<Breakpoint>() {
+    @Override
+    long getLineNumber(Breakpoint breakpoint) {
+      return breakpoint.getLineNumber();
+    }
+
+    @Override
+    VmResourceId getVmResourceId(Breakpoint breakpoint) {
+      if (breakpoint.getType() == Breakpoint.Type.SCRIPT_NAME) {
+        return VmResourceId.forName(breakpoint.getScriptName());
+      } else {
+        Long scriptId = breakpoint.getScriptId();
+        if (scriptId == null) {
+          return null;
+        }
+        return VmResourceId.forId(scriptId);
+      }
+    }
+  };
+
+  /**
+   * A helping structure that holds field of complicated type.
+   */
+  private static class SortedBreakpoints<B> {
+    final Map<VmResourceId, Map<Long, B>> data;
+
+    SortedBreakpoints(Map<VmResourceId, Map<Long, B>> data) {
+      this.data = data;
+    }
+  }
+
+  /**
+   * Put all breakpoints into map script_name -> line_number -> breakpoint.
+   */
+  private static <B> SortedBreakpoints<B> sortBreakpoints(Collection<? extends B> breakpoints,
+      PropertyHandler<B> handler) {
+    Map<VmResourceId, Map<Long, B>> result = new HashMap<VmResourceId, Map<Long, B>>();
+    for (B breakpoint : breakpoints) {
+      VmResourceId vmResourceId = handler.getVmResourceId(breakpoint);
+      if (vmResourceId == null) {
+        continue;
+      }
+      Map<Long, B> subMap = result.get(vmResourceId);
+      if (subMap == null) {
+        subMap = new HashMap<Long, B>(3);
+        result.put(vmResourceId, subMap);
+      }
+      long line = handler.getLineNumber(breakpoint);
+      // For simplicity we ignore multiple breakpoints on the same line.
+      subMap.put(line, breakpoint);
+    }
+    return new SortedBreakpoints<B>(result);
+  }
+
+  /**
+   * A class that implements merge operation for a particular complete/incomplete pair of values.
+   */
+  private static abstract class Merger<V1, V2> {
+    abstract void onlyFirst(V1 v1);
+    abstract void onlySecond(V2 v2);
+    abstract void both(V1 v1, V2 v2);
+  }
+
+  /**
+   * Merges values of 2 maps.
+   * @param map2 must implement {@link Map#remove} method.
+   */
+  private static <K, V1, V2> void mergeMaps(Map<K, V1> map1, Map<K, V2> map2,
+      Merger<V1, V2> merger) {
+    for (Map.Entry<K, V1> en : map1.entrySet()) {
+      V2 v2 = map2.remove(en.getKey());
+      if (v2 == null) {
+        merger.onlyFirst(en.getValue());
+      } else {
+        merger.both(en.getValue(), v2);
+      }
+    }
+    for (V2 v2 : map2.values()) {
+      merger.onlySecond(v2);
+    }
+  }
+
+  private static <B1, B2> void mergeBreakpoints(final Merger<B1, B2> perBreakpointMerger,
+      SortedBreakpoints<B1> side1, SortedBreakpoints<B2> side2) {
+    Merger<Map<Long, B1>, Map<Long, B2>> perScriptMerger =
+        new Merger<Map<Long,B1>, Map<Long,B2>>() {
+      @Override
+      void both(Map<Long, B1> v1, Map<Long, B2> v2) {
+        mergeMaps(v1, v2, perBreakpointMerger);
+      }
+
+      @Override
+      void onlyFirst(Map<Long, B1> v1) {
+        mergeMaps(v1, Collections.<Long, B2>emptyMap(), perBreakpointMerger);
+      }
+
+      @Override
+      void onlySecond(Map<Long, B2> v2) {
+        mergeMaps(Collections.<Long, B1>emptyMap(), v2, perBreakpointMerger);
+      }
+    };
+    mergeMaps(side1.data, side2.data, perScriptMerger);
+  }
+
+
+  private static Collection<? extends Breakpoint> readSdkBreakpoints(JavascriptVm javascriptVm) {
+    class CallbackImpl implements JavascriptVm.ListBreakpointsCallback {
+      public void failure(Exception exception) {
+        problem = exception;
+      }
+
+      public void success(Collection<? extends Breakpoint> breakpoints) {
+        result = breakpoints;
+      }
+      Collection<? extends Breakpoint> getResult() {
+        if (problem != null) {
+          throw new RuntimeException("Failed to synchronize breakpoints", problem); //$NON-NLS-1$
+        }
+        return result;
+      }
+      Exception problem = null;
+      Collection<? extends Breakpoint> result = null;
+    }
+
+    CallbackImpl callback = new CallbackImpl();
+    CallbackSemaphore callbackSemaphore = new CallbackSemaphore();
+
+    javascriptVm.listBreakpoints(callback, callbackSemaphore);
+    boolean res = callbackSemaphore.tryAcquireDefault();
+    if (!res) {
+      throw new RuntimeException("Timeout"); //$NON-NLS-1$
+    }
+
+    return callback.getResult();
+  }
+
+  // We need this method to return Set for future purposes.
+  private Set<ChromiumLineBreakpoint> getUiBreakpoints() {
+    IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
+    Set<ChromiumLineBreakpoint> result = new HashSet<ChromiumLineBreakpoint>();
+
+    for (IBreakpoint breakpoint: breakpointManager.getBreakpoints(debugModelId)) {
+      if (breakpoint instanceof ChromiumLineBreakpoint == false) {
+        continue;
+      }
+      ChromiumLineBreakpoint chromiumLineBreakpoint = (ChromiumLineBreakpoint) breakpoint;
+      result.add(chromiumLineBreakpoint);
+    }
+    return result;
+  }
+
+  public static class ProtocolNotSupportedOnRemote extends Exception {
+    ProtocolNotSupportedOnRemote() {
+    }
+    ProtocolNotSupportedOnRemote(String message, Throwable cause) {
+      super(message, cause);
+    }
+    ProtocolNotSupportedOnRemote(String message) {
+      super(message);
+    }
+    ProtocolNotSupportedOnRemote(Throwable cause) {
+      super(cause);
+    }
+  }
+}
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/ChromiumLineBreakpoint.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/ChromiumLineBreakpoint.java	Mon Jun 07 16:51:19 2010 -0700
@@ -4,13 +4,21 @@
 
 package org.chromium.debug.core.model;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.chromium.debug.core.ChromiumDebugPlugin;
 import org.chromium.sdk.Breakpoint;
+import org.chromium.sdk.JavascriptVm;
+import org.chromium.sdk.SyncCallback;
+import org.chromium.sdk.JavascriptVm.BreakpointCallback;
+import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IWorkspaceRunnable;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.debug.core.IBreakpointManager;
 import org.eclipse.debug.core.model.IBreakpoint;
 import org.eclipse.debug.core.model.LineBreakpoint;
 import org.eclipse.osgi.util.NLS;
@@ -26,25 +34,14 @@
   /** Condition */
   private static final String CONDITION_ATTR = ChromiumDebugPlugin.PLUGIN_ID + ".condition"; //$NON-NLS-1$
 
-  private Breakpoint browserBreakpoint;
-
   /**
    * Default constructor is required for the breakpoint manager to re-create
    * persisted breakpoints. After instantiating a breakpoint, the setMarker
    * method is called to restore this breakpoint's attributes.
    */
-  // FIXME(apavlov): now this does not restore the browserBreakpoint value
   public ChromiumLineBreakpoint() {
   }
 
-  public void setBreakpoint(Breakpoint breakpoint) {
-    this.browserBreakpoint = breakpoint;
-  }
-
-  public Breakpoint getBrowserBreakpoint() {
-    return browserBreakpoint;
-  }
-
   /**
    * Constructs a line breakpoint on the given resource at the given line number
    * (line number is 1-based).
@@ -53,15 +50,15 @@
    * @param lineNumber 1-based line number of the breakpoint
    * @throws CoreException if unable to create the breakpoint
    */
-  public ChromiumLineBreakpoint(final IResource resource, final int lineNumber)
-      throws CoreException {
+  public ChromiumLineBreakpoint(final IResource resource, final int lineNumber,
+      final String modelId) throws CoreException {
     IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
       public void run(IProgressMonitor monitor) throws CoreException {
         IMarker marker = resource.createMarker(ChromiumDebugPlugin.BP_MARKER);
         setMarker(marker);
         marker.setAttribute(IBreakpoint.ENABLED, Boolean.TRUE);
         marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
-        marker.setAttribute(IBreakpoint.ID, getModelIdentifier());
+        marker.setAttribute(IBreakpoint.ID, modelId);
         marker.setAttribute(IMarker.MESSAGE, NLS.bind(
             Messages.JsLineBreakpoint_MessageMarkerFormat, resource.getName(), lineNumber));
       }
@@ -104,21 +101,100 @@
   }
 
   public String getModelIdentifier() {
-    return VProjectWorkspaceBridge.DEBUG_MODEL_ID;
+    return getMarker().getAttribute(IBreakpoint.ID, "");
   }
 
-  public void changed() {
-    if (browserBreakpoint != null) {
-      browserBreakpoint.setCondition(getCondition());
-      browserBreakpoint.setEnabled(isEnabled());
-      browserBreakpoint.setIgnoreCount(getIgnoreCount());
-      browserBreakpoint.flush(null);
+  /**
+   * A helper that propagates changes in Eclipse Debugger breakpoints (i.e.
+   * {@link ChromiumLineBreakpoint}) to ChromeDevTools SDK breakpoints. Note that
+   * {@link ChromiumLineBreakpoint} can't do it itself, because it may correspond to several
+   * SDK {@link JavascriptVm}'s simultaneously.
+   */
+  public static class Helper {
+    public interface CreateOnRemoveCallback {
+      void success(Breakpoint breakpoint);
+      void failure(String errorMessage);
+    }
+
+    public static void createOnRemote(ChromiumLineBreakpoint uiBreakpoint,
+        VmResourceId scriptId, DebugTargetImpl debugTarget,
+        final CreateOnRemoveCallback createOnRemoveCallback,
+        SyncCallback syncCallback) throws CoreException {
+      JavascriptVm javascriptVm = debugTarget.getJavascriptEmbedder().getJavascriptVm();
+
+      // ILineBreakpoint lines are 1-based while V8 lines are 0-based
+      final int line = (uiBreakpoint.getLineNumber() - 1);
+      BreakpointCallback callback = new BreakpointCallback() {
+        public void success(Breakpoint sdkBreakpoint) {
+          createOnRemoveCallback.success(sdkBreakpoint);
+        }
+        public void failure(String errorMessage) {
+          createOnRemoveCallback.failure(errorMessage);
+        }
+      };
+
+      javascriptVm.setBreakpoint(scriptId.getTypeForBreakpoint(),
+          scriptId.getTargetForBreakpoint(),
+          line,
+          Breakpoint.EMPTY_VALUE,
+          uiBreakpoint.isEnabled(),
+          uiBreakpoint.getCondition(),
+          uiBreakpoint.getIgnoreCount(),
+          callback, syncCallback);
+    }
+
+    public static void updateOnRemote(Breakpoint sdkBreakpoint,
+        ChromiumLineBreakpoint uiBreakpoint) {
+      sdkBreakpoint.setCondition(uiBreakpoint.getCondition());
+      sdkBreakpoint.setEnabled(uiBreakpoint.isEnabled());
+      sdkBreakpoint.setIgnoreCount(uiBreakpoint.getIgnoreCount());
+      sdkBreakpoint.flush(null, null);
+    }
+
+    public static ChromiumLineBreakpoint createLocal(Breakpoint sdkBreakpoint,
+        IBreakpointManager breakpointManager, IFile resource, int script_line_offset,
+        String debugModelId) throws CoreException {
+      ChromiumLineBreakpoint uiBreakpoint = new ChromiumLineBreakpoint(resource,
+          (int) sdkBreakpoint.getLineNumber() + 1 + script_line_offset,
+          debugModelId);
+      uiBreakpoint.setCondition(sdkBreakpoint.getCondition());
+      uiBreakpoint.setEnabled(sdkBreakpoint.isEnabled());
+      uiBreakpoint.setIgnoreCount(sdkBreakpoint.getIgnoreCount());
+      ignoreList.add(uiBreakpoint);
+      try {
+        breakpointManager.addBreakpoint(uiBreakpoint);
+      } finally {
+        ignoreList.remove(uiBreakpoint);
+      }
+      return uiBreakpoint;
     }
   }
 
-  public void clear() {
-    if (browserBreakpoint != null) {
-      browserBreakpoint.clear(null);
+  private static final BreakpointIgnoreList ignoreList = new BreakpointIgnoreList();
+
+  public static BreakpointIgnoreList getIgnoreList() {
+    return ignoreList;
+  }
+
+  public static class BreakpointIgnoreList {
+    private final List<ChromiumLineBreakpoint> list = new ArrayList<ChromiumLineBreakpoint>(1);
+
+    public boolean contains(IBreakpoint breakpoint) {
+      return list.contains(breakpoint);
+    }
+
+    public void remove(ChromiumLineBreakpoint lineBreakpoint) {
+      boolean res = list.remove(lineBreakpoint);
+      if (!res) {
+        throw new IllegalStateException();
+      }
+    }
+
+    public void add(ChromiumLineBreakpoint lineBreakpoint) {
+      if (list.contains(lineBreakpoint)) {
+        throw new IllegalStateException();
+      }
+      list.add(lineBreakpoint);
     }
   }
 }
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/DebugTargetImpl.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/DebugTargetImpl.java	Mon Jun 07 16:51:19 2010 -0700
@@ -4,6 +4,7 @@
 
 package org.chromium.debug.core.model;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import org.chromium.debug.core.ChromiumDebugPlugin;
@@ -12,7 +13,9 @@
 import org.chromium.sdk.DebugEventListener;
 import org.chromium.sdk.ExceptionData;
 import org.chromium.sdk.JavascriptVm;
+import org.chromium.sdk.LiveEditDebugEventListener;
 import org.chromium.sdk.Script;
+import org.chromium.sdk.UpdatableScript;
 import org.chromium.sdk.DebugContext.State;
 import org.chromium.sdk.DebugContext.StepAction;
 import org.eclipse.core.resources.IFile;
@@ -338,8 +341,8 @@
     return super.getAdapter(adapter);
   }
 
-  public IFile getScriptResource(Script script) {
-    return workspaceRelations.getScriptResource(script);
+  public VmResource getVmResource(IFile resource) throws CoreException {
+    return workspaceRelations.findVmResourceFromWorkspaceFile(resource);
   }
 
   public JavascriptThread getThread() {
@@ -361,7 +364,7 @@
 
   private final DebugEventListenerImpl debugEventListener = new DebugEventListenerImpl();
 
-  class DebugEventListenerImpl implements DebugEventListener {
+  class DebugEventListenerImpl implements DebugEventListener, LiveEditDebugEventListener {
     // Synchronizes calls from ReaderThread of Connection and one call from some worker thread
     private final Object suspendResumeMonitor = new Object();
     private boolean alreadyResumedOrSuspended = false;
@@ -396,6 +399,11 @@
       workspaceRelations.scriptLoaded(newScript);
     }
 
+    public void scriptContentChanged(UpdatableScript newScript) {
+      listenerBlock.waitUntilReady();
+      workspaceRelations.reloadScript(newScript);
+    }
+
     public void suspended(DebugContext context) {
       listenerBlock.waitUntilReady();
       synchronized (suspendResumeMonitor) {
@@ -419,6 +427,12 @@
     }
   }
 
+  public void synchronizeBreakpoints(BreakpointSynchronizer.Direction direction,
+      BreakpointSynchronizer.Callback callback) {
+    workspaceRelations.synchronizeBreakpoints(direction, callback);
+  }
+
+
   private void logExceptionFromContext(DebugContext context) {
     ExceptionData exceptionData = context.getExceptionData();
     List<? extends CallFrame> callFrames = context.getCallFrames();
@@ -491,9 +505,18 @@
   public WorkspaceBridge.JsLabelProvider getLabelProvider() {
     return workspaceBridgeFactory.getLabelProvider();
   }
-  
-  public int getLineNumber(CallFrame stackFrame) {
-    return workspaceRelations.getLineNumber(stackFrame);
+
+  public static List<DebugTargetImpl> getAllDebugTargetImpls() {
+    IDebugTarget[] array = DebugPlugin.getDefault().getLaunchManager().getDebugTargets();
+    List<DebugTargetImpl> result = new ArrayList<DebugTargetImpl>(array.length);
+    for (IDebugTarget target : array) {
+      if (target instanceof DebugTargetImpl == false) {
+        continue;
+      }
+      DebugTargetImpl debugTargetImpl = (DebugTargetImpl) target;
+      result.add(debugTargetImpl);
+    }
+    return result;
   }
 
   private static class ListenerBlock {
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/LineBreakpointAdapter.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/LineBreakpointAdapter.java	Mon Jun 07 16:51:19 2010 -0700
@@ -28,7 +28,7 @@
         ITextEditor editorPart = (ITextEditor) part;
         IResource resource = (IResource) editorPart.getEditorInput().getAdapter(IResource.class);
         if (resource != null &&
-            ChromiumDebugPluginUtil.CHROMIUM_EXTENSION.equals(resource.getFileExtension())) {
+            ChromiumDebugPluginUtil.SUPPORTED_EXTENSIONS.contains(resource.getFileExtension())) {
           return editorPart;
         }
       }
@@ -62,7 +62,8 @@
       }
 
       // Line numbers start with 0 in V8, with 1 in Eclipse.
-      ChromiumLineBreakpoint lineBreakpoint = new ChromiumLineBreakpoint(resource, lineNumber + 1);
+      ChromiumLineBreakpoint lineBreakpoint = new ChromiumLineBreakpoint(resource, lineNumber + 1,
+          getDebugModelId());
       DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(lineBreakpoint);
     }
   }
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/Messages.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/Messages.java	Mon Jun 07 16:51:19 2010 -0700
@@ -59,6 +59,12 @@
 
   public static String JsThread_ThreadLabelSuspendedExceptionFormat;
 
+  public static String MockUpResourceWriter_NOT_A_JAVASCRIPT;
+
+  public static String MockUpResourceWriter_SCRIPT_WITHOUT_TEXT;
+
+  public static String MockUpResourceWriter_SCRIPTS_OVERLAPPED;
+
   public static String ResourceManager_UnnamedScriptName;
 
   public static String StackFrame_NameFormat;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/MockUpResourceWriter.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,140 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.chromium.sdk.Script;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Creates from a set of scripts a mock-up of full resource (scripts are positioned according
+ * to their line numbers and the whitespace is filled with text pattern).
+ */
+class MockUpResourceWriter {
+  static String writeScriptSource(List<Script> scripts) {
+    ArrayList<Script> sortedScriptsArrayList = new ArrayList<Script>(scripts);
+    Collections.sort(sortedScriptsArrayList, scriptPositionComparator);
+    MockUpResourceWriter writer = new MockUpResourceWriter();
+    for (Script script : sortedScriptsArrayList) {
+      writer.writeSript(script);
+    }
+    return writer.getResult();
+  }
+
+
+  private int line = 0;
+  private int col = 0;
+  private final StringBuilder builder = new StringBuilder();
+
+  private void writeSript(Script script) {
+    int scriptLine = script.getStartLine();
+    if (scriptLine > line) {
+      fillLines(scriptLine - line);
+      line = scriptLine;
+    } else if (scriptLine < line) {
+      writeLineMissMessage(scriptLine);
+    } else {
+      int scriptCol = script.getStartColumn();
+      if (col < scriptCol) {
+        fillColumns(scriptCol - col);
+      } else if (col > scriptCol) {
+        final boolean expectCorrectStartColumn = false;
+        if (expectCorrectStartColumn) {
+          writeln(""); //$NON-NLS-1$
+          writeLineMissMessage(scriptLine);
+        } else {
+          // Ignore.
+        }
+      }
+    }
+
+    if (script.hasSource()) {
+      writeText(script.getSource());
+    } else {
+      writeln(Messages.MockUpResourceWriter_SCRIPT_WITHOUT_TEXT);
+    }
+  }
+
+  private void writeLineMissMessage(int scriptLine) {
+    writeln(NLS.bind(Messages.MockUpResourceWriter_SCRIPTS_OVERLAPPED,
+        line + 1 - scriptLine, scriptLine + 1));
+  }
+
+  private void writeText(String text) {
+    int pos = 0;
+    while (true) {
+      int nlPos = text.indexOf('\n', pos);
+      if (nlPos == -1) {
+        String rest = text.substring(pos);
+        builder.append(rest);
+        col += rest.length();
+        break;
+      }
+      writeln(text.substring(pos, nlPos));
+      pos = nlPos + 1;
+    }
+  }
+
+  private void writeln(String str) {
+    builder.append(str).append('\n');
+    line++;
+    col = 0;
+  }
+
+  private void fillLines(int lines) {
+    if (col != 0) {
+      builder.append('\n');
+      line++;
+    }
+    for (int i = 0; i < lines; i++) {
+      builder.append(NOT_A_JAVASCRIPT_FILLER).append('\n');
+    }
+    line += lines;
+    col = 0;
+  }
+
+  private void fillColumns(int number) {
+    if (number < NOT_A_JAVASCRIPT_FILLER.length()) {
+      if (number < 1) {
+        // Nothing.
+      } else if (number == 1) {
+        builder.append('*');
+        col += 1;
+      } else {
+        builder.append('{');
+        for (int i = 2; i < number; i++) {
+          builder.append('*');
+        }
+        builder.append('}');
+        col += number;
+      }
+    }
+  }
+
+  private String getResult() {
+    return builder.toString();
+  }
+
+  private static final String NOT_A_JAVASCRIPT_FILLER =
+      Messages.MockUpResourceWriter_NOT_A_JAVASCRIPT;
+
+  private static final Comparator<Script> scriptPositionComparator = new Comparator<Script>() {
+    public int compare(Script o1, Script o2) {
+      int line1 = o1.getStartLine();
+      int line2 = o2.getStartLine();
+      if (line1 < line2) {
+        return -1;
+      } else if (line1 == line2) {
+        return 0;
+      } else {
+        return 1;
+      }
+    }
+  };
+}
\ No newline at end of file
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/ResourceManager.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/ResourceManager.java	Mon Jun 07 16:51:19 2010 -0700
@@ -1,134 +1,154 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 package org.chromium.debug.core.model;
 
+import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
 import org.chromium.debug.core.ChromiumDebugPlugin;
-import org.chromium.debug.core.model.BreakpointRegistry.BreakpointEntry;
-import org.chromium.debug.core.model.BreakpointRegistry.ScriptIdentifier;
 import org.chromium.debug.core.util.ChromiumDebugPluginUtil;
 import org.chromium.sdk.Script;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.debug.core.DebugPlugin;
 
 /**
  * This object handles the mapping between {@link Script}s and their corresponding resources
  * inside Eclipse.
  */
 public class ResourceManager {
-  private final Map<IFile, Script> resourceToScript = new HashMap<IFile, Script>();
-  private final Map<ScriptIdentifier, IFile> scriptIdToResource =
-      new HashMap<ScriptIdentifier, IFile>();
   private final IProject debugProject;
-  private final BreakpointRegistry breakpointRegistry;
-  private Object fileBeingAdded;
+
+  private final Map<VmResourceId, VmResourceInfo> vmResourceId2Info =
+      new HashMap<VmResourceId, VmResourceInfo>();
+  private final Map<IFile, VmResourceInfo> file2Info = new HashMap<IFile, VmResourceInfo>();
+
+  public ResourceManager(IProject debugProject) {
+    this.debugProject = debugProject;
+  }
 
-  public ResourceManager(IProject debugProject, BreakpointRegistry breakpointRegistry) {
-    this.debugProject = debugProject;
-    this.breakpointRegistry = breakpointRegistry;
+  public synchronized VmResource getVmResource(VmResourceId id) {
+    VmResourceInfo info = vmResourceId2Info.get(id);
+    if (info == null) {
+      return null;
+    }
+    return info.vmResourceImpl;
+  }
+
+  /**
+   * @param eclipseSourceName eclipse source file name
+   *   (what {@link VmResourceId#getEclipseSourceName()} returns)
+   */
+  public IFile getFile(String eclipseSourceName) {
+    VmResourceId id = VmResourceId.parseString(eclipseSourceName);
+    VmResourceInfo info = vmResourceId2Info.get(id);
+    if (info == null) {
+      return null;
+    }
+    return info.file;
   }
 
-  public synchronized void putScript(Script script, IFile resource) {
-    ScriptIdentifier scriptId = ScriptIdentifier.forScript(script);
-    resourceToScript.put(resource, script);
-    scriptIdToResource.put(scriptId, resource);
+  public synchronized VmResourceId getResourceId(IFile resource) {
+    VmResourceInfo info = file2Info.get(resource);
+    if (info == null) {
+      return null;
+    }
+    return info.id;
   }
 
-  public synchronized Script getScript(IFile resource) {
-    return resourceToScript.get(resource);
+  public synchronized void addScript(Script newScript) {
+    VmResourceId id = VmResourceId.forScript(newScript);
+    VmResourceInfo info = vmResourceId2Info.get(id);
+    if (info == null) {
+      String fileNameTemplate = createFileNameTemplate(id, newScript);
+      IFile scriptFile = ChromiumDebugPluginUtil.createFile(debugProject, fileNameTemplate);
+      info = new VmResourceInfo(scriptFile, id);
+      vmResourceId2Info.put(id, info);
+      file2Info.put(scriptFile, info);
+
+      info.scripts.add(newScript);
+      writeScriptSource(info.scripts, info.file);
+    } else {
+      // TODO(peter.rybin): support adding scripts to one resource at once not to rewrite file
+      // every time.
+      info.scripts.add(newScript);
+      writeScriptSource(info.scripts, info.file);
+    }
   }
 
-  public synchronized IFile getResource(Script script) {
-    return scriptIdToResource.get(ScriptIdentifier.forScript(script));
-  }
-
-  public synchronized boolean scriptHasResource(Script script) {
-    return getResource(script) != null;
+  public synchronized void reloadScript(Script script) {
+    VmResourceId id = VmResourceId.forScript(script);
+    VmResourceInfo info = vmResourceId2Info.get(id);
+    if (info == null) {
+      throw new RuntimeException("Script file not found"); //$NON-NLS-1$
+    }
+    if (!info.scripts.contains(script)) {
+      throw new RuntimeException("Script not found in internal list"); //$NON-NLS-1$
+    }
+    writeScriptSource(info.scripts, info.file);
   }
 
   public synchronized void clear() {
     deleteAllScriptFiles();
-    resourceToScript.clear();
-    scriptIdToResource.clear();
+
+    vmResourceId2Info.clear();
+    file2Info.clear();
   }
 
   private void deleteAllScriptFiles() {
-    if (!resourceToScript.isEmpty()) {
-      try {
-        ResourcesPlugin.getWorkspace().delete(
-            resourceToScript.keySet().toArray(new IFile[resourceToScript.size()]), true, null);
-      } catch (CoreException e) {
-        ChromiumDebugPlugin.log(e);
-      }
+    try {
+      ResourcesPlugin.getWorkspace().delete(
+          file2Info.keySet().toArray(new IFile[file2Info.size()]), true, null);
+    } catch (CoreException e) {
+      ChromiumDebugPlugin.log(e);
+    }
+  }
+
+  private String createFileNameTemplate(VmResourceId id, Script newScript) {
+    return id.createFileNameTemplate(true);
+  }
+
+  private static void writeScriptSource(List<Script> scripts, IFile file) {
+    String fileSource = MockUpResourceWriter.writeScriptSource(scripts);
+
+    try {
+      ChromiumDebugPluginUtil.writeFile(file, fileSource);
+    } catch (final CoreException e) {
+      ChromiumDebugPlugin.log(e);
     }
   }
 
-  public synchronized void addScript(Script script) {
-    IFile scriptFile = getResource(script);
-    if (scriptFile == null) {
-      scriptFile = ChromiumDebugPluginUtil.createFile(debugProject, getScriptResourceName(script));
-      fileBeingAdded = scriptFile;
-      try {
-        putScript(script, scriptFile);
-        writeScriptSource(script, scriptFile);
-        // Perhaps restore breakpoints for the reloaded script
-        List<ChromiumLineBreakpoint> breakpoints = new LinkedList<ChromiumLineBreakpoint>();
-        for (BreakpointEntry entry : breakpointRegistry.getBreakpointEntries(script)) {
-          ChromiumLineBreakpoint lineBreakpoint;
-          try {
-            lineBreakpoint = new ChromiumLineBreakpoint(scriptFile, entry.line + 1);
-          } catch (CoreException e) {
-            ChromiumDebugPlugin.log(e);
-            continue;
+  private class VmResourceInfo {
+    final IFile file;
+    final VmResourceId id;
+    final ArrayList<Script> scripts = new ArrayList<Script>(1);
+    VmResourceInfo(IFile file, VmResourceId id) {
+      this.file = file;
+      this.id = id;
+    }
+
+    final VmResource vmResourceImpl = new VmResource() {
+      public VmResourceId getId() {
+        return id;
+      }
+
+      public Script getScript() {
+        synchronized (ResourceManager.this) {
+          if (scripts.size() != 1) {
+            throw new UnsupportedOperationException(
+                "Not supported for complex resources"); //$NON-NLS-1$
           }
-          lineBreakpoint.setBreakpoint(entry.breakpoint);
-          breakpoints.add(lineBreakpoint);
-        }
-        if (!breakpoints.isEmpty()) {
-          try {
-            DebugPlugin.getDefault().getBreakpointManager().addBreakpoints(
-                breakpoints.toArray(new ChromiumLineBreakpoint[breakpoints.size()]));
-          } catch (CoreException e) {
-            ChromiumDebugPlugin.log(e);
-          }
+          return scripts.get(0);
         }
-      } finally {
-        fileBeingAdded = null;
       }
-    }
-  }
-
-  private String getScriptResourceName(Script script) {
-    String name = script.getName();
-    if (name == null) {
-      name = Messages.ResourceManager_UnnamedScriptName;
-    }
-    return name;
-  }
-
-  private static void writeScriptSource(Script script, IFile file) {
-    if (script.hasSource()) {
-      try {
-        ChromiumDebugPluginUtil.writeFile(file, script.getSource());
-      } catch (final CoreException e) {
-        ChromiumDebugPlugin.log(e);
+      public String getFileName() {
+        return file.getName();
       }
-    }
-  }
-
-  /**
-   * @return whether the given file is being added to the target project
-   */
-  public boolean isAddingFile(IFile file) {
-    return file.equals(fileBeingAdded);
+    };
   }
 }
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/StackFrame.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/StackFrame.java	Mon Jun 07 16:51:19 2010 -0700
@@ -201,7 +201,14 @@
   }
 
   public int getLineNumber() throws DebugException {
-    return getDebugTarget().getLineNumber(stackFrame);
+    // convert 0-based to 1-based
+    int inScriptLine = stackFrame.getLineNumber() + 1;
+    Script script = stackFrame.getScript();
+    if (script != null) {
+      return inScriptLine + script.getStartLine();
+    } else {
+      return inScriptLine;
+    }
   }
 
   public int getCharStart() throws DebugException {
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/VProjectWorkspaceBridge.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/VProjectWorkspaceBridge.java	Mon Jun 07 16:51:19 2010 -0700
@@ -7,6 +7,8 @@
 import java.util.Collection;
 
 import org.chromium.debug.core.ChromiumDebugPlugin;
+import org.chromium.debug.core.ChromiumSourceDirector;
+import org.chromium.debug.core.model.BreakpointSynchronizer.Callback;
 import org.chromium.debug.core.util.ChromiumDebugPluginUtil;
 import org.chromium.sdk.Breakpoint;
 import org.chromium.sdk.CallFrame;
@@ -14,19 +16,15 @@
 import org.chromium.sdk.ExceptionData;
 import org.chromium.sdk.JavascriptVm;
 import org.chromium.sdk.Script;
-import org.chromium.sdk.JavascriptVm.BreakpointCallback;
+import org.chromium.sdk.SyncCallback;
 import org.chromium.sdk.JavascriptVm.ScriptsCallback;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IMarkerDelta;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.debug.core.DebugException;
-import org.eclipse.debug.core.DebugPlugin;
-import org.eclipse.debug.core.IBreakpointManager;
 import org.eclipse.debug.core.ILaunch;
 import org.eclipse.debug.core.model.IBreakpoint;
-import org.eclipse.debug.core.model.ISourceLocator;
-import org.eclipse.debug.core.model.IStackFrame;
 import org.eclipse.osgi.util.NLS;
 
 /**
@@ -61,17 +59,28 @@
   private final IProject debugProject;
   private final JavascriptVm javascriptVm;
   private final ResourceManager resourceManager;
-  private final BreakpointRegistry breakpointRegistry = new BreakpointRegistry();
   private final DebugTargetImpl debugTargetImpl;
+  private final BreakpointMap.InTargetMap breakpointInTargetMap = new BreakpointMap.InTargetMap();
+  private final ChromiumSourceDirector sourceDirector;
 
   public VProjectWorkspaceBridge(String projectName, DebugTargetImpl debugTargetImpl,
       JavascriptVm javascriptVm) {
     this.debugTargetImpl = debugTargetImpl;
     this.javascriptVm = javascriptVm;
     this.debugProject = ChromiumDebugPluginUtil.createEmptyProject(projectName);
-    this.resourceManager = new ResourceManager(debugProject, breakpointRegistry);
+    this.resourceManager = new ResourceManager(debugProject);
+
     ILaunch launch = debugTargetImpl.getLaunch();
-    launch.setSourceLocator(sourceLocator);
+
+    sourceDirector = (ChromiumSourceDirector) launch.getSourceLocator();
+    sourceDirector.initializeVProjectContainers(debugProject, resourceManager);
+  }
+
+  public void synchronizeBreakpoints(BreakpointSynchronizer.Direction direction,
+      Callback callback) {
+    BreakpointSynchronizer synchronizer = new BreakpointSynchronizer(javascriptVm,
+        breakpointInTargetMap, sourceDirector, breakpointHandler, DEBUG_MODEL_ID);
+    synchronizer.syncBreakpoints(direction, callback);
   }
 
   public void launchRemoved() {
@@ -81,21 +90,6 @@
   }
 
   public void beforeDetach() {
-    IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
-    IBreakpoint[] breakpoints =
-        breakpointManager.getBreakpoints(DEBUG_MODEL_ID);
-    for (IBreakpoint bp : breakpoints) {
-      ChromiumLineBreakpoint clb = (ChromiumLineBreakpoint) bp;
-      if (clb.getBrowserBreakpoint() != null &&
-          clb.getBrowserBreakpoint().getId() != Breakpoint.INVALID_ID) {
-        clb.getBrowserBreakpoint().clear(null);
-      }
-    }
-    try {
-      breakpointManager.removeBreakpoints(breakpoints, true);
-    } catch (CoreException e) {
-      ChromiumDebugPlugin.log(e);
-    }
   }
 
   public void handleVmResetEvent() {
@@ -123,139 +117,183 @@
     });
   }
 
-  public IFile getScriptResource(Script script) {
-    return resourceManager.getResource(script);
+  public VmResource findVmResourceFromWorkspaceFile(IFile resource) throws CoreException {
+    VmResourceId id = findVmResourceIdFromWorkspaceFile(resource);
+    if (id == null) {
+      return null;
+    }
+    return resourceManager.getVmResource(id);
+  }
+
+  private VmResourceId findVmResourceIdFromWorkspaceFile(IFile resource) throws CoreException {
+    return sourceDirector.getReverseSourceLookup().findVmResource(resource);
+  }
+
+  public void reloadScript(Script script) {
+    resourceManager.reloadScript(script);
   }
 
   public BreakpointHandler getBreakpointHandler() {
     return breakpointHandler;
   }
 
-  private final BreakpointHandler breakpointHandler = new BreakpointHandler() {
+  private final BreakpointHandlerImpl breakpointHandler = new BreakpointHandlerImpl();
+
+  private class BreakpointHandlerImpl implements BreakpointHandler,
+      BreakpointSynchronizer.BreakpointHelper {
     public boolean supportsBreakpoint(IBreakpoint breakpoint) {
       return DEBUG_MODEL_ID.equals(breakpoint.getModelIdentifier()) &&
           !debugTargetImpl.isDisconnected();
     }
+
+    public ChromiumLineBreakpoint tryCastBreakpoint(IBreakpoint breakpoint) {
+      if (!supportsBreakpoint(breakpoint)) {
+        return null;
+      }
+      if (breakpoint instanceof ChromiumLineBreakpoint == false) {
+        return null;
+      }
+      return (ChromiumLineBreakpoint) breakpoint;
+    }
+
     public void breakpointAdded(IBreakpoint breakpoint) {
-      if (!supportsBreakpoint(breakpoint)) {
+      ChromiumLineBreakpoint lineBreakpoint = tryCastBreakpoint(breakpoint);
+      if (lineBreakpoint == null) {
+        return;
+      }
+      if (ChromiumLineBreakpoint.getIgnoreList().contains(breakpoint)) {
+        return;
+      }
+      if (!lineBreakpoint.isEnabled()) {
+        return;
+      }
+      IFile file = (IFile) lineBreakpoint.getMarker().getResource();
+      VmResourceId vmResourceId;
+      try {
+        vmResourceId = findVmResourceIdFromWorkspaceFile(file);
+      } catch (CoreException e) {
+        ChromiumDebugPlugin.log(
+            new Exception("Failed to resolve script for the file " + file, e)); //$NON-NLS-1$
         return;
       }
+      if (vmResourceId == null) {
+        // Might be a script from a different debug target
+        return;
+      }
+
+      createBreakpointOnRemote(lineBreakpoint, vmResourceId, null, null);
+    }
+
+    public void createBreakpointOnRemote(final ChromiumLineBreakpoint lineBreakpoint,
+        final VmResourceId vmResourceId,
+        final CreateCallback createCallback, SyncCallback syncCallback) {
       try {
-        if (breakpoint.isEnabled()) {
-          // Class cast is ensured by the supportsBreakpoint implementation
-          final ChromiumLineBreakpoint lineBreakpoint = (ChromiumLineBreakpoint) breakpoint;
-          IFile file = (IFile) breakpoint.getMarker().getResource();
-          if (resourceManager.isAddingFile(file)) {
-            return; // restoring breakpoints in progress
-          }
-          final Script script = resourceManager.getScript(file);
-          if (script == null) {
-            // Might be a script from a different debug target
-            return;
-          }
-          // ILineBreakpoint lines are 1-based while V8 lines are 0-based
-          final int line = (lineBreakpoint.getLineNumber() - 1) + script.getStartLine();
-          BreakpointCallback callback = new BreakpointCallback() {
-            public void success(Breakpoint breakpoint) {
-              lineBreakpoint.setBreakpoint(breakpoint);
-              breakpointRegistry.add(script, line, breakpoint);
-            }
-
-            public void failure(String errorMessage) {
-              ChromiumDebugPlugin.logError(errorMessage);
+        ChromiumLineBreakpoint.Helper.CreateOnRemoveCallback callback =
+            new ChromiumLineBreakpoint.Helper.CreateOnRemoveCallback() {
+          public void success(Breakpoint breakpoint) {
+            breakpointInTargetMap.add(breakpoint, lineBreakpoint);
+            if (createCallback != null) {
+              createCallback.success();
             }
-          };
-          if (script.getName() != null) {
-            javascriptVm.setBreakpoint(Breakpoint.Type.SCRIPT_NAME,
-                script.getName(),
-                line,
-                Breakpoint.EMPTY_VALUE,
-                breakpoint.isEnabled(),
-                lineBreakpoint.getCondition(),
-                lineBreakpoint.getIgnoreCount(),
-                callback);
-          } else {
-            javascriptVm.setBreakpoint(Breakpoint.Type.SCRIPT_ID,
-                String.valueOf(script.getId()),
-                line,
-                Breakpoint.EMPTY_VALUE,
-                breakpoint.isEnabled(),
-                lineBreakpoint.getCondition(),
-                lineBreakpoint.getIgnoreCount(),
-                callback);
+          }
+          public void failure(String errorMessage) {
+            if (createCallback == null) {
+              ChromiumDebugPlugin.logError(errorMessage);
+            } else {
+              createCallback.failure(new Exception(errorMessage));
+            }
           }
-        }
+        };
+
+        ChromiumLineBreakpoint.Helper.createOnRemote(lineBreakpoint, vmResourceId, debugTargetImpl,
+            callback, syncCallback);
       } catch (CoreException e) {
-        ChromiumDebugPlugin.log(e);
+        ChromiumDebugPlugin.log(new Exception("Failed to create breakpoint in " + //$NON-NLS-1$
+            getTargetNameSafe(), e));
       }
     }
 
     public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
-      if (!supportsBreakpoint(breakpoint)) {
+      ChromiumLineBreakpoint lineBreakpoint = tryCastBreakpoint(breakpoint);
+      if (lineBreakpoint == null) {
+        return;
+      }
+      if (ChromiumLineBreakpoint.getIgnoreList().contains(lineBreakpoint)) {
         return;
       }
-      // Class cast is ensured by the supportsBreakpoint implementation
-      ((ChromiumLineBreakpoint) breakpoint).changed();
+      Breakpoint sdkBreakpoint = breakpointInTargetMap.getSdkBreakpoint(lineBreakpoint);
+      if (sdkBreakpoint == null) {
+        return;
+      }
+
+      try {
+        ChromiumLineBreakpoint.Helper.updateOnRemote(sdkBreakpoint, lineBreakpoint);
+      } catch (RuntimeException e) {
+        ChromiumDebugPlugin.log(new Exception("Failed to change breakpoint in " + //$NON-NLS-1$
+            getTargetNameSafe(), e));
+      }
+
     }
 
     public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
-      if (!supportsBreakpoint(breakpoint)) {
+      ChromiumLineBreakpoint lineBreakpoint = tryCastBreakpoint(breakpoint);
+      if (lineBreakpoint == null) {
+        return;
+      }
+      if (ChromiumLineBreakpoint.getIgnoreList().contains(lineBreakpoint)) {
         return;
       }
+
+      Breakpoint sdkBreakpoint = breakpointInTargetMap.getSdkBreakpoint(lineBreakpoint);
+      if (sdkBreakpoint == null) {
+        return;
+      }
+
       try {
-        if (breakpoint.isEnabled()) {
-          // Class cast is ensured by the supportsBreakpoint implementation
-          ChromiumLineBreakpoint lineBreakpoint = (ChromiumLineBreakpoint) breakpoint;
-          lineBreakpoint.clear();
-          breakpointRegistry.remove(
-              resourceManager.getScript((IFile) breakpoint.getMarker().getResource()),
-              lineBreakpoint.getLineNumber() - 1,
-              lineBreakpoint.getBrowserBreakpoint());
+        if (!breakpoint.isEnabled()) {
+          return;
         }
       } catch (CoreException e) {
         ChromiumDebugPlugin.log(e);
+        return;
       }
+      JavascriptVm.BreakpointCallback callback = new JavascriptVm.BreakpointCallback() {
+        public void failure(String errorMessage) {
+          ChromiumDebugPlugin.log(new Exception("Failed to remove breakpoint in " + //$NON-NLS-1$
+              getTargetNameSafe() + ": " + errorMessage)); //$NON-NLS-1$
+        }
+        public void success(Breakpoint breakpoint) {
+        }
+      };
+      try {
+        sdkBreakpoint.clear(callback, null);
+      } catch (RuntimeException e) {
+        ChromiumDebugPlugin.log(new Exception("Failed to remove breakpoint in " + //$NON-NLS-1$
+            getTargetNameSafe(), e));
+      }
+      breakpointInTargetMap.remove(lineBreakpoint);
     }
 
     public void breakpointsHit(Collection<? extends Breakpoint> breakpointsHit) {
       if (breakpointsHit.isEmpty()) {
         return;
       }
-      IBreakpoint[] breakpoints =
-          DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(DEBUG_MODEL_ID);
-      for (IBreakpoint breakpoint : breakpoints) {
-        ChromiumLineBreakpoint jsBreakpoint = (ChromiumLineBreakpoint) breakpoint;
-        if (breakpointsHit.contains(jsBreakpoint.getBrowserBreakpoint())) {
-          jsBreakpoint.setIgnoreCount(-1); // reset ignore count as we've hit it
+
+      for (Breakpoint sdkBreakpoint : breakpointsHit) {
+        ChromiumLineBreakpoint uiBreakpoint = breakpointInTargetMap.getUiBreakpoint(sdkBreakpoint);
+        if (uiBreakpoint != null) {
+          uiBreakpoint.setIgnoreCount(-1); // reset ignore count as we've hit it
         }
       }
     }
-  };
-
-  public int getLineNumber(CallFrame stackFrame) {
-    // convert 0-based to 1-based
-    return stackFrame.getLineNumber() + 1;
+    private String getTargetNameSafe() {
+      try {
+        return debugTargetImpl.getLaunch().getLaunchConfiguration().getName();
+      } catch (RuntimeException e) {
+        return "<unknown>"; //$NON-NLS-1$
+      }
+    }
   }
-  
-  /**
-   * This very simple source locator works because we provide our own source files.
-   * We'll have to try harder, once we link with resource js files.
-   */
-  private final ISourceLocator sourceLocator = new ISourceLocator() {
-    public Object getSourceElement(IStackFrame stackFrame) {
-      if (stackFrame instanceof StackFrame == false) {
-        return null;
-      }
-      StackFrame jsStackFrame = (StackFrame) stackFrame;
-
-      Script script = jsStackFrame.getCallFrame().getScript();
-      if (script == null) {
-        return null;
-      }
-
-      return resourceManager.getResource(script);
-    }
-  };
 
   private final static JsLabelProvider LABEL_PROVIDER = new JsLabelProvider() {
     public String getTargetLabel(DebugTargetImpl debugTarget) {
@@ -298,16 +336,18 @@
       CallFrame callFrame = stackFrame.getCallFrame();
       String name = callFrame.getFunctionName();
       Script script = callFrame.getScript();
+      String scriptName;
       if (script == null) {
-        return Messages.StackFrame_UnknownScriptName;
+        scriptName = Messages.StackFrame_UnknownScriptName;
+      } else {
+        scriptName = VmResourceId.forScript(script).getEclipseSourceName();
       }
-      int line = script.getStartLine() + stackFrame.getLineNumber();
+      int line = stackFrame.getLineNumber();
       if (line != -1) {
         name = NLS.bind(Messages.StackFrame_NameFormat,
-            new Object[] {name, script.getName(), line});
+            new Object[] {name, scriptName, line});
       }
       return name;
     }
   };
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/VmResource.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core.model;
+
+import org.chromium.sdk.Script;
+
+/**
+ * A representation of V8 VM resource. The exact nature of the resource is unspecified, we
+ * only know it may contain one or more {@link Script}s. Typically resource is .js or .html file.
+ */
+
+public interface VmResource {
+  VmResourceId getId();
+
+  /**
+   * @return script if this resource entirely consists of 1 script, otherwise throws exception
+   * @throws UnsupportedOperationException if this resource does not entirely consists of 1 script
+   * TODO(peter.rybin): redesign this method to normally work with html resources.
+   */
+  Script getScript();
+
+  String getFileName();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/VmResourceId.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,118 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core.model;
+
+import org.chromium.sdk.Breakpoint;
+import org.chromium.sdk.JavascriptVm;
+import org.chromium.sdk.Script;
+
+/**
+ * Id of resources loaded in V8 VM. We only know that they may have name (typically filename or
+ * URL) or numerical id instead. This class reflects this.
+ * The class also contains several utility methods that probably should be separated in the future.
+ */
+public class VmResourceId {
+
+  public static VmResourceId forName(String scriptName) {
+    return new VmResourceId(scriptName);
+  }
+
+  public static VmResourceId forId(long scriptId) {
+    return new VmResourceId(Long.valueOf(scriptId));
+  }
+
+  public static VmResourceId forScript(Script script) {
+    if (script.getName() != null) {
+      return forName(script.getName());
+    } else {
+      return forId(script.getId());
+    }
+  }
+
+  private final Object value;
+
+  private VmResourceId(Object value) {
+    if (value == null) {
+      throw new IllegalArgumentException("Null id value"); //$NON-NLS-1$
+    }
+    this.value = value;
+  }
+
+  @Override
+  public int hashCode() {
+    return value.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof VmResourceId == false) {
+      return false;
+    }
+    VmResourceId other = (VmResourceId) obj;
+    return this.value.equals(other.value);
+  }
+
+  /**
+   * @return parameter for {@link JavascriptVm#setBreakpoint} method.
+   */
+  public Breakpoint.Type getTypeForBreakpoint() {
+    if (value instanceof String) {
+      return Breakpoint.Type.SCRIPT_NAME;
+    } else {
+      return Breakpoint.Type.SCRIPT_ID;
+    }
+  }
+
+  /**
+   * @return parameter for {@link JavascriptVm#setBreakpoint} method.
+   */
+  public String getTargetForBreakpoint() {
+    return value.toString();
+  }
+
+  String createFileNameTemplate(boolean isEval) {
+    if (value instanceof String) {
+      return value.toString();
+    } else {
+      if (isEval) {
+        return "<eval #" + value + ">"; //$NON-NLS-1$ //$NON-NLS-2$
+      } else {
+        return "<no name #" + value + ">"; //$NON-NLS-1$ //$NON-NLS-2$
+      }
+    }
+
+  }
+
+  @Override
+  public String toString() {
+    return getEclipseSourceName();
+  }
+
+  /**
+   * @return source name that is suitable for Eclipse debug source lookup.
+   */
+  public String getEclipseSourceName() {
+    if (value instanceof String) {
+      String stringValue = (String) value;
+      if (stringValue.startsWith("#")) {
+        // Quote it.
+        stringValue = "#" + stringValue;
+      }
+      return stringValue;
+    } else {
+      return "#" + value;
+    }
+  }
+
+  public static VmResourceId parseString(String name) {
+    if (name.startsWith("##")) {
+      return VmResourceId.forName(name.substring(1));
+    } else if (name.startsWith("#")) {
+      return VmResourceId.forId(Long.parseLong(name.substring(1)));
+    } else {
+      return VmResourceId.forName(name);
+    }
+  }
+}
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/WorkspaceBridge.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/WorkspaceBridge.java	Mon Jun 07 16:51:19 2010 -0700
@@ -7,10 +7,10 @@
 import java.util.Collection;
 
 import org.chromium.sdk.Breakpoint;
-import org.chromium.sdk.CallFrame;
 import org.chromium.sdk.JavascriptVm;
 import org.chromium.sdk.Script;
 import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
 import org.eclipse.debug.core.DebugException;
 import org.eclipse.debug.core.IBreakpointListener;
 import org.eclipse.debug.core.model.IBreakpoint;
@@ -48,10 +48,12 @@
     JsLabelProvider getLabelProvider();
   }
 
+  VmResource findVmResourceFromWorkspaceFile(IFile resource) throws CoreException;
+
   /**
-   * Helps UI actions to map from script to resource.
+   * Initiates script reloading from remote VM.
    */
-  IFile getScriptResource(Script script);
+  void reloadScript(Script script);
 
   /**
    * Called at starting period of time, requires all scripts to be (re)loaded.
@@ -87,12 +89,6 @@
   BreakpointHandler getBreakpointHandler();
 
   /**
-   * Returns editor line number for the provided call stack frame applying all required 
-   * editor-specific translations.
-   */
-  int getLineNumber(CallFrame stackFrame);
-
-  /**
    * Breakpoint-related aspect of {@link WorkspaceBridge} interface.
    */
   interface BreakpointHandler extends IBreakpointListener {
@@ -119,4 +115,12 @@
      */
     String getStackFrameLabel(StackFrame stackFrame) throws DebugException;
   }
+
+  /**
+   * Performs breakpoint synchronization between remote VM and Eclipse IDE. This operation is
+   * partially asynchronous: it blocks for reading breakpoints, but returns before all remote
+   * changes are performed. When operations is fully complete, callback gets invoked.
+   */
+  void synchronizeBreakpoints(BreakpointSynchronizer.Direction direction,
+      BreakpointSynchronizer.Callback callback);
 }
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/messages.properties	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/messages.properties	Mon Jun 07 16:51:19 2010 -0700
@@ -25,6 +25,9 @@
 JsThread_ThreadLabelRunning=Running
 JsThread_ThreadLabelSuspended=Suspended
 JsThread_ThreadLabelSuspendedExceptionFormat=Suspended (exception "{0}")
+MockUpResourceWriter_NOT_A_JAVASCRIPT={not a JavaScript}
+MockUpResourceWriter_SCRIPT_WITHOUT_TEXT={ JavaScript script without text }
+MockUpResourceWriter_SCRIPTS_OVERLAPPED={ scripts overlapped. the following script must be {0} line(s) upper (at line {1}) }
 ResourceManager_UnnamedScriptName=(program)
 StackFrame_NameFormat={0} [{1}:{2}]
 StackFrame_UnknownScriptName=<unknown>
--- a/org.chromium.debug.core/src/org/chromium/debug/core/util/ChromiumDebugPluginUtil.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/util/ChromiumDebugPluginUtil.java	Mon Jun 07 16:51:19 2010 -0700
@@ -7,6 +7,11 @@
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 import org.chromium.debug.core.ChromiumDebugPlugin;
 import org.chromium.debug.core.efs.ChromiumScriptFileSystem;
@@ -36,7 +41,19 @@
  */
 public class ChromiumDebugPluginUtil {
 
-  public static final String CHROMIUM_EXTENSION = "chromium"; //$NON-NLS-1$
+  private static final String CHROMIUM_EXTENSION = "chromium"; //$NON-NLS-1$
+
+  public static final Set<String> SUPPORTED_EXTENSIONS =
+      new HashSet<String>(Arrays.asList(CHROMIUM_EXTENSION, "js", //$NON-NLS-1$
+          "html", "htm")); //$NON-NLS-1$ //$NON-NLS-2$
+
+  public static final List<String> SUPPORTED_EXTENSIONS_SUFFIX_LIST;
+  static {
+    SUPPORTED_EXTENSIONS_SUFFIX_LIST = new ArrayList<String>(SUPPORTED_EXTENSIONS.size());
+    for (String extension : SUPPORTED_EXTENSIONS) {
+      SUPPORTED_EXTENSIONS_SUFFIX_LIST.add("." + extension); //$NON-NLS-1$
+    }
+  }
 
   public static final String JS_DEBUG_PROJECT_NATURE = "org.chromium.debug.core.jsnature"; //$NON-NLS-1$
 
--- a/org.chromium.debug.ui/META-INF/MANIFEST.MF	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.ui/META-INF/MANIFEST.MF	Mon Jun 07 16:51:19 2010 -0700
@@ -14,6 +14,7 @@
  org.eclipse.core.variables;bundle-version="3.2.100",
  org.eclipse.ui.ide;bundle-version="3.4.1",
  org.chromium.debug.core;bundle-version="0.1.5",
- org.chromium.sdk;bundle-version="0.1.5"
+ org.chromium.sdk;bundle-version="0.1.5",
+ org.eclipse.compare;bundle-version="3.5.0"
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: J2SE-1.5
--- a/org.chromium.debug.ui/plugin.xml	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.ui/plugin.xml	Mon Jun 07 16:51:19 2010 -0700
@@ -29,7 +29,9 @@
         modes="debug"
         name="%chromiumLaunchName"
         delegateName="Debug Chromium JavaScript"
-        delegateDescription="JavaScript debugger for Chromium">
+        delegateDescription="JavaScript debugger for Chromium"
+        sourceLocatorId="org.chromium.debug.core.ChromiumSourceDirector"
+        sourcePathComputerId="org.chromium.debug.core.ChromiumSourceComputer">
     </launchConfigurationType>
     <launchConfigurationType
         id="org.chromium.debug.ui.LaunchType$StandaloneV8"
@@ -37,7 +39,9 @@
         modes="debug"
         name="%standaloneV8LaunchName"
         delegateName="Debug Standalone V8 JavaScript"
-        delegateDescription="JavaScript debugger for Standalone V8">
+        delegateDescription="JavaScript debugger for Standalone V8"
+        sourceLocatorId="org.chromium.debug.core.ChromiumSourceDirector"
+        sourcePathComputerId="org.chromium.debug.core.ChromiumSourceComputer">
     </launchConfigurationType>
     <launchConfigurationType
         id="org.chromium.debug.ui.ConsolePseudoConfigurationType"
@@ -185,9 +189,9 @@
             id="org.chromium.debug.ui.actions.EnableDisableBreakpointAction"/>
         <action
             label="Breakpoint Properties..."
-            class="org.chromium.debug.ui.actions.JsBreakpointPropertiesRulerActionDelegate"
+            class="org.chromium.debug.ui.actions.JsBreakpointPropertiesRulerAction$Delegate"
             menubarPath="group.properties"
-            id="org.chromium.debug.ui.actions.JavaBreakpointPropertiesRulerActionDelegate">
+            id="org.chromium.debug.ui.actions.JavaBreakpointPropertiesRulerAction$Delegate">
         </action>
         <action
             label="Toggle Enablement"
@@ -200,17 +204,111 @@
   <extension
          point="org.eclipse.ui.popupMenus">
       <objectContribution
+            objectClass="org.chromium.debug.core.model.ChromiumLineBreakpoint"
+            id="org.chromium.debug.core.model.ChromiumLineBreakpoint.object_actions">
+        <action
+            label="Breakpoint Properties..."
+            class="org.chromium.debug.ui.actions.JsBreakpointPropertiesAction"
+            menubarPath="group.properties"
+            id="org.chromium.debug.ui.actions.JsBreakpointPropertiesAction">
+        </action>
+      </objectContribution>
+         
+      <objectContribution
             objectClass="org.chromium.debug.core.model.Variable"
             id="org.chromium.debug.ui.ChromiumVariableActions">
          <action
                label="%OpenFunctionAction.label"
-               class="org.chromium.debug.ui.actions.OpenFunctionAction"
+               class="org.chromium.debug.ui.actions.OpenFunctionAction$ForVariable"
+               menubarPath="emptyNavigationGroup"
+               enablesFor="1"
+               id="org.chromium.debug.ui.actions.OpenFunctionAction$ForVariable">
+         </action>
+      </objectContribution>
+      <objectContribution
+            objectClass="org.eclipse.debug.core.model.IWatchExpression"
+            id="org.chromium.debug.ui.ChromiumExpressionActions">
+         <action
+               label="%OpenFunctionAction.label"
+               class="org.chromium.debug.ui.actions.OpenFunctionAction$ForExpression"
                menubarPath="emptyNavigationGroup"
                enablesFor="1"
-               id="org.chromium.debug.ui.actions.OpenFunctionAction">
+               id="org.chromium.debug.ui.actions.OpenFunctionAction$ForExpression">
+         </action>
+      </objectContribution>
+      <!-- Experimental actions, temporary disabled. -->
+      <objectContribution
+            objectClass="org.eclipse.core.resources.mapping.ResourceMapping"
+            adaptable="true"
+            id="org.chromium.debug.ui.ChromiumSourceFileActionsId">
+         <menu id="org.chromium.debug.ui.ChromiumSourceFileActionsId.MenuId"
+               label="V8 Debugging"
+         >
+            <separator
+                  name="group0">
+            </separator>
+         </menu>
+         <action
+               label="Compare with VM Source"
+               class="org.chromium.debug.ui.actions.CompareChangesAction"
+               menubarPath="org.chromium.debug.ui.ChromiumSourceFileActionsId.MenuId/group0"
+               enablesFor="1"
+               id="org.chromium.debug.ui.actions.CompareChangesAction">
+         </action>
+         <action
+               label="Push Changes to VM"
+               class="org.chromium.debug.ui.actions.PushChangesAction"
+               menubarPath="org.chromium.debug.ui.ChromiumSourceFileActionsId.MenuId/group0"
+               enablesFor="1"
+               id="org.chromium.debug.ui.actions.PushChangesAction">
          </action>
       </objectContribution>
   </extension>
+
+   <extension
+         point="org.eclipse.ui.perspectiveExtensions">
+      <perspectiveExtension
+            targetID="org.eclipse.debug.ui.DebugPerspective">
+         <actionSet
+               id="org.chromium.debug.ui.ChromiumDebugActionSet">
+         </actionSet>
+      </perspectiveExtension>
+  </extension>
+
+  <extension
+         point="org.eclipse.ui.popupMenus">
+      <objectContribution
+            objectClass="org.eclipse.debug.core.ILaunch"
+            id="org.chromium.debug.ui.actions.actions-for-launch">
+         <menu id="org.chromium.debug.ui.actions.SynchronizeBreakpoints.MenuId"
+               label="Synchronize JavaScript Breakpoints"
+               path="launchGroup"
+         >
+            <separator
+                  name="group0">
+            </separator>
+         </menu>
+         <action
+               label="Reset on Remote"
+               class="org.chromium.debug.ui.actions.SynchronizeBreakpoints$ResetRemote"
+               menubarPath="org.chromium.debug.ui.actions.SynchronizeBreakpoints.MenuId/group0"
+               id="org.chromium.debug.ui.actions.SynchronizeBreakpoints$ResetRemote.forLaunch">
+         </action>
+         <action
+               label="Reset on Local"
+               class="org.chromium.debug.ui.actions.SynchronizeBreakpoints$ResetLocal"
+               menubarPath="org.chromium.debug.ui.actions.SynchronizeBreakpoints.MenuId/group0"
+               id="org.chromium.debug.ui.actions.SynchronizeBreakpoints$ResetLocal.forLaunch">
+         </action>
+         <action
+               label="Merge Remote and Local"
+               class="org.chromium.debug.ui.actions.SynchronizeBreakpoints$Merge"
+               menubarPath="org.chromium.debug.ui.actions.SynchronizeBreakpoints.MenuId/group0"
+               id="org.chromium.debug.ui.actions.SynchronizeBreakpoints$Merge.forLaunch">
+         </action>
+      </objectContribution>
+  </extension>
+
   <extension
          point="org.eclipse.ui.propertyPages">
     <page
@@ -229,4 +327,14 @@
       </enabledWhen>
     </page>
   </extension>
+  
+
+  <extension point="org.eclipse.debug.ui.sourceContainerPresentations">
+      <sourceContainerPresentation
+            browserClass="org.chromium.debug.ui.source.SourceNameMapperContainerPresentation"
+            containerTypeID="org.chromium.debug.core.SourceNameMapperContainer.type"
+            icon="res/standalone_v8_16.png"
+            id="org.chromium.debug.ui.SourceNameMapperContainerPresentation">
+      </sourceContainerPresentation>
+  </extension>
 </plugin>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,404 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * A small set of utility classes that help programming dialog window logic.
+ */
+public class DialogUtils {
+  /*
+   * Part 1. Update graph.
+   *
+   * All logical elements of dialog and dependencies between them are modeled as DAG.
+   * The data flows from vertices that corresponds to various input fields through
+   * some transformations to the terminal vertices that are in-dialog helper messages and OK button.
+   *
+   * A linear data flow (no forks, no merges) is suitable for data transformation and
+   * is programmed manually. Forks and merges are hard to dispatch manually and are managed by
+   * class Updater.
+   *
+   * Updater knows about "source" vertices that may have several outgoing edges and about
+   * "consumer" vertices that may have several incoming edges. Based on source vertex changes
+   * updater updates consumer vertices in topological order.
+   */
+
+  /**
+   * Represents source vertex for Updater. Technically updater uses this interface only as a flag
+   * interface, because the only methods it uses are {@link Object#equals}/Object{@link #hashCode}.
+   */
+  public interface ValueSource<T> {
+    /**
+     * Method is not used by updater, for convenience only.
+     * @return current value of the vertex
+     */
+    T getValue();
+  }
+
+  /**
+   * Represents consumer vertex for Updater. Each instance should be explicitly registered in
+   * Updater.
+   */
+  public interface ValueConsumer {
+    /**
+     * Called by updater when some linked sources have changed and it's time this vertex
+     * got updated. {@link Updater#reportChanged} may be called if some {@line ValueSource}s have
+     * changed during this update (but this should not break topological order of the graph).
+     */
+    void update(Updater updater);
+  }
+
+  /**
+   * Helps to conduct update for vertices in a value graph.
+   * Technically Updater does not see a real graph, because it doesn't support vertices
+   * that are simultaneously source and consumer. Programmer helps manage other edges manually by
+   * calling {@link #reportChanged} method.
+   */
+  public static class Updater {
+    private final LinkedHashMap<ValueConsumer, Boolean> needsUpdateMap =
+        new LinkedHashMap<ValueConsumer, Boolean>();
+    private final Map<ValueSource<?>, List<ValueConsumer>> reversedDependenciesMap =
+        new HashMap<ValueSource<?>, List<ValueConsumer>>();
+    private boolean alreadyUpdating = false;
+
+    public void addConsumer(ValueConsumer value, ValueSource<?> ... dependencies) {
+      addConsumer(value, Arrays.asList(dependencies));
+    }
+
+    /**
+     * Registers a consumer vertex with all its dependencies.
+     */
+    public void addConsumer(ValueConsumer value, List<? extends ValueSource<?>> dependencies) {
+      Boolean res = needsUpdateMap.put(value, Boolean.FALSE);
+      if (res != null) {
+        throw new IllegalArgumentException("Already added"); //$NON-NLS-1$
+      }
+      for (ValueSource<?> dep : dependencies) {
+        List<ValueConsumer> reversedDeps = reversedDependenciesMap.get(dep);
+        if (reversedDeps == null) {
+          reversedDeps = new ArrayList<ValueConsumer>(2);
+          reversedDependenciesMap.put(dep, reversedDeps);
+        }
+        reversedDeps.add(value);
+      }
+    }
+
+    /**
+     * Reports about sources that have been changed and plans future update of consumers. This
+     * method may be called at any time (it is not thread-safe though).
+     */
+    public void reportChanged(ValueSource<?> source) {
+      List<ValueConsumer> reversedDeps = reversedDependenciesMap.get(source);
+      if (reversedDeps != null) {
+        for (ValueConsumer consumer : reversedDeps) {
+          needsUpdateMap.put(consumer, Boolean.TRUE);
+        }
+      }
+    }
+
+    /**
+     * Performs update of all vertices that need it. If some sources are reported changed
+     * during the run of this method, their consumers are also updated.
+     */
+    public void update() {
+      if (alreadyUpdating) {
+        return;
+      }
+      alreadyUpdating = true;
+      try {
+        updateImpl();
+      } finally {
+        alreadyUpdating = false;
+      }
+    }
+
+    private void updateImpl() {
+      boolean hasChanges = true;
+      while (hasChanges) {
+        hasChanges = false;
+        for (Map.Entry<ValueConsumer, Boolean> en : needsUpdateMap.entrySet()) {
+          if (en.getValue() == Boolean.TRUE) {
+            en.setValue(Boolean.FALSE);
+            ValueConsumer currentValue = en.getKey();
+            currentValue.update(this);
+          }
+        }
+      }
+    }
+
+    /**
+     * Updates all consumer vertices in graph.
+     */
+    public void updateAll() {
+      for (Map.Entry<?, Boolean> en : needsUpdateMap.entrySet()) {
+        en.setValue(Boolean.TRUE);
+      }
+      update();
+    }
+  }
+
+  /**
+   * A basic implementation of object that is both consumer and source. Updater will treat
+   * as 2 separate objects.
+   */
+  public static abstract class ValueProcessor<T> implements ValueConsumer, ValueSource<T> {
+    private T currentValue = null;
+    public T getValue() {
+      return currentValue;
+    }
+    protected void setCurrentValue(T currentValue) {
+      this.currentValue = currentValue;
+    }
+  }
+
+  /*
+   * Part 2. Optional data type etc
+   *
+   * Since dialog should deal with error user entry, the typical data type is either value or error.
+   * This is implemented as Optional interface. Most of data transformations should work only
+   * when all inputs are non-error and generate error in return otherwise. This is implemented in
+   * ExpressionProcessor.
+   */
+
+
+  /**
+   * A primitive approach to "optional" algebraic type. This type is T + Set<Message>.
+   */
+  public interface Optional<V> {
+    V getNormal();
+    boolean isNormal();
+    Set<? extends Message> errorMessages();
+  }
+
+  public static <V> Optional<V> createOptional(final V value) {
+    return new Optional<V>() {
+      public Set<Message> errorMessages() {
+        return Collections.emptySet();
+      }
+      public V getNormal() {
+        return value;
+      }
+      public boolean isNormal() {
+        return true;
+      }
+    };
+  }
+
+  public static <V> Optional<V> createErrorOptional(Message message) {
+    return createErrorOptional(Collections.singleton(message));
+  }
+
+  public static <V> Optional<V> createErrorOptional(final Set<? extends Message> messages) {
+    return new Optional<V>() {
+      public Set<? extends Message> errorMessages() {
+        return messages;
+      }
+      public V getNormal() {
+        throw new UnsupportedOperationException();
+      }
+      public boolean isNormal() {
+        return false;
+      }
+    };
+  }
+
+  /**
+   * A user interface message for dialog window. It has text and priority that helps choosing
+   * the most important message it there are many of them.
+   */
+  public static class Message {
+    private final String text;
+    private final MessagePriority priority;
+    public Message(String text, MessagePriority priority) {
+      this.text = text;
+      this.priority = priority;
+    }
+    public String getText() {
+      return text;
+    }
+    public MessagePriority getPriority() {
+      return priority;
+    }
+  }
+
+  /**
+   * Priority of a user interface message.
+   * Constants are listed from most important to least important.
+   */
+  public enum MessagePriority {
+    BLOCKING_PROBLEM(IMessageProvider.ERROR),
+    BLOCKING_INFO(IMessageProvider.NONE),
+    WARNING(IMessageProvider.WARNING);
+
+    private final int messageProviderType;
+    private MessagePriority(int messageProviderType) {
+      this.messageProviderType = messageProviderType;
+    }
+    public int getMessageProviderType() {
+      return messageProviderType;
+    }
+  }
+
+  /**
+   * A base class for the source-consumer pair that accepts several values as a consumer,
+   * performs a calculation over them and gives it away the result via source interface.
+   * Some sources may be of Optional type. If some of sources have error value the corresponding
+   * error value is returned automatically.
+   * <p>
+   * The implementation should override a single method {@link #calculateNormal}.
+   */
+  public static abstract class ExpressionProcessor<T> extends ValueProcessor<Optional<T>> {
+    private final List<ValueSource<? extends Optional<?>>> optionalSources;
+    public ExpressionProcessor(List<ValueSource<? extends Optional<?>>> optionalSources) {
+      this.optionalSources = optionalSources;
+    }
+
+    protected abstract Optional<T> calculateNormal();
+
+    private Optional<T> calculateNewValue() {
+      Set<Message> errors = new LinkedHashSet<Message>(0);
+      for (ValueSource<? extends Optional<?>> source : optionalSources) {
+        if (!source.getValue().isNormal()) {
+          errors.addAll(source.getValue().errorMessages());
+        }
+      }
+      if (errors.isEmpty()) {
+        return calculateNormal();
+      } else {
+        return createErrorOptional(errors);
+      }
+    }
+    public void update(Updater updater) {
+      Optional<T> result = calculateNewValue();
+      Optional<T> oldValue = getValue();
+      setCurrentValue(result);
+      if (!result.equals(oldValue)) {
+        updater.reportChanged(this);
+      }
+    }
+  }
+
+  /*
+   * Part 3. Various utils.
+   */
+
+  /**
+   * A general-purpose implementation of OK button vertex. It works as a consumer of
+   * 1 result value and several warning sources. From its sources it decides whether
+   * OK button should be enabled and also provides dialog messages (errors, warnings, infos).
+   */
+  public static class OkButtonControl implements ValueConsumer {
+    private final ValueSource<? extends Optional<?>> resultSource;
+    private final List<? extends ValueSource<String>> warningSources;
+    private final DialogElements dialogElements;
+
+    public OkButtonControl(ValueSource<? extends Optional<?>> resultSource,
+        List<? extends ValueSource<String>> warningSources, DialogElements dialogElements) {
+      this.resultSource = resultSource;
+      this.warningSources = warningSources;
+      this.dialogElements = dialogElements;
+    }
+
+    /**
+     * Returns a list of dependencies for updater -- a convenience method.
+     */
+    public List<? extends ValueSource<?>> getDependencies() {
+      ArrayList<ValueSource<?>> result = new ArrayList<ValueSource<?>>();
+      result.add(resultSource);
+      result.addAll(warningSources);
+      return result;
+    }
+
+    public void update(Updater updater) {
+      Optional<?> result = resultSource.getValue();
+      List<Message> messages = new ArrayList<Message>();
+      for (ValueSource<String> warningSource : warningSources) {
+        if (warningSource.getValue() != null) {
+          messages.add(new Message(warningSource.getValue(), MessagePriority.WARNING));
+        }
+      }
+      boolean enabled;
+      if (result.isNormal()) {
+        enabled = true;
+      } else {
+        enabled = false;
+        messages.addAll(result.errorMessages());
+      }
+      dialogElements.getOkButton().setEnabled(enabled);
+      String errorMessage;
+      int type;
+      if (messages.isEmpty()) {
+        errorMessage = null;
+        type = IMessageProvider.NONE;
+      } else {
+        Message visibleMessage = Collections.max(messages, messageComparatorBySeverity);
+        errorMessage = visibleMessage.getText();
+        type = visibleMessage.getPriority().getMessageProviderType();
+      }
+      dialogElements.setMessage(errorMessage, type);
+    }
+
+    private static final Comparator<Message> messageComparatorBySeverity =
+        new Comparator<Message>() {
+      public int compare(Message o1, Message o2) {
+        int ordinal1 = o1.getPriority().ordinal();
+        int ordinal2 = o2.getPriority().ordinal();
+        if (ordinal1 < ordinal2) {
+          return +1;
+        } else if (ordinal1 == ordinal2) {
+          return 0;
+        } else {
+          return -1;
+        }
+      }
+    };
+  }
+
+  /**
+   * A basic interface to elements of the dialog window from dialog logic part. The user may extend
+   * this interface with more elements.
+   */
+  public interface DialogElements {
+    Shell getShell();
+    Button getOkButton();
+    void setMessage(String message, int type);
+  }
+
+  /**
+   * A wrapper around Combo that provides logic-level data-oriented access to the control.
+   * This is not a simply convenience wrapper, because Combo itself does not keep a real data,
+   * but only its string representation.
+   */
+  public static abstract class ComboWrapper<E> {
+    private final Combo combo;
+    public ComboWrapper(Combo combo) {
+      this.combo = combo;
+    }
+    public Combo getCombo() {
+      return combo;
+    }
+    public void addSelectionListener(SelectionListener listener) {
+      combo.addSelectionListener(listener);
+    }
+    public abstract E getSelected();
+    public abstract void setSelected(E element);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/CompareChangesAction.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,86 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.ui.actions;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+
+import org.chromium.debug.core.model.VmResource;
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.compare.CompareUI;
+import org.eclipse.compare.IModificationDate;
+import org.eclipse.compare.IStreamContentAccessor;
+import org.eclipse.compare.ITypedElement;
+import org.eclipse.compare.structuremergeviewer.DiffNode;
+import org.eclipse.compare.structuremergeviewer.Differencer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * A very preliminary implementation of action that should let user compare his script with
+ * its current state on remote VM.
+ */
+public class CompareChangesAction extends V8ScriptAction {
+  @Override
+  protected void execute(FilePair filePair) {
+    LiveEditCompareInput input = new LiveEditCompareInput(filePair.getFile(), filePair.getVmResource());
+    CompareUI.openCompareEditor(input);
+  }
+
+  private static class LiveEditCompareInput extends CompareEditorInput {
+    private final IFile file;
+    private final VmResource script;
+
+    LiveEditCompareInput(IFile file, VmResource vmResource) {
+      super(createCompareConfiguration());
+      this.file = file;
+      this.script = vmResource;
+    }
+
+    private static CompareConfiguration createCompareConfiguration() {
+      return new CompareConfiguration();
+    }
+
+    @Override
+    protected Object prepareInput(IProgressMonitor monitor) throws InvocationTargetException,
+        InterruptedException {
+
+      abstract class CompareItem implements ITypedElement, IStreamContentAccessor,
+          IModificationDate {
+        public Image getImage() {
+          return null;
+        }
+        public String getType() {
+          return TEXT_TYPE;
+        }
+        public long getModificationDate() {
+          return 0;
+        }
+      }
+      CompareItem left = new CompareItem() {
+        public String getName() {
+          return "Local file " + file.getName(); //$NON-NLS-1$
+        }
+        public InputStream getContents() throws CoreException {
+          return file.getContents();
+        }
+      };
+      CompareItem right = new CompareItem() {
+        public String getName() {
+          return "File in VM " + script.getFileName(); //$NON-NLS-1$
+        }
+        public InputStream getContents() throws CoreException {
+          return new ByteArrayInputStream(script.getScript().getSource().getBytes());
+        }
+      };
+      DiffNode diffNode = new DiffNode(null, Differencer.PSEUDO_CONFLICT, null, left, right);
+      return diffNode;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/JsBreakpointPropertiesAction.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,83 @@
+// Copyright (c) 20109 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.ui.actions;
+
+import org.chromium.debug.core.model.ChromiumLineBreakpoint;
+import org.eclipse.debug.core.model.IBreakpoint;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.window.IShellProvider;
+import org.eclipse.ui.IObjectActionDelegate;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchPartSite;
+import org.eclipse.ui.dialogs.PropertyDialogAction;
+
+/**
+ * Action to bring up the breakpoint properties dialog.
+ */
+public class JsBreakpointPropertiesAction implements IObjectActionDelegate {
+
+  private Runnable currentRunnable;
+  private IWorkbenchPartSite site = null;
+
+  public void setActivePart(IAction action, IWorkbenchPart targetPart) {
+    site = targetPart.getSite();
+  }
+
+  public void run(IAction action) {
+    currentRunnable.run();
+  }
+
+  public void selectionChanged(IAction action, ISelection selection) {
+    currentRunnable = createRunnable(selection);
+    action.setEnabled(currentRunnable != null);
+  }
+
+
+  private Runnable createRunnable(ISelection selection) {
+    if (selection instanceof IStructuredSelection == false) {
+      return null;
+    }
+    IStructuredSelection structuredSelection = (IStructuredSelection) selection;
+    if (structuredSelection.size() != 1) {
+      return null;
+    }
+    Object element = structuredSelection.getFirstElement();
+    if (element instanceof ChromiumLineBreakpoint == false) {
+      return null;
+    }
+    final ChromiumLineBreakpoint breakpoint = (ChromiumLineBreakpoint) element;
+
+    return new Runnable() {
+      public void run() {
+        runAction(breakpoint, site);
+      }
+    };
+  }
+
+  protected static void runAction(final IBreakpoint breakpoint, IShellProvider shell) {
+    PropertyDialogAction action =
+      new PropertyDialogAction(shell,
+          new ISelectionProvider() {
+            public void addSelectionChangedListener(ISelectionChangedListener listener) {
+            }
+
+            public ISelection getSelection() {
+              return new StructuredSelection(breakpoint);
+            }
+
+            public void removeSelectionChangedListener(ISelectionChangedListener listener) {
+            }
+
+            public void setSelection(ISelection selection) {
+            }
+          });
+    action.run();
+  }
+}
--- a/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/JsBreakpointPropertiesRulerAction.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/JsBreakpointPropertiesRulerAction.java	Mon Jun 07 16:51:19 2010 -0700
@@ -7,12 +7,9 @@
 import org.chromium.debug.core.model.ChromiumLineBreakpoint;
 import org.eclipse.debug.core.model.IBreakpoint;
 import org.eclipse.debug.ui.actions.RulerBreakpointAction;
+import org.eclipse.jface.action.IAction;
 import org.eclipse.jface.text.source.IVerticalRulerInfo;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.ISelectionProvider;
-import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.ui.dialogs.PropertyDialogAction;
+import org.eclipse.ui.texteditor.AbstractRulerActionDelegate;
 import org.eclipse.ui.texteditor.ITextEditor;
 import org.eclipse.ui.texteditor.IUpdate;
 
@@ -31,23 +28,7 @@
   @Override
   public void run() {
     if (getBreakpoint() != null) {
-      PropertyDialogAction action =
-          new PropertyDialogAction(getEditor().getEditorSite(),
-              new ISelectionProvider() {
-                public void addSelectionChangedListener(ISelectionChangedListener listener) {
-                }
-
-                public ISelection getSelection() {
-                  return new StructuredSelection(getBreakpoint());
-                }
-
-                public void removeSelectionChangedListener(ISelectionChangedListener listener) {
-                }
-
-                public void setSelection(ISelection selection) {
-                }
-              });
-      action.run();
+      JsBreakpointPropertiesAction.runAction(getBreakpoint(), getEditor().getEditorSite());
     }
   }
 
@@ -61,4 +42,13 @@
     setEnabled(breakpoint != null);
   }
 
+
+  public static class Delegate extends AbstractRulerActionDelegate {
+
+    @Override
+    protected IAction createAction(ITextEditor editor, IVerticalRulerInfo rulerInfo) {
+      return new JsBreakpointPropertiesRulerAction(editor, rulerInfo);
+    }
+
+  }
 }
--- a/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/JsBreakpointPropertiesRulerActionDelegate.java	Mon Jun 07 16:33:07 2010 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.debug.ui.actions;
-
-import org.eclipse.jface.action.IAction;
-import org.eclipse.jface.text.source.IVerticalRulerInfo;
-import org.eclipse.ui.texteditor.AbstractRulerActionDelegate;
-import org.eclipse.ui.texteditor.ITextEditor;
-
-/**
- * Delegate for JsBreakpointPropertiesRulerAction.
- */
-public class JsBreakpointPropertiesRulerActionDelegate extends AbstractRulerActionDelegate {
-
-  @Override
-  protected IAction createAction(ITextEditor editor, IVerticalRulerInfo rulerInfo) {
-    return new JsBreakpointPropertiesRulerAction(editor, rulerInfo);
-  }
-
-}
--- a/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/Messages.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/Messages.java	Mon Jun 07 16:51:19 2010 -0700
@@ -26,6 +26,8 @@
   public static String ExpressionEvaluator_UnableToEvaluateExpression;
 
   public static String JsBreakpointPropertiesRulerAction_ItemLabel;
+
+  public static String SynchronizeBreakpoints_JOB_TITLE;
   static {
     // initialize resource bundle
     NLS.initializeMessages(BUNDLE_NAME, Messages.class);
--- a/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/OpenFunctionAction.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/OpenFunctionAction.java	Mon Jun 07 16:51:19 2010 -0700
@@ -1,5 +1,7 @@
 package org.chromium.debug.ui.actions;
 
+import org.chromium.debug.core.model.DebugTargetImpl;
+import org.chromium.debug.core.model.Value;
 import org.chromium.debug.core.model.Variable;
 import org.chromium.debug.ui.JsDebugModelPresentation;
 import org.chromium.debug.ui.editors.JsEditor;
@@ -9,6 +11,11 @@
 import org.chromium.sdk.JsVariable;
 import org.chromium.sdk.Script;
 import org.eclipse.core.resources.IFile;
+import org.eclipse.debug.core.model.IDebugTarget;
+import org.eclipse.debug.core.model.ISourceLocator;
+import org.eclipse.debug.core.model.IValue;
+import org.eclipse.debug.core.model.IWatchExpression;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
 import org.eclipse.jface.action.IAction;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.IStructuredSelection;
@@ -27,7 +34,53 @@
 /**
  * The action for context view in Variable view that opens selected function source text in editor.
  */
-public class OpenFunctionAction implements IObjectActionDelegate, IActionDelegate2 {
+public abstract class OpenFunctionAction<ELEMENT> implements IObjectActionDelegate,
+    IActionDelegate2 {
+  public static class ForVariable extends OpenFunctionAction<Variable> {
+    @Override
+    protected Variable castElement(Object element) {
+      if (element instanceof Variable == false) {
+        return null;
+      }
+      return (Variable) element;
+    }
+    @Override
+    protected JsValue getJsValue(Variable variable) {
+      JsVariable jsVariable = variable.getJsVariable();
+      return jsVariable.getValue();
+    }
+    @Override
+    protected DebugTargetImpl getDebugTarget(Variable variable) {
+      return variable.getDebugTarget();
+    }
+  }
+  public static class ForExpression extends OpenFunctionAction<IWatchExpression> {
+    @Override
+    protected IWatchExpression castElement(Object element) {
+      if (element instanceof IWatchExpression == false) {
+        return null;
+      }
+      return (IWatchExpression) element;
+    }
+    @Override
+    protected DebugTargetImpl getDebugTarget(IWatchExpression expression) {
+      IDebugTarget debugTarget = expression.getDebugTarget();
+      if (debugTarget instanceof DebugTargetImpl == false) {
+        return null;
+      }
+      return (DebugTargetImpl) debugTarget;
+    }
+    @Override
+    protected JsValue getJsValue(IWatchExpression expression) {
+      IValue value = expression.getValue();
+      if (value instanceof Value == false) {
+        return null;
+      }
+      Value chromiumValue = (Value) value;
+      return chromiumValue.getJsValue();
+    }
+  }
+
   private Runnable currentRunnable = null;
 
   public void setActivePart(IAction action, IWorkbenchPart targetPart) {
@@ -41,14 +94,14 @@
   }
 
   public void selectionChanged(IAction action, ISelection selection) {
-    final Variable variable = getVariableFromSelection(selection);
-    final JsFunction jsFunction = getJsFunctionFromVariable(variable);
+    final ELEMENT variable = getElementFromSelection(selection);
+    final JsFunction jsFunction = getJsFunctionFromElement(variable);
 
     currentRunnable = createRunnable(variable, jsFunction);
     action.setEnabled(currentRunnable != null);
   }
 
-  private Runnable createRunnable(final Variable variable, final JsFunction jsFunction) {
+  private Runnable createRunnable(final ELEMENT element, final JsFunction jsFunction) {
     if (jsFunction == null) {
       return null;
     }
@@ -63,7 +116,20 @@
         if (script == null) {
           return;
         }
-        IFile resource = variable.getDebugTarget().getScriptResource(script);
+        DebugTargetImpl debugTarget = getDebugTarget(element);
+        if (debugTarget == null) {
+          return;
+        }
+        ISourceLocator sourceLocator = debugTarget.getLaunch().getSourceLocator();
+        if (sourceLocator instanceof ISourceLookupDirector == false) {
+          return;
+        }
+        ISourceLookupDirector director = (ISourceLookupDirector) sourceLocator;
+        Object sourceObject = director.getSourceElement(script);
+        if (sourceObject instanceof IFile == false) {
+          return;
+        }
+        IFile resource = (IFile) sourceObject;
         IEditorInput input = JsDebugModelPresentation.toEditorInput(resource);
         IEditorPart editor;
         try {
@@ -94,12 +160,14 @@
     currentRunnable.run();
   }
 
-  private JsFunction getJsFunctionFromVariable(Variable variable) {
-    if (variable == null) {
+  private JsFunction getJsFunctionFromElement(ELEMENT element) {
+    if (element == null) {
       return null;
     }
-    JsVariable jsVariable = variable.getJsVariable();
-    JsValue jsValue = jsVariable.getValue();
+    JsValue jsValue = getJsValue(element);
+    if (jsValue == null) {
+      return null;
+    }
     JsObject jsObject = jsValue.asObject();
     if (jsObject == null) {
       return null;
@@ -107,7 +175,7 @@
     return jsObject.asFunction();
   }
 
-  private Variable getVariableFromSelection(ISelection selection) {
+  private ELEMENT getElementFromSelection(ISelection selection) {
     if (selection instanceof IStructuredSelection == false) {
       return null;
     }
@@ -117,9 +185,13 @@
       return null;
     }
     Object element = structuredSelection.getFirstElement();
-    if (element instanceof Variable == false) {
-      return null;
-    }
-    return (Variable) element;
+    ELEMENT typedElement = castElement(element);
+    return typedElement;
   }
+
+  protected abstract ELEMENT castElement(Object element);
+
+  protected abstract JsValue getJsValue(ELEMENT element);
+
+  protected abstract DebugTargetImpl getDebugTarget(ELEMENT element);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/PushChangesAction.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,81 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.ui.actions;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.chromium.debug.core.ChromiumDebugPlugin;
+import org.chromium.sdk.LiveEditExtension;
+import org.chromium.sdk.UpdatableScript;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * The main action of LiveEdit feature. It gets the current state of a working file and pushes
+ * it into running V8 VM.
+ */
+public class PushChangesAction extends V8ScriptAction {
+  @Override
+  protected void execute(final FilePair filePair) {
+    UpdatableScript updatableScript =
+        LiveEditExtension.castToUpdatableScript(filePair.getVmResource().getScript());
+
+    if (updatableScript == null) {
+      throw new RuntimeException();
+    }
+
+    byte[] fileData;
+    try {
+      fileData = readFileContents(filePair.getFile());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    } catch (CoreException e) {
+      throw new RuntimeException(e);
+    }
+
+    // We are using default charset here like usually.
+    String newSource = new String(fileData);
+
+    UpdatableScript.UpdateCallback callback = new UpdatableScript.UpdateCallback() {
+      public void success(Object report) {
+        ChromiumDebugPlugin.log(new Status(IStatus.OK, ChromiumDebugPlugin.PLUGIN_ID,
+            "Script has been successfully updated on remote: " + report)); //$NON-NLS-1$
+      }
+      public void failure(String message) {
+        ChromiumDebugPlugin.log(new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID,
+            "Failed to change script on remote: " + message)); //$NON-NLS-1$
+      }
+    };
+
+    updatableScript.setSourceOnRemote(newSource, callback, null);
+  }
+
+
+  private static byte[] readFileContents(IFile file) throws IOException, CoreException {
+    InputStream inputStream = file.getContents();
+    try {
+      return readBytes(inputStream);
+    } finally {
+      inputStream.close();
+    }
+  }
+
+  private static byte[] readBytes(InputStream inputStream) throws IOException {
+    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+    byte[] array = new byte[1024];
+    while (true) {
+      int len = inputStream.read(array);
+      if (len == -1) {
+        break;
+      }
+      buffer.write(array, 0, len);
+    }
+    return buffer.toByteArray();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/SynchronizeBreakpoints.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,127 @@
+package org.chromium.debug.ui.actions;
+
+import java.text.MessageFormat;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.chromium.debug.core.ChromiumDebugPlugin;
+import org.chromium.debug.core.model.BreakpointSynchronizer;
+import org.chromium.debug.core.model.DebugTargetImpl;
+import org.chromium.debug.core.model.BreakpointSynchronizer.Direction;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.model.IDebugElement;
+import org.eclipse.debug.core.model.IDebugTarget;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+
+public class SynchronizeBreakpoints implements IWorkbenchWindowActionDelegate {
+
+  public static class ResetRemote extends SynchronizeBreakpoints {
+    public ResetRemote() {
+      super(BreakpointSynchronizer.Direction.RESET_REMOTE);
+    }
+  }
+
+  public static class ResetLocal extends SynchronizeBreakpoints {
+    public ResetLocal() {
+      super(BreakpointSynchronizer.Direction.RESET_LOCAL);
+    }
+  }
+
+  public static class Merge extends SynchronizeBreakpoints {
+    public Merge() {
+      super(BreakpointSynchronizer.Direction.MERGE);
+    }
+  }
+
+  private final BreakpointSynchronizer.Direction direction;
+
+  protected SynchronizeBreakpoints(Direction direction) {
+    this.direction = direction;
+  }
+
+  public void dispose() {
+  }
+
+  public void init(IWorkbenchWindow window) {
+  }
+
+  public void run(IAction action) {
+    if (currentRunnable == null) {
+      return;
+    }
+    currentRunnable.run();
+    currentRunnable = null;
+  }
+
+  public void selectionChanged(IAction action, ISelection selection) {
+    currentRunnable = createRunnable(selection);
+    action.setEnabled(currentRunnable != null);
+  }
+
+  private Runnable createRunnable(ISelection selection) {
+    if (selection instanceof IStructuredSelection == false) {
+      return null;
+    }
+    IStructuredSelection structuredSelection = (IStructuredSelection) selection;
+    final Set<DebugTargetImpl> targets = new HashSet<DebugTargetImpl>(3);
+    for (Iterator<?> it = structuredSelection.iterator(); it.hasNext(); ) {
+      Object element = it.next();
+      IDebugTarget debugTarget;
+      if (element instanceof ILaunch) {
+        ILaunch launch = (ILaunch) element;
+        debugTarget = launch.getDebugTarget();
+      } else if (element instanceof IDebugElement) {
+        IDebugElement debugElement = (IDebugElement) element;
+        debugTarget = debugElement.getDebugTarget();
+      } else {
+        continue;
+      }
+      if (debugTarget instanceof DebugTargetImpl == false) {
+        continue;
+      }
+      DebugTargetImpl debugTargetImpl = (DebugTargetImpl) debugTarget;
+      targets.add(debugTargetImpl);
+    }
+    if (targets.isEmpty()) {
+      return null;
+    }
+    if (direction != BreakpointSynchronizer.Direction.RESET_REMOTE && targets.size() > 1) {
+      // Only "reset remote" mode is implemented for a multiple selection.
+      return null;
+    }
+
+    return new Runnable() {
+      public void run() {
+        new Job(MessageFormat.format(Messages.SynchronizeBreakpoints_JOB_TITLE, targets.size())) {
+          @Override
+          protected IStatus run(IProgressMonitor monitor) {
+            // TODO(peter.rybin): consider blocking this method until callback is invoked to
+            // keep the UI jobs open while something is still happening.
+            BreakpointSynchronizer.Callback callback = new BreakpointSynchronizer.Callback() {
+              public void onDone(IStatus status) {
+                ChromiumDebugPlugin.log(status);
+              }
+            };
+
+            // TODO(peter.rybin): consider showing progress for several targets.
+            for (DebugTargetImpl target : targets) {
+              target.synchronizeBreakpoints(direction, callback);
+            }
+            return Status.OK_STATUS;
+          }
+        }.schedule();
+      }
+    };
+  }
+
+  private Runnable currentRunnable;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/V8ScriptAction.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,193 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.ui.actions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.chromium.debug.core.model.DebugTargetImpl;
+import org.chromium.debug.core.model.VmResource;
+import org.chromium.debug.core.util.ChromiumDebugPluginUtil;
+import org.chromium.sdk.JavascriptVm;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.resources.mapping.ResourceMapping;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.ui.IActionDelegate2;
+import org.eclipse.ui.IObjectActionDelegate;
+import org.eclipse.ui.IWorkbenchPart;
+
+/**
+ * A base class for all LiveEdit actions that are scoped to a working file from user workspace.
+ * It makes all necessary checks and prepares data in form of {@link FilePair} class.
+ * The concrete actions implement the {@link #execute(FilePair)} method.
+ */
+abstract class V8ScriptAction implements IObjectActionDelegate, IActionDelegate2 {
+  private Runnable currentRunnable = null;
+
+  public void setActivePart(IAction action, IWorkbenchPart targetPart) {
+  }
+
+  public void run(IAction action) {
+    if (currentRunnable == null) {
+      return;
+    }
+    currentRunnable.run();
+    currentRunnable = null;
+  }
+
+  public void selectionChanged(IAction action, ISelection selection) {
+    currentRunnable = createRunnable(selection);
+    action.setEnabled(currentRunnable != null);
+  }
+
+  private Runnable createRunnable(ISelection selection) {
+    if (selection instanceof IStructuredSelection == false) {
+      return null;
+    }
+    IStructuredSelection structured = (IStructuredSelection) selection;
+    if (structured.size() != 1) {
+      return null;
+    }
+
+    Object firstElement = structured.getFirstElement();
+    if (firstElement instanceof ResourceMapping == false) {
+      return null;
+    }
+    ResourceMapping resourceMapping = (ResourceMapping) firstElement;
+    final List<IResource> resourceList = new ArrayList<IResource>(1);
+    IResourceVisitor visitor = new IResourceVisitor() {
+      public boolean visit(IResource resource) throws CoreException {
+        resourceList.add(resource);
+        return false;
+      }
+    };
+    try {
+      resourceMapping.accept(null, visitor, null);
+    } catch (CoreException e) {
+      throw new RuntimeException(e);
+    }
+    if (resourceList.size() != 1) {
+      return null;
+    }
+    if (resourceList.get(0) instanceof IFile == false) {
+      return null;
+    }
+    final IFile file = (IFile) resourceList.get(0);
+    if (!filterFileName(file.getName())) {
+      return null;
+    }
+    return new Runnable() {
+      public void run() {
+        try {
+          execute(file);
+        } catch (RuntimeException e) {
+          // TODO(peter.rybin): Handle it.
+          throw e;
+        }
+      }
+    };
+  }
+
+  private void execute(IFile file) {
+    List<? extends FilePair> filePairList = getFilePairs(file);
+    FilePair filePair = getSingleFilePair(filePairList);
+    execute(filePair);
+  }
+
+  protected abstract void execute(FilePair filePair);
+
+  /**
+   * A temporary method that excludes all cases when there are more than one file pair for a
+   * user file. The proper solution ought to provide a UI for user so that he could review
+   * which debug sessions should be included in action.
+   */
+  private static FilePair getSingleFilePair(List<? extends FilePair> pairs) {
+    if (pairs.size() == 0) {
+      throw new RuntimeException("File is not associated with any V8 VM");
+    }
+    if (pairs.size() != 1) {
+      throw new RuntimeException(
+          "File is associated with several V8 VMs, this is not supported yet.");
+    }
+    return pairs.get(0);
+  }
+
+  /**
+   * Finds all file pairs for a user working file. One working file may correspond to several
+   * scripts if there are more than one debug sessions.
+   */
+  private static List<? extends FilePair> getFilePairs(IFile localFile) {
+    List<DebugTargetImpl> targetList = DebugTargetImpl.getAllDebugTargetImpls();
+    ArrayList<FilePair> result = new ArrayList<FilePair>(targetList.size());
+
+    for (DebugTargetImpl target : targetList) {
+      VmResource script;
+      try {
+        script = target.getVmResource(localFile);
+      } catch (CoreException e) {
+        throw new RuntimeException("Failed to resolve script from the file " + localFile, e);
+      }
+      if (script == null) {
+        continue;
+      }
+      result.add(new FilePair(localFile, script, target));
+    }
+    return result;
+  }
+
+  protected static class FilePair {
+    private final IFile file;
+    private final VmResource vmResource;
+    private final DebugTargetImpl debugTargetImpl;
+
+    FilePair(IFile file, VmResource vmResource, DebugTargetImpl debugTargetImpl) {
+      this.file = file;
+      this.vmResource = vmResource;
+      this.debugTargetImpl = debugTargetImpl;
+    }
+    protected IFile getFile() {
+      return file;
+    }
+    protected VmResource getVmResource() {
+      return vmResource;
+    }
+    protected JavascriptVm getJavascriptVm() {
+      return debugTargetImpl.getJavascriptEmbedder().getJavascriptVm();
+    }
+    protected DebugTargetImpl getDebugTarget() {
+      return debugTargetImpl;
+    }
+  }
+
+  /**
+   * @return true if action should be enabled for this file name
+   */
+  private boolean filterFileName(String name) {
+    for (String suffix : ChromiumDebugPluginUtil.SUPPORTED_EXTENSIONS_SUFFIX_LIST) {
+      if (name.endsWith(suffix)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public void dispose() {
+    currentRunnable = null;
+  }
+
+  public void init(IAction action) {
+  }
+
+  public void runWithEvent(IAction action, Event event) {
+    run(action);
+  }
+}
+
--- a/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/messages.properties	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/actions/messages.properties	Mon Jun 07 16:51:19 2010 -0700
@@ -5,3 +5,4 @@
 ExpressionEvaluator_SocketError=Socket error while evaluating expression
 ExpressionEvaluator_UnableToEvaluateExpression=Unable to evaluate expression
 JsBreakpointPropertiesRulerAction_ItemLabel=Breakpoint Properties...
+SynchronizeBreakpoints_JOB_TITLE=Synchronize breakpoints in {0} target(s)
--- a/org.chromium.debug.ui/src/org/chromium/debug/ui/editors/JsDocumentProvider.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/editors/JsDocumentProvider.java	Mon Jun 07 16:51:19 2010 -0700
@@ -4,10 +4,12 @@
 
 package org.chromium.debug.ui.editors;
 
+import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.jface.text.IDocument;
 import org.eclipse.jface.text.IDocumentPartitioner;
 import org.eclipse.jface.text.rules.FastPartitioner;
+import org.eclipse.ui.IFileEditorInput;
 import org.eclipse.ui.editors.text.FileDocumentProvider;
 
 /**
@@ -27,4 +29,22 @@
     return doc;
   }
 
+  /**
+   * Alternative implementation of the method that does not require file to be a physical file.
+   */
+  @Override
+  public boolean isDeleted(Object element) {
+    if (element instanceof IFileEditorInput) {
+      IFileEditorInput input= (IFileEditorInput) element;
+
+      IProject project = input.getFile().getProject();
+      if (project != null && !project.exists()) {
+        return true;
+      }
+
+      return !input.getFile().exists();
+    }
+    return super.isDeleted(element);
+  }
+
 }
--- a/org.chromium.debug.ui/src/org/chromium/debug/ui/launcher/LaunchTabGroup.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/launcher/LaunchTabGroup.java	Mon Jun 07 16:51:19 2010 -0700
@@ -8,6 +8,7 @@
 import org.eclipse.debug.ui.CommonTab;
 import org.eclipse.debug.ui.ILaunchConfigurationDialog;
 import org.eclipse.debug.ui.ILaunchConfigurationTab;
+import org.eclipse.debug.ui.sourcelookup.SourceLookupTab;
 
 /**
  * The Chromium JavaScript debugger launch configuration tab group.
@@ -20,7 +21,8 @@
   }
 
   public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
-    setTabs(new ILaunchConfigurationTab[] { new ChromiumRemoteTab(), new CommonTab() });
+    setTabs(new ILaunchConfigurationTab[] { new ChromiumRemoteTab(),
+        new SourceLookupTab(), new CommonTab() });
   }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/source/Messages.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,30 @@
+package org.chromium.debug.ui.source;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+  private static final String BUNDLE_NAME = "org.chromium.debug.ui.source.messages"; //$NON-NLS-1$
+  public static String SourceNameMapperContainerDialog_CONFIGURE_BUTTON;
+  public static String SourceNameMapperContainerDialog_CONFIGURE_TARGET_CONTAINER;
+  public static String SourceNameMapperContainerDialog_CONFIGURED_CONTAINER;
+  public static String SourceNameMapperContainerDialog_CONTAINER_GROUP;
+  public static String SourceNameMapperContainerDialog_DIALOG_SUBTITLE;
+  public static String SourceNameMapperContainerDialog_DIALOG_TITLE;
+  public static String SourceNameMapperContainerDialog_ENTER_PREFIX;
+  public static String SourceNameMapperContainerDialog_EXAMPLE_1;
+  public static String SourceNameMapperContainerDialog_EXAMPLE_2;
+  public static String SourceNameMapperContainerDialog_EXPLANATION_1;
+  public static String SourceNameMapperContainerDialog_EXPLANATION_2;
+  public static String SourceNameMapperContainerDialog_NOTHING_CONFIGURED;
+  public static String SourceNameMapperContainerDialog_PREFIX_GROUP;
+  public static String SourceNameMapperContainerDialog_PREFIX_NORMALLY_ENDS;
+  public static String SourceNameMapperContainerDialog_SAMPLE_FILE_NAME;
+  public static String SourceNameMapperContainerDialog_TYPE_LABEL;
+  static {
+    // initialize resource bundle
+    NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+  }
+
+  private Messages() {
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/source/SourceNameMapperContainerDialog.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,316 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.ui.source;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.chromium.debug.ui.DialogUtils.ComboWrapper;
+import org.chromium.debug.ui.DialogUtils.DialogElements;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerType;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+import org.eclipse.debug.ui.DebugUITools;
+import org.eclipse.debug.ui.sourcelookup.ISourceContainerBrowser;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * A dialog for adding and editing JavaScript source name mapper containers.
+ */
+public class SourceNameMapperContainerDialog extends TitleAreaDialog {
+  private final ISourceLookupDirector director;
+  private final PresetFieldValues initialParams;
+
+  private Result result = null;
+  private SourceNameMapperContainerDialogLogic logic = null;
+
+  /**
+   * An optional set of preset dialog field values. Useful in "edit" (not "add") mode of dialog.
+   */
+  public interface PresetFieldValues {
+    String getPrefix();
+    ISourceContainer getContainer();
+  }
+
+  public interface Result {
+    String getResultPrefix();
+    ISourceContainer getResultContainer();
+  }
+
+  public Result getResult() {
+    return result;
+  }
+
+  public SourceNameMapperContainerDialog(Shell shell, ISourceLookupDirector director,
+    PresetFieldValues initialParams) {
+    super(shell);
+    setShellStyle(getShellStyle() | SWT.RESIZE);
+    this.director = director;
+    this.initialParams = initialParams;
+  }
+
+  @Override
+  protected Control createDialogArea(Composite ancestor) {
+    getShell().setText(Messages.SourceNameMapperContainerDialog_DIALOG_TITLE);
+    setTitle(Messages.SourceNameMapperContainerDialog_DIALOG_SUBTITLE);
+
+    Composite parent = new Composite(ancestor, SWT.NULL);
+    {
+      GridLayout topLayout = new GridLayout();
+      topLayout.numColumns = 1;
+      parent.setLayout(topLayout);
+      parent.setLayoutData(new GridData(GridData.FILL_BOTH));
+    }
+
+    Label explanationOne = new Label(parent, 0);
+    explanationOne.setText(
+        Messages.SourceNameMapperContainerDialog_EXPLANATION_1);
+
+    Group prefixGroup = new Group(parent, SWT.NONE);
+    prefixGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+    prefixGroup.setText(Messages.SourceNameMapperContainerDialog_PREFIX_GROUP);
+    prefixGroup.setLayout(new GridLayout(1, false));
+    final Text prefixEditor = new Text(prefixGroup, SWT.SINGLE | SWT.BORDER);
+    prefixEditor.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+    final Label prefixExampleLine1Label = new Label(prefixGroup, 0);
+    prefixExampleLine1Label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+    final Label prefixExampleLine2Label = new Label(prefixGroup, 0);
+    prefixExampleLine2Label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+    Label explanationTwo = new Label(parent, 0);
+    explanationTwo.setText(Messages.SourceNameMapperContainerDialog_EXPLANATION_2);
+
+    Group containerGroup = new Group(parent, SWT.NONE);
+    containerGroup.setLayout(new GridLayout(1, false));
+    containerGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+    containerGroup.setText(Messages.SourceNameMapperContainerDialog_CONTAINER_GROUP);
+
+    Composite typeBlock = new Composite(containerGroup, SWT.NULL);
+    typeBlock.setLayout(new GridLayout(3, false));
+    typeBlock.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+    final List<ISourceContainerType> types =
+        filterTypes(DebugPlugin.getDefault().getLaunchManager().getSourceContainerTypes());
+
+    Collections.sort(types, TYPE_COMPARATOR_BY_NAME);
+
+    String[] typeNameArray = new String[types.size()];
+    for (int i = 0; i < typeNameArray.length; i++) {
+      typeNameArray[i] = types.get(i).getName();
+    }
+
+    Label comboLabel = new Label(typeBlock, 0);
+    comboLabel.setText(Messages.SourceNameMapperContainerDialog_TYPE_LABEL);
+
+    Combo typesCombo = new Combo(typeBlock, SWT.READ_ONLY);
+    typesCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+    typesCombo.setFont(parent.getFont());
+    typesCombo.setItems(typeNameArray);
+    if (typeNameArray.length > 0) {
+      typesCombo.select(0);
+    }
+    final Button configureButton = new Button(typeBlock, SWT.PUSH);
+    configureButton.setText(Messages.SourceNameMapperContainerDialog_CONFIGURE_BUTTON);
+
+    final Composite statusBox = new Composite(containerGroup, SWT.NULL);
+    statusBox.setLayout(new GridLayout(3, false));
+    statusBox.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+    final Label statusLabel = new Label(statusBox, 0);
+    final Label containerTypeIconLabel = new Label(statusBox, 0);
+    final Label containerNameLabel = new Label(statusBox, 0);
+
+    Dialog.applyDialogFont(parent);
+
+    // Implementing Elements interface
+    final ComboWrapper<ISourceContainerType> comboWrapper =
+        new ComboWrapper<ISourceContainerType>(typesCombo) {
+      @Override
+      public ISourceContainerType getSelected() {
+        return types.get(getCombo().getSelectionIndex());
+      }
+      @Override
+      public void setSelected(ISourceContainerType element) {
+        int index = types.indexOf(element);
+        if (index != -1) {
+          getCombo().select(index);
+        }
+      }
+    };
+
+    final ContainerStatusGroup containerStatusGroup = new ContainerStatusGroup() {
+      public Label getStatusLabel() {
+        return statusLabel;
+      }
+      public Label getTypeImageLabel() {
+        return containerTypeIconLabel;
+      }
+      public Label getContainerNameLabel() {
+        return containerNameLabel;
+      }
+      public void layout() {
+        statusBox.layout();
+      }
+      public void setEnabled(boolean enabled) {
+        statusLabel.setEnabled(enabled);
+        containerTypeIconLabel.setEnabled(enabled);
+        containerNameLabel.setEnabled(enabled);
+      }
+    };
+
+    Elements elements = new Elements() {
+      public Text getPrefixField() {
+        return prefixEditor;
+      }
+      public Label getPrefixExampleLine1Label() {
+        return prefixExampleLine1Label;
+      }
+      public Label getPrefixExampleLine2Label() {
+        return prefixExampleLine2Label;
+      }
+      public Button getConfigureButton() {
+        return configureButton;
+      }
+      public ComboWrapper<ISourceContainerType> getContainerTypeCombo() {
+        return comboWrapper;
+      }
+      public Shell getShell() {
+        return SourceNameMapperContainerDialog.this.getShell();
+      }
+      public ContainerStatusGroup getContainerStatusGroup() {
+        return containerStatusGroup;
+      }
+      public Button getOkButton() {
+        return SourceNameMapperContainerDialog.this.getButton(IDialogConstants.OK_ID);
+      }
+      public void setMessage(String message, int type) {
+        SourceNameMapperContainerDialog.this.setMessage(message, type);
+      }
+    };
+
+    logic = SourceNameMapperContainerDialogLogic.create(elements, director, initialParams);
+
+    return parent;
+  }
+
+  @Override
+  public void create() {
+    super.create();
+    logic.updateAll();
+  }
+
+  @Override
+  protected void okPressed() {
+    result = logic.getResult();
+    super.okPressed();
+  }
+
+  /**
+   * A main interface to dialog elements, that are used from logic engine.
+   */
+  interface Elements extends DialogElements {
+    Text getPrefixField();
+    Label getPrefixExampleLine1Label();
+    Label getPrefixExampleLine2Label();
+    ComboWrapper<ISourceContainerType> getContainerTypeCombo();
+    Button getConfigureButton();
+    ContainerStatusGroup getContainerStatusGroup();
+  }
+
+  interface ContainerStatusGroup {
+    Label getStatusLabel();
+    Label getTypeImageLabel();
+    Label getContainerNameLabel();
+    void layout();
+    void setEnabled(boolean enabled);
+  }
+
+  interface ConfigureButtonAction {
+    ISourceContainer run(Shell shell);
+  }
+
+  // Creates action implementation for a configure button or return null.
+  static ConfigureButtonAction prepareConfigureAction(ISourceContainerType type,
+      ISourceContainer alreadyCreatedContainer,
+      final ISourceLookupDirector director) {
+    if (type == null) {
+      return null;
+    }
+    final ISourceContainerBrowser browser = DebugUITools.getSourceContainerBrowser(type.getId());
+    if (browser == null) {
+      return null;
+    }
+    abstract class ActionBase implements ConfigureButtonAction {
+      public ISourceContainer run(Shell shell) {
+        ISourceContainer[] containers = runImpl(shell);
+        if (containers.length != 1) {
+          return null;
+        }
+        return containers[0];
+      }
+      abstract ISourceContainer[] runImpl(Shell shell);
+    }
+    ISourceContainer[] containers;
+    if (alreadyCreatedContainer != null && alreadyCreatedContainer.getType().equals(type)) {
+      // Edit existing.
+      final ISourceContainer[] alreadyCreatedContainerArray = { alreadyCreatedContainer };
+      if (browser.canEditSourceContainers(director, alreadyCreatedContainerArray)) {
+        return new ActionBase() {
+          @Override ISourceContainer[] runImpl(Shell shell) {
+            return browser.editSourceContainers(shell, director, alreadyCreatedContainerArray);
+          }
+        };
+      }
+    }
+    // Add new.
+    if (browser.canAddSourceContainers(director)) {
+      return new ActionBase() {
+        @Override ISourceContainer[] runImpl(Shell shell) {
+          return browser.addSourceContainers(shell, director);
+        }
+      };
+    }
+    return null;
+  }
+
+  private static final Comparator<ISourceContainerType> TYPE_COMPARATOR_BY_NAME =
+      new Comparator<ISourceContainerType>() {
+    public int compare(ISourceContainerType o1, ISourceContainerType o2) {
+      return o1.getName().compareTo(o2.getName());
+    }
+  };
+
+  private List<ISourceContainerType> filterTypes(ISourceContainerType[] types){
+    ArrayList<ISourceContainerType> result = new ArrayList<ISourceContainerType>();
+    for (int i = 0; i< types.length; i++) {
+      ISourceContainerType type = types[i];
+      if (director.supportsSourceContainerType(type)) {
+        ISourceContainerBrowser sourceContainerBrowser =
+            DebugUITools.getSourceContainerBrowser(type.getId());
+        if(sourceContainerBrowser != null &&
+            sourceContainerBrowser.canAddSourceContainers(director)) {
+          result.add(type);
+        }
+      }
+    }
+    return result;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/source/SourceNameMapperContainerDialogLogic.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,272 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.ui.source;
+
+import static org.chromium.debug.ui.DialogUtils.createErrorOptional;
+import static org.chromium.debug.ui.DialogUtils.createOptional;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.chromium.debug.ui.DialogUtils.ExpressionProcessor;
+import org.chromium.debug.ui.DialogUtils.Message;
+import org.chromium.debug.ui.DialogUtils.MessagePriority;
+import org.chromium.debug.ui.DialogUtils.OkButtonControl;
+import org.chromium.debug.ui.DialogUtils.Optional;
+import org.chromium.debug.ui.DialogUtils.Updater;
+import org.chromium.debug.ui.DialogUtils.ValueConsumer;
+import org.chromium.debug.ui.DialogUtils.ValueProcessor;
+import org.chromium.debug.ui.DialogUtils.ValueSource;
+import org.chromium.debug.ui.source.SourceNameMapperContainerDialog.ConfigureButtonAction;
+import org.chromium.debug.ui.source.SourceNameMapperContainerDialog.ContainerStatusGroup;
+import org.chromium.debug.ui.source.SourceNameMapperContainerDialog.Elements;
+import org.chromium.debug.ui.source.SourceNameMapperContainerDialog.PresetFieldValues;
+import org.chromium.debug.ui.source.SourceNameMapperContainerDialog.Result;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerType;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+import org.eclipse.debug.ui.DebugUITools;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * A separated logic of {@link SourceNameMapperContainerDialog}. It describes how data flows
+ * from input elements to the OK button with all necessary asserts. Basically it only uses
+ * {@link SourceNameMapperContainerDialog.Elements} interface from the dialog.
+ */
+abstract class SourceNameMapperContainerDialogLogic {
+  abstract Result getResult();
+  abstract void updateAll();
+
+  static SourceNameMapperContainerDialogLogic create(
+      final Elements elements, final ISourceLookupDirector director,
+      final PresetFieldValues initialParams) {
+    final Updater updater = new Updater();
+
+    final List<ValueSource<String>> warningSources = new ArrayList<ValueSource<String>>(2);
+
+    // Represents value entered as prefix.
+    final ValueSource<String> prefixEditor = new ValueSource<String>() {
+      public String getValue() {
+        return elements.getPrefixField().getText();
+      }
+      {
+        if (initialParams != null) {
+          elements.getPrefixField().setText(initialParams.getPrefix());
+        }
+        final ValueSource<String> updatableThis = this;
+        ModifyListener listener = new ModifyListener() {
+          public void modifyText(ModifyEvent e) {
+            updater.reportChanged(updatableThis);
+            updater.update();
+          }
+        };
+        elements.getPrefixField().addModifyListener(listener);
+      }
+    };
+
+    // Represents prefix value after it has been validated.
+    final ValueProcessor<Optional<String>> prefixValue = new ExpressionProcessor<String>(
+        Collections.<ValueSource<? extends Optional<?>>>emptyList()) {
+      @Override
+      protected Optional<String> calculateNormal() {
+        String prefix = prefixEditor.getValue();
+        Optional<String> result;
+        if (prefix == null || prefix.length() == 0) {
+          return createErrorOptional(new Message(
+              Messages.SourceNameMapperContainerDialog_ENTER_PREFIX,
+              MessagePriority.BLOCKING_INFO));
+        } else {
+          return createOptional(prefix);
+        }
+      }
+    };
+    updater.addConsumer(prefixValue, prefixEditor);
+
+    // Represents possible warning about prefix value having no trailing slash.
+    ValueProcessor<String> noSlashWarning = new ValueProcessor<String>() {
+      public void update(Updater updater) {
+        Optional<String> prefix = prefixValue.getValue();
+        String result;
+        if (prefix.isNormal() && !prefix.getNormal().endsWith("/")) { //$NON-NLS-1$
+          result = Messages.SourceNameMapperContainerDialog_PREFIX_NORMALLY_ENDS;
+        } else {
+          result = null;
+        }
+        setCurrentValue(result);
+        updater.reportChanged(this);
+      }
+    };
+    updater.addConsumer(noSlashWarning, prefixValue);
+    warningSources.add(noSlashWarning);
+
+    // Represents prefix rule example printer.
+    ValueConsumer prefixExample = new ValueConsumer() {
+      public void update(Updater updater) {
+        Optional<String> prefix = prefixValue.getValue();
+        String line1;
+        String line2;
+        if (prefix.isNormal()) {
+          String sampleFileName = Messages.SourceNameMapperContainerDialog_SAMPLE_FILE_NAME;
+          line1 = NLS.bind(Messages.SourceNameMapperContainerDialog_EXAMPLE_1,
+              prefix.getNormal() + sampleFileName);
+          line2 = NLS.bind(Messages.SourceNameMapperContainerDialog_EXAMPLE_2, sampleFileName);
+        } else {
+          line1 = ""; //$NON-NLS-1$
+          line2 = ""; //$NON-NLS-1$
+        }
+        elements.getPrefixExampleLine1Label().setText(line1);
+        elements.getPrefixExampleLine2Label().setText(line2);
+      }
+    };
+    updater.addConsumer(prefixExample, prefixValue);
+
+    // Represents container type combo box.
+    final ValueSource<ISourceContainerType> selectedTypeValue =
+        new ValueSource<ISourceContainerType>() {
+      public ISourceContainerType getValue() {
+        return elements.getContainerTypeCombo().getSelected();
+      }
+      {
+        if (initialParams != null) {
+          ISourceContainerType type = initialParams.getContainer().getType();
+          elements.getContainerTypeCombo().setSelected(type);
+        }
+        final ValueSource<ISourceContainerType> updatableThis = this;
+        SelectionListener listener = new SelectionAdapter() {
+          @Override
+          public void widgetSelected(SelectionEvent e) {
+            updater.reportChanged(updatableThis);
+            updater.update();
+          }
+        };
+        elements.getContainerTypeCombo().addSelectionListener(listener);
+      }
+    };
+
+    // Represents "Configure" button that acts like a container factory.
+    final ValueProcessor<ISourceContainer> containerFactoryButtonValue =
+        new ValueProcessor<ISourceContainer>() {
+      private ConfigureButtonAction preparedAction = null;
+      {
+        if (initialParams != null) {
+          setCurrentValue(initialParams.getContainer());
+        }
+        final ValueSource<ISourceContainer> valueSourceThis = this;
+        elements.getConfigureButton().addSelectionListener(new SelectionAdapter() {
+          @Override
+          public void widgetSelected(SelectionEvent e) {
+            if (preparedAction != null) {
+              ISourceContainer value = preparedAction.run(elements.getShell());
+              if (value != null) {
+                setCurrentValue(value);
+              }
+              updater.reportChanged(valueSourceThis);
+              updater.update();
+              updateAction();
+            }
+          }
+        });
+      }
+      public void update(Updater updater) {
+        if (getValue() != null && !getValue().getType().equals(selectedTypeValue.getValue())) {
+          setCurrentValue(null);
+          updater.reportChanged(this);
+        }
+        updateAction();
+      }
+      private void updateAction() {
+        preparedAction = SourceNameMapperContainerDialog.prepareConfigureAction(
+            selectedTypeValue.getValue(), getValue(), director);
+        elements.getConfigureButton().setEnabled(preparedAction != null);
+      }
+    };
+    updater.addConsumer(containerFactoryButtonValue, selectedTypeValue);
+
+    // Represents printer that shows type and name of the created container.
+    ValueConsumer showContainerTypeValue = new ValueConsumer() {
+      public void update(Updater updater) {
+        ISourceContainer container = containerFactoryButtonValue.getValue();
+        String status;
+        Image image;
+        String name;
+        boolean enabled;
+        if (container == null) {
+          status = Messages.SourceNameMapperContainerDialog_NOTHING_CONFIGURED;
+          name = ""; //$NON-NLS-1$
+          image = null;
+          enabled = false;
+        } else {
+          status = Messages.SourceNameMapperContainerDialog_CONFIGURED_CONTAINER;
+          ISourceContainerType type = container.getType();
+          name = container.getName();
+          image = DebugUITools.getSourceContainerImage(type.getId());
+          enabled = true;
+        }
+        ContainerStatusGroup group = elements.getContainerStatusGroup();
+        group.getStatusLabel().setText(status);
+        group.getTypeImageLabel().setImage(image);
+        group.getContainerNameLabel().setText(name);
+        group.setEnabled(enabled);
+        group.layout();
+      }
+    };
+    updater.addConsumer(showContainerTypeValue, containerFactoryButtonValue);
+
+    // Represents expression that constructs dialog window result.
+    final ValueProcessor<? extends Optional<Result>> resultValue =
+        new ExpressionProcessor<Result>(
+            Arrays.<ValueSource<? extends Optional<?>>>asList(prefixValue) ) {
+          @Override
+          protected Optional<Result> calculateNormal() {
+            final String prefix = prefixValue.getValue().getNormal();
+            final ISourceContainer container = containerFactoryButtonValue.getValue();
+            if (container == null) {
+              return createErrorOptional(
+                  new Message(Messages.SourceNameMapperContainerDialog_CONFIGURE_TARGET_CONTAINER,
+                      MessagePriority.BLOCKING_INFO));
+            }
+            Result result = new Result() {
+              public ISourceContainer getResultContainer() {
+                return container;
+              }
+              public String getResultPrefix() {
+                return prefix;
+              }
+            };
+            return createOptional(result);
+          }
+    };
+    updater.addConsumer(resultValue, prefixValue, containerFactoryButtonValue);
+
+    // Represents controller that updates state of OK button and error messages.
+    OkButtonControl okButtonControl = new OkButtonControl(resultValue, warningSources, elements);
+    updater.addConsumer(okButtonControl, okButtonControl.getDependencies());
+
+    return new SourceNameMapperContainerDialogLogic() {
+      @Override
+      Result getResult() {
+        Optional<Result> optional = resultValue.getValue();
+        if (optional.isNormal()) {
+          return optional.getNormal();
+        } else {
+          // Normally should not be reachable, because UI should have disabled OK button.
+          return null;
+        }
+      }
+      @Override
+      void updateAll() {
+        updater.updateAll();
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/source/SourceNameMapperContainerPresentation.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,60 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.ui.source;
+
+import org.chromium.debug.core.SourceNameMapperContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+import org.eclipse.debug.ui.sourcelookup.ISourceContainerBrowser;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * A presentation for JavaScript Source Name Mapper container that supports adding and editing.
+ */
+public class SourceNameMapperContainerPresentation implements ISourceContainerBrowser {
+
+  public ISourceContainer[] addSourceContainers(Shell shell, ISourceLookupDirector director) {
+    return openDialog(shell, director, null);
+  }
+
+  public boolean canAddSourceContainers(ISourceLookupDirector director) {
+    return true;
+  }
+
+  public boolean canEditSourceContainers(ISourceLookupDirector director,
+      ISourceContainer[] containers) {
+    return containers.length == 1;
+  }
+
+  public ISourceContainer[] editSourceContainers(Shell shell, ISourceLookupDirector director,
+      ISourceContainer[] containers) {
+    final SourceNameMapperContainer originalContainer = (SourceNameMapperContainer) containers[0];
+    SourceNameMapperContainerDialog.PresetFieldValues params =
+        new SourceNameMapperContainerDialog.PresetFieldValues() {
+      public ISourceContainer getContainer() {
+        return originalContainer.getTargetContainer();
+      }
+      public String getPrefix() {
+        return originalContainer.getPrefix();
+      }
+    };
+
+    return openDialog(shell, director, params);
+  }
+
+  private ISourceContainer[] openDialog(Shell shell, ISourceLookupDirector director,
+      SourceNameMapperContainerDialog.PresetFieldValues params) {
+    SourceNameMapperContainerDialog dialog =
+        new SourceNameMapperContainerDialog(shell, director, params);
+    dialog.open();
+    SourceNameMapperContainerDialog.Result dialogResult = dialog.getResult();
+    if (dialogResult == null) {
+      return new ISourceContainer[0];
+    }
+    ISourceContainer result = new SourceNameMapperContainer(dialogResult.getResultPrefix(),
+        dialogResult.getResultContainer());
+    return new ISourceContainer[] { result };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.ui/src/org/chromium/debug/ui/source/messages.properties	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,16 @@
+SourceNameMapperContainerDialog_CONFIGURE_BUTTON=Configure ...
+SourceNameMapperContainerDialog_CONFIGURE_TARGET_CONTAINER=Configure target container
+SourceNameMapperContainerDialog_CONFIGURED_CONTAINER=Configured container: 
+SourceNameMapperContainerDialog_CONTAINER_GROUP=Target source container
+SourceNameMapperContainerDialog_DIALOG_SUBTITLE=Configure how source names get translated and applied to target source container
+SourceNameMapperContainerDialog_DIALOG_TITLE=JavaScript Source Name Mapper
+SourceNameMapperContainerDialog_ENTER_PREFIX=Enter prefix
+SourceNameMapperContainerDialog_EXAMPLE_1=E.g.: source name ''{0}''
+SourceNameMapperContainerDialog_EXAMPLE_2=gets converted into ''{0}''
+SourceNameMapperContainerDialog_EXPLANATION_1=All resource names that start with prefix will have this prefix removed...
+SourceNameMapperContainerDialog_EXPLANATION_2=... and the short name will be looked up in source container.
+SourceNameMapperContainerDialog_NOTHING_CONFIGURED=<nothing configured>
+SourceNameMapperContainerDialog_PREFIX_GROUP=Prefix of source name
+SourceNameMapperContainerDialog_PREFIX_NORMALLY_ENDS=Prefix normally ends with '/'
+SourceNameMapperContainerDialog_SAMPLE_FILE_NAME=utils/timer.js
+SourceNameMapperContainerDialog_TYPE_LABEL=Type:
--- a/org.chromium.sdk/src/org/chromium/sdk/Breakpoint.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/Breakpoint.java	Mon Jun 07 16:51:19 2010 -0700
@@ -7,7 +7,7 @@
 /**
  * A breakpoint in the browser JavaScript virtual machine. The {@code set*}
  * method invocations will not take effect until
- * {@link #flush(org.chromium.sdk.BrowserTab.BreakpointCallback)} is called.
+ * {@link #flush(org.chromium.sdk.JavascriptVm.BreakpointCallback)} is called.
  */
 public interface Breakpoint {
 
@@ -25,7 +25,7 @@
    *
    * @see #getIgnoreCount()
    * @see #setIgnoreCount(int)
-   * @see BrowserTab#setBreakpoint(Type, String, int, int, boolean, String, int, org.chromium.sdk.BrowserTab.BreakpointCallback)
+   * @see JavascriptVm#setBreakpoint
    */
   int EMPTY_VALUE = -1;
 
@@ -46,6 +46,26 @@
   long getId();
 
   /**
+   * @return scriptName as reported by the JavaScript VM debugger; may be null
+   */
+  String getScriptName();
+
+  /**
+   * @return scriptId as reported by the JavaScript VM debugger; may be null
+   */
+  Long getScriptId();
+
+  /**
+   * Returns line number of the breakpoint. As source is changed (typically with LiveEdit feature,
+   * and particularly by calling {@link UpdatableScript#setSourceOnRemote}) this value
+   * may become stale. It gets updated when {@link JavascriptVm#listBreakpoints} asynchronous
+   * method completes.
+   *
+   * @return 1-based line number in script source
+   */
+  long getLineNumber();
+
+  /**
    * @return whether this breakpoint is enabled
    */
   boolean isEnabled();
@@ -88,7 +108,7 @@
    *
    * @param callback to invoke once the operation result is available
    */
-  void clear(BrowserTab.BreakpointCallback callback);
+  void clear(JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback);
 
   /**
    * Flushes the breakpoint parameter changes (set* methods) into the browser
@@ -97,5 +117,5 @@
    *
    * @param callback to invoke once the operation result is available
    */
-  void flush(BrowserTab.BreakpointCallback callback);
+  void flush(JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback);
 }
--- a/org.chromium.sdk/src/org/chromium/sdk/CallFrame.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/CallFrame.java	Mon Jun 07 16:51:19 2010 -0700
@@ -32,6 +32,7 @@
   /**
    * @return the current line number in the Script corresponding to this frame
    *         (0-based) or {@code -1} if unknown
+   * TODO(peter.rybin): consider returning absolute line number here, not in-script number.
    */
   int getLineNumber();
 
--- a/org.chromium.sdk/src/org/chromium/sdk/JavascriptVm.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/JavascriptVm.java	Mon Jun 07 16:51:19 2010 -0700
@@ -98,7 +98,7 @@
    *        may be {@code null}
    */
   void setBreakpoint(Breakpoint.Type type, String target, int line, int position, boolean enabled,
-      String condition, int ignoreCount, BreakpointCallback callback);
+      String condition, int ignoreCount, BreakpointCallback callback, SyncCallback syncCallback);
 
   /**
    * Tries to suspend VM. If successful, {@link DebugEventListener#suspended(DebugContext)}
@@ -107,4 +107,15 @@
    *        may be {@code null}
    */
   void suspend(SuspendCallback callback);
+
+  interface ListBreakpointsCallback {
+    void success(Collection<? extends Breakpoint> breakpoints);
+    void failure(Exception exception);
+  }
+
+  /**
+   * Asynchronously reads breakpoints from remote VM. The now-effective collection of breakpoints
+   * is returned to callback. Already existing {@link Breakpoint} instances are preserved.
+   */
+  void listBreakpoints(ListBreakpointsCallback callback, SyncCallback syncCallback);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/LiveEditDebugEventListener.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,16 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk;
+
+/**
+ * An optional extension of {@link DebugEventListener} that supports experimental LiveEdit-related
+ * events.
+ */
+public interface LiveEditDebugEventListener extends DebugEventListener {
+  /**
+   * Reports that script source has been altered in remote VM.
+   */
+  void scriptContentChanged(UpdatableScript newScript);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/LiveEditExtension.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,38 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk;
+
+/**
+ * Helper class for experimental support of LiveEdit feature. While regular API does not
+ * support LiveEdit (not to break compatibility with existing clients), it gives access to extended
+ * interfaces.
+ * <p>
+ * This class encapsulates all instanceofs/casts that are considered to be untrackable
+ * in the main code and therefore harmful.
+ */
+public class LiveEditExtension {
+  /**
+   * Casts script to the interface that supports updating source on remote VM.
+   * @return extended interface or null if unsupported
+   */
+  public static UpdatableScript castToUpdatableScript(Script script) {
+    if (script instanceof UpdatableScript == false) {
+      return null;
+    }
+    return (UpdatableScript) script;
+  }
+
+  /**
+   * Casts listener to interface that accepts LiveEdit-related events.
+   * @return extended interface or null if unsupported
+   */
+  public static LiveEditDebugEventListener castToLiveEditListener(
+      DebugEventListener debugEventListener) {
+    if (debugEventListener instanceof LiveEditDebugEventListener == false) {
+      return null;
+    }
+    return (LiveEditDebugEventListener) debugEventListener;
+  }
+}
--- a/org.chromium.sdk/src/org/chromium/sdk/Script.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/Script.java	Mon Jun 07 16:51:19 2010 -0700
@@ -44,11 +44,17 @@
 
   /**
    * @return the start line of this script in the original document
-   *         (zero-based), inclusive
+   *         (zero-based)
    */
   int getStartLine();
 
   /**
+   * @return the start column of this script in the original document
+   *         (zero-based)
+   */
+  int getStartColumn();
+
+  /**
    * @return the end line of this script in the original document (zero-based),
    *         inclusive
    */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/UpdatableScript.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,27 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk;
+
+/**
+ * This interface is a part of {@link Script} interface. It extends {@link Script} in order
+ * to support experimental feature and is under development.
+ */
+public interface UpdatableScript extends Script {
+  /**
+   * Demands that script text should be replaced with a new one if possible.
+   * @param newSource new text of script
+   */
+  void setSourceOnRemote(String newSource, UpdateCallback callback, SyncCallback syncCallback);
+
+  interface UpdateCallback {
+    /**
+     * Script text has been successfully changed. {@link DebugEventListener#scriptChanged} will
+     * be called additionally. Besides, a current context may be dismissed and recreated after this
+     * event. The order of all listed event notifications is not currently specified.
+     */
+    void success(Object report);
+    void failure(String message);
+  }
+}
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/ContextBuilder.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/ContextBuilder.java	Mon Jun 07 16:51:19 2010 -0700
@@ -237,6 +237,17 @@
       }
     }
 
+    private boolean sendNoMessageAndInvalidate() {
+      synchronized (sendContextCommandsMonitor) {
+        if (!isValid) {
+          return false;
+        }
+        isValid = false;
+        return true;
+      }
+    }
+
+
     private class UserContext implements DebugContext {
       private final DebugContextData data;
 
@@ -266,6 +277,14 @@
         return data.exceptionData;
       }
 
+      Collection<Breakpoint> getBreakpointsHitSafe() {
+        return data.breakpointsHit;
+      }
+
+      ExceptionData getExceptionDataSafe() {
+        return data.exceptionData;
+      }
+
       public JsEvaluateContext getGlobalEvaluateContext() {
         return evaluateContext;
       }
@@ -306,6 +325,19 @@
         sendMessageAsyncAndIvalidate(message, commandCallback, true, null);
       }
 
+      /**
+       * Behaves as continue but does not send any messages to remote VM.
+       * This brings ContextBuilder to state when we are about to build a new context.
+       */
+      boolean continueLocally() {
+        if (!sendNoMessageAndInvalidate()) {
+          return false;
+        }
+        contextDismissed(UserContext.this);
+        getDebugSession().getDebugEventListener().resumed();
+        return true;
+      }
+
       InternalContext getInternalContextForTests() {
         return PreContext.this;
       }
@@ -385,6 +417,24 @@
     }
   }
 
+  /**
+   * Drops the current context and initiates reading data and building a new one.
+   * Must be called from Dispatch thread.
+   * @return ExpectingBacktraceStep or null if there is no active context currently
+   */
+  ExpectingBacktraceStep startRebuildCurrentContext() {
+    // We can use currentStep as long as we are being operated from Dispatch thread.
+    if (currentStep instanceof PreContext.UserContext == false) {
+      return null;
+    }
+    PreContext.UserContext userContext = (PreContext.UserContext) currentStep;
+    if (!userContext.continueLocally()) {
+      return null;
+    }
+    return buildNewContext().setContextState(userContext.getBreakpointsHitSafe(),
+        userContext.getExceptionDataSafe());
+  }
+
   static InternalContext getInternalContextForTests(DebugContext debugContext) {
     PreContext.UserContext userContext = (PreContext.UserContext) debugContext;
     return userContext.getInternalContextForTests();
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/DebugSession.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/DebugSession.java	Mon Jun 07 16:51:19 2010 -0700
@@ -10,6 +10,7 @@
 import java.util.List;
 
 import org.chromium.sdk.Breakpoint;
+import org.chromium.sdk.DebugContext;
 import org.chromium.sdk.DebugEventListener;
 import org.chromium.sdk.InvalidContextException;
 import org.chromium.sdk.Script;
@@ -32,7 +33,7 @@
 import org.chromium.sdk.internal.tools.v8.request.DebuggerMessageFactory;
 
 /**
- * A default, thread-safe implementation of the JsDebugContext interface.
+ * A class that holds and administers main parts of debug protocol implementation.
  */
 public class DebugSession {
 
@@ -58,7 +59,7 @@
 
   public DebugSession(DebugSessionManager sessionManager, V8ContextFilter contextFilter,
       V8CommandOutput v8CommandOutput) {
-    this.scriptManager = new ScriptManager(contextFilter);
+    this.scriptManager = new ScriptManager(contextFilter, this);
     this.sessionManager = sessionManager;
     this.breakpointManager = new BreakpointManager(this);
 
@@ -125,6 +126,27 @@
     return contextBuilder;
   }
 
+  /**
+   * Drops current context and creates a new one. This is useful if context is known to have changed
+   * (e.g. experimental feature LiveEdit may change current stack while execution is suspended).
+   * The method is asynchronous and returns immediately.
+   * Does nothing if currently there is no active context. Otherwise dismisses current context,
+   * invokes {@link DebugEventListener#resumed()} and initiates downloading stack frame descriptions
+   * and building new context. When the context is built,
+   * calls {@link DebugEventListener#suspended(DebugContext)}.
+   * <p>
+   * Must be called from Dispatch Thread.
+   * @return true if context has been actually dropped.
+   */
+  public boolean recreateCurrentContext() {
+    ContextBuilder.ExpectingBacktraceStep step = contextBuilder.startRebuildCurrentContext();
+    if (step == null) {
+      return false;
+    }
+    defaultResponseHandler.getBreakpointProcessor().processNextStep(step);
+    return true;
+  }
+
   public void suspend(final SuspendCallback suspendCallback) {
     V8CommandProcessor.V8HandlerCallback v8Callback = new V8CommandCallbackBase() {
       @Override
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/JavascriptVmImpl.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/JavascriptVmImpl.java	Mon Jun 07 16:51:19 2010 -0700
@@ -9,6 +9,7 @@
 import org.chromium.sdk.Breakpoint;
 import org.chromium.sdk.CallbackSemaphore;
 import org.chromium.sdk.JavascriptVm;
+import org.chromium.sdk.SyncCallback;
 import org.chromium.sdk.internal.tools.v8.MethodIsBlockingException;
 
 /**
@@ -35,9 +36,14 @@
 
   public void setBreakpoint(Breakpoint.Type type, String target, int line,
       int position, boolean enabled, String condition, int ignoreCount,
-      BreakpointCallback callback) {
+      BreakpointCallback callback, SyncCallback syncCallback) {
     getDebugSession().getBreakpointManager()
-        .setBreakpoint(type, target, line, position, enabled, condition, ignoreCount, callback);
+        .setBreakpoint(type, target, line, position, enabled, condition, ignoreCount, callback,
+        syncCallback);
+  }
+
+  public void listBreakpoints(final ListBreakpointsCallback callback, SyncCallback syncCallback) {
+    getDebugSession().getBreakpointManager().reloadBreakpoints(callback, syncCallback);
   }
 
   protected abstract DebugSession getDebugSession();
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/JsObjectImpl.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/JsObjectImpl.java	Mon Jun 07 16:51:19 2010 -0700
@@ -167,10 +167,10 @@
     List<JsVariableImpl> getPropertiesLazily() throws MethodIsBlockingException {
       synchronized (this) {
         if (properties == null) {
-
-        List<? extends PropertyReference> propertyRefs = getPropertyRefs(getSubpropertiesMirror());
-        ValueLoader valueLoader = context.getValueLoader();
-        List<ValueMirror> subMirrors = valueLoader.getOrLoadValueFromRefs(propertyRefs);
+          List<? extends PropertyReference> propertyRefs =
+              getPropertyRefs(getSubpropertiesMirror());
+          ValueLoader valueLoader = context.getValueLoader();
+          List<ValueMirror> subMirrors = valueLoader.getOrLoadValueFromRefs(propertyRefs);
 
           List<JsVariableImpl> wrappedProperties = createPropertiesFromMirror(subMirrors,
               propertyRefs);
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/ScriptImpl.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/ScriptImpl.java	Mon Jun 07 16:51:19 2010 -0700
@@ -4,20 +4,37 @@
 
 package org.chromium.sdk.internal;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
+import org.chromium.sdk.LiveEditDebugEventListener;
+import org.chromium.sdk.LiveEditExtension;
 import org.chromium.sdk.Script;
+import org.chromium.sdk.SyncCallback;
+import org.chromium.sdk.UpdatableScript;
+import org.chromium.sdk.internal.protocol.ChangeLiveBody;
+import org.chromium.sdk.internal.protocol.SuccessCommandResponse;
 import org.chromium.sdk.internal.protocol.data.ScriptHandle;
 import org.chromium.sdk.internal.protocol.data.SomeHandle;
 import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
+import org.chromium.sdk.internal.tools.v8.V8CommandCallbackBase;
+import org.chromium.sdk.internal.tools.v8.V8CommandProcessor;
+import org.chromium.sdk.internal.tools.v8.V8Helper;
 import org.chromium.sdk.internal.tools.v8.V8ProtocolUtil;
+import org.chromium.sdk.internal.tools.v8.V8Helper.ScriptLoadCallback;
+import org.chromium.sdk.internal.tools.v8.request.ChangeLiveMessage;
 
 /**
  * An objects that holds data for a "script" which is a part of a resource
  * loaded into the browser, identified by its original document URL, line offset
  * in the original document, and the line count this script spans.
  */
-public class ScriptImpl implements Script {
+public class ScriptImpl implements Script, UpdatableScript {
+
+  /** The class logger. */
+  private static final Logger LOGGER = Logger.getLogger(ScriptImpl.class.getName());
 
   /**
    * An object containing data that uniquely identify a V8 script chunk.
@@ -29,15 +46,19 @@
 
     public final int lineOffset;
 
+    public final int columnOffset;
+
     public final int endLine;
 
     public final long id;
 
-    public Descriptor(Type type, long id, String name, int lineOffset, int lineCount) {
+    public Descriptor(Type type, long id, String name, int lineOffset, int columnOffset,
+        int lineCount) {
       this.type = type;
       this.id = id;
       this.name = name;
       this.lineOffset = lineOffset;
+      this.columnOffset = columnOffset;
       this.endLine = lineOffset + lineCount - 1;
     }
 
@@ -45,8 +66,8 @@
     public int hashCode() {
       return
           name != null ? name.hashCode() : (int) id * 0x101 +
-          lineOffset * 0x1001 +
-          endLine * 0x10001;
+          lineOffset * 0x1001 + columnOffset * 0x10001 +
+          endLine * 0x100001;
     }
 
     @Override
@@ -61,6 +82,7 @@
       // The id equality is stronger than the name equality.
       return this.id == that.id &&
           this.lineOffset == that.lineOffset &&
+          this.columnOffset == that.columnOffset &&
           this.endLine == that.endLine;
     }
 
@@ -78,9 +100,10 @@
           return null;
         }
         int lineOffset = (int) script.lineOffset();
+        int columnOffset = (int) script.columnOffset();
         int lineCount = (int) script.lineCount();
         int id = V8ProtocolUtil.getScriptIdFromResponse(script).intValue();
-        return new Descriptor(type, id, name, lineOffset, lineCount);
+        return new Descriptor(type, id, name, lineOffset, columnOffset, lineCount);
       } catch (Exception e) {
         // not a script object has been passed in
         return null;
@@ -92,12 +115,15 @@
 
   private volatile String source = null;
 
+  private final DebugSession debugSession;
+
   /**
    * @param descriptor of the script retrieved from a "scripts" response
    */
-  public ScriptImpl(Descriptor descriptor) {
+  public ScriptImpl(Descriptor descriptor, DebugSession debugSession) {
     this.descriptor = descriptor;
     this.source = null;
+    this.debugSession = debugSession;
   }
 
   public Type getType() {
@@ -112,6 +138,10 @@
     return descriptor.lineOffset;
   }
 
+  public int getStartColumn() {
+    return descriptor.columnOffset;
+  }
+
   public int getEndLine() {
     return descriptor.endLine;
   }
@@ -132,6 +162,56 @@
     this.source = source;
   }
 
+  public void setSourceOnRemote(String newSource, UpdateCallback callback,
+      SyncCallback syncCallback) {
+    V8CommandProcessor.V8HandlerCallback v8Callback = createScriptUpdateCallback(callback);
+    debugSession.sendMessageAsync(new ChangeLiveMessage(getId(), newSource),
+        true, v8Callback, syncCallback);
+  }
+
+  private V8CommandProcessor.V8HandlerCallback createScriptUpdateCallback(
+      final UpdateCallback callback) {
+    if (callback == null) {
+      return null;
+    }
+    return new V8CommandCallbackBase() {
+      @Override
+      public void success(SuccessCommandResponse successResponse) {
+        ChangeLiveBody body;
+        try {
+          body = successResponse.getBody().asChangeLiveBody();
+        } catch (JsonProtocolParseException e) {
+          throw new RuntimeException(e);
+        }
+
+        ScriptLoadCallback scriptCallback = new ScriptLoadCallback() {
+          public void failure(String message) {
+            LOGGER.log(Level.SEVERE,
+                "Failed to reload script after LiveEdit script update; " + message);
+          }
+          public void success() {
+            LiveEditDebugEventListener listener =
+                LiveEditExtension.castToLiveEditListener(debugSession.getDebugEventListener());
+            if (listener != null) {
+              listener.scriptContentChanged(ScriptImpl.this);
+            }
+          }
+        };
+        V8Helper.reloadScriptAsync(debugSession, Collections.singletonList(getId()),
+            scriptCallback, null);
+
+        debugSession.recreateCurrentContext();
+
+        callback.success(body.getChangeLog());
+      }
+
+      @Override
+      public void failure(String message) {
+        callback.failure(message);
+      }
+    };
+  }
+
   @Override
   public int hashCode() {
     return
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/ScriptManager.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/ScriptManager.java	Mon Jun 07 16:51:19 2010 -0700
@@ -40,9 +40,11 @@
       Collections.synchronizedMap(new HashMap<Long, ScriptImpl>());
 
   private final V8ContextFilter contextFilter;
+  private final DebugSession debugSession;
 
-  ScriptManager(V8ContextFilter contextFilter) {
+  ScriptManager(V8ContextFilter contextFilter, DebugSession debugSession) {
     this.contextFilter = contextFilter;
+    this.debugSession = debugSession;
   }
 
   /**
@@ -62,7 +64,7 @@
       if (desc == null) {
         return null;
       }
-      theScript = new ScriptImpl(desc);
+      theScript = new ScriptImpl(desc, debugSession);
       idToScript.put(desc.id, theScript);
     }
     if (scriptBody.source() != null) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/protocol/ChangeLiveBody.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,15 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk.internal.protocol;
+
+import org.chromium.sdk.internal.protocolparser.JsonField;
+import org.chromium.sdk.internal.protocolparser.JsonSubtype;
+import org.chromium.sdk.internal.protocolparser.JsonType;
+
+@JsonType
+public interface ChangeLiveBody extends JsonSubtype<CommandResponseBody>  {
+  @JsonField(jsonLiteralName="change_log")
+  Object getChangeLog();
+}
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/protocol/CommandResponse.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/protocol/CommandResponse.java	Mon Jun 07 16:51:19 2010 -0700
@@ -8,6 +8,7 @@
 
 import org.chromium.sdk.internal.protocolparser.EnumValueCondition;
 import org.chromium.sdk.internal.protocolparser.JsonField;
+import org.chromium.sdk.internal.protocolparser.JsonOptionalField;
 import org.chromium.sdk.internal.protocolparser.JsonOverrideField;
 import org.chromium.sdk.internal.protocolparser.JsonSubtype;
 import org.chromium.sdk.internal.protocolparser.JsonSubtypeCasting;
@@ -37,6 +38,7 @@
   @JsonField(jsonLiteralName="request_seq")
   long getRequestSeq();
 
+  @JsonOptionalField
   String getCommand();
 
   boolean success();
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/protocol/CommandResponseBody.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/protocol/CommandResponseBody.java	Mon Jun 07 16:51:19 2010 -0700
@@ -41,4 +41,10 @@
 
   @JsonSubtypeCasting
   VersionBody asVersionBody() throws JsonProtocolParseException;
+
+  @JsonSubtypeCasting
+  ChangeLiveBody asChangeLiveBody() throws JsonProtocolParseException;
+
+  @JsonSubtypeCasting
+  ListBreakpointsBody asListBreakpointsBody() throws JsonProtocolParseException;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/protocol/ListBreakpointsBody.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,16 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk.internal.protocol;
+
+import java.util.List;
+
+import org.chromium.sdk.internal.protocol.data.BreakpointInfo;
+import org.chromium.sdk.internal.protocolparser.JsonSubtype;
+import org.chromium.sdk.internal.protocolparser.JsonType;
+
+@JsonType
+public interface ListBreakpointsBody extends JsonSubtype<CommandResponseBody> {
+  List<BreakpointInfo> breakpoints();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/protocol/data/BreakpointInfo.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,43 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk.internal.protocol.data;
+
+import org.chromium.sdk.internal.protocolparser.JsonNullable;
+import org.chromium.sdk.internal.protocolparser.JsonOptionalField;
+import org.chromium.sdk.internal.protocolparser.JsonType;
+
+@JsonType
+public interface BreakpointInfo {
+  Type type();
+
+  @JsonOptionalField
+  String script_name();
+
+  @JsonOptionalField
+  Long script_id();
+
+  long number();
+
+  long line();
+
+  Long column();
+
+  Long groupId();
+
+  long hit_count();
+
+  boolean active();
+
+  @JsonNullable
+  String condition();
+
+  long ignoreCount();
+
+  enum Type {
+    scriptName,
+    scriptId,
+    function
+  }
+}
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/protocolparser/dynamicimpl/JsonProtocolParser.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/protocolparser/dynamicimpl/JsonProtocolParser.java	Mon Jun 07 16:51:19 2010 -0700
@@ -374,7 +374,7 @@
 
         if (fieldTypeParser.asQuickParser() != null) {
           LazyParseFieldMethodHandler onDemandHandler = new LazyParseFieldMethodHandler(
-              fieldTypeParser.asQuickParser(), isOptional, fieldName);
+              fieldTypeParser.asQuickParser(), isOptional, fieldName, typeClass);
           onDemandHanlers.add(onDemandHandler);
           methodHandler = onDemandHandler;
         } else {
@@ -534,11 +534,14 @@
     private final QuickParser<?> quickParser;
     private final boolean isOptional;
     private final String fieldName;
+    private final Class<?> typeClass;
 
-    LazyParseFieldMethodHandler(QuickParser<?> quickParser, boolean isOptional, String fieldName) {
+    LazyParseFieldMethodHandler(QuickParser<?> quickParser, boolean isOptional, String fieldName,
+        Class<?> typeClass) {
       this.quickParser = quickParser;
       this.isOptional = isOptional;
       this.fieldName = fieldName;
+      this.typeClass = typeClass;
     }
 
     @Override
@@ -546,7 +549,8 @@
       try {
         return parse(objectData);
       } catch (JsonProtocolParseException e) {
-        throw new JsonProtocolParseRuntimeException("On demand parsing failed", e);
+        throw new JsonProtocolParseRuntimeException(
+            "On demand parsing failed for " + objectData.getUnderlyingObject(), e);
       }
     }
 
@@ -568,11 +572,13 @@
         try {
           return quickParser.parseValueQuick(value);
         } catch (JsonProtocolParseException e) {
-          throw new JsonProtocolParseException("Failed to parse field " + fieldName, e);
+          throw new JsonProtocolParseException("Failed to parse field " + fieldName + " in type " +
+              typeClass.getName(), e);
         }
       } else {
         if (!isOptional) {
-          throw new JsonProtocolParseException("Field is not optional: " + fieldName);
+          throw new JsonProtocolParseException("Field is not optional: " + fieldName +
+              " (in type " + typeClass.getName() + ")");
         }
         return null;
       }
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/BreakpointImpl.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/BreakpointImpl.java	Mon Jun 07 16:51:19 2010 -0700
@@ -5,7 +5,9 @@
 package org.chromium.sdk.internal.tools.v8;
 
 import org.chromium.sdk.Breakpoint;
-import org.chromium.sdk.BrowserTab;
+import org.chromium.sdk.JavascriptVm;
+import org.chromium.sdk.SyncCallback;
+import org.chromium.sdk.internal.protocol.data.BreakpointInfo;
 
 /**
  * A generic implementation of the Breakpoint interface.
@@ -23,6 +25,21 @@
   private long id;
 
   /**
+   * The corresponding script name as reported by the JavaScript VM. May be null.
+   */
+  private String scriptName;
+
+  /**
+   * The corresponding script id as reported by the JavaScript VM. May be null.
+   */
+  private Long scriptId;
+
+  /**
+   * Breakpoint line number. May become invalidated by LiveEdit actions.
+   */
+  private long lineNumber;
+
+  /**
    * Whether the breakpoint is enabled.
    */
   private boolean isEnabled;
@@ -50,16 +67,40 @@
    */
   private volatile boolean isDirty = false;
 
-  public BreakpointImpl(Type type, long id, boolean enabled, int ignoreCount, String condition,
-      BreakpointManager breakpointManager) {
+  public BreakpointImpl(Type type, long id, String scriptName, Long scriptId, long lineNumber,
+      boolean enabled, int ignoreCount, String condition, BreakpointManager breakpointManager) {
     this.type = type;
+    this.scriptName = scriptName;
+    this.scriptId = scriptId;
     this.id = id;
     this.isEnabled = enabled;
     this.ignoreCount = ignoreCount;
     this.condition = condition;
+    this.lineNumber = lineNumber;
     this.breakpointManager = breakpointManager;
   }
 
+  public BreakpointImpl(BreakpointInfo info, BreakpointManager breakpointManager) {
+    this.type = getType(info);
+    this.id = info.number();
+    this.breakpointManager = breakpointManager;
+    updateFromRemote(info);
+  }
+  public void updateFromRemote(BreakpointInfo info) {
+    if (this.type != getType(info)) {
+      throw new IllegalArgumentException();
+    }
+    if (this.id != info.number()) {
+      throw new IllegalArgumentException();
+    }
+    this.lineNumber = info.line();
+    this.isEnabled = info.active();
+    this.ignoreCount = (int) info.ignoreCount();
+    this.condition = info.condition();
+    this.scriptName = info.script_name();
+    this.scriptId = info.script_id();
+  }
+
   public boolean isEnabled() {
     return isEnabled;
   }
@@ -72,6 +113,14 @@
     return id;
   }
 
+  public String getScriptName() {
+    return scriptName;
+  }
+
+  public Long getScriptId() {
+    return scriptId;
+  }
+
   public int getIgnoreCount() {
     return ignoreCount;
   }
@@ -80,6 +129,10 @@
     return condition;
   }
 
+  public long getLineNumber() {
+    return lineNumber;
+  }
+
   public void setEnabled(boolean enabled) {
     if (this.isEnabled != enabled) {
       setDirty(true);
@@ -106,21 +159,21 @@
     return left == right || (left != null && left.equals(right));
   }
 
-  public void clear(final BrowserTab.BreakpointCallback callback) {
-    breakpointManager.clearBreakpoint(this, callback);
+  public void clear(JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback) {
+    breakpointManager.clearBreakpoint(this, callback, syncCallback);
     // The order must be preserved, otherwise the breakpointProcessor will not be able
     // to identify the original breakpoint ID.
     this.id = INVALID_ID;
   }
 
-  public void flush(final BrowserTab.BreakpointCallback callback) {
+  public void flush(final JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback) {
     if (!isDirty()) {
       if (callback != null) {
         callback.success(this);
       }
       return;
     }
-    breakpointManager.changeBreakpoint(this, callback);
+    breakpointManager.changeBreakpoint(this, callback, syncCallback);
     setDirty(false);
   }
 
@@ -132,4 +185,13 @@
     return isDirty;
   }
 
-}
\ No newline at end of file
+  private static Type getType(BreakpointInfo info) {
+    BreakpointInfo.Type infoType = info.type();
+    switch (infoType) {
+      case scriptId: return Type.SCRIPT_ID;
+      case scriptName: return Type.SCRIPT_NAME;
+      case function: return Type.FUNCTION;
+    }
+    throw new RuntimeException("Unknown type: " + infoType);
+  }
+}
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/BreakpointManager.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/BreakpointManager.java	Mon Jun 07 16:51:19 2010 -0700
@@ -1,22 +1,35 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
 package org.chromium.sdk.internal.tools.v8;
 
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 
 import org.chromium.sdk.Breakpoint;
-import org.chromium.sdk.BrowserTab;
+import org.chromium.sdk.JavascriptVm;
+import org.chromium.sdk.SyncCallback;
 import org.chromium.sdk.JavascriptVm.BreakpointCallback;
+import org.chromium.sdk.JavascriptVm.ListBreakpointsCallback;
 import org.chromium.sdk.internal.DebugSession;
 import org.chromium.sdk.internal.protocol.BreakpointBody;
+import org.chromium.sdk.internal.protocol.CommandResponseBody;
+import org.chromium.sdk.internal.protocol.ListBreakpointsBody;
 import org.chromium.sdk.internal.protocol.SuccessCommandResponse;
+import org.chromium.sdk.internal.protocol.data.BreakpointInfo;
 import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
 import org.chromium.sdk.internal.tools.v8.request.DebuggerMessageFactory;
+import org.chromium.sdk.internal.tools.v8.request.ListBreakpointsMessage;
 
 public class BreakpointManager {
   /**
    * This map shall contain only breakpoints with valid IDs.
    */
-  private final Map<Long, Breakpoint> idToBreakpoint = new HashMap<Long, Breakpoint>();
+  private final Map<Long, BreakpointImpl> idToBreakpoint = new HashMap<Long, BreakpointImpl>();
 
   private final DebugSession debugSession;
 
@@ -24,11 +37,28 @@
     this.debugSession = debugSession;
   }
 
-  public void setBreakpoint(final Breakpoint.Type type, String target, int line, int position,
-      final boolean enabled, final String condition, final int ignoreCount,
-      final BrowserTab.BreakpointCallback callback) {
+  public void setBreakpoint(final Breakpoint.Type type, final String target,
+      final int line, int position, final boolean enabled, final String condition,
+      final int ignoreCount, final JavascriptVm.BreakpointCallback callback,
+      SyncCallback syncCallback) {
+
+    final String scriptName;
+    final Long scriptId;
+    Object targetObject;
+    if (type == Breakpoint.Type.SCRIPT_ID) {
+      scriptName = null;
+      scriptId = Long.parseLong(target);
+      targetObject = scriptId;
+    } else if (type == Breakpoint.Type.SCRIPT_NAME) {
+      scriptName = target;
+      scriptId = null;
+      targetObject = scriptName;
+    } else {
+      throw new IllegalArgumentException("Unsupported breakpoint type " + type);
+    }
+
     debugSession.sendMessageAsync(
-        DebuggerMessageFactory.setBreakpoint(type, target, toNullableInteger(line),
+        DebuggerMessageFactory.setBreakpoint(type, targetObject, toNullableInteger(line),
             toNullableInteger(position), enabled, condition,
             toNullableInteger(ignoreCount)),
         true,
@@ -46,7 +76,7 @@
                 long id = body.getBreakpoint();
 
                 final BreakpointImpl breakpoint =
-                    new BreakpointImpl(type, id, enabled, ignoreCount,
+                    new BreakpointImpl(type, id, scriptName, scriptId, line, enabled, ignoreCount,
                         condition, BreakpointManager.this);
 
                 callback.success(breakpoint);
@@ -59,7 +89,7 @@
                 }
               }
             },
-            null);
+            syncCallback);
   }
 
   public Breakpoint getBreakpoint(Long id) {
@@ -67,7 +97,8 @@
   }
 
   public void clearBreakpoint(
-      final BreakpointImpl breakpointImpl, final BreakpointCallback callback) {
+      final BreakpointImpl breakpointImpl, final BreakpointCallback callback,
+      SyncCallback syncCallback) {
     long id = breakpointImpl.getId();
     if (id == Breakpoint.INVALID_ID) {
       return;
@@ -90,11 +121,11 @@
             }
           }
         },
-        null);
+        syncCallback);
   }
 
   public void changeBreakpoint(final BreakpointImpl breakpointImpl,
-      final BreakpointCallback callback) {
+      final BreakpointCallback callback, SyncCallback syncCallback) {
     debugSession.sendMessageAsync(
         DebuggerMessageFactory.changeBreakpoint(breakpointImpl),
         true,
@@ -112,7 +143,39 @@
             }
           }
         },
-        null);
+        syncCallback);
+  }
+
+  /**
+   * Reads a list of breakpoints from remote and updates local instances and the map.
+   */
+  public void reloadBreakpoints(final ListBreakpointsCallback callback, SyncCallback syncCallback) {
+    V8CommandCallbackBase v8Callback = new V8CommandCallbackBase() {
+      @Override
+      public void failure(String message) {
+        callback.failure(new Exception(message));
+      }
+      @Override
+      public void success(SuccessCommandResponse successResponse) {
+        CommandResponseBody body = successResponse.getBody();
+        ListBreakpointsBody listBreakpointsBody;
+        try {
+          listBreakpointsBody = body.asListBreakpointsBody();
+        } catch (JsonProtocolParseException e) {
+          callback.failure(new Exception("Failed to read server response", e));
+          return;
+        }
+        List<BreakpointInfo> infos = listBreakpointsBody.breakpoints();
+        try {
+          syncBreakpoints(infos);
+        } catch (RuntimeException e) {
+          callback.failure(new Exception("Failed to read server response", e));
+          return;
+        }
+        callback.success(Collections.unmodifiableCollection(idToBreakpoint.values()));
+      }
+    };
+    debugSession.sendMessageAsync(new ListBreakpointsMessage(), true, v8Callback, syncCallback);
   }
 
   private static Integer toNullableInteger(int value) {
@@ -120,4 +183,41 @@
         ? null
         : value;
   }
+
+  private void syncBreakpoints(List<BreakpointInfo> infoList) {
+    Map<Long, BreakpointImpl> actualBreakpoints = new HashMap<Long, BreakpointImpl>();
+    // Wrap all loaded BreakpointInfo as BreakpointImpl, possibly reusing old instances.
+    // Also check that all breakpoint id's in loaded list are unique.
+    for (BreakpointInfo info : infoList) {
+      if (info.type() == BreakpointInfo.Type.function) {
+        // We does not support function type breakpoints and ignore them.
+        continue;
+      }
+      BreakpointImpl breakpoint = idToBreakpoint.get(info.number());
+      if (breakpoint == null) {
+        breakpoint = new BreakpointImpl(info, this);
+      } else {
+        breakpoint.updateFromRemote(info);
+      }
+      Object conflict = actualBreakpoints.put(info.number(), breakpoint);
+      if (conflict != null) {
+        throw new RuntimeException("Duplicated breakpoint number " + info.number());
+      }
+    }
+
+    // Remove all obsolete breakpoints from the map.
+    for (Iterator<Long> it = idToBreakpoint.keySet().iterator(); it.hasNext(); ) {
+      Long id = it.next();
+      if (!actualBreakpoints.containsKey(id)) {
+        it.remove();
+      }
+    }
+
+    // Add breakpoints that are not in the main map yet.
+    for (BreakpointImpl breakpoint : actualBreakpoints.values()) {
+      if (!idToBreakpoint.containsKey(breakpoint.getId())) {
+        idToBreakpoint.put(breakpoint.getId(), breakpoint);
+      }
+    }
+  }
 }
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/DebuggerCommand.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/DebuggerCommand.java	Mon Jun 07 16:51:19 2010 -0700
@@ -16,11 +16,13 @@
   BACKTRACE("backtrace"),
   FRAME("frame"),
   SCRIPTS("scripts"),
+  CHANGELIVE("changelive"),
   SOURCE("source"),
   SCOPE("scope"),
   SETBREAKPOINT("setbreakpoint"),
   CHANGEBREAKPOINT("changebreakpoint"),
   CLEARBREAKPOINT("clearbreakpoint"),
+  LISTBREAKPOINTS("listbreakpoints"),
   LOOKUP("lookup"),
   SUSPEND("suspend"),
   VERSION("version"),
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8CommandCallbackBase.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8CommandCallbackBase.java	Mon Jun 07 16:51:19 2010 -0700
@@ -19,7 +19,7 @@
   public void messageReceived(CommandResponse response) {
     SuccessCommandResponse successResponse = response.asSuccess();
     if (successResponse == null) {
-      this.failure(response.asFailure().getMessage());
+      this.failure("Remote error: " + response.asFailure().getMessage());
       return;
     } else {
       success(successResponse);
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8Helper.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8Helper.java	Mon Jun 07 16:51:19 2010 -0700
@@ -19,6 +19,7 @@
 import org.chromium.sdk.internal.PropertyHoldingValueMirror;
 import org.chromium.sdk.internal.PropertyReference;
 import org.chromium.sdk.internal.ScopeMirror;
+import org.chromium.sdk.internal.ScriptImpl;
 import org.chromium.sdk.internal.ScriptManager;
 import org.chromium.sdk.internal.SubpropertiesMirror;
 import org.chromium.sdk.internal.ValueLoadException;
@@ -34,6 +35,7 @@
 import org.chromium.sdk.internal.protocol.data.ScriptHandle;
 import org.chromium.sdk.internal.protocol.data.ValueHandle;
 import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
+import org.chromium.sdk.internal.tools.v8.request.ContextlessDebuggerMessage;
 import org.chromium.sdk.internal.tools.v8.request.DebuggerMessageFactory;
 import org.chromium.sdk.internal.tools.v8.request.ScriptsMessage;
 
@@ -42,13 +44,7 @@
  */
 public class V8Helper {
 
-  /**
-   * The debug context in which the operations are performed.
-   */
-  private final DebugSession debugSession;
-
   public V8Helper(DebugSession debugSession) {
-    this.debugSession = debugSession;
   }
 
   public interface ScriptLoadCallback {
@@ -63,13 +59,33 @@
    */
   public static void reloadAllScriptsAsync(final DebugSession debugSession,
       final ScriptLoadCallback callback, SyncCallback syncCallback) {
+    reloadScriptAsync(debugSession, null, callback, syncCallback);
+  }
+
+  /**
+   * Loads specified scripts or all existing scripts and stores them in ScriptManager.
+   * @param ids ids of requested scripts or null for all scripts
+   * @param callback to invoke when the script reloading has completed
+   * @param syncCallback to invoke after callback, regardless of whether it has returned normally
+   *        or thrown an exception
+   */
+  public static void reloadScriptAsync(final DebugSession debugSession, final List<Long> ids,
+      final ScriptLoadCallback callback, SyncCallback syncCallback) {
+    ContextlessDebuggerMessage message = DebuggerMessageFactory.scripts(ids, true);
+    if (ids == null) {
+      message = DebuggerMessageFactory.scripts(ScriptsMessage.SCRIPTS_NORMAL, true);
+    } else {
+      message = DebuggerMessageFactory.scripts(ids, true);
+    }
     debugSession.sendMessageAsync(
-        DebuggerMessageFactory.scripts(ScriptsMessage.SCRIPTS_NORMAL, true),
+        message,
         true,
         new V8CommandCallbackBase() {
           @Override
           public void failure(String message) {
-            callback.failure(message);
+            if (callback != null) {
+              callback.failure(message);
+            }
           }
 
           @Override
@@ -83,13 +99,24 @@
             ScriptManager scriptManager = debugSession.getScriptManager();
             for (int i = 0; i < body.size(); ++i) {
               ScriptHandle scriptHandle = body.get(i);
+              if (ChromeDevToolSessionManager.JAVASCRIPT_VOID.equals(scriptHandle.source())) {
+                continue;
+              }
               Long id = V8ProtocolUtil.getScriptIdFromResponse(scriptHandle);
-              if (scriptManager.findById(id) == null &&
-                  !ChromeDevToolSessionManager.JAVASCRIPT_VOID.equals(scriptHandle.source())) {
+              ScriptImpl scriptById = scriptManager.findById(id);
+              if (scriptById == null) {
                 scriptManager.addScript(scriptHandle, successResponse.getRefs());
+              } else {
+                // A scrupulous refactoring note:
+                // do not call setSource in a legacy case, when ids parameter is null.
+                if (ids != null) {
+                  scriptById.setSource(scriptHandle.source());
+                }
               }
             }
-            callback.success();
+            if (callback != null) {
+              callback.success();
+            }
           }
         },
         syncCallback);
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8ProtocolUtil.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8ProtocolUtil.java	Mon Jun 07 16:51:19 2010 -0700
@@ -19,6 +19,7 @@
 import org.chromium.sdk.internal.protocol.BacktraceCommandBody;
 import org.chromium.sdk.internal.protocol.BreakEventBody;
 import org.chromium.sdk.internal.protocol.BreakpointBody;
+import org.chromium.sdk.internal.protocol.ChangeLiveBody;
 import org.chromium.sdk.internal.protocol.CommandResponse;
 import org.chromium.sdk.internal.protocol.CommandResponseBody;
 import org.chromium.sdk.internal.protocol.EventNotification;
@@ -26,10 +27,12 @@
 import org.chromium.sdk.internal.protocol.FailedCommandResponse;
 import org.chromium.sdk.internal.protocol.FrameObject;
 import org.chromium.sdk.internal.protocol.IncomingMessage;
+import org.chromium.sdk.internal.protocol.ListBreakpointsBody;
 import org.chromium.sdk.internal.protocol.ScopeBody;
 import org.chromium.sdk.internal.protocol.ScopeRef;
 import org.chromium.sdk.internal.protocol.SuccessCommandResponse;
 import org.chromium.sdk.internal.protocol.VersionBody;
+import org.chromium.sdk.internal.protocol.data.BreakpointInfo;
 import org.chromium.sdk.internal.protocol.data.ContextData;
 import org.chromium.sdk.internal.protocol.data.ContextHandle;
 import org.chromium.sdk.internal.protocol.data.FunctionValueHandle;
@@ -367,6 +370,8 @@
           ScopeRef.class,
           VersionBody.class,
           AfterCompileBody.class,
+          ChangeLiveBody.class,
+          ListBreakpointsBody.class,
 
           SomeHandle.class,
           ScriptHandle.class,
@@ -381,6 +386,7 @@
           SomeSerialized.class,
           ContextHandle.class,
           ContextData.class,
+          BreakpointInfo.class,
       });
     } catch (JsonProtocolModelParseException e) {
       throw new RuntimeException(e);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/ChangeLiveMessage.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,18 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk.internal.tools.v8.request;
+
+import org.chromium.sdk.internal.tools.v8.DebuggerCommand;
+
+/**
+ * Represents a "changelive" experimental V8 request message.
+ */
+public class ChangeLiveMessage extends ContextlessDebuggerMessage {
+  public ChangeLiveMessage(long scriptId, String newSource) {
+    super(DebuggerCommand.CHANGELIVE.value);
+    putArgument("script_id", scriptId);
+    putArgument("new_source", newSource);
+  }
+}
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/DebuggerMessageFactory.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/DebuggerMessageFactory.java	Mon Jun 07 16:51:19 2010 -0700
@@ -45,7 +45,7 @@
     return new SourceMessage(frame, fromLine, toLine);
   }
 
-  public static ContextlessDebuggerMessage setBreakpoint(Breakpoint.Type type, String target,
+  public static ContextlessDebuggerMessage setBreakpoint(Breakpoint.Type type, Object target,
       Integer line, Integer position, Boolean enabled, String condition, Integer ignoreCount) {
     return new SetBreakpointMessage(type, target, line, position, enabled, condition, ignoreCount);
   }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/ListBreakpointsMessage.java	Mon Jun 07 16:51:19 2010 -0700
@@ -0,0 +1,17 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk.internal.tools.v8.request;
+
+import org.chromium.sdk.internal.tools.v8.DebuggerCommand;
+
+/**
+ * Represents a "listbreakpoints" V8 request message.
+ */
+public class ListBreakpointsMessage extends ContextlessDebuggerMessage {
+
+  public ListBreakpointsMessage() {
+    super(DebuggerCommand.LISTBREAKPOINTS.value);
+  }
+}
--- a/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/SetBreakpointMessage.java	Mon Jun 07 16:33:07 2010 -0700
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/request/SetBreakpointMessage.java	Mon Jun 07 16:51:19 2010 -0700
@@ -34,7 +34,7 @@
    * @param ignoreCount nullable number specifying the amount of break point hits to ignore.
    *        Default is 0
    */
-  public SetBreakpointMessage(Breakpoint.Type type, String target,
+  public SetBreakpointMessage(Breakpoint.Type type, Object target,
       Integer line, Integer position, Boolean enabled, String condition,
       Integer ignoreCount) {
     super(DebuggerCommand.SETBREAKPOINT.value);