// 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.v8native; import org.chromium.sdk.Breakpoint; import org.chromium.sdk.BreakpointTypeExtension; import org.chromium.sdk.IgnoreCountBreakpointExtension; import org.chromium.sdk.JavascriptVm; import org.chromium.sdk.RelayOk; import org.chromium.sdk.SyncCallback; import org.chromium.sdk.internal.ScriptRegExpBreakpointTarget; import org.chromium.sdk.internal.v8native.protocol.input.SuccessCommandResponse; import org.chromium.sdk.internal.v8native.protocol.input.data.BreakpointInfo; import org.chromium.sdk.internal.v8native.protocol.output.ChangeBreakpointMessage; import org.chromium.sdk.util.GenericCallback; import org.chromium.sdk.util.RelaySyncCallback; /** * A generic implementation of the Breakpoint interface. */ public class BreakpointImpl implements Breakpoint { /** * The breakpoint target. */ private Target target; /** * The breakpoint id as reported by the JavaScript VM. */ private long id; /** * Breakpoint line number. May become invalidated by LiveEdit actions. */ private long lineNumber; /** * Whether the breakpoint is enabled. */ private boolean isEnabled; /** * The breakpoint condition (plain JavaScript) that should be {@code true} * for the breakpoint to fire. */ private String condition; /** * The breakpoint manager that manages this breakpoint. */ private final BreakpointManager breakpointManager; /** * Whether the breakpoint data have changed with respect * to the JavaScript VM data. */ private volatile boolean isDirty = false; public BreakpointImpl(long id, Target target, long lineNumber, boolean enabled, String condition, BreakpointManager breakpointManager) { this.target = target; this.id = id; this.isEnabled = enabled; this.condition = condition; this.lineNumber = lineNumber; this.breakpointManager = breakpointManager; } public BreakpointImpl(BreakpointInfo info, BreakpointManager breakpointManager) { this.target = getType(info); this.id = info.number(); this.breakpointManager = breakpointManager; updateFromRemote(info); } public void updateFromRemote(BreakpointInfo info) { if (this.id != info.number()) { throw new IllegalArgumentException(); } this.lineNumber = info.line(); this.isEnabled = info.active(); this.condition = info.condition(); } @Override public boolean isEnabled() { return isEnabled; } @Override public Target getTarget() { return target; } @Override public long getId() { return id; } @Override public String getCondition() { return condition; } @Override public long getLineNumber() { return lineNumber; } @Override public void setEnabled(boolean enabled) { if (this.isEnabled != enabled) { setDirty(true); } this.isEnabled = enabled; } private RelayOk setIgnoreCount(int ignoreCount, final GenericCallback<Void> callback, SyncCallback syncCallback) { ChangeBreakpointMessage message = new ChangeBreakpointMessage(id, ignoreCount); V8CommandCallbackBase wrappedCallback; if (callback == null) { wrappedCallback = null; } else { wrappedCallback = new V8CommandCallbackBase() { @Override public void success(SuccessCommandResponse successResponse) { callback.success(null); } @Override public void failure(String message) { callback.failure(new Exception(message)); } }; } DebugSession debugSession = breakpointManager.getDebugSession(); return debugSession.sendMessageAsync(message, true, wrappedCallback, syncCallback); } @Override public void setCondition(String condition) { if (!eq(this.condition, condition)) { setDirty(true); } this.condition = condition; } private static <T> boolean eq(T left, T right) { return left == right || (left != null && left.equals(right)); } @Override public RelayOk clear(JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback) { // TODO: make this code thread-safe. long originalId = this.id; this.id = INVALID_ID; return breakpointManager.clearBreakpoint(this, callback, syncCallback, originalId); } @Override public RelayOk flush(final JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback) { if (!isDirty()) { if (callback != null) { callback.success(this); } return RelaySyncCallback.finish(syncCallback); } setDirty(false); return breakpointManager.changeBreakpoint(this, callback, syncCallback); } @Override public IgnoreCountBreakpointExtension getIgnoreCountBreakpointExtension() { JavascriptVm javascriptVm = breakpointManager.getDebugSession().getJavascriptVm(); return javascriptVm.getIgnoreCountBreakpointExtension(); } private void setDirty(boolean isDirty) { this.isDirty = isDirty; } private boolean isDirty() { return isDirty; } private static Target getType(BreakpointInfo info) { BreakpointInfo.Type infoType = info.type(); switch (infoType) { case SCRIPTID: return new Target.ScriptId(info.script_id()); case SCRIPTNAME: return new Target.ScriptName(info.script_name()); case SCRIPTREGEXP: return new ScriptRegExpBreakpointTarget(info.script_regexp()); case FUNCTION: return new FunctionTarget(null); } throw new RuntimeException("Unknown type: " + infoType); } /** * Visitor interface that includes all extensions. */ public interface TargetExtendedVisitor<R> extends BreakpointTypeExtension.FunctionSupport.Visitor<R>, BreakpointTypeExtension.ScriptRegExpSupport.Visitor<R> { } static class FunctionTarget extends Target { private final String expression; FunctionTarget(String expression) { this.expression = expression; } @Override public <R> R accept(Visitor<R> visitor) { if (visitor instanceof BreakpointTypeExtension.FunctionSupport.Visitor) { BreakpointTypeExtension.FunctionSupport.Visitor<R> functionVisitor = (BreakpointTypeExtension.FunctionSupport.Visitor<R>) visitor; return functionVisitor.visitFunction(expression); } else { return visitor.visitUnknown(this); } } } static final IgnoreCountBreakpointExtension IGNORE_COUNT_EXTENSION = new IgnoreCountBreakpointExtension() { @Override public RelayOk setBreakpoint(JavascriptVm javascriptVm, Breakpoint.Target target, int line, int column, boolean enabled, String condition, int ignoreCount, JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback) { JavascriptVmImpl javascriptVmImpl = (JavascriptVmImpl) javascriptVm; BreakpointManager breakpointManager = javascriptVmImpl.getDebugSession().getBreakpointManager(); return breakpointManager.setBreakpoint(target, line, column, enabled, condition, ignoreCount, callback, syncCallback); } @Override public RelayOk setIgnoreCount(Breakpoint breakpoint, int ignoreCount, GenericCallback<Void> callback, SyncCallback syncCallback) { BreakpointImpl breakpointImpl = (BreakpointImpl) breakpoint; return breakpointImpl.setIgnoreCount(ignoreCount, callback, syncCallback); } }; }