4if (!window.location.origin) {
5 window.location.origin =
6 window.location.protocol + "//" +
7 window.location.hostname +
8 (window.location.port ? (':' + window.location.port) : '');
12Elm.debuggerAttach = function(module, hotSwapState /* =undefined */) {
14 make: function(runtime) {
15 var wrappedModule = debugModule(module, runtime);
16 Elm.Debugger = debuggerInit(wrappedModule, runtime, hotSwapState);
17 dispatchElmDebuggerInit();
18 return wrappedModule.debuggedModule;
23var EVENTS_PER_SAVE = 100;
25function dispatchElmDebuggerInit() {
27 var dispatch = function() {
28 parent.window.postMessage("elmDebuggerInit", window.location.origin);
30 if (parent.window.document.readyState === "complete") {
34 parent.window.addEventListener("load", dispatch);
39function debugModule(module, runtime) {
40 var programPaused = false;
41 var recordedEvents = [];
42 var asyncCallbacks = [];
44 var watchTracker = Elm.Native.Debug.make(runtime).watchTracker;
46 var eventsUntilSnapshot = EVENTS_PER_SAVE;
48 // runtime is the prototype of wrappedRuntime
49 // so we can access all runtime properties too
50 var wrappedRuntime = Object.create(runtime);
51 wrappedRuntime.notify = wrapNotify;
52 wrappedRuntime.runDelayed = wrapRunDelayed;
54 // make a copy of the wrappedRuntime
55 var assignedPropTracker = Object.create(wrappedRuntime);
56 var debuggedModule = module.make(assignedPropTracker);
58 // make sure the signal graph is actually a signal & extract the visual model
59 if ( !('recv' in debuggedModule.main) ) {
60 debuggedModule.main = Elm.Signal.make(runtime).constant(debuggedModule.main);
63 // The main module stores imported modules onto the runtime.
64 // To ensure only one instance of each module is created,
65 // we assign them back on the original runtime object.
66 Object.keys(assignedPropTracker).forEach(function(key) {
67 runtime[key] = assignedPropTracker[key];
70 var signalGraphNodes = flattenSignalGraph(wrappedRuntime.inputs);
71 var tracePath = tracePathInit(runtime, debuggedModule.main);
73 snapshots.push(snapshotSignalGraph(signalGraphNodes));
75 function wrapNotify(id, v) {
76 var timestep = runtime.timer.now();
79 // ignore async events generated while playing back
80 // or user events while program is paused
84 var changed = runtime.notify(id, v, timestep);
85 recordEvent(id, v, timestep);
86 if (eventsUntilSnapshot === 1) {
88 eventsUntilSnapshot = EVENTS_PER_SAVE;
90 eventsUntilSnapshot -= 1;
93 parent.window.postMessage("elmNotify", window.location.origin);
99 function wrapRunDelayed(func, delayMs) {
101 // Don't push timers and such to the callback stack while we're paused.
102 // It causes too many callbacks to be fired during unpausing.
105 var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
106 var timerId = setTimeout(function() {
107 cbObj.executed = true;
110 cbObj.timerId = timerId;
111 asyncCallbacks.push(cbObj);
115 function recordEvent(id, v, timestep) {
116 watchTracker.pushFrame();
117 recordedEvents.push({ id:id, value:v, timestep:timestep });
120 function clearAsyncCallbacks() {
121 asyncCallbacks.forEach(function(timer) {
122 if (!timer.executed) {
123 clearTimeout(timer.timerId);
128 function clearRecordedEvents() {
132 function getRecordedEventsLength() {
133 return recordedEvents.length;
136 function getRecordedEventAt(i) {
137 return recordedEvents[i];
140 function copyRecordedEvents() {
141 return recordedEvents.slice();
144 function loadRecordedEvents(events) {
145 recordedEvents = events.slice();
148 function clearSnapshots() {
149 snapshots = [snapshotSignalGraph(signalGraphNodes)];
152 function getSnapshotAt(i) {
153 var snapshotEvent = Math.floor(i / EVENTS_PER_SAVE);
154 assert(snapshotEvent < snapshots.length && snapshotEvent >= 0, "Out of bounds index: " + snapshotEvent);
155 return snapshots[snapshotEvent];
158 function saveSnapshot() {
159 snapshots.push(snapshotSignalGraph(signalGraphNodes));
162 function setPaused() {
163 programPaused = true;
164 clearAsyncCallbacks();
165 tracePath.stopRecording();
166 pauseTime = Date.now();
169 function setContinue(position) {
170 var timerDelay = Date.now() - pauseTime;
171 runtime.timer.addDelay(timerDelay);
172 programPaused = false;
174 // If we're not unpausing at the head, then we need to dump the
175 // events that are ahead of where we're continuing.
176 var lastSnapshotPosition = Math.ceil(position / EVENTS_PER_SAVE);
177 eventsUntilSnapshot = EVENTS_PER_SAVE - (position % EVENTS_PER_SAVE);
178 snapshots = snapshots.slice(0, lastSnapshotPosition);
179 recordedEvents = recordedEvents.slice(0, position);
180 executeCallbacks(asyncCallbacks, false);
182 tracePath.startRecording();
185 function getPaused() {
186 return programPaused;
190 debuggedModule: debuggedModule,
191 signalGraphNodes: signalGraphNodes,
192 initialSnapshot: snapshotSignalGraph(signalGraphNodes),
193 initialAsyncCallbacks: asyncCallbacks.slice(),
195 clearAsyncCallbacks: clearAsyncCallbacks,
196 clearRecordedEvents: clearRecordedEvents,
197 getRecordedEventsLength: getRecordedEventsLength,
198 getRecordedEventAt: getRecordedEventAt,
199 copyRecordedEvents: copyRecordedEvents,
200 loadRecordedEvents: loadRecordedEvents,
201 clearSnapshots: clearSnapshots,
202 getSnapshotAt: getSnapshotAt,
203 saveSnapshot: saveSnapshot,
204 getPaused: getPaused,
205 setPaused: setPaused,
206 setContinue: setContinue,
207 tracePath: tracePath,
208 watchTracker: watchTracker
212function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
213 var currentEventIndex = 0;
215 function resetProgram(position) {
216 var closestSnapshot = debugModule.getSnapshotAt(position);
217 debugModule.clearAsyncCallbacks();
218 restoreSnapshot(debugModule.signalGraphNodes, closestSnapshot);
219 // clear traces here.
223 function restartProgram() {
225 debugModule.watchTracker.clear();
226 debugModule.tracePath.clearTraces();
227 debugModule.clearRecordedEvents();
228 debugModule.clearSnapshots();
229 debugModule.setContinue(0);
230 executeCallbacks(debugModule.initialAsyncCallbacks, true);
233 function pauseProgram() {
234 debugModule.setPaused();
235 currentEventIndex = debugModule.getRecordedEventsLength();
238 function continueProgram() {
239 if (debugModule.getPaused())
241 if (currentEventIndex === 0) {
245 var closestSnapshotIndex = Math.floor(currentEventIndex / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;
246 resetProgram(currentEventIndex);
247 var continueIndex = currentEventIndex;
248 currentEventIndex = closestSnapshotIndex;
249 stepTo(continueIndex);
250 debugModule.setContinue(currentEventIndex);
254 function stepTo(index) {
255 if (!debugModule.getPaused()) {
256 debugModule.setPaused();
260 if (index < 0 || index > getMaxSteps()) {
261 throw "Index out of bounds: " + index;
264 if (index < currentEventIndex) {
265 var closestSnapshotIndex = Math.floor(index / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;
267 currentEventIndex = closestSnapshotIndex;
270 while (currentEventIndex < index) {
271 var nextEvent = debugModule.getRecordedEventAt(currentEventIndex);
272 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
274 currentEventIndex += 1;
278 function getMaxSteps() {
279 return debugModule.getRecordedEventsLength();
282 function redrawGraphics() {
283 var main = debugModule.debuggedModule.main
284 for (var i = main.kids.length ; i-- ; ) {
285 main.kids[i].recv(runtime.timer.now(), true, main.id);
289 function getHotSwapState() {
290 var continueIndex = currentEventIndex;
291 if (!debugModule.getPaused()) {
292 continueIndex = getMaxSteps();
295 recordedEvents: debugModule.copyRecordedEvents(),
296 currentEventIndex: continueIndex
301 var parentNode = runtime.node.parentNode;
302 parentNode.removeChild(debugModule.tracePath.canvas);
303 parentNode.removeChild(runtime.node);
307 // The problem is that we want to previous paused state. But
308 // by the time JS reaches here, the old code has been swapped out
309 // and the new modules are being generated. So we can ask the
310 // debugging console what it thinks the pause state is and go
312 var paused = top.debug.paused;
313 debugModule.setPaused();
314 debugModule.loadRecordedEvents(hotSwapState.recordedEvents);
315 var index = getMaxSteps();
316 var eventsUntilSnapshot = EVENTS_PER_SAVE;
318 // draw new trace path
319 debugModule.tracePath.startRecording();
320 while(currentEventIndex < index) {
321 var nextEvent = debugModule.getRecordedEventAt(currentEventIndex);
322 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
323 if (eventsUntilSnapshot === 1) {
324 debugModule.saveSnapshot();
325 eventsUntilSnapshot = EVENTS_PER_SAVE;
327 eventsUntilSnapshot -= 1;
329 currentEventIndex += 1;
331 debugModule.tracePath.stopRecording();
333 stepTo(hotSwapState.currentEventIndex);
335 debugModule.setContinue(hotSwapState.currentEventIndex);
339 runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
342 restart: restartProgram,
344 kontinue: continueProgram,
345 getMaxSteps: getMaxSteps,
347 getPaused: debugModule.getPaused,
348 getHotSwapState: getHotSwapState,
350 allNodes: debugModule.signalGraphNodes,
351 watchTracker: debugModule.watchTracker
357function Point(x, y) {
361 this.translate = function(x, y) {
362 return new Point(this.x + x, this.y + y);
365 this.equals = function(p) {
366 return this.x == p.x && this.y == p.y;
370function tracePathInit(runtime, signalGraphMain) {
371 var List = Elm.List.make(runtime);
372 var Signal = Elm.Signal.make(runtime);
373 var tracePathNode = A2(Signal.lift, graphicsUpdate, signalGraphMain);
374 var tracePathCanvas = createCanvas();
375 var tracePositions = {};
376 var recordingTraces = true;
378 function findPositions(currentScene) {
380 function processElement(elem, offset) {
381 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
383 List.map(F2(processForm)(offset))(elem.element.model.forms);
385 if (elem.props.debugTracePathId)
387 positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
391 function processForm(offset, form) {
392 if (form.form.ctor == "FElement")
394 processElement(form.form._0, offset.translate(form.x, -form.y));
398 processElement(currentScene, new Point(0, 0));
402 function appendPositions(positions) {
403 for (var id in positions) {
404 var pos = positions[id];
405 if (tracePositions.hasOwnProperty(id)) {
406 var points = tracePositions[id];
407 if (!pos.equals(points[points.length-1])) {
412 tracePositions[id] = [pos];
417 function graphicsUpdate(currentScene) {
418 if (!recordingTraces) {
422 var ctx = tracePathCanvas.getContext('2d');
423 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
426 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
427 appendPositions(findPositions(currentScene));
428 for (var id in tracePositions)
431 var points = tracePositions[id];
432 for (var i=0; i < points.length; i++)
436 ctx.moveTo(p.x, p.y);
439 ctx.lineTo(p.x, p.y);
443 ctx.strokeStyle = "rgba(50, 50, 50, 0.4)";
446 for (var i=0; i < points.length; i++)
450 ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
451 ctx.fillStyle = "rgba(50, 50, 50, 0.4)";
459 function clearTraces() {
463 function stopRecording() {
464 recordingTraces = false;
467 function startRecording() {
468 recordingTraces = true;
472 graphicsUpdate: graphicsUpdate,
473 canvas: tracePathCanvas,
474 clearTraces: clearTraces,
475 stopRecording: stopRecording,
476 startRecording: startRecording
480function executeCallbacks(callbacks, reexecute) {
481 callbacks.forEach(function(timer) {
482 if (reexecute || !timer.executed) {
483 var func = timer.func;
484 timer.executed = true;
490function createCanvas() {
491 var c = document.createElement('canvas');
492 c.width = window.innerWidth;
493 c.height = window.innerHeight;
494 c.style.position = "absolute";
497 c.style.pointerEvents = "none";
501function assert(bool, msg) {
503 throw "Assertion error: " + msg;
507function snapshotSignalGraph(signalGraphNodes) {
510 signalGraphNodes.forEach(function(node) {
511 nodeValues.push({ value: node.value, id: node.id });
517function restoreSnapshot(signalGraphNodes, snapshot) {
518 assert(signalGraphNodes.length == snapshot.length, "saved program state has wrong length");
519 for (var i=0; i < signalGraphNodes.length; i++) {
520 var node = signalGraphNodes[i];
521 var state = snapshot[i];
522 assert(node.id == state.id, "the nodes moved position");
524 node.value = state.value;
528function flattenSignalGraph(nodes) {
531 function addAllToDict(node) {
532 nodesById[node.id] = node;
533 node.kids.forEach(addAllToDict);
535 nodes.forEach(addAllToDict);
537 var allNodes = Object.keys(nodesById).sort().map(function(key) {
538 return nodesById[key];
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list