@@ -0,0 +1,457 @@
6+Elm.debuggerAttach = function(module, hotSwapState /* =undefined */) {
8+ make: function(runtime) {
9+ var wrappedModule = debugModule(module, runtime);
10+ Elm.Debugger = debuggerInit(wrappedModule, runtime, hotSwapState);
11+ dispatchElmDebuggerInit();
12+ return wrappedModule.moduleInstance;
17+function dispatchElmDebuggerInit() {
19+ var dispatch = function(_) {
20+ parent.window.postMessage("elmDebuggerInit", window.location.origin);
22+ if (parent.window.document.readyState === "complete") {
26+ parent.window.addEventListener("load", dispatch);
31+function debugModule(module, runtime) {
32+ var programPaused = false;
33+ var recordedEvents = [];
34+ var asyncCallbacks = [];
36+ function wrapNotify(id, v) {
37+ var timestep = Date.now();
40+ // ignore async events generated while playing back
41+ // or user events while program is paused
45+ var changed = runtime.notify(id, v, timestep);
46+ recordEvent(id, v, timestep);
48+ parent.window.postMessage("elmNotify", window.location.origin);
54+ function wrapRunDelayed(func, delayMs) {
55+ var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
56+ var timerId = setTimeout(function() {
57+ cbObj.executed = true;
60+ cbObj.timerId = timerId;
61+ asyncCallbacks.push(cbObj);
65+ function recordEvent(id, v, timestep) {
66+ recordedEvents.push({ id:id, value:v, timestep:timestep });
69+ function clearAsyncCallbacks() {
70+ asyncCallbacks.forEach(function(timer) {
71+ if (!timer.executed) {
72+ clearTimeout(timer.timerId);
77+ function clearRecordedEvents() {
81+ function getRecordedEventsLength() {
82+ return recordedEvents.length;
85+ function getRecordedEventAt(i) {
86+ return recordedEvents[i];
89+ function copyRecordedEvents() {
90+ return recordedEvents.slice();
93+ function loadRecordedEvents(events) {
94+ recordedEvents = events.slice();
97+ function setPaused(v) {
100+ clearAsyncCallbacks();
101+ tracePath.stopRecording();
104+ // executeCallbacks should only be called when continuing, not when restarting.
105+ //executeCallbacks(asyncCallbacks, false);
106+ tracePath.startRecording();
110+ function getPaused() {
111+ return programPaused;
114+ // runtime is the prototype of wrappedRuntime
115+ // so we can access all runtime properties too
116+ var wrappedRuntime = Object.create(runtime);
117+ wrappedRuntime.notify = wrapNotify;
118+ wrappedRuntime.runDelayed = wrapRunDelayed;
120+ var assignedPropTracker = Object.create(wrappedRuntime);
121+ var moduleInstance = module.make(assignedPropTracker);
122+ // The main module stores imported modules onto the runtime.
123+ // To ensure only one instance of each module is created,
124+ // we assign them back on the original runtime object.
125+ Object.keys(assignedPropTracker).forEach(function(key) {
126+ runtime[key] = assignedPropTracker[key];
129+ var moduleNodes = flattenNodes(wrappedRuntime.inputs);
130+ var tracePath = tracePathInit(runtime, moduleInstance.main);
133+ moduleInstance: moduleInstance,
134+ moduleNodes: moduleNodes,
135+ initialNodeValues: saveNodeValues(moduleNodes),
136+ initialAsyncCallbacks: asyncCallbacks.slice(),
138+ clearAsyncCallbacks: clearAsyncCallbacks,
139+ clearRecordedEvents: clearRecordedEvents,
140+ getRecordedEventsLength: getRecordedEventsLength,
141+ getRecordedEventAt: getRecordedEventAt,
142+ copyRecordedEvents: copyRecordedEvents,
143+ loadRecordedEvents: loadRecordedEvents,
144+ getPaused: getPaused,
145+ setPaused: setPaused,
146+ tracePath: tracePath
150+function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
151+ var eventCounter = 0;
153+ function resetProgram() {
154+ debugModule.clearAsyncCallbacks();
155+ restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
159+ function restartProgram() {
161+ debugModule.tracePath.clearTraces();
162+ debugModule.clearRecordedEvents();
163+ debugModule.setPaused(false);
164+ executeCallbacks(debugModule.initialAsyncCallbacks, true);
167+ function pauseProgram() {
168+ debugModule.setPaused(true);
169+ eventCounter = debugModule.getRecordedEventsLength();
172+ function continueProgram() {
173+ if (debugModule.getPaused())
175+ debugModule.setPaused(false);
179+ function stepTo(index) {
180+ if (!debugModule.getPaused()) {
181+ debugModule.setPaused(true);
185+ if (index < 0 || index > getMaxSteps()) {
186+ throw "Index out of bounds: " + index;
189+ if (index < eventCounter) {
194+ assert(index >= eventCounter, "index must be bad");
195+ while (eventCounter < index) {
196+ var nextEvent = debugModule.getRecordedEventAt(eventCounter);
197+ runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
201+ assert(eventCounter == index, "while loop didn't work");
204+ function getMaxSteps() {
205+ return debugModule.getRecordedEventsLength();
208+ function doPlayback(eventList) {
209+ var x = eventList.shift();
211+ runtime.notify(x[0], x[1], x[2]);
213+ if (eventList.length > 0) {
214+ var delta = eventList[0][2] - time;
215+ setTimeout(function() { doPlayback(eventList); }, delta);
219+ function redrawGraphics() {
220+ var graphicsNode = debugModule.moduleInstance.main.kids[0];
221+ graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
224+ function getHotSwapState() {
225+ var counter = eventCounter;
226+ if (!debugModule.getPaused()) {
227+ counter = getMaxSteps();
230+ recordedEvents: debugModule.copyRecordedEvents(),
231+ eventCounter: counter
235+ function dispose() {
236+ var parentNode = runtime.node.parentNode;
237+ parentNode.removeChild(debugModule.tracePath.canvas);
238+ parentNode.removeChild(runtime.node);
242+ debugModule.setPaused(true);
243+ debugModule.loadRecordedEvents(hotSwapState.recordedEvents);
245+ // draw new trace path
246+ debugModule.tracePath.startRecording();
247+ stepTo(getMaxSteps());
248+ debugModule.tracePath.stopRecording();
250+ stepTo(hotSwapState.eventCounter);
253+ runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
256+ restart: restartProgram,
257+ pause: pauseProgram,
258+ kontinue: continueProgram,
259+ getMaxSteps: getMaxSteps,
261+ getPaused: debugModule.getPaused,
262+ getHotSwapState: getHotSwapState,
264+ allNodes: debugModule.moduleNodes,
265+ mainNode: debugModule.moduleInstance.main
271+function Point(x, y) {
275+ this.translate = function(x, y) {
276+ return new Point(this.x + x, this.y + y);
279+ this.equals = function(p) {
280+ return this.x == p.x && this.y == p.y;
284+function tracePathInit(runtime, mainNode) {
285+ var List = Elm.List.make(runtime);
286+ var Signal = Elm.Signal.make(runtime);
287+ var tracePathNode = A2(Signal.lift, graphicsUpdate, mainNode);
288+ var tracePathCanvas = createCanvas();
289+ var tracePositions = {};
290+ var recordingTraces = true;
292+ function findPositions(currentScene) {
294+ function processElement(elem, offset) {
295+ if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
297+ List.map(F2(processForm)(offset))(elem.element.model.forms);
299+ else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
301+ positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
305+ function processForm(offset, form) {
306+ if (form.form.ctor == "FElement")
308+ processElement(form.form._0, offset.translate(form.x, -form.y));
312+ processElement(currentScene, new Point(0, 0));
316+ function appendPositions(positions) {
317+ for (var id in positions) {
318+ var pos = positions[id];
319+ if (tracePositions.hasOwnProperty(id)) {
320+ var points = tracePositions[id];
321+ if (!pos.equals(points[points.length-1])) {
326+ tracePositions[id] = [pos];
331+ function graphicsUpdate(currentScene) {
332+ if (!recordingTraces) {
336+ var ctx = tracePathCanvas.getContext('2d');
337+ ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
340+ ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
341+ appendPositions(findPositions(currentScene));
342+ for (var id in tracePositions)
345+ var points = tracePositions[id];
346+ for (var i=0; i < points.length; i++)
350+ ctx.moveTo(p.x, p.y);
353+ ctx.lineTo(p.x, p.y);
357+ ctx.strokeStyle = "#888a85";
360+ for (var i=0; i < points.length; i++)
364+ ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
365+ ctx.fillStyle = "#2e3436";
373+ function clearTraces() {
374+ tracePositions = {};
377+ function stopRecording() {
378+ recordingTraces = false;
381+ function startRecording() {
382+ recordingTraces = true;
386+ graphicsUpdate: graphicsUpdate,
387+ canvas: tracePathCanvas,
388+ clearTraces: clearTraces,
389+ stopRecording: stopRecording,
390+ startRecording: startRecording
395+function executeCallbacks(callbacks, reexecute) {
396+ callbacks.forEach(function(timer) {
397+ if (reexecute || !timer.executed) {
398+ var func = timer.func;
404+function createCanvas() {
405+ var c = document.createElement('canvas');
406+ c.width = window.innerWidth;
407+ c.height = window.innerHeight;
408+ c.style.position = "absolute";
414+function assert(bool, msg) {
416+ throw "Assertion error: " + msg;
420+function saveNodeValues(allNodes) {
421+ var nodeValues = [];
423+ allNodes.forEach(function(node) {
424+ nodeValues.push({ value: node.value, id: node.id });
431+function restoreNodeValues(allNodes, nodeStates) {
432+ assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
433+ for (var i=0; i < allNodes.length; i++) {
434+ var node = allNodes[i];
435+ var state = nodeStates[i];
436+ assert(node.id == state.id, "the nodes moved position");
438+ node.value = state.value;
442+function flattenNodes(nodes) {
445+ function addAllToDict(node) {
446+ nodesById[node.id] = node;
447+ node.kids.forEach(addAllToDict);
449+ nodes.forEach(addAllToDict);
451+ var allNodes = Object.keys(nodesById).sort().map(function(key) {
452+ return nodesById[key];