@@ -0,0 +1,604 @@
4+if (!window.location.origin) {
5+ window.location.origin =
6+ window.location.protocol + "//" +
7+ window.location.hostname +
8+ (window.location.port ? (':' + window.location.port) : '');
11+Elm.fullscreenDebugHooks = function(module, hotSwapState /* =undefined */) {
12+ var exposedDebugger = {};
13+ function debuggerAttach(module, hotSwapState) {
15+ make: function(runtime) {
16+ var wrappedModule = debugModule(module, runtime);
17+ exposedDebugger = debuggerInit(wrappedModule, runtime, hotSwapState);
18+ return wrappedModule.debuggedModule;
22+ var mainHandle = Elm.fullscreen(debuggerAttach(module,hotSwapState));
23+ mainHandle.debugger = exposedDebugger;
27+var EVENTS_PER_SAVE = 100;
29+function debugModule(module, runtime) {
30+ var programPaused = false;
31+ var recordedEvents = [];
32+ var asyncCallbacks = [];
34+ var watchTracker = Elm.Native.Debug.make(runtime).watchTracker;
36+ var eventsUntilSnapshot = EVENTS_PER_SAVE;
37+ runtime.debuggerStatus = runtime.debuggerStatus || {};
38+ runtime.debuggerStatus.eventCounter = runtime.debuggerStatus.eventCounter || 0;
40+ // runtime is the prototype of wrappedRuntime
41+ // so we can access all runtime properties too
42+ var wrappedRuntime = Object.create(runtime);
43+ wrappedRuntime.notify = notifyWrapper;
44+ wrappedRuntime.setTimeout = setTimeoutWrapper;
46+ // make a copy of the wrappedRuntime
47+ var assignedPropTracker = Object.create(wrappedRuntime);
48+ var debuggedModule = module.make(assignedPropTracker);
50+ // make sure the signal graph is actually a signal & extract the visual model
51+ if ( !('recv' in debuggedModule.main) ) {
52+ debuggedModule.main = Elm.Signal.make(runtime).constant(debuggedModule.main);
55+ // The main module stores imported modules onto the runtime.
56+ // To ensure only one instance of each module is created,
57+ // we assign them back on the original runtime object.
58+ Object.keys(assignedPropTracker).forEach(function(key) {
59+ runtime[key] = assignedPropTracker[key];
62+ var signalGraphNodes = flattenSignalGraph(wrappedRuntime.inputs);
63+ var tracePath = tracePathInit(runtime, debuggedModule.main);
65+ snapshots.push(snapshotSignalGraph(signalGraphNodes));
67+ function notifyWrapper(id, v) {
68+ var timestep = runtime.timer.now();
71+ // ignore async events generated while playing back
72+ // or user events while program is paused
76+ recordEvent(id, v, timestep);
77+ var changed = runtime.notify(id, v, timestep);
78+ snapshotOnCheckpoint();
80+ parent.window.postMessage("elmNotify", window.location.origin);
86+ function setTimeoutWrapper(func, delayMs) {
88+ // Don't push timers and such to the callback stack while we're paused.
89+ // It causes too many callbacks to be fired during unpausing.
92+ var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
93+ var timerId = setTimeout(function() {
94+ cbObj.executed = true;
97+ cbObj.timerId = timerId;
98+ asyncCallbacks.push(cbObj);
102+ function recordEvent(id, v, timestep) {
103+ watchTracker.pushFrame();
104+ recordedEvents.push({ id:id, value:v, timestep:timestep });
105+ runtime.debuggerStatus.eventCounter += 1;
108+ function clearAsyncCallbacks() {
109+ asyncCallbacks.forEach(function(timer) {
110+ if (!timer.executed) {
111+ clearTimeout(timer.timerId);
116+ function clearRecordedEvents() {
117+ recordedEvents = [];
118+ runtime.debuggerStatus.eventCounter = 0;
121+ function getRecordedEventsLength() {
122+ return recordedEvents.length;
125+ function getRecordedEventAt(i) {
126+ return recordedEvents[i];
129+ function copyRecordedEvents() {
130+ return recordedEvents.slice();
133+ function loadRecordedEvents(events) {
134+ recordedEvents = events.slice();
137+ function clearSnapshots() {
138+ snapshots = [snapshotSignalGraph(signalGraphNodes)];
141+ function getSnapshotAt(i) {
142+ var snapshotEvent = Math.floor(i / EVENTS_PER_SAVE);
143+ assert(snapshotEvent < snapshots.length && snapshotEvent >= 0,
144+ "Out of bounds index: " + snapshotEvent);
145+ return snapshots[snapshotEvent];
148+ function snapshotOnCheckpoint() {
149+ if (eventsUntilSnapshot === 1) {
150+ snapshots.push(snapshotSignalGraph(signalGraphNodes));
151+ eventsUntilSnapshot = EVENTS_PER_SAVE;
153+ eventsUntilSnapshot -= 1;
157+ function setPaused() {
158+ programPaused = true;
159+ clearAsyncCallbacks();
160+ pauseTime = Date.now();
161+ tracePath.stopRecording();
162+ preventInputEvents();
165+ function setContinue(position) {
166+ var pauseDelay = Date.now() - pauseTime;
167+ runtime.timer.addDelay(pauseDelay);
168+ programPaused = false;
170+ // we need to dump the events that are ahead of where we're continuing.
171+ var lastSnapshotPosition = Math.floor(position / EVENTS_PER_SAVE);
172+ eventsUntilSnapshot = EVENTS_PER_SAVE - (position % EVENTS_PER_SAVE);
173+ snapshots = snapshots.slice(0, lastSnapshotPosition + 1);
175+ if (position < getRecordedEventsLength()) {
176+ var lastEventTime = recordedEvents[position].timestep;
177+ var scrubTime = runtime.timer.now() - lastEventTime;
178+ runtime.timer.addDelay(scrubTime);
181+ recordedEvents = recordedEvents.slice(0, position);
182+ tracePath.clearTracesAfter(position);
183+ runtime.debuggerStatus.eventCounter = position;
184+ executeCallbacks(asyncCallbacks);
185+ permitInputEvents();
187+ tracePath.startRecording();
190+ function getPaused() {
191+ return programPaused;
194+ function preventInputEvents(){
196+ [ "click", "mousemove", "mouseup", "mousedown", "mouseclick"
197+ , "keydown", "keypress", "keyup", "touchstart", "touchend"
198+ , "touchcancel", "touchleave", "touchmove", "pointermove"
199+ , "pointerdown", "pointerup", "pointerover", "pointerout"
200+ , "pointerenter", "pointerleave", "pointercancel"
203+ var ignore = function(e) {
204+ var evt = e ? e : window.event;
205+ if (evt.stopPropagation) {
206+ evt.stopPropagation();
208+ if (evt.cancelBubble !== null) {
209+ evt.cancelBubble = true;
211+ if (evt.preventDefault) {
212+ evt.preventDefault();
217+ var ignoringDiv = document.getElementById("elmEventIgnorer");
219+ ignoringDiv = document.createElement("div");
220+ ignoringDiv.id = "elmEventIgnorer";
221+ ignoringDiv.style.position = "absolute";
222+ ignoringDiv.style.top = "0px";
223+ ignoringDiv.style.left = "0px";
224+ ignoringDiv.style.width = "100%";
225+ ignoringDiv.style.height = "100%";
227+ for (var i = events.length; i-- ;) {
228+ ignoringDiv.addEventListener(events[i], ignore, true);
230+ runtime.node.appendChild(ignoringDiv);
234+ function permitInputEvents(){
235+ var ignoringDiv = document.getElementById("elmEventIgnorer");
236+ ignoringDiv.parentNode.removeChild(ignoringDiv);
240+ debuggedModule: debuggedModule,
241+ signalGraphNodes: signalGraphNodes,
242+ initialSnapshot: snapshotSignalGraph(signalGraphNodes),
243+ initialAsyncCallbacks: asyncCallbacks.slice(),
245+ clearAsyncCallbacks: clearAsyncCallbacks,
246+ clearRecordedEvents: clearRecordedEvents,
247+ getRecordedEventsLength: getRecordedEventsLength,
248+ getRecordedEventAt: getRecordedEventAt,
249+ copyRecordedEvents: copyRecordedEvents,
250+ loadRecordedEvents: loadRecordedEvents,
251+ clearSnapshots: clearSnapshots,
252+ getSnapshotAt: getSnapshotAt,
253+ snapshotOnCheckpoint: snapshotOnCheckpoint,
254+ getPaused: getPaused,
255+ setPaused: setPaused,
256+ setContinue: setContinue,
257+ tracePath: tracePath,
258+ watchTracker: watchTracker
262+// The debuggerHistory variable is passed in on hotswap. It represents
263+// the a state of the debugger for it to assume during init. It contains
264+// the paused state of the debugger, the recorded events, and the current
265+// event being processed.
266+function debuggerInit(debugModule, runtime, debuggerHistory /* =undefined */) {
267+ var currentEventIndex = 0;
269+ function resetProgram(position) {
270+ var closestSnapshot = debugModule.getSnapshotAt(position);
271+ debugModule.clearAsyncCallbacks();
272+ restoreSnapshot(debugModule.signalGraphNodes, closestSnapshot);
276+ function restartProgram() {
279+ debugModule.watchTracker.clear();
280+ debugModule.tracePath.clearTraces();
281+ debugModule.clearRecordedEvents();
282+ debugModule.clearSnapshots();
283+ debugModule.setContinue(0);
284+ executeCallbacks(debugModule.initialAsyncCallbacks);
287+ function pauseProgram() {
288+ debugModule.setPaused();
289+ currentEventIndex = debugModule.getRecordedEventsLength();
292+ function continueProgram() {
293+ if (debugModule.getPaused())
295+ var closestSnapshotIndex =
296+ Math.floor(currentEventIndex / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;
297+ resetProgram(currentEventIndex);
298+ var continueIndex = currentEventIndex;
299+ currentEventIndex = closestSnapshotIndex;
300+ stepTo(continueIndex);
301+ debugModule.setContinue(currentEventIndex);
305+ function stepTo(index) {
306+ if (!debugModule.getPaused()) {
307+ debugModule.setPaused();
311+ if (index < 0 || index > getMaxSteps()) {
312+ throw "Index out of bounds: " + index;
315+ if (index < currentEventIndex) {
316+ var closestSnapshotIndex = Math.floor(index / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;
317+ resetProgram(index);
318+ currentEventIndex = closestSnapshotIndex;
321+ while (currentEventIndex < index) {
322+ var nextEvent = debugModule.getRecordedEventAt(currentEventIndex);
323+ runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
325+ currentEventIndex += 1;
329+ function getMaxSteps() {
330+ return debugModule.getRecordedEventsLength();
333+ function redrawGraphics() {
334+ var main = debugModule.debuggedModule.main
335+ for (var i = main.kids.length ; i-- ; ) {
336+ main.kids[i].recv(runtime.timer.now(), true, main.id);
340+ function getHotSwapState() {
341+ var continueIndex = currentEventIndex;
342+ if (!debugModule.getPaused()) {
343+ continueIndex = getMaxSteps();
346+ paused: debugModule.getPaused(),
347+ recordedEvents: debugModule.copyRecordedEvents(),
348+ currentEventIndex: continueIndex
352+ function dispose() {
353+ var parentNode = runtime.node.parentNode;
354+ parentNode.removeChild(debugModule.tracePath.canvas);
355+ parentNode.removeChild(runtime.node);
358+ if (debuggerHistory) {
359+ // The problem is that we want to previous paused state. But
360+ // by the time JS reaches here, the old code has been swapped out
361+ // and the new modules are being generated. So we can ask the
362+ // debugging console what it thinks the pause state is and go
364+ var paused = debuggerHistory.paused;
365+ debugModule.setPaused();
366+ debugModule.loadRecordedEvents(debuggerHistory.recordedEvents);
367+ var index = getMaxSteps();
368+ runtime.debuggerStatus.eventCounter = 0;
369+ debugModule.tracePath.clearTraces();
371+ // draw new trace path
372+ debugModule.tracePath.startRecording();
373+ while(currentEventIndex < index) {
374+ var nextEvent = debugModule.getRecordedEventAt(currentEventIndex);
375+ runtime.debuggerStatus.eventCounter += 1;
376+ runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
377+ debugModule.snapshotOnCheckpoint();
378+ currentEventIndex += 1;
380+ debugModule.tracePath.stopRecording();
382+ stepTo(debuggerHistory.currentEventIndex);
384+ debugModule.setContinue(debuggerHistory.currentEventIndex);
388+ runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
391+ restart: restartProgram,
392+ pause: pauseProgram,
393+ kontinue: continueProgram,
394+ getMaxSteps: getMaxSteps,
396+ getPaused: debugModule.getPaused,
397+ getHotSwapState: getHotSwapState,
399+ allNodes: debugModule.signalGraphNodes,
400+ watchTracker: debugModule.watchTracker
406+function Point(x, y) {
410+ this.translate = function(x, y) {
411+ return new Point(this.x + x, this.y + y);
414+ this.equals = function(p) {
415+ return this.x == p.x && this.y == p.y;
419+function tracePathInit(runtime, signalGraphMain) {
420+ var List = Elm.List.make(runtime);
421+ var Signal = Elm.Signal.make(runtime);
422+ var tracePathNode = A2(Signal.lift, graphicsUpdate, signalGraphMain);
423+ var tracePathCanvas = createCanvas();
424+ var tracePositions = {};
425+ var recordingTraces = true;
427+ function findPositions(currentScene) {
429+ function processElement(elem, offset) {
430+ if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
432+ List.map(F2(processForm)(offset))(elem.element.model.forms);
436+ function processForm(offset, form) {
437+ if (form.form.ctor == "FElement")
439+ processElement(form.form._0, offset.translate(form.x, -form.y));
441+ if (form.form.ctor == "FGroup")
443+ var newOffset = offset.translate(form.x, -form.y);
444+ List.map(F2(processForm)(newOffset))(form.form._1);
446+ if (form.debugTracePathId)
448+ positions[form.debugTracePathId] = new Point(form.x + offset.x, -form.y + offset.y);
452+ processElement(currentScene, new Point(0, 0));
456+ function appendPositions(positions) {
457+ for (var id in positions) {
458+ var pos = positions[id];
459+ if (tracePositions.hasOwnProperty(id)) {
460+ tracePositions[id].push(pos);
463+ tracePositions[id] = [pos];
465+ if (tracePositions[id].length < runtime.debuggerStatus.eventCounter) {
466+ var padCount = runtime.debuggerStatus.eventCounter - tracePositions[id].length;
467+ var lastTracePosition = tracePositions[id][tracePositions[id].length - 1];
468+ for (var i = padCount; i--;) {
469+ tracePositions[id].push(lastTracePosition)
472+ assert(tracePositions[id].length === runtime.debuggerStatus.eventCounter,
473+ "We don't have a 1-1 mapping of trace positions to events");
477+ function graphicsUpdate(currentScene) {
478+ if (!recordingTraces) {
482+ var ctx = tracePathCanvas.getContext('2d');
483+ ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
486+ ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
487+ appendPositions(findPositions(currentScene));
488+ for (var id in tracePositions)
491+ var points = tracePositions[id];
492+ for (var i=0; i < points.length; i++)
496+ ctx.moveTo(p.x, p.y);
499+ ctx.lineTo(p.x, p.y);
503+ ctx.strokeStyle = "rgba(50, 50, 50, 0.4)";
510+ function clearTraces() {
511+ tracePositions = {};
514+ function stopRecording() {
515+ recordingTraces = false;
518+ function startRecording() {
519+ recordingTraces = true;
522+ function clearTracesAfter(position) {
524+ for (var id in tracePositions) {
525+ newTraces[id] = tracePositions[id].slice(0,position);
527+ tracePositions = newTraces;
531+ graphicsUpdate: graphicsUpdate,
532+ canvas: tracePathCanvas,
533+ clearTraces: clearTraces,
534+ clearTracesAfter: clearTracesAfter,
535+ stopRecording: stopRecording,
536+ startRecording: startRecording
540+function executeCallbacks(callbacks) {
541+ callbacks.forEach(function(timer) {
542+ if (!timer.executed) {
543+ var func = timer.func;
544+ timer.executed = true;
550+function createCanvas() {
551+ var c = document.createElement('canvas');
552+ c.width = window.innerWidth;
553+ c.height = window.innerHeight;
554+ c.style.position = "absolute";
557+ c.style.pointerEvents = "none";
561+function assert(bool, msg) {
563+ throw "Assertion error: " + msg;
567+function snapshotSignalGraph(signalGraphNodes) {
568+ var nodeValues = [];
570+ signalGraphNodes.forEach(function(node) {
571+ nodeValues.push({ value: node.value, id: node.id });
577+function restoreSnapshot(signalGraphNodes, snapshot) {
578+ assert(signalGraphNodes.length == snapshot.length,
579+ "saved program state has wrong length");
580+ for (var i=0; i < signalGraphNodes.length; i++) {
581+ var node = signalGraphNodes[i];
582+ var state = snapshot[i];
583+ assert(node.id == state.id, "the nodes moved position");
585+ node.value = state.value;
589+function flattenSignalGraph(nodes) {
592+ function addAllToDict(node) {
593+ nodesById[node.id] = node;
594+ node.kids.forEach(addAllToDict);
596+ nodes.forEach(addAllToDict);
598+ var allNodes = Object.keys(nodesById).sort().map(function(key) {
599+ return nodesById[key];