| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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.wip; | |
| 6 | |
| 7 import java.util.ArrayList; | |
| 8 import java.util.List; | |
| 9 import java.util.Map; | |
| 10 import java.util.concurrent.atomic.AtomicInteger; | |
| 11 | |
| 12 import org.chromium.sdk.JsEvaluateContext; | |
| 13 import org.chromium.sdk.JsEvaluateContext.ResultOrException; | |
| 14 import org.chromium.sdk.RelayOk; | |
| 15 import org.chromium.sdk.SyncCallback; | |
| 16 import org.chromium.sdk.internal.wip.WipExpressionBuilder.ValueNameBuilder; | |
| 17 import org.chromium.sdk.internal.wip.WipRelayRunner.ProcessException; | |
| 18 import org.chromium.sdk.internal.wip.WipRelayRunner.Step; | |
| 19 import org.chromium.sdk.internal.wip.WipValueBuilder.SerializableValue; | |
| 20 import org.chromium.sdk.internal.wip.protocol.input.runtime.CallFunctionOnData; | |
| 21 import org.chromium.sdk.internal.wip.protocol.input.runtime.EvaluateData; | |
| 22 import org.chromium.sdk.internal.wip.protocol.input.runtime.RemoteObjectValue; | |
| 23 import org.chromium.sdk.internal.wip.protocol.output.WipParamsWithResponse; | |
| 24 import org.chromium.sdk.internal.wip.protocol.output.runtime.CallArgumentParam; | |
| 25 import org.chromium.sdk.internal.wip.protocol.output.runtime.CallFunctionOnParam
s; | |
| 26 import org.chromium.sdk.internal.wip.protocol.output.runtime.EvaluateParams; | |
| 27 import org.chromium.sdk.util.GenericCallback; | |
| 28 import org.chromium.sdk.util.RelaySyncCallback; | |
| 29 | |
| 30 /** | |
| 31 * Helper class that implements evaluate with additional context and | |
| 32 * destination group id operation. This implementation is a hack because it adds
(injects) | |
| 33 * a property to the global object and works with its properties. The normal app
roach is when | |
| 34 * the protocol itself supports this operation. As it hopefully will. | |
| 35 */ | |
| 36 public class EvaluateHack { | |
| 37 | |
| 38 private final WipTabImpl tabImpl; | |
| 39 private final AtomicInteger uniqueIdCounter = new AtomicInteger(0); | |
| 40 private boolean objectInjected = false; | |
| 41 | |
| 42 public EvaluateHack(WipTabImpl tabImpl) { | |
| 43 this.tabImpl = tabImpl; | |
| 44 } | |
| 45 | |
| 46 /** | |
| 47 * Implements evaluate with additional context and destination group id operat
ion. | |
| 48 * The implementation modifies a global object. | |
| 49 * @param destinationValueLoader value loader that corresponds to the destinat
ion group | |
| 50 * @param evaluateCommandHandler provides a particular request type | |
| 51 */ | |
| 52 public RelayOk evaluateAsync(String expression, ValueNameBuilder valueNameBuid
ler, | |
| 53 Map<String, ? extends SerializableValue> additionalContext, | |
| 54 WipValueLoader destinationValueLoader, EvaluateCommandHandler<?> evaluateC
ommandHandler, | |
| 55 final JsEvaluateContext.EvaluateCallback callback, SyncCallback syncCallba
ck) { | |
| 56 | |
| 57 RelaySyncCallback relaySyncCallback = new RelaySyncCallback(syncCallback); | |
| 58 | |
| 59 final EvaluateSession evaluateSession = new EvaluateSession(expression, valu
eNameBuidler, | |
| 60 additionalContext, destinationValueLoader, evaluateCommandHandler); | |
| 61 | |
| 62 final RelaySyncCallback.Guard guard = relaySyncCallback.newGuard(); | |
| 63 | |
| 64 GenericCallback<Void> postEnsureCallback = new GenericCallback<Void>() { | |
| 65 @Override | |
| 66 public void success(Void value) { | |
| 67 RelayOk relayOk = evaluateSession.run(callback, guard.getRelay()); | |
| 68 guard.discharge(relayOk); | |
| 69 } | |
| 70 | |
| 71 @Override | |
| 72 public void failure(Exception exception) { | |
| 73 if (callback != null) { | |
| 74 callback.failure(exception); | |
| 75 } | |
| 76 } | |
| 77 }; | |
| 78 | |
| 79 return ensureObjectInjected(postEnsureCallback, guard.asSyncCallback()); | |
| 80 } | |
| 81 | |
| 82 /** | |
| 83 * Provides an actual evaluate request. It may or may not refer to a particula
r call frame | |
| 84 * or deal with other details that are out of scope of this class. | |
| 85 * | |
| 86 * @param <DATA> type of request's response | |
| 87 */ | |
| 88 public interface EvaluateCommandHandler<DATA> { | |
| 89 WipParamsWithResponse<DATA> createRequest(String patchedUserExpression, | |
| 90 WipValueLoader destinationValueLoader); | |
| 91 | |
| 92 ResultOrException processResult(DATA response, WipValueLoader destinationVal
ueLoader, | |
| 93 ValueNameBuilder valueNameBuidler); | |
| 94 | |
| 95 /** | |
| 96 * Return the same exception or wraps it with a more high-level error detail
s. | |
| 97 * @return not null | |
| 98 */ | |
| 99 Exception processFailure(Exception cause); | |
| 100 } | |
| 101 | |
| 102 synchronized void pageReloaded() { | |
| 103 objectInjected = false; | |
| 104 } | |
| 105 | |
| 106 /** | |
| 107 * Corresponds to a one evaluate operation. Holds most of parameters. It does
following: | |
| 108 * <ol> | |
| 109 * <li>creates a temporary object inside the main injected object, | |
| 110 * <li>puts all values from additional context thus making it a 'with' objec
t, | |
| 111 * <li>evaluates user expression inside the 'with' operator, | |
| 112 * <li>returns result to a user callback, | |
| 113 * <li>deletes the temporary object. | |
| 114 * </ol> | |
| 115 * | |
| 116 * It uses {@link WipRelayRunner} as an engine. | |
| 117 */ | |
| 118 private class EvaluateSession { | |
| 119 private final String userExpression; | |
| 120 private final ValueNameBuilder valueNameBuidler; | |
| 121 private final Map<String, ? extends SerializableValue> additionalContext; | |
| 122 private final WipValueLoader destinationValueLoader; | |
| 123 private final EvaluateCommandHandler<?> evaluateCommandHandler; | |
| 124 | |
| 125 private final String dataId = "d" + uniqueIdCounter.incrementAndGet(); | |
| 126 | |
| 127 EvaluateSession(String expression, ValueNameBuilder valueNameBuidler, | |
| 128 Map<String, ? extends SerializableValue> additionalContext, | |
| 129 WipValueLoader destinationValueLoader, EvaluateCommandHandler<?> evaluat
eCommandHandler) { | |
| 130 this.userExpression = expression; | |
| 131 this.valueNameBuidler = valueNameBuidler; | |
| 132 this.additionalContext = additionalContext; | |
| 133 this.destinationValueLoader = destinationValueLoader; | |
| 134 this.evaluateCommandHandler = evaluateCommandHandler; | |
| 135 } | |
| 136 | |
| 137 RelayOk run(final JsEvaluateContext.EvaluateCallback callback, RelaySyncCall
back relay) { | |
| 138 WipRelayRunner.Step<ResultOrException> step = createFillDataObjectStep(); | |
| 139 | |
| 140 GenericCallback<ResultOrException> innerCallback; | |
| 141 if (callback == null) { | |
| 142 innerCallback = null; | |
| 143 } else { | |
| 144 innerCallback = new GenericCallback<ResultOrException>() { | |
| 145 @Override public void success(ResultOrException value) { | |
| 146 callback.success(value); | |
| 147 } | |
| 148 @Override public void failure(Exception exception) { | |
| 149 callback.failure(exception); | |
| 150 } | |
| 151 }; | |
| 152 } | |
| 153 | |
| 154 return WipRelayRunner.run(tabImpl.getCommandProcessor(), step, | |
| 155 innerCallback, relay); | |
| 156 } | |
| 157 | |
| 158 /** | |
| 159 * Sends request that create a temporary object and fills it with user value
s. | |
| 160 * User values are passed as 1. 'this', 2. additional arguments to the funct
ion. | |
| 161 */ | |
| 162 private WipRelayRunner.Step<ResultOrException> createFillDataObjectStep() { | |
| 163 if (additionalContext.isEmpty()) { | |
| 164 throw new IllegalArgumentException("Empty context"); | |
| 165 } | |
| 166 | |
| 167 StringBuilder assigmentBuilder = new StringBuilder(); | |
| 168 StringBuilder parametersBuilder = new StringBuilder(); | |
| 169 | |
| 170 String thisObjectId = null; | |
| 171 final List<CallArgumentParam> additionalObjectIds = new ArrayList<CallArgu
mentParam>(0); | |
| 172 String tempObjectRef = GLOBAL_VARIABLE_NAME + ".data." + dataId + "."; | |
| 173 for (Map.Entry<String, ? extends SerializableValue> entry : additionalCont
ext.entrySet()) { | |
| 174 SerializableValue jsValueBase = entry.getValue(); | |
| 175 String commandParamName; | |
| 176 if (thisObjectId == null && jsValueBase.getRefId() != null) { | |
| 177 commandParamName = "this"; | |
| 178 thisObjectId = jsValueBase.getRefId(); | |
| 179 } else { | |
| 180 commandParamName = "p" + additionalObjectIds.size(); | |
| 181 CallArgumentParam callArgumentParam = jsValueBase.createCallArgumentPa
ram(); | |
| 182 if (callArgumentParam == null) { | |
| 183 throw new IllegalArgumentException("Cannot serialize additional cont
ext property " + | |
| 184 entry.getKey()); | |
| 185 } | |
| 186 additionalObjectIds.add(callArgumentParam); | |
| 187 if (parametersBuilder.length() != 0) { | |
| 188 parametersBuilder.append(", "); | |
| 189 } | |
| 190 parametersBuilder.append(commandParamName); | |
| 191 } | |
| 192 assigmentBuilder.append(tempObjectRef + entry.getKey() + " = " + command
ParamName + ";\n"); | |
| 193 } | |
| 194 if (thisObjectId == null) { | |
| 195 // TODO: remove this limitation in protocol. | |
| 196 throw new IllegalArgumentException("At least one additional parameter mu
st be an object"); | |
| 197 } | |
| 198 | |
| 199 final String functionText = "function(" + parametersBuilder + ") { " + | |
| 200 GLOBAL_VARIABLE_NAME + ".data." + dataId + " = {};\n" + | |
| 201 assigmentBuilder + "}"; | |
| 202 | |
| 203 final String thisObjectIdFinal = thisObjectId; | |
| 204 | |
| 205 return new WipRelayRunner.SendStepWithResponse<CallFunctionOnData, ResultO
rException>() { | |
| 206 @Override | |
| 207 public WipParamsWithResponse<CallFunctionOnData> getParams() { | |
| 208 List<CallArgumentParam> arguments; | |
| 209 if (additionalObjectIds.isEmpty()) { | |
| 210 arguments = null; | |
| 211 } else { | |
| 212 arguments = additionalObjectIds; | |
| 213 } | |
| 214 return new CallFunctionOnParams(thisObjectIdFinal, functionText, argum
ents, null, true); | |
| 215 } | |
| 216 | |
| 217 @Override | |
| 218 public Step<ResultOrException> processResponse(CallFunctionOnData respon
se) { | |
| 219 if (response.wasThrown() == Boolean.TRUE) { | |
| 220 return createHandleErrorStep(response.result()); | |
| 221 } | |
| 222 return createEvaluateStep(evaluateCommandHandler); | |
| 223 } | |
| 224 | |
| 225 @Override | |
| 226 public Exception processFailure(Exception cause) { | |
| 227 return cause; | |
| 228 } | |
| 229 }; | |
| 230 } | |
| 231 | |
| 232 private <EVAL_DATA> WipRelayRunner.Step<ResultOrException> createEvaluateSte
p( | |
| 233 final EvaluateCommandHandler<EVAL_DATA> commandHandler) { | |
| 234 return new WipRelayRunner.SendStepWithResponse<EVAL_DATA, ResultOrExceptio
n>() { | |
| 235 @Override | |
| 236 public WipParamsWithResponse<EVAL_DATA> getParams() { | |
| 237 String script = "with (" + GLOBAL_VARIABLE_NAME + ".data." + dataId + | |
| 238 ") { return (" + userExpression + "); }"; | |
| 239 String wrappedExpression = "(function() {" + script +"})()"; | |
| 240 | |
| 241 WipParamsWithResponse<EVAL_DATA> paramsWithResponse = commandHandler.c
reateRequest( | |
| 242 wrappedExpression, destinationValueLoader); | |
| 243 | |
| 244 return paramsWithResponse; | |
| 245 } | |
| 246 | |
| 247 @Override | |
| 248 public Step<ResultOrException> processResponse(EVAL_DATA response) { | |
| 249 ResultOrException resultOrException = | |
| 250 commandHandler.processResult(response, destinationValueLoader, val
ueNameBuidler); | |
| 251 | |
| 252 clearTempObjectAsync(); | |
| 253 | |
| 254 return WipRelayRunner.createFinalStep(resultOrException); | |
| 255 } | |
| 256 | |
| 257 @Override | |
| 258 public Exception processFailure(Exception cause) { | |
| 259 return commandHandler.processFailure(cause); | |
| 260 } | |
| 261 }; | |
| 262 } | |
| 263 | |
| 264 /** | |
| 265 * Clears the temporary object. It is done asynchronously, outside the main
relay, because | |
| 266 * user shouldn't wait for its result. | |
| 267 */ | |
| 268 private void clearTempObjectAsync() { | |
| 269 String script = "delete " + GLOBAL_VARIABLE_NAME + ".data." + dataId + ";"
; | |
| 270 String deleteDataExpression = "(function() {" + script +"})()"; | |
| 271 EvaluateParams evaluateParams = | |
| 272 new EvaluateParams(deleteDataExpression, null, null, null, null, true)
; | |
| 273 tabImpl.getCommandProcessor().send(evaluateParams, (WipCommandCallback) nu
ll, null); | |
| 274 } | |
| 275 | |
| 276 /** | |
| 277 * An alternative spin-off in the relay, that handles an exception we ran in
to. | |
| 278 * The additional step is needed because the exception message is only avail
able from | |
| 279 * its 'message' pseudo-property (a getter). | |
| 280 */ | |
| 281 private Step<ResultOrException> createHandleErrorStep(final RemoteObjectValu
e remoteObjectValue) { | |
| 282 return new WipRelayRunner.SendStepWithResponse<CallFunctionOnData, ResultO
rException>() { | |
| 283 @Override | |
| 284 public WipParamsWithResponse<CallFunctionOnData> getParams() { | |
| 285 String functionText = "function() { return String(this.message); }"; | |
| 286 return new CallFunctionOnParams(remoteObjectValue.objectId(), function
Text, null, null, true); | |
| 287 } | |
| 288 | |
| 289 @Override | |
| 290 public Step<ResultOrException> processResponse(CallFunctionOnData respon
se) | |
| 291 throws ProcessException { | |
| 292 throw new ProcessException("Helper script failed on remote: " + | |
| 293 response.result().value()); | |
| 294 } | |
| 295 | |
| 296 @Override | |
| 297 public Exception processFailure(Exception cause) { | |
| 298 return cause; | |
| 299 } | |
| 300 }; | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 /** | |
| 305 * Makes sure that we injected a helper object inside a global object. | |
| 306 * This cannot be implemented as step in {@link WipRelayRunner}, because the m
ethod | |
| 307 * is synchronized and cannot undergo required control inversion. | |
| 308 */ | |
| 309 private synchronized RelayOk ensureObjectInjected(GenericCallback<Void> callba
ck, | |
| 310 SyncCallback syncCallback) { | |
| 311 if (objectInjected) { | |
| 312 callback.success(null); | |
| 313 return RelaySyncCallback.finish(syncCallback); | |
| 314 } else { | |
| 315 objectInjected = true; | |
| 316 return injectObject(callback, syncCallback); | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 private RelayOk injectObject(final GenericCallback<Void> callback, SyncCallbac
k syncCallback) { | |
| 321 // 'data' is for temporary objects. | |
| 322 // 'code' is for utility methods. | |
| 323 String injectedObjectText = "{ data: {}, code: {}}"; | |
| 324 String expression = "(function() { " + GLOBAL_VARIABLE_NAME + " = " + inject
edObjectText + | |
| 325 " ; })()"; | |
| 326 | |
| 327 EvaluateParams evaluateParams = new EvaluateParams(expression, null, false,
null, null, true); | |
| 328 | |
| 329 GenericCallback<EvaluateData> wrappedCallback = new GenericCallback<Evaluate
Data>() { | |
| 330 @Override | |
| 331 public void success(EvaluateData value) { | |
| 332 // TODO: check result. | |
| 333 callback.success(null); | |
| 334 } | |
| 335 | |
| 336 @Override | |
| 337 public void failure(Exception exception) { | |
| 338 callback.failure(new Exception("Failed to inject evaluate helper script
into remote VM", | |
| 339 exception)); | |
| 340 } | |
| 341 }; | |
| 342 | |
| 343 return tabImpl.getCommandProcessor().send(evaluateParams, wrappedCallback, s
yncCallback); | |
| 344 } | |
| 345 | |
| 346 private static final String GLOBAL_VARIABLE_NAME = "_com_chromium_debug_helper
"; | |
| 347 } | |
| OLD | NEW |