/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.github.sdbg.debug.core.internal.webkit.model; import com.github.sdbg.debug.core.SDBGDebugCorePlugin; import com.github.sdbg.debug.core.breakpoints.IBreakpointPathResolver; import com.github.sdbg.debug.core.breakpoints.SDBGBreakpoint; import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitBreakpoint; import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitCallback; import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitLocation; import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitResult; import com.github.sdbg.debug.core.internal.webkit.protocol.WebkitScript; import com.github.sdbg.debug.core.model.IResourceResolver; import com.github.sdbg.debug.core.util.Trace; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IStorage; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.Platform; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IBreakpointListener; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.model.ILineBreakpoint; /** * Handle adding a removing breakpoints to the WebKit connection for the WebkitDebugTarget class. */ public class BreakpointManager implements IBreakpointListener, ISDBGBreakpointManager { public static class NullBreakpointManager implements ISDBGBreakpointManager { public NullBreakpointManager() { } @Override public void addBreakpointsConcerningScript(IStorage script) { } @Override public void connect() throws IOException { } @Override public void dispose(boolean deleteAll) { } @Override public SDBGBreakpoint getBreakpointFor(WebkitLocation location) { return null; } @Override public void handleBreakpointResolved(WebkitBreakpoint breakpoint) { } @Override public void handleGlobalObjectCleared() { } @Override public void removeBreakpointsConcerningScript(IStorage script) { } } private static Collection<IBreakpointPathResolver> breakpointPathResolvers; private WebkitDebugTarget debugTarget; private Map<IBreakpoint, List<String>> breakpointToIdMap = new HashMap<IBreakpoint, List<String>>(); private Map<String, IBreakpoint> breakpointsToUpdateMap = new HashMap<String, IBreakpoint>(); private List<IBreakpoint> ignoredBreakpoints = new ArrayList<IBreakpoint>(); static synchronized Collection<IBreakpointPathResolver> getBreakpointPathResolvers() { if (breakpointPathResolvers == null) { breakpointPathResolvers = new ArrayList<IBreakpointPathResolver>(); IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint( IBreakpointPathResolver.EXTENSION_ID); for (IConfigurationElement element : extensionPoint.getConfigurationElements()) { try { breakpointPathResolvers.add((IBreakpointPathResolver) element.createExecutableExtension("class")); } catch (CoreException e) { SDBGDebugCorePlugin.logError(e); } } } return breakpointPathResolvers; } public BreakpointManager(WebkitDebugTarget debugTarget) { this.debugTarget = debugTarget; } @Override public void addBreakpointsConcerningScript(IStorage script) { SourceMapManager sourceMapManager = debugTarget.getSourceMapManager(); for (IBreakpoint breakpoint : new ArrayList<IBreakpoint>(breakpointToIdMap.keySet())) { if (!isJSBreakpoint(breakpoint) && sourceMapManager.isMapTarget(script, getBreakpointPath(breakpoint))) { breakpointAdded(breakpoint); } } } @Override public void breakpointAdded(IBreakpoint breakpoint) { if (debugTarget.supportsBreakpoint(breakpoint)) { try { addBreakpoint(breakpoint); } catch (IOException exception) { if (!debugTarget.isTerminated()) { SDBGDebugCorePlugin.logError(exception); } } } } @Override public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) { // TODO: This is happening frequently, therefore scan the delta for changes concerning us and only then do the breakpoint remove+add trick // We generate this change event in the handleBreakpointResolved() method - ignore one // instance of the event. if (ignoredBreakpoints.contains(breakpoint)) { ignoredBreakpoints.remove(breakpoint); return; } if (debugTarget.supportsBreakpoint(breakpoint)) { breakpointRemoved(breakpoint, delta); breakpointAdded(breakpoint); } } @Override public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) { if (debugTarget.supportsBreakpoint(breakpoint)) { List<String> breakpointIds = breakpointToIdMap.remove(breakpoint); if (breakpointIds != null) { for (String breakpointId : breakpointIds) { breakpointsToUpdateMap.remove(breakpointId); try { debugTarget.getWebkitConnection().getDebugger().removeBreakpoint(breakpointId); } catch (IOException exception) { if (!debugTarget.isTerminated()) { SDBGDebugCorePlugin.logError(exception); } } } } } } @Override public void connect() throws IOException { IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(); for (IBreakpoint breakpoint : breakpoints) { if (debugTarget.supportsBreakpoint(breakpoint)) { addBreakpoint(breakpoint); } } DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener(this); } @Override public void dispose(boolean deleteAll) { // Null check for when the editor is shutting down. if (DebugPlugin.getDefault() != null) { if (deleteAll) { try { for (List<String> ids : breakpointToIdMap.values()) { if (ids != null) { for (String id : ids) { debugTarget.getWebkitConnection().getDebugger().removeBreakpoint(id); } } } } catch (IOException exception) { if (!debugTarget.isTerminated()) { SDBGDebugCorePlugin.logError(exception); } } } DebugPlugin.getDefault().getBreakpointManager().removeBreakpointListener(this); } } @Override public IBreakpoint getBreakpointFor(WebkitLocation location) { try { IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints( SDBGDebugCorePlugin.DEBUG_MODEL_ID); WebkitScript script = debugTarget.getWebkitConnection().getDebugger().getScript( location.getScriptId()); if (script == null) { return null; } String url = script.getUrl(); int line = WebkitLocation.webkitToElipseLine(location.getLineNumber()); for (IBreakpoint bp : breakpoints) { if (debugTarget.supportsBreakpoint(bp) && bp instanceof ILineBreakpoint) { ILineBreakpoint breakpoint = (ILineBreakpoint) bp; if (breakpoint.getLineNumber() == line) { String bpUrl = null; if (breakpoint instanceof SDBGBreakpoint) { SDBGBreakpoint sdbgBreakpoint = (SDBGBreakpoint) breakpoint; IFile file = sdbgBreakpoint.getFile(); if (file != null) { bpUrl = getResourceResolver().getUrlForResource(file); } else { bpUrl = sdbgBreakpoint.getFilePath(); } } else { bpUrl = getResourceResolver().getUrlForResource(breakpoint.getMarker().getResource()); } if (bpUrl != null && bpUrl.equals(url)) { return breakpoint; } } } } return null; } catch (CoreException e) { throw new RuntimeException(e); } } @Override public void handleBreakpointResolved(WebkitBreakpoint webkitBreakpoint) { try { IBreakpoint bp = breakpointsToUpdateMap.get(webkitBreakpoint.getBreakpointId()); if (bp != null && bp instanceof ILineBreakpoint) { ILineBreakpoint breakpoint = (ILineBreakpoint) bp; int eclipseLine = WebkitLocation.webkitToElipseLine(webkitBreakpoint.getLocation().getLineNumber()); if (breakpoint.getLineNumber() != eclipseLine) { ignoredBreakpoints.add(breakpoint); String message = "[breakpoint in " + (breakpoint instanceof SDBGBreakpoint ? ((SDBGBreakpoint) breakpoint).getName() : breakpoint.getMarker().getResource().getName()) + " moved from line " + breakpoint.getLineNumber() + " to " + eclipseLine + "]"; debugTarget.writeToStdout(message); breakpoint.getMarker().setAttribute(IMarker.LINE_NUMBER, eclipseLine); } } } catch (CoreException e) { throw new RuntimeException(e); } } @Override public void handleGlobalObjectCleared() { for (IBreakpoint breakpoint : new ArrayList<IBreakpoint>(breakpointToIdMap.keySet())) { if (!isJSBreakpoint(breakpoint)) { // This excercise is necessary so that the V8 breakpoints are removed // and re-added later when the sourcemaps are re-parsed breakpointRemoved(breakpoint, null/*delta*/); breakpointAdded(breakpoint); } } } @Override public void removeBreakpointsConcerningScript(IStorage script) { SourceMapManager sourceMapManager = debugTarget.getSourceMapManager(); for (IBreakpoint breakpoint : new ArrayList<IBreakpoint>(breakpointToIdMap.keySet())) { if (!isJSBreakpoint(breakpoint) && sourceMapManager.isMapTarget(script, getBreakpointPath(breakpoint))) { breakpointRemoved(breakpoint, null/*delta*/); } } } private void addBreakpoint(final IBreakpoint bp) throws IOException { try { if (bp.isEnabled() && bp instanceof ILineBreakpoint) { final ILineBreakpoint breakpoint = (ILineBreakpoint) bp; addToBreakpointMap(breakpoint, null/*id*/, true/*trackChanges*/); String path = getBreakpointPath(breakpoint); if (path != null) { int line = WebkitLocation.eclipseToWebkitLine(breakpoint.getLineNumber()); if (isJSBreakpoint(breakpoint)) { // Handle pure JavaScript breakpoints trace("Set breakpoint [" + path + "," + line + "]"); debugTarget.getWebkitConnection().getDebugger().setBreakpointByUrl( null, path, line, -1, new WebkitCallback<String>() { @Override public void handleResult(WebkitResult<String> result) { if (!result.isError()) { addToBreakpointMap(breakpoint, result.getResult(), true); } } }); } else { // Handle source mapped breakpoints SourceMapManager sourceMapManager = debugTarget.getSourceMapManager(); if (sourceMapManager.isMapTarget(path)) { List<SourceMapManager.SourceLocation> locations = sourceMapManager.getReverseMappingsFor( path, line); for (SourceMapManager.SourceLocation location : locations) { String mappedPath; if (location.getStorage() instanceof IFile) { mappedPath = getResourceResolver().getUrlRegexForResource( (IFile) location.getStorage()); } else if (location.getStorage() != null) { mappedPath = location.getStorage().getFullPath().toPortableString(); } else { mappedPath = location.getPath(); } if (mappedPath != null) { trace("Breakpoint [" + path + "," + (breakpoint instanceof ILineBreakpoint ? breakpoint.getLineNumber() : "") + ",-1] ==> mapped to [" + mappedPath + "," + location.getLine() + "," + location.getColumn() + "]"); trace("Set breakpoint [" + mappedPath + "," + location.getLine() + "]"); debugTarget.getWebkitConnection().getDebugger().setBreakpointByUrl( null, mappedPath, location.getLine(), location.getColumn(), new WebkitCallback<String>() { @Override public void handleResult(WebkitResult<String> result) { if (!result.isError()) { addToBreakpointMap(breakpoint, result.getResult(), false); } } }); } } } } } } } catch (CoreException e) { throw new IOException(e); } } private void addToBreakpointMap(IBreakpoint breakpoint, String id, boolean trackChanges) { synchronized (breakpointToIdMap) { if (breakpointToIdMap.get(breakpoint) == null) { breakpointToIdMap.put(breakpoint, new ArrayList<String>()); } if (id != null) { breakpointToIdMap.get(breakpoint).add(id); if (trackChanges) { breakpointsToUpdateMap.put(id, breakpoint); } } } } private String getBreakpointPath(IBreakpoint bp) { String path = null; for (IBreakpointPathResolver resolver : getBreakpointPathResolvers()) { if (resolver.isSupported(bp)) { try { path = resolver.getPath(bp); } catch (CoreException e) { } if (path != null) { break; } } } if (path == null) { if (bp instanceof SDBGBreakpoint) { IResource file = ((SDBGBreakpoint) bp).getFile(); if (file != null) { path = getResourceResolver().getUrlRegexForResource(file); } else { path = ((SDBGBreakpoint) bp).getFilePath(); } } else { path = getResourceResolver().getUrlRegexForResource(bp.getMarker().getResource()); } } return path; } private IResourceResolver getResourceResolver() { return debugTarget.getResourceResolver(); } private boolean isJSBreakpoint(IBreakpoint breakpoint) { return breakpoint instanceof SDBGBreakpoint; // TODO: Extend IBreakpointPathResolver so that it has a say on that as well } private void trace(String message) { Trace.trace(Trace.BREAKPOINTS, message); } }