// 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 java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import org.chromium.sdk.CallbackSemaphore; import org.chromium.sdk.JsValue.Type; import org.chromium.sdk.RelayOk; import org.chromium.sdk.SyncCallback; import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException; import org.chromium.sdk.internal.v8native.protocol.V8ProtocolUtil; import org.chromium.sdk.internal.v8native.protocol.input.CommandResponse; import org.chromium.sdk.internal.v8native.protocol.input.FrameObject; import org.chromium.sdk.internal.v8native.protocol.input.SuccessCommandResponse; import org.chromium.sdk.internal.v8native.protocol.input.data.ScriptHandle; import org.chromium.sdk.internal.v8native.protocol.input.data.SomeRef; import org.chromium.sdk.internal.v8native.protocol.input.data.ValueHandle; import org.chromium.sdk.internal.v8native.protocol.output.ContextlessDebuggerMessage; import org.chromium.sdk.internal.v8native.protocol.output.DebuggerMessageFactory; import org.chromium.sdk.internal.v8native.protocol.output.ScriptsMessage; import org.chromium.sdk.internal.v8native.value.JsDataTypeUtil; import org.chromium.sdk.internal.v8native.value.LoadableString; import org.chromium.sdk.internal.v8native.value.PropertyReference; import org.chromium.sdk.internal.v8native.value.ValueLoadException; import org.chromium.sdk.util.MethodIsBlockingException; /** * A helper class for performing complex V8-related operations. */ public class V8Helper { public interface ScriptLoadCallback { void success(); void failure(String message); } /** * Loads all scripts and stores them in ScriptManager. * @param callback to invoke when the script reloading has completed * @param syncCallback to invoke after callback whether it normally returned * or threw an exception */ public static RelayOk reloadAllScriptsAsync(final DebugSession debugSession, final ScriptLoadCallback callback, SyncCallback syncCallback) { return 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 RelayOk 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); } return debugSession.sendMessageAsync( message, true, new V8CommandCallbackBase() { @Override public void failure(String message) { if (callback != null) { callback.failure(message); } } @Override public void success(SuccessCommandResponse successResponse) { List<ScriptHandle> body; try { body = successResponse.body().asScripts(); } catch (JsonProtocolParseException e) { throw new RuntimeException(e); } ScriptManager scriptManager = debugSession.getScriptManager(); for (int i = 0; i < body.size(); ++i) { ScriptHandle scriptHandle = body.get(i); if (V8Helper.JAVASCRIPT_VOID.equals(scriptHandle.source())) { continue; } Long id = V8ProtocolUtil.getScriptIdFromResponse(scriptHandle); ScriptImpl scriptById = scriptManager.findById(id); if (scriptById == null) { scriptManager.addScript(scriptHandle, successResponse.refs()); } 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()); } } } if (callback != null) { callback.success(); } } }, syncCallback); } public static PropertyReference computeReceiverRef(FrameObject frame) { SomeRef receiverObject = frame.receiver(); return V8ProtocolUtil.extractProperty(receiverObject, V8ProtocolUtil.PropertyNameGetter.THIS); } public static LoadableString createLoadableString(ValueHandle handle, LoadableString.Factory stringFactory) { Long len = handle.length(); Long toIndex = handle.toIndex(); if (len != null && toIndex != null && len.longValue() != toIndex.longValue()) { // String is not fully loaded. return stringFactory.create(handle); } return new LoadableString.Immutable(handle.text()); } public static Type calculateType(String typeString, String className, boolean tolerateNullType) { Type type = JsDataTypeUtil.fromJsonTypeAndClassName(typeString, className); if (type == null) { if (tolerateNullType) { type = Type.TYPE_OBJECT; } else { throw new ValueLoadException("Bad value object"); } } return type; } public static <MESSAGE, RES, EX extends Exception> RES callV8Sync( V8CommandSender<MESSAGE, EX> commandSender, MESSAGE message, V8BlockingCallback<RES> callback) throws EX, MethodIsBlockingException { return callV8Sync(commandSender, message, callback, CallbackSemaphore.OPERATION_TIMEOUT_MS); } public static <MESSAGE, RES, EX extends Exception> RES callV8Sync( V8CommandSender<MESSAGE, EX> commandSender, MESSAGE message, final V8BlockingCallback<RES> callback, long timeoutMs) throws EX, MethodIsBlockingException { CallbackSemaphore syncCallback = new CallbackSemaphore(); final Exception [] exBuff = { null }; // A long way of creating buffer for generic type without warnings. final List<RES> resBuff = new ArrayList<RES>(Collections.nCopies(1, (RES)null)); V8CommandProcessor.V8HandlerCallback callbackWrapper = new V8CommandProcessor.V8HandlerCallback() { @Override public void failure(String message) { exBuff[0] = new Exception("Failure: " + message); } @Override public void messageReceived(CommandResponse response) { RES result = callback.messageReceived(response); resBuff.set(0, result); } }; commandSender.sendV8CommandAsync(message, true, callbackWrapper, syncCallback); boolean waitRes; try { waitRes = syncCallback.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); } catch (RuntimeException e) { throw new CallbackException(e); } if (!waitRes) { throw new CallbackException("Timeout"); } if (exBuff[0] != null) { throw new CallbackException(exBuff[0]); } return resBuff.get(0); } /** * A no-op JavaScript to evaluate. */ public static final String JAVASCRIPT_VOID = "javascript:void(0);"; /** * Special kind of exceptions for problems in receiving or waiting for the answer. * Clients may try to catch it. */ public static class CallbackException extends RuntimeException { CallbackException() { } CallbackException(String message, Throwable cause) { super(message, cause); } CallbackException(String message) { super(message); } CallbackException(Throwable cause) { super(cause); } } }