# HG changeset patch # User paulb # Date 1276038771 25200 # Node ID dd7d3426144c5598686b8e8cf6d86bdb6708c575 # Parent 1f666f57e7776fd4b34326ae2ac72134cf83d81a# Parent 8e561bc07a21b26593c3743fc1480703900de4ee merged heads diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/plugin.properties --- a/org.chromium.debug.core/plugin.properties Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/plugin.properties Tue Jun 08 16:12:51 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 diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/plugin.xml --- a/org.chromium.debug.core/plugin.xml Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/plugin.xml Tue Jun 08 16:12:51 2010 -0700 @@ -41,4 +41,40 @@ + + + + + + + + + + + + + + + + + + + + + + + diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/ChromiumDebugPlugin.java --- a/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumDebugPlugin.java Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumDebugPlugin.java Tue Jun 08 16:12:51 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 */ diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceComputer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceComputer.java Tue Jun 08 16:12:51 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() }; + } +} diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceDirector.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceDirector.java Tue Jun 08 16:12:51 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; + } +} diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/Messages.java --- a/org.chromium.debug.core/src/org/chromium/debug/core/Messages.java Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/Messages.java Tue Jun 08 16:12:51 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); diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/ReverseSourceLookup.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/ReverseSourceLookup.java Tue Jun 08 16:12:51 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; + } +} diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/SourceNameMapperContainer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/SourceNameMapperContainer.java Tue Jun 08 16:12:51 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); + } + } + } +} diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/VProjectSourceContainer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/VProjectSourceContainer.java Tue Jun 08 16:12:51 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. + *

+ * 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$ + } + } +} diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/messages.properties --- a/org.chromium.debug.core/src/org/chromium/debug/core/messages.properties Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/messages.properties Tue Jun 08 16:12:51 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 diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointAdapterFactory.java --- a/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointAdapterFactory.java Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointAdapterFactory.java Tue Jun 08 16:12:51 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(); } } diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointMap.java Tue Jun 08 16:12:51 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 sdkToUiMap = + new HashMap(); + private final Map uiToSdkMap = + new HashMap(); + + 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(); + } + } +} diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointRegistry.java --- a/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointRegistry.java Tue Jun 08 16:06:40 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> scriptIdToBreakpointEntries = - new HashMap>(); - - /** - * 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 entries = scriptIdToBreakpointEntries.get(scriptId); - if (entries == null) { - entries = new HashSet(); - 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 getBreakpointEntries(Script script) { - if (script == null) { - return Collections.emptySet(); - } - Collection entries = - scriptIdToBreakpointEntries.get(ScriptIdentifier.forScript(script)); - if (entries == null) { - return Collections.emptySet(); - } - Collection scriptBreakpoints = new LinkedList(); - // 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 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)); - } - -} diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointSynchronizer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointSynchronizer.java Tue Jun 08 16:12:51 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 sdkBreakpoints = readSdkBreakpoints(javascriptVm); + // Collect all local breakpoints. + Set uiBreakpoints = getUiBreakpoints(); + + List sdkBreakpoints2 = new ArrayList(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 sortedUiBreakpoints = + sortBreakpoints(uiBreakpoints, uiBreakpointHandler); + SortedBreakpoints sortedSdkBreakpoints = + sortBreakpoints(sdkBreakpoints2, sdkBreakpointHandler); + + BreakpointMerger breakpointMerger = new BreakpointMerger(direction, breakpointInTargetMap); + + // Find all unlinked breakpoints on both sides. + mergeBreakpoints(breakpointMerger, sortedUiBreakpoints, sortedSdkBreakpoints); + + List sdkBreakpointsToDelete; + List sdkBreakpointsToCreate; + List uiBreakpointsToDelete; + List 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 sdkBreakpointsToDelete, + List 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 sdkBreakpointsToCreate, + List 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 { + private final Direction direction; + private final List missingUi = new ArrayList(); + private final List missingSdk = new ArrayList(); + 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 getMissingUi() { + return missingUi; + } + List 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 exceptions = new ArrayList(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 counters; + + ReportBuilder(Direction direction) { + this.direction = direction; + counters = new EnumMap(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 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 { + /** @return vm resource name or null */ + abstract VmResourceId getVmResourceId(B breakpoint); + /** @return 0-based number */ + abstract long getLineNumber(B breakpoint); + } + + private final PropertyHandler uiBreakpointHandler = + new PropertyHandler() { + @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 sdkBreakpointHandler = + new PropertyHandler() { + @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 { + final Map> data; + + SortedBreakpoints(Map> data) { + this.data = data; + } + } + + /** + * Put all breakpoints into map script_name -> line_number -> breakpoint. + */ + private static SortedBreakpoints sortBreakpoints(Collection breakpoints, + PropertyHandler handler) { + Map> result = new HashMap>(); + for (B breakpoint : breakpoints) { + VmResourceId vmResourceId = handler.getVmResourceId(breakpoint); + if (vmResourceId == null) { + continue; + } + Map subMap = result.get(vmResourceId); + if (subMap == null) { + subMap = new HashMap(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(result); + } + + /** + * A class that implements merge operation for a particular complete/incomplete pair of values. + */ + private static abstract class Merger { + 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 void mergeMaps(Map map1, Map map2, + Merger merger) { + for (Map.Entry 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 void mergeBreakpoints(final Merger perBreakpointMerger, + SortedBreakpoints side1, SortedBreakpoints side2) { + Merger, Map> perScriptMerger = + new Merger, Map>() { + @Override + void both(Map v1, Map v2) { + mergeMaps(v1, v2, perBreakpointMerger); + } + + @Override + void onlyFirst(Map v1) { + mergeMaps(v1, Collections.emptyMap(), perBreakpointMerger); + } + + @Override + void onlySecond(Map v2) { + mergeMaps(Collections.emptyMap(), v2, perBreakpointMerger); + } + }; + mergeMaps(side1.data, side2.data, perScriptMerger); + } + + + private static Collection readSdkBreakpoints(JavascriptVm javascriptVm) { + class CallbackImpl implements JavascriptVm.ListBreakpointsCallback { + public void failure(Exception exception) { + problem = exception; + } + + public void success(Collection breakpoints) { + result = breakpoints; + } + Collection getResult() { + if (problem != null) { + throw new RuntimeException("Failed to synchronize breakpoints", problem); //$NON-NLS-1$ + } + return result; + } + Exception problem = null; + Collection 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 getUiBreakpoints() { + IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager(); + Set result = new HashSet(); + + 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); + } + } +} diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/ChromiumLineBreakpoint.java --- a/org.chromium.debug.core/src/org/chromium/debug/core/model/ChromiumLineBreakpoint.java Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/ChromiumLineBreakpoint.java Tue Jun 08 16:12:51 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 list = new ArrayList(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); } } } diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/DebugTargetImpl.java --- a/org.chromium.debug.core/src/org/chromium/debug/core/model/DebugTargetImpl.java Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/DebugTargetImpl.java Tue Jun 08 16:12:51 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 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 getAllDebugTargetImpls() { + IDebugTarget[] array = DebugPlugin.getDefault().getLaunchManager().getDebugTargets(); + List result = new ArrayList(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 { diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/LineBreakpointAdapter.java --- a/org.chromium.debug.core/src/org/chromium/debug/core/model/LineBreakpointAdapter.java Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/LineBreakpointAdapter.java Tue Jun 08 16:12:51 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); } } diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/Messages.java --- a/org.chromium.debug.core/src/org/chromium/debug/core/model/Messages.java Tue Jun 08 16:06:40 2010 -0700 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/Messages.java Tue Jun 08 16:12:51 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; diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/MockUpResourceWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/MockUpResourceWriter.java Tue Jun 08 16:12:51 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