| 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 static org.chromium.sdk.util.BasicUtil.containsKeySafe; | |
| 8 import static org.chromium.sdk.util.BasicUtil.getSafe; | |
| 9 | |
| 10 import java.util.ArrayDeque; | |
| 11 import java.util.ArrayList; | |
| 12 import java.util.Collection; | |
| 13 import java.util.HashMap; | |
| 14 import java.util.List; | |
| 15 import java.util.Map; | |
| 16 import java.util.Queue; | |
| 17 import java.util.Set; | |
| 18 | |
| 19 import org.chromium.sdk.DebugEventListener; | |
| 20 import org.chromium.sdk.JavascriptVm; | |
| 21 import org.chromium.sdk.RelayOk; | |
| 22 import org.chromium.sdk.Script; | |
| 23 import org.chromium.sdk.SyncCallback; | |
| 24 import org.chromium.sdk.internal.ScriptBase; | |
| 25 import org.chromium.sdk.internal.wip.protocol.input.debugger.GetScriptSourceData
; | |
| 26 import org.chromium.sdk.internal.wip.protocol.input.debugger.ScriptParsedEventDa
ta; | |
| 27 import org.chromium.sdk.internal.wip.protocol.output.debugger.GetScriptSourcePar
ams; | |
| 28 import org.chromium.sdk.util.AsyncFuture; | |
| 29 import org.chromium.sdk.util.AsyncFuture.Callback; | |
| 30 import org.chromium.sdk.util.AsyncFutureMerger; | |
| 31 import org.chromium.sdk.util.AsyncFutureRef; | |
| 32 import org.chromium.sdk.util.GenericCallback; | |
| 33 import org.chromium.sdk.util.RelaySyncCallback; | |
| 34 | |
| 35 /** | |
| 36 * Keeps all current scripts for the debug session and handles script source loa
ding. | |
| 37 */ | |
| 38 class WipScriptManager { | |
| 39 private final WipTabImpl tabImpl; | |
| 40 // Access must be synchronized. | |
| 41 private final Map<String, ScriptData> scriptIdToData = new HashMap<String, Scr
iptData>(); | |
| 42 | |
| 43 /** | |
| 44 * A future for script pre-load operation. User may call {@link #getScripts} a
t any time, | |
| 45 * but we return result only once we have loaded all pre-existing scripts. | |
| 46 */ | |
| 47 private final AsyncFutureRef<Void> scriptsPreloaded; | |
| 48 | |
| 49 /** Accessed from Dispatch thread only. */ | |
| 50 private ScriptPopulateMode populateMode = new ScriptPopulateMode(); | |
| 51 | |
| 52 WipScriptManager(WipTabImpl tabImpl) { | |
| 53 this.tabImpl = tabImpl; | |
| 54 this.scriptsPreloaded = populateMode.createAndInitMasterFuture(); | |
| 55 } | |
| 56 | |
| 57 WipTabImpl getTabImpl() { | |
| 58 return tabImpl; | |
| 59 } | |
| 60 | |
| 61 // Run command in dispatch thread so that no scripts event could happen in the
meantime. | |
| 62 // TODO: make sure we do not return those scripts that are reported compiled b
ut not loaded yet. | |
| 63 RelayOk getScripts(final GenericCallback<Collection<Script>> callback, | |
| 64 SyncCallback syncCallback) { | |
| 65 | |
| 66 // Async command chain here, wrap syncCallback to guaranteed calling. | |
| 67 RelaySyncCallback relay = new RelaySyncCallback(syncCallback); | |
| 68 | |
| 69 // Guard for the step one. | |
| 70 final RelaySyncCallback.Guard guardOne = relay.newGuard(); | |
| 71 | |
| 72 // Chain commands are in the reverse order. | |
| 73 | |
| 74 // Wait for script pre-load operation and return scripts. | |
| 75 final AsyncFuture.Callback<Void> futureCallback = new AsyncFuture.Callback<V
oid>() { | |
| 76 @Override | |
| 77 public void done(Void res) { | |
| 78 if (callback != null) { | |
| 79 callback.success(getCurrentScripts()); | |
| 80 } | |
| 81 } | |
| 82 }; | |
| 83 | |
| 84 // Start everything in dispatch thread (otherwise user may be called from th
is thread). | |
| 85 Runnable mainRunnable = new Runnable() { | |
| 86 @Override | |
| 87 public void run() { | |
| 88 RelayOk relayOk = | |
| 89 scriptsPreloaded.getAsync(futureCallback, guardOne.getRelay().getUse
rSyncCallback()); | |
| 90 guardOne.discharge(relayOk); | |
| 91 } | |
| 92 }; | |
| 93 | |
| 94 return tabImpl.getCommandProcessor().runInDispatchThread(mainRunnable, | |
| 95 guardOne.asSyncCallback()); | |
| 96 } | |
| 97 | |
| 98 Script getScript(String scriptId) { | |
| 99 ScriptData data; | |
| 100 synchronized (scriptIdToData) { | |
| 101 data = getSafe(scriptIdToData, scriptId); | |
| 102 } | |
| 103 if (data == null) { | |
| 104 return null; | |
| 105 } | |
| 106 if (!data.sourceLoadedFuture.isDone()) { | |
| 107 return null; | |
| 108 } | |
| 109 return data.scriptImpl; | |
| 110 } | |
| 111 | |
| 112 | |
| 113 private Collection<Script> getCurrentScripts() { | |
| 114 synchronized (scriptIdToData) { | |
| 115 List<Script> list = new ArrayList<Script>(scriptIdToData.size()); | |
| 116 for (ScriptData data : scriptIdToData.values()) { | |
| 117 if (data.sourceLoadedFuture.isDone()) { | |
| 118 list.add(data.scriptImpl); | |
| 119 } | |
| 120 } | |
| 121 return list; | |
| 122 } | |
| 123 } | |
| 124 | |
| 125 public void scriptIsReportedParsed(ScriptParsedEventData data) { | |
| 126 final String sourceID = data.scriptId(); | |
| 127 | |
| 128 String url = data.url(); | |
| 129 if (url.isEmpty()) { | |
| 130 url = null; | |
| 131 } | |
| 132 | |
| 133 ScriptBase.Descriptor<String> descriptor = new ScriptBase.Descriptor<String>
(Script.Type.NORMAL, | |
| 134 sourceID, url, (int) data.startLine(), (int) data.startColumn(), -1); | |
| 135 final WipScriptImpl script = new WipScriptImpl(this, descriptor); | |
| 136 final ScriptData scriptData = new ScriptData(script); | |
| 137 | |
| 138 synchronized (scriptIdToData) { | |
| 139 if (containsKeySafe(scriptIdToData, sourceID)) { | |
| 140 throw new IllegalStateException("Already has script with id " + sourceID
); | |
| 141 } | |
| 142 scriptIdToData.put(sourceID, scriptData); | |
| 143 } | |
| 144 | |
| 145 scriptData.sourceLoadedFuture.initializeRunning(new SourceLoadOperation(scri
pt, sourceID)); | |
| 146 | |
| 147 final ScriptPopulateMode populateModeSaved = populateMode; | |
| 148 | |
| 149 AsyncFuture.Callback<Boolean> callback; | |
| 150 SyncCallback syncCallback; | |
| 151 | |
| 152 if (populateModeSaved == null) { | |
| 153 callback = new AsyncFuture.Callback<Boolean>() { | |
| 154 @Override | |
| 155 public void done(Boolean res) { | |
| 156 tabImpl.getTabListener().getDebugEventListener().scriptLoaded(script); | |
| 157 } | |
| 158 }; | |
| 159 syncCallback = null; | |
| 160 } else { | |
| 161 populateModeSaved.anotherSourceToWait(); | |
| 162 | |
| 163 callback = new AsyncFuture.Callback<Boolean>() { | |
| 164 @Override | |
| 165 public void done(Boolean res) { | |
| 166 populateModeSaved.sourceLoaded(res); | |
| 167 } | |
| 168 }; | |
| 169 syncCallback = new SyncCallback() { | |
| 170 @Override | |
| 171 public void callbackDone(RuntimeException e) { | |
| 172 populateModeSaved.sourceLoadedSync(e); | |
| 173 } | |
| 174 }; | |
| 175 } | |
| 176 | |
| 177 scriptData.sourceLoadedFuture.getAsync(callback, syncCallback); | |
| 178 } | |
| 179 | |
| 180 /** | |
| 181 * Asynchronously loads script source. | |
| 182 */ | |
| 183 private final class SourceLoadOperation implements AsyncFuture.Operation<Boole
an> { | |
| 184 private final WipScriptImpl script; | |
| 185 private final String sourceID; | |
| 186 | |
| 187 private SourceLoadOperation(WipScriptImpl script, String sourceID) { | |
| 188 this.script = script; | |
| 189 this.sourceID = sourceID; | |
| 190 } | |
| 191 | |
| 192 @Override | |
| 193 public RelayOk start(final Callback<Boolean> operationCallback, SyncCallback
syncCallback) { | |
| 194 GenericCallback<GetScriptSourceData> commandCallback = | |
| 195 new GenericCallback<GetScriptSourceData>() { | |
| 196 @Override | |
| 197 public void success(GetScriptSourceData data) { | |
| 198 String source = data.scriptSource(); | |
| 199 script.setSource(source); | |
| 200 operationCallback.done(true); | |
| 201 } | |
| 202 @Override | |
| 203 public void failure(Exception exception) { | |
| 204 throw new RuntimeException(exception); | |
| 205 } | |
| 206 }; | |
| 207 GetScriptSourceParams params = new GetScriptSourceParams(sourceID); | |
| 208 return tabImpl.getCommandProcessor().send(params, commandCallback, syncCal
lback); | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 private class ScriptData { | |
| 213 final WipScriptImpl scriptImpl; | |
| 214 final AsyncFutureRef<Boolean> sourceLoadedFuture = new AsyncFutureRef<Boolea
n>(); | |
| 215 | |
| 216 ScriptData(WipScriptImpl scriptImpl) { | |
| 217 this.scriptImpl = scriptImpl; | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 /** | |
| 222 * Asynchronously loads all script sources that will be referenced from a new
debug context | |
| 223 * (from its stack frames). | |
| 224 * Must be called from Dispatch thread. | |
| 225 */ | |
| 226 RelayOk loadScriptSourcesAsync(Set<String> ids, ScriptSourceLoadCallback callb
ack, | |
| 227 SyncCallback syncCallback) { | |
| 228 Queue<ScriptData> scripts = new ArrayDeque<ScriptData>(ids.size()); | |
| 229 Map<String, WipScriptImpl> result = new HashMap<String, WipScriptImpl>(ids.s
ize()); | |
| 230 synchronized (scriptIdToData) { | |
| 231 for (String id : ids) { | |
| 232 ScriptData data = getSafe(scriptIdToData, id); | |
| 233 if (data == null) { | |
| 234 // We probably got id of internal script (usually happens when we susp
end on breakpoint | |
| 235 // thrown from internals). | |
| 236 result.put(id, null); | |
| 237 continue; | |
| 238 } | |
| 239 result.put(id, data.scriptImpl); | |
| 240 if (!data.sourceLoadedFuture.isDone()) { | |
| 241 scripts.add(data); | |
| 242 } | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 // Start a chain of asynchronous operations. | |
| 247 // Make sure we call this sync callback sooner or later. | |
| 248 RelaySyncCallback relay = new RelaySyncCallback(syncCallback); | |
| 249 | |
| 250 return loadNextScript(scripts, result, callback, relay); | |
| 251 } | |
| 252 | |
| 253 interface ScriptSourceLoadCallback { | |
| 254 void done(Map<String, WipScriptImpl> loadedScripts); | |
| 255 } | |
| 256 | |
| 257 static String convertAlienSourceId(Object sourceIdObj) { | |
| 258 if (sourceIdObj instanceof String == false) { | |
| 259 throw new IllegalArgumentException("Script id must be string"); | |
| 260 } | |
| 261 return (String) sourceIdObj; | |
| 262 } | |
| 263 | |
| 264 // TODO: scripts are loaded in-series; make load parallel instead (to wait les
s). | |
| 265 private RelayOk loadNextScript(final Queue<ScriptData> scripts, | |
| 266 final Map<String, WipScriptImpl> result, final ScriptSourceLoadCallback ca
llback, | |
| 267 final RelaySyncCallback relay) { | |
| 268 final ScriptData data = scripts.poll(); | |
| 269 if (data == null) { | |
| 270 // Terminate the chain of asynchronous loads and pass a result to the call
back. | |
| 271 RelayOk relayOk; | |
| 272 if (callback != null) { | |
| 273 callback.done(result); | |
| 274 } | |
| 275 return relay.finish(); | |
| 276 } | |
| 277 | |
| 278 // Create a guard for the case that we fail before issuing next #loadNextScr
ipt() call. | |
| 279 final RelaySyncCallback.Guard guard = relay.newGuard(); | |
| 280 | |
| 281 AsyncFuture.Callback<Boolean> futureCallback = new AsyncFuture.Callback<Bool
ean>() { | |
| 282 @Override | |
| 283 public void done(Boolean res) { | |
| 284 RelayOk relayOk = loadNextScript(scripts, result, callback, relay); | |
| 285 // We successfully relayed responsibility for operationDestructable to n
ext async call, | |
| 286 // discharge guard. | |
| 287 guard.discharge(relayOk); | |
| 288 } | |
| 289 }; | |
| 290 | |
| 291 // The async operation will call a guard even if something failed within the
AsyncFuture. | |
| 292 return data.sourceLoadedFuture.getAsync(futureCallback, guard.asSyncCallback
()); | |
| 293 } | |
| 294 | |
| 295 public void pageReloaded() { | |
| 296 synchronized (scriptIdToData) { | |
| 297 scriptIdToData.clear(); | |
| 298 } | |
| 299 } | |
| 300 | |
| 301 void endPopulateScriptMode() { | |
| 302 populateMode.endMode(); | |
| 303 populateMode = null; | |
| 304 } | |
| 305 | |
| 306 /** | |
| 307 * Right after initialization we come into 'populate scripts' mode, when back-
end | |
| 308 * reports about all pre-existing scripts as if they have just been parsed. | |
| 309 * <p> | |
| 310 * We treat this notifications differently: all these scripts must be returned
from | |
| 311 * {@link JavascriptVm#getScripts} from the beginning and only truly new scrip
ts get | |
| 312 * reported via {@link DebugEventListener#scriptLoaded}. | |
| 313 * <p> | |
| 314 * This means that until 'populate mode' ends (and all sources are loaded), | |
| 315 * {@link JavascriptVm#getScripts} call blocks. | |
| 316 */ | |
| 317 private static class ScriptPopulateMode { | |
| 318 /** | |
| 319 * Future for script preload operation. It completes when all pre-exising sc
ripts | |
| 320 * are fully loaded (with sources). The operation result value is an array o
f | |
| 321 * source loading success/failure flags. | |
| 322 */ | |
| 323 private final AsyncFutureMerger<Boolean> populateAndLoadSourcesFuture = | |
| 324 new AsyncFutureMerger<Boolean>(); | |
| 325 | |
| 326 /** | |
| 327 * Reports that 'populate script' mode is finished. However we may still be
waiting for | |
| 328 * the corresponding script sources. | |
| 329 */ | |
| 330 void endMode() { | |
| 331 populateAndLoadSourcesFuture.subOperationDone(null); | |
| 332 populateAndLoadSourcesFuture.subOperationDoneSync(null); | |
| 333 } | |
| 334 | |
| 335 /** | |
| 336 * We learned about another pre-existing script. Now we have to wait for its
source. | |
| 337 */ | |
| 338 void anotherSourceToWait() { | |
| 339 populateAndLoadSourcesFuture.addSubOperation(); | |
| 340 } | |
| 341 | |
| 342 void sourceLoaded(Boolean result) { | |
| 343 populateAndLoadSourcesFuture.subOperationDone(result); | |
| 344 } | |
| 345 | |
| 346 /** | |
| 347 * Additional method that completes {@link #sourceLoaded} and used to be com
patible with | |
| 348 * {@link SyncCallback} paradigm. | |
| 349 */ | |
| 350 void sourceLoadedSync(RuntimeException e) { | |
| 351 populateAndLoadSourcesFuture.subOperationDoneSync(e); | |
| 352 } | |
| 353 | |
| 354 /** | |
| 355 * Creates a 'master operation' future that hides the complex result value | |
| 356 * of {@link #populateAndLoadSourcesFuture}. It hides it from Java GC also, | |
| 357 * so the {@link ArrayList} gets collected once operation is finished. | |
| 358 */ | |
| 359 AsyncFutureRef<Void> createAndInitMasterFuture() { | |
| 360 AsyncFutureRef<Void> asyncFutureRef = new AsyncFutureRef<Void>(); | |
| 361 asyncFutureRef.initializeRunning(new AsyncFuture.Operation<Void>() { | |
| 362 @Override | |
| 363 public RelayOk start(final Callback<Void> callback, SyncCallback syncCal
lback) { | |
| 364 AsyncFuture<?> innerFuture = populateAndLoadSourcesFuture.getFuture(); | |
| 365 return innerFuture.getAsync(new Callback<Object>() { | |
| 366 @Override | |
| 367 public void done(Object res) { | |
| 368 callback.done(null); | |
| 369 } | |
| 370 }, syncCallback); | |
| 371 } | |
| 372 }); | |
| 373 return asyncFutureRef; | |
| 374 } | |
| 375 } | |
| 376 } | |
| OLD | NEW |