org.chromium.sdk/src/org/chromium/sdk/internal/tools/v8/V8Helper.java
changeset 2 e4420d2515f1
child 276 f2f4a1259de8
equal deleted inserted replaced
1:ef76fc2ac88c 2:e4420d2515f1
       
     1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
       
     2 // Use of this source code is governed by a BSD-style license that can be
       
     3 // found in the LICENSE file.
       
     4 
       
     5 package org.chromium.sdk.internal.tools.v8;
       
     6 
       
     7 import java.util.ArrayList;
       
     8 import java.util.Collections;
       
     9 import java.util.List;
       
    10 import java.util.concurrent.Semaphore;
       
    11 import java.util.concurrent.TimeUnit;
       
    12 
       
    13 import org.chromium.sdk.CallbackSemaphore;
       
    14 import org.chromium.sdk.SyncCallback;
       
    15 import org.chromium.sdk.JsValue.Type;
       
    16 import org.chromium.sdk.internal.DataWithRef;
       
    17 import org.chromium.sdk.internal.DebugSession;
       
    18 import org.chromium.sdk.internal.FunctionAdditionalProperties;
       
    19 import org.chromium.sdk.internal.JsDataTypeUtil;
       
    20 import org.chromium.sdk.internal.PropertyHoldingValueMirror;
       
    21 import org.chromium.sdk.internal.PropertyReference;
       
    22 import org.chromium.sdk.internal.ScopeMirror;
       
    23 import org.chromium.sdk.internal.ScriptManager;
       
    24 import org.chromium.sdk.internal.SubpropertiesMirror;
       
    25 import org.chromium.sdk.internal.ValueLoadException;
       
    26 import org.chromium.sdk.internal.ValueMirror;
       
    27 import org.chromium.sdk.internal.protocol.CommandResponse;
       
    28 import org.chromium.sdk.internal.protocol.FrameObject;
       
    29 import org.chromium.sdk.internal.protocol.ScopeRef;
       
    30 import org.chromium.sdk.internal.protocol.SuccessCommandResponse;
       
    31 import org.chromium.sdk.internal.protocol.data.FunctionValueHandle;
       
    32 import org.chromium.sdk.internal.protocol.data.ObjectValueHandle;
       
    33 import org.chromium.sdk.internal.protocol.data.PropertyObject;
       
    34 import org.chromium.sdk.internal.protocol.data.RefWithDisplayData;
       
    35 import org.chromium.sdk.internal.protocol.data.ScriptHandle;
       
    36 import org.chromium.sdk.internal.protocol.data.ValueHandle;
       
    37 import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
       
    38 import org.chromium.sdk.internal.tools.v8.request.DebuggerMessageFactory;
       
    39 import org.chromium.sdk.internal.tools.v8.request.ScriptsMessage;
       
    40 
       
    41 /**
       
    42  * A helper class for performing complex V8-related operations.
       
    43  */
       
    44 public class V8Helper {
       
    45 
       
    46   /**
       
    47    * The debug context in which the operations are performed.
       
    48    */
       
    49   private final DebugSession debugSession;
       
    50 
       
    51   /**
       
    52    * A semaphore that prevents concurrent script reloading (which may effectively
       
    53    * double the efforts.)
       
    54    */
       
    55   private final Semaphore scriptsReloadSemaphore = new Semaphore(1);
       
    56 
       
    57   public V8Helper(DebugSession debugSession) {
       
    58     this.debugSession = debugSession;
       
    59   }
       
    60 
       
    61   /**
       
    62    * Reloads all normal scripts found in the page. First, all scripts without
       
    63    * their sources are retrieved to save bandwidth (script list change during a
       
    64    * page lifetime is a relatively rare event.) If at least one script has been
       
    65    * added, the script cache is dropped and re-populated with new scripts that
       
    66    * are re-requested together with their sources.
       
    67    *
       
    68    * @param callback to invoke when the script reloading has completed
       
    69    */
       
    70   public void reloadAllScriptsAsync(V8CommandProcessor.V8HandlerCallback callback,
       
    71       SyncCallback syncCallback) {
       
    72     final V8CommandProcessor.V8HandlerCallback finalCallback = callback != null
       
    73         ? callback
       
    74         : V8CommandProcessor.V8HandlerCallback.NULL_CALLBACK;
       
    75     lock();
       
    76     debugSession.sendMessageAsync(
       
    77         DebuggerMessageFactory.scripts(ScriptsMessage.SCRIPTS_NORMAL, true),
       
    78         true,
       
    79         new V8CommandProcessor.V8HandlerCallback() {
       
    80           public void failure(String message) {
       
    81             unlock();
       
    82             finalCallback.failure(message);
       
    83           }
       
    84 
       
    85           public void messageReceived(CommandResponse response) {
       
    86             SuccessCommandResponse successResponse = response.asSuccess();
       
    87 
       
    88             // TODO(peter.rybin): add try/finally for unlock, with some error reporting probably.
       
    89             List<ScriptHandle> body;
       
    90             try {
       
    91               body = successResponse.getBody().asScripts();
       
    92             } catch (JsonProtocolParseException e) {
       
    93               throw new RuntimeException(e);
       
    94             }
       
    95             ScriptManager scriptManager = debugSession.getScriptManager();
       
    96             for (int i = 0; i < body.size(); ++i) {
       
    97               ScriptHandle scriptHandle = body.get(i);
       
    98               Long id = V8ProtocolUtil.getScriptIdFromResponse(scriptHandle);
       
    99               if (scriptManager.findById(id) == null &&
       
   100                   !ChromeDevToolSessionManager.JAVASCRIPT_VOID.equals(scriptHandle.source())) {
       
   101                 scriptManager.addScript(
       
   102                     scriptHandle,
       
   103                     successResponse.getRefs());
       
   104               }
       
   105             }
       
   106             unlock();
       
   107             finalCallback.messageReceived(response);
       
   108           }
       
   109         },
       
   110         syncCallback);
       
   111   }
       
   112 
       
   113   protected void lock() {
       
   114     try {
       
   115       scriptsReloadSemaphore.acquire();
       
   116     } catch (InterruptedException e) {
       
   117       // consider it a successful acquisition
       
   118     }
       
   119   }
       
   120 
       
   121   protected void unlock() {
       
   122     scriptsReloadSemaphore.release();
       
   123   }
       
   124 
       
   125   /**
       
   126    * Gets all resolved locals for the call frame, caches scripts and objects in
       
   127    * the scriptManager and handleManager.
       
   128    *
       
   129    * @param frame to get the data for
       
   130    * @return the mirrors corresponding to the frame locals
       
   131    */
       
   132   public static List<PropertyReference> computeLocals(FrameObject frame) {
       
   133     List<PropertyObject> args = frame.getArguments();
       
   134     List<PropertyObject> locals = frame.getLocals();
       
   135 
       
   136     int maxLookups = args.size() + locals.size() + 1 /* "this" */;
       
   137 
       
   138     List<PropertyReference> localRefs = new ArrayList<PropertyReference>(maxLookups);
       
   139 
       
   140     {
       
   141       // Receiver ("this")
       
   142       RefWithDisplayData receiverObject = frame.getReceiver().asWithDisplayData();
       
   143       V8ProtocolUtil.putMirror(localRefs, receiverObject,
       
   144           V8ProtocolUtil.PropertyNameGetter.THIS);
       
   145     }
       
   146 
       
   147     // Arguments
       
   148     for (int i = 0; i < args.size(); i++) {
       
   149       PropertyObject arg = args.get(i);
       
   150       V8ProtocolUtil.putMirror(localRefs, arg, V8ProtocolUtil.PropertyNameGetter.SUBPROPERTY);
       
   151     }
       
   152 
       
   153     // Locals
       
   154     for (int i = 0; i < locals.size(); i++) {
       
   155       PropertyObject local = locals.get(i);
       
   156       V8ProtocolUtil.putMirror(localRefs, local, V8ProtocolUtil.PropertyNameGetter.SUBPROPERTY);
       
   157     }
       
   158 
       
   159     return localRefs;
       
   160   }
       
   161 
       
   162   public static List<ScopeMirror> computeScopes(FrameObject frame) {
       
   163     List<ScopeRef> scopes = frame.getScopes();
       
   164 
       
   165     final List<ScopeMirror> result = new ArrayList<ScopeMirror>(scopes.size());
       
   166 
       
   167     for (int i = 0; i < scopes.size(); i++) {
       
   168       ScopeRef scope = scopes.get(i);
       
   169       int type = (int) scope.type();
       
   170       int index = (int) scope.index();
       
   171 
       
   172       result.add(new ScopeMirror(type, index));
       
   173     }
       
   174 
       
   175     return result;
       
   176   }
       
   177 
       
   178   public static PropertyReference computeReceiverRef(FrameObject frame) {
       
   179     RefWithDisplayData receiverObject = frame.getReceiver().asWithDisplayData();
       
   180     return V8ProtocolUtil.extractProperty(receiverObject,
       
   181         V8ProtocolUtil.PropertyNameGetter.THIS);
       
   182   }
       
   183 
       
   184   /**
       
   185    * Constructs a ValueMirror given a V8 debugger object specification.
       
   186    *
       
   187    * @param jsonValue containing the object specification from the V8 debugger
       
   188    * @return a {@link PropertyHoldingValueMirror} instance, containing data
       
   189    *         from jsonValue; not null
       
   190    */
       
   191   public static PropertyHoldingValueMirror createMirrorFromLookup(ValueHandle valueHandle) {
       
   192     String text = valueHandle.text();
       
   193     if (text == null) {
       
   194       throw new ValueLoadException("Bad lookup result");
       
   195     }
       
   196     String typeString = valueHandle.type();
       
   197     String className = valueHandle.className();
       
   198     Type type = JsDataTypeUtil.fromJsonTypeAndClassName(typeString, className);
       
   199     if (type == null) {
       
   200       throw new ValueLoadException("Bad lookup result: type field not recognized: " + typeString);
       
   201     }
       
   202     return createMirrorFromLookup(valueHandle, type);
       
   203   }
       
   204 
       
   205   /**
       
   206    * Constructs a {@link ValueMirror} given a V8 debugger object specification if it's possible.
       
   207    * @return a {@link ValueMirror} instance, containing data
       
   208    *         from {@code jsonValue}; or {@code null} if {@code jsonValue} is not a handle
       
   209    */
       
   210   public static ValueMirror createValueMirrorOptional(DataWithRef handleFromProperty) {
       
   211     RefWithDisplayData withData = handleFromProperty.getWithDisplayData();
       
   212     if (withData == null) {
       
   213       return null;
       
   214     }
       
   215     return createValueMirror(withData);
       
   216   }
       
   217   public static ValueMirror createValueMirrorOptional(ValueHandle valueHandle) {
       
   218     return createValueMirror(valueHandle);
       
   219   }
       
   220 
       
   221   private static ValueMirror createValueMirror(ValueHandle valueHandle) {
       
   222     String className = valueHandle.className();
       
   223     Type type = JsDataTypeUtil.fromJsonTypeAndClassName(valueHandle.type(), className);
       
   224     if (type == null) {
       
   225       throw new ValueLoadException("Bad value object");
       
   226     }
       
   227     String text = valueHandle.text();
       
   228     return createMirrorFromLookup(valueHandle, type).getValueMirror();
       
   229   }
       
   230 
       
   231   private static ValueMirror createValueMirror(RefWithDisplayData jsonValue) {
       
   232     String className = jsonValue.className();
       
   233     Type type = JsDataTypeUtil.fromJsonTypeAndClassName(jsonValue.type(), className);
       
   234     if (type == null) {
       
   235       throw new ValueLoadException("Bad value object");
       
   236     }
       
   237     { // try another format
       
   238       if (Type.isObjectType(type)) {
       
   239         int refId = (int) jsonValue.ref();
       
   240         return ValueMirror.createObjectUnknownProperties(refId, type, className);
       
   241       } else {
       
   242         // try another format
       
   243         Object valueObj = jsonValue.value();
       
   244         String valueStr;
       
   245         if (valueObj == null) {
       
   246           valueStr = jsonValue.type(); // e.g. "undefined"
       
   247         } else {
       
   248           valueStr = valueObj.toString();
       
   249         }
       
   250         return ValueMirror.createScalar(valueStr, type, className).getValueMirror();
       
   251       }
       
   252     }
       
   253   }
       
   254 
       
   255   private static PropertyHoldingValueMirror createMirrorFromLookup(ValueHandle valueHandle,
       
   256       Type type) {
       
   257     if (Type.isObjectType(type)) {
       
   258       ObjectValueHandle objectValueHandle = valueHandle.asObject();
       
   259       int refId = (int) valueHandle.handle();
       
   260       SubpropertiesMirror subpropertiesMirror;
       
   261       if (type == Type.TYPE_FUNCTION) {
       
   262         FunctionValueHandle functionValueHandle = objectValueHandle.asFunction();
       
   263         subpropertiesMirror = new SubpropertiesMirror.FunctionValueBased(functionValueHandle,
       
   264             FUNCTION_PROPERTY_FACTORY2);
       
   265       } else {
       
   266         subpropertiesMirror =
       
   267           new SubpropertiesMirror.ObjectValueBased(objectValueHandle, null);
       
   268       }
       
   269       return ValueMirror.createObject(refId, subpropertiesMirror, type, valueHandle.className());
       
   270     } else {
       
   271       return ValueMirror.createScalar(valueHandle.text(), type, valueHandle.className());
       
   272     }
       
   273   }
       
   274 
       
   275   // TODO(peter.rybin): Get rid of this monstrosity once we switched to type JSON interfaces.
       
   276   private static final
       
   277       SubpropertiesMirror.JsonBased.AdditionalPropertyFactory<FunctionValueHandle>
       
   278       FUNCTION_PROPERTY_FACTORY2 =
       
   279       new SubpropertiesMirror.JsonBased.AdditionalPropertyFactory<FunctionValueHandle>() {
       
   280     public Object createAdditionalProperties(FunctionValueHandle jsonWithProperties) {
       
   281       Long pos = jsonWithProperties.position();
       
   282       if (pos == null) {
       
   283         pos = Long.valueOf(FunctionAdditionalProperties.NO_POSITION);
       
   284       }
       
   285       Long scriptId = jsonWithProperties.scriptId();
       
   286       if (scriptId == null) {
       
   287         scriptId = Long.valueOf(FunctionAdditionalProperties.NO_SCRIPT_ID);
       
   288       }
       
   289       return new FunctionAdditionalProperties(pos.intValue(), scriptId.intValue());
       
   290     }
       
   291   };
       
   292 
       
   293   public static <MESSAGE, RES, EX extends Exception> RES callV8Sync(
       
   294       V8CommandSender<MESSAGE, EX> commandSender, MESSAGE message,
       
   295       V8BlockingCallback<RES> callback) throws EX {
       
   296     return callV8Sync(commandSender, message, callback,
       
   297         CallbackSemaphore.OPERATION_TIMEOUT_MS);
       
   298   }
       
   299 
       
   300   public static <MESSAGE, RES, EX extends Exception> RES callV8Sync(
       
   301       V8CommandSender<MESSAGE, EX> commandSender,
       
   302       MESSAGE message, final V8BlockingCallback<RES> callback, long timeoutMs) throws EX {
       
   303     CallbackSemaphore syncCallback = new CallbackSemaphore();
       
   304     final Exception [] exBuff = { null };
       
   305     // A long way of creating buffer for generic type without warnings.
       
   306     final List<RES> resBuff = new ArrayList<RES>(Collections.nCopies(1, (RES)null));
       
   307     V8CommandProcessor.V8HandlerCallback callbackWrapper =
       
   308         new V8CommandProcessor.V8HandlerCallback() {
       
   309       public void failure(String message) {
       
   310         exBuff[0] = new Exception("Failure: " + message);
       
   311       }
       
   312 
       
   313       public void messageReceived(CommandResponse response) {
       
   314         RES result = callback.messageReceived(response);
       
   315         resBuff.set(0, result);
       
   316       }
       
   317     };
       
   318     commandSender.sendV8CommandAsync(message, true, callbackWrapper, syncCallback);
       
   319 
       
   320     boolean waitRes;
       
   321     try {
       
   322       waitRes = syncCallback.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
       
   323     } catch (RuntimeException e) {
       
   324       throw new CallbackException(e);
       
   325     }
       
   326 
       
   327     if (!waitRes) {
       
   328       throw new CallbackException("Timeout");
       
   329     }
       
   330 
       
   331     if (exBuff[0] != null) {
       
   332       throw new CallbackException(exBuff[0]);
       
   333     }
       
   334 
       
   335     return resBuff.get(0);
       
   336   }
       
   337 
       
   338   /**
       
   339    * Special kind of exceptions for problems in receiving or waiting for the answer.
       
   340    * Clients may try to catch it.
       
   341    */
       
   342   public static class CallbackException extends RuntimeException {
       
   343     CallbackException() {
       
   344     }
       
   345     CallbackException(String message, Throwable cause) {
       
   346       super(message, cause);
       
   347     }
       
   348     CallbackException(String message) {
       
   349       super(message);
       
   350     }
       
   351     CallbackException(Throwable cause) {
       
   352       super(cause);
       
   353     }
       
   354   }
       
   355 }