4if (!window.location.origin) {
5 window.location.origin =
6 window.location.protocol + "//" +
7 window.location.hostname +
8 (window.location.port ? (':' + window.location.port) : '');
12Elm.debug = function(module, moduleFile, hotSwapState /* =undefined */) {
13 var mainHandle = Elm.fulscreen(debuggerAttach(module,hotSwapState));
14 mainHandle.fileName = moduleFile;
15 mainHandle.debug = Elm.Debugger;
18function debuggerAttach(module, hotSwapState /* =undefined */) {
20 make: function(runtime) {
21 var wrappedModule = debugModule(module, runtime);
22 Elm.Debugger = debuggerInit(wrappedModule, runtime, hotSwapState);
23 dispatchElmDebuggerInit();
24 return wrappedModule.debuggedModule;
29var EVENTS_PER_SAVE = 100;
31function dispatchElmDebuggerInit() {
33 var dispatch = function() {
34 parent.window.postMessage("elmDebuggerInit", window.location.origin);
36 if (parent.window.document.readyState === "complete") {
40 parent.window.addEventListener("load", dispatch);
45function debugModule(module, runtime) {
46 var programPaused = false;
47 var recordedEvents = [];
48 var asyncCallbacks = [];
50 var watchTracker = Elm.Native.Debug.make(runtime).watchTracker;
52 var eventsUntilSnapshot = EVENTS_PER_SAVE;
53 runtime.debuggerStatus = runtime.debuggerStatus || {};
54 runtime.debuggerStatus.eventCounter = runtime.debuggerStatus.eventCounter || 0;
56 // runtime is the prototype of wrappedRuntime
57 // so we can access all runtime properties too
58 var wrappedRuntime = Object.create(runtime);
59 wrappedRuntime.notify = wrapNotify;
60 wrappedRuntime.runDelayed = wrapRunDelayed;
62 // make a copy of the wrappedRuntime
63 var assignedPropTracker = Object.create(wrappedRuntime);
64 var debuggedModule = module.make(assignedPropTracker);
66 // make sure the signal graph is actually a signal & extract the visual model
67 if ( !('recv' in debuggedModule.main) ) {
68 debuggedModule.main = Elm.Signal.make(runtime).constant(debuggedModule.main);
71 // The main module stores imported modules onto the runtime.
72 // To ensure only one instance of each module is created,
73 // we assign them back on the original runtime object.
74 Object.keys(assignedPropTracker).forEach(function(key) {
75 runtime[key] = assignedPropTracker[key];
78 var signalGraphNodes = flattenSignalGraph(wrappedRuntime.inputs);
79 var tracePath = tracePathInit(runtime, debuggedModule.main);
81 snapshots.push(snapshotSignalGraph(signalGraphNodes));
83 function wrapNotify(id, v) {
84 var timestep = runtime.timer.now();
87 // ignore async events generated while playing back
88 // or user events while program is paused
92 recordEvent(id, v, timestep);
93 var changed = runtime.notify(id, v, timestep);
94 snapshotOnCheckpoint();
96 parent.window.postMessage("elmNotify", window.location.origin);
102 function wrapRunDelayed(func, delayMs) {
104 // Don't push timers and such to the callback stack while we're paused.
105 // It causes too many callbacks to be fired during unpausing.
108 var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
109 var timerId = setTimeout(function() {
110 cbObj.executed = true;
113 cbObj.timerId = timerId;
114 asyncCallbacks.push(cbObj);
118 function recordEvent(id, v, timestep) {
119 watchTracker.pushFrame();
120 recordedEvents.push({ id:id, value:v, timestep:timestep });
121 runtime.debuggerStatus.eventCounter += 1;
124 function clearAsyncCallbacks() {
125 asyncCallbacks.forEach(function(timer) {
126 if (!timer.executed) {
127 clearTimeout(timer.timerId);
132 function clearRecordedEvents() {
134 runtime.debuggerStatus.eventCounter = 0;
137 function getRecordedEventsLength() {
138 return recordedEvents.length;
141 function getRecordedEventAt(i) {
142 return recordedEvents[i];
145 function copyRecordedEvents() {
146 return recordedEvents.slice();
149 function loadRecordedEvents(events) {
150 recordedEvents = events.slice();
153 function clearSnapshots() {
154 snapshots = [snapshotSignalGraph(signalGraphNodes)];
157 function getSnapshotAt(i) {
158 var snapshotEvent = Math.floor(i / EVENTS_PER_SAVE);
159 assert(snapshotEvent < snapshots.length && snapshotEvent >= 0,
160 "Out of bounds index: " + snapshotEvent);
161 return snapshots[snapshotEvent];
164 function snapshotOnCheckpoint() {
165 if (eventsUntilSnapshot === 1) {
166 snapshots.push(snapshotSignalGraph(signalGraphNodes));
167 eventsUntilSnapshot = EVENTS_PER_SAVE;
169 eventsUntilSnapshot -= 1;
173 function setPaused() {
174 programPaused = true;
175 clearAsyncCallbacks();
176 pauseTime = Date.now();
177 tracePath.stopRecording();
178 preventInputEvents();
181 function setContinue(position) {
182 var pauseDelay = Date.now() - pauseTime;
183 runtime.timer.addDelay(pauseDelay);
184 programPaused = false;
186 // we need to dump the events that are ahead of where we're continuing.
187 var lastSnapshotPosition = Math.floor(position / EVENTS_PER_SAVE);
188 eventsUntilSnapshot = EVENTS_PER_SAVE - (position % EVENTS_PER_SAVE);
189 snapshots = snapshots.slice(0, lastSnapshotPosition + 1);
191 if (position < getRecordedEventsLength()) {
192 var lastEventTime = recordedEvents[position].timestep;
193 var scrubTime = runtime.timer.now() - lastEventTime;
194 runtime.timer.addDelay(scrubTime);
197 recordedEvents = recordedEvents.slice(0, position);
198 tracePath.clearTracesAfter(position);
199 runtime.debuggerStatus.eventCounter = position;
200 executeCallbacks(asyncCallbacks);
203 tracePath.startRecording();
206 function getPaused() {
207 return programPaused;
210 function preventInputEvents(){
212 [ "click", "mousemove", "mouseup", "mousedown", "mouseclick"
213 , "keydown", "keypress", "keyup", "touchstart", "touchend"
214 , "touchcancel", "touchleave", "touchmove", "pointermove"
215 , "pointerdown", "pointerup", "pointerover", "pointerout"
216 , "pointerenter", "pointerleave", "pointercancel"
219 var ignore = function(e) {
220 var evt = e ? e : window.event;
221 if (evt.stopPropagation) {
222 evt.stopPropagation();
224 if (evt.cancelBubble !== null) {
225 evt.cancelBubble = true;
227 if (evt.preventDefault) {
228 evt.preventDefault();
233 var ignoringDiv = document.getElementById("elmEventIgnorer");
235 ignoringDiv = document.createElement("div");
236 ignoringDiv.id = "elmEventIgnorer";
237 ignoringDiv.style.position = "absolute";
238 ignoringDiv.style.top = "0px";
239 ignoringDiv.style.left = "0px";
240 ignoringDiv.style.width = "100%";
241 ignoringDiv.style.height = "100%";
243 for (var i = events.length; i-- ;) {
244 ignoringDiv.addEventListener(events[i], ignore, true);
246 runtime.node.appendChild(ignoringDiv);
250 function permitInputEvents(){
251 var ignoringDiv = document.getElementById("elmEventIgnorer");
252 ignoringDiv.parentNode.removeChild(ignoringDiv);
256 debuggedModule: debuggedModule,
257 signalGraphNodes: signalGraphNodes,
258 initialSnapshot: snapshotSignalGraph(signalGraphNodes),
259 initialAsyncCallbacks: asyncCallbacks.slice(),
261 clearAsyncCallbacks: clearAsyncCallbacks,
262 clearRecordedEvents: clearRecordedEvents,
263 getRecordedEventsLength: getRecordedEventsLength,
264 getRecordedEventAt: getRecordedEventAt,
265 copyRecordedEvents: copyRecordedEvents,
266 loadRecordedEvents: loadRecordedEvents,
267 clearSnapshots: clearSnapshots,
268 getSnapshotAt: getSnapshotAt,
269 snapshotOnCheckpoint: snapshotOnCheckpoint,
270 getPaused: getPaused,
271 setPaused: setPaused,
272 setContinue: setContinue,
273 tracePath: tracePath,
274 watchTracker: watchTracker
278// The debuggerHistory variable is passed in on hotswap. It represents
279// the a state of the debugger for it to assume during init. It contains
280// the paused state of the debugger, the recorded events, and the current
281// event being processed.
282function debuggerInit(debugModule, runtime, debuggerHistory /* =undefined */) {
283 var currentEventIndex = 0;
285 function resetProgram(position) {
286 var closestSnapshot = debugModule.getSnapshotAt(position);
287 debugModule.clearAsyncCallbacks();
288 restoreSnapshot(debugModule.signalGraphNodes, closestSnapshot);
292 function restartProgram() {
295 debugModule.watchTracker.clear();
296 debugModule.tracePath.clearTraces();
297 debugModule.clearRecordedEvents();
298 debugModule.clearSnapshots();
299 debugModule.setContinue(0);
300 executeCallbacks(debugModule.initialAsyncCallbacks);
303 function pauseProgram() {
304 debugModule.setPaused();
305 currentEventIndex = debugModule.getRecordedEventsLength();
308 function continueProgram() {
309 if (debugModule.getPaused())
311 var closestSnapshotIndex =
312 Math.floor(currentEventIndex / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;
313 resetProgram(currentEventIndex);
314 var continueIndex = currentEventIndex;
315 currentEventIndex = closestSnapshotIndex;
316 stepTo(continueIndex);
317 debugModule.setContinue(currentEventIndex);
321 function stepTo(index) {
322 if (!debugModule.getPaused()) {
323 debugModule.setPaused();
327 if (index < 0 || index > getMaxSteps()) {
328 throw "Index out of bounds: " + index;
331 if (index < currentEventIndex) {
332 var closestSnapshotIndex = Math.floor(index / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;
334 currentEventIndex = closestSnapshotIndex;
337 while (currentEventIndex < index) {
338 var nextEvent = debugModule.getRecordedEventAt(currentEventIndex);
339 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
341 currentEventIndex += 1;
345 function getMaxSteps() {
346 return debugModule.getRecordedEventsLength();
349 function redrawGraphics() {
350 var main = debugModule.debuggedModule.main
351 for (var i = main.kids.length ; i-- ; ) {
352 main.kids[i].recv(runtime.timer.now(), true, main.id);
356 function getHotSwapState() {
357 var continueIndex = currentEventIndex;
358 if (!debugModule.getPaused()) {
359 continueIndex = getMaxSteps();
362 paused: debugModule.getPaused(),
363 recordedEvents: debugModule.copyRecordedEvents(),
364 currentEventIndex: continueIndex
369 var parentNode = runtime.node.parentNode;
370 parentNode.removeChild(debugModule.tracePath.canvas);
371 parentNode.removeChild(runtime.node);
374 if (debuggerHistory) {
375 // The problem is that we want to previous paused state. But
376 // by the time JS reaches here, the old code has been swapped out
377 // and the new modules are being generated. So we can ask the
378 // debugging console what it thinks the pause state is and go
380 var paused = debuggerHistory.paused;
381 debugModule.setPaused();
382 debugModule.loadRecordedEvents(debuggerHistory.recordedEvents);
383 var index = getMaxSteps();
384 runtime.debuggerStatus.eventCounter = 0;
385 debugModule.tracePath.clearTraces();
387 // draw new trace path
388 debugModule.tracePath.startRecording();
389 while(currentEventIndex < index) {
390 var nextEvent = debugModule.getRecordedEventAt(currentEventIndex);
391 runtime.debuggerStatus.eventCounter += 1;
392 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
393 debugModule.snapshotOnCheckpoint();
394 currentEventIndex += 1;
396 debugModule.tracePath.stopRecording();
398 stepTo(debuggerHistory.currentEventIndex);
400 debugModule.setContinue(debuggerHistory.currentEventIndex);
404 runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
407 restart: restartProgram,
409 kontinue: continueProgram,
410 getMaxSteps: getMaxSteps,
412 getPaused: debugModule.getPaused,
413 getHotSwapState: getHotSwapState,
415 allNodes: debugModule.signalGraphNodes,
416 watchTracker: debugModule.watchTracker
422function Point(x, y) {
426 this.translate = function(x, y) {
427 return new Point(this.x + x, this.y + y);
430 this.equals = function(p) {
431 return this.x == p.x && this.y == p.y;
435function tracePathInit(runtime, signalGraphMain) {
436 var List = Elm.List.make(runtime);
437 var Signal = Elm.Signal.make(runtime);
438 var tracePathNode = A2(Signal.lift, graphicsUpdate, signalGraphMain);
439 var tracePathCanvas = createCanvas();
440 var tracePositions = {};
441 var recordingTraces = true;
443 function findPositions(currentScene) {
445 function processElement(elem, offset) {
446 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
448 List.map(F2(processForm)(offset))(elem.element.model.forms);
452 function processForm(offset, form) {
453 if (form.form.ctor == "FElement")
455 processElement(form.form._0, offset.translate(form.x, -form.y));
457 if (form.form.ctor == "FGroup")
459 var newOffset = offset.translate(form.x, -form.y);
460 List.map(F2(processForm)(newOffset))(form.form._1);
462 if (form.debugTracePathId)
464 positions[form.debugTracePathId] = new Point(form.x + offset.x, -form.y + offset.y);
468 processElement(currentScene, new Point(0, 0));
472 function appendPositions(positions) {
473 for (var id in positions) {
474 var pos = positions[id];
475 if (tracePositions.hasOwnProperty(id)) {
476 tracePositions[id].push(pos);
479 tracePositions[id] = [pos];
481 if (tracePositions[id].length < runtime.debuggerStatus.eventCounter) {
482 var padCount = runtime.debuggerStatus.eventCounter - tracePositions[id].length;
483 var lastTracePosition = tracePositions[id][tracePositions[id].length - 1];
484 for (var i = padCount; i--;) {
485 tracePositions[id].push(lastTracePosition)
488 assert(tracePositions[id].length === runtime.debuggerStatus.eventCounter,
489 "We don't have a 1-1 mapping of trace positions to events");
493 function graphicsUpdate(currentScene) {
494 if (!recordingTraces) {
498 var ctx = tracePathCanvas.getContext('2d');
499 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
502 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
503 appendPositions(findPositions(currentScene));
504 for (var id in tracePositions)
507 var points = tracePositions[id];
508 for (var i=0; i < points.length; i++)
512 ctx.moveTo(p.x, p.y);
515 ctx.lineTo(p.x, p.y);
519 ctx.strokeStyle = "rgba(50, 50, 50, 0.4)";
526 function clearTraces() {
530 function stopRecording() {
531 recordingTraces = false;
534 function startRecording() {
535 recordingTraces = true;
538 function clearTracesAfter(position) {
540 for (var id in tracePositions) {
541 newTraces[id] = tracePositions[id].slice(0,position);
543 tracePositions = newTraces;
547 graphicsUpdate: graphicsUpdate,
548 canvas: tracePathCanvas,
549 clearTraces: clearTraces,
550 clearTracesAfter: clearTracesAfter,
551 stopRecording: stopRecording,
552 startRecording: startRecording
556function executeCallbacks(callbacks) {
557 callbacks.forEach(function(timer) {
558 if (!timer.executed) {
559 var func = timer.func;
560 timer.executed = true;
566function createCanvas() {
567 var c = document.createElement('canvas');
568 c.width = window.innerWidth;
569 c.height = window.innerHeight;
570 c.style.position = "absolute";
573 c.style.pointerEvents = "none";
577function assert(bool, msg) {
579 throw "Assertion error: " + msg;
583function snapshotSignalGraph(signalGraphNodes) {
586 signalGraphNodes.forEach(function(node) {
587 nodeValues.push({ value: node.value, id: node.id });
593function restoreSnapshot(signalGraphNodes, snapshot) {
594 assert(signalGraphNodes.length == snapshot.length,
595 "saved program state has wrong length");
596 for (var i=0; i < signalGraphNodes.length; i++) {
597 var node = signalGraphNodes[i];
598 var state = snapshot[i];
599 assert(node.id == state.id, "the nodes moved position");
601 node.value = state.value;
605function flattenSignalGraph(nodes) {
608 function addAllToDict(node) {
609 nodesById[node.id] = node;
610 node.kids.forEach(addAllToDict);
612 nodes.forEach(addAllToDict);
614 var allNodes = Object.keys(nodesById).sort().map(function(key) {
615 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