Эх сурвалжийг харах

Re-writing ethereum.js. Added future/promises support.

obscuren 11 жил өмнө
parent
commit
8585e59718

+ 425 - 423
mist/assets/debugger/debugger.qml

@@ -7,427 +7,429 @@ import QtQuick.Controls.Styles 1.1
 import Ethereum 1.0
 import Ethereum 1.0
 
 
 ApplicationWindow {
 ApplicationWindow {
-	id: win
-	visible: false
-	title: "IceCREAM"
-	minimumWidth: 1280
-	minimumHeight: 700
-	width: 1290
-	height: 750
-
-	property alias codeText: codeEditor.text
-	property alias dataText: rawDataField.text
-
-	onClosing: {
-		//compileTimer.stop()
-	}
-
-	MenuBar {
-		Menu {
-			title: "Debugger"
-			MenuItem {
-				text: "Run"
-				shortcut: "Ctrl+r"
-				onTriggered: debugCurrent()
-			}
-
-			MenuItem {
-				text: "Next"
-				shortcut: "Ctrl+n"
-				onTriggered: dbg.next()
-			}
-
-			MenuItem {
-				text: "Continue"
-				shortcut: "Ctrl+g"
-				onTriggered: dbg.continue()
-			}
-			MenuItem {
-				text: "Command"
-				shortcut: "Ctrl+l"
-				onTriggered: {
-					dbgCommand.focus = true
-				}
-			}
-			MenuItem {
-				text: "Focus code"
-				shortcut: "Ctrl+1"
-				onTriggered: {
-					codeEditor.focus = true
-				}
-			}
-			MenuItem {
-				text: "Focus data"
-				shortcut: "Ctrl+2"
-				onTriggered: {
-					rawDataField.focus = true
-				}
-			}
-
-			/*
-			MenuItem {
-				text: "Close window"
-				shortcut: "Ctrl+w"
-				onTriggered: {
-					win.close()
-				}
-			}
-			*/
-		}
-	}
-
-
-	SplitView {
-		anchors.fill: parent
-		property var asmModel: ListModel {
-			id: asmModel
-		}
-
-		TableView {
-			id: asmTableView
-			width: 200
-			headerVisible: false
-			TableViewColumn{ role: "value" ; title: "" ; width: asmTableView.width - 2 }
-			model: asmModel
-			/*
-			alternatingRowColors: false
-			itemDelegate: Item {
-				Rectangle {
-					anchors.fill: parent
-					color: "#DDD"
-					Text {
-						anchors {
-							left: parent.left
-							right: parent.right
-							leftMargin: 10
-							verticalCenter: parent.verticalCenter
-						}
-						color: "#333"
-						elide: styleData.elideMode
-						text: styleData.value
-						font.pixelSize: 11
-						MouseArea {
-							acceptedButtons: Qt.LeftButton
-							anchors.fill: parent
-							onClicked: {
-								mouse.accepted = true
-							}
-						}
-					}
-				}
-			}
-			*/
-		}
-
-		Rectangle {
-			color: "#00000000"
-			anchors.left: asmTableView.right
-			anchors.right: parent.right
-			SplitView {
-				orientation: Qt.Vertical
-				anchors.fill: parent
-
-				Rectangle {
-					color: "#00000000"
-					height: 330
-					anchors.left: parent.left
-					anchors.right: parent.right
-
-					TextArea {
-						id: codeEditor
-						anchors.top: parent.top
-						anchors.bottom: parent.bottom
-						anchors.left: parent.left
-						anchors.right: settings.left
-						focus: true
-
-						/*
-						Timer {
-							id: compileTimer
-							interval: 500 ; running: true ;  repeat: true
-							onTriggered: {
-								dbg.autoComp(codeEditor.text)
-							}
-						}
-						*/
-					}
-
-					Column {
-						id: settings
-						spacing: 5
-						width: 300
-						height: parent.height
-						anchors.right: parent.right
-						anchors.top: parent.top
-						anchors.bottom: parent.bottom
-
-						Label {
-							text: "Arbitrary data"
-						}
-						TextArea {
-							id: rawDataField
-							anchors.left: parent.left
-							anchors.right: parent.right
-							height: 150
-						}
-
-						Label {
-							text: "Amount"
-						}
-						TextField {
-							id: txValue
-							width: 200
-							placeholderText: "Amount"
-							validator: RegExpValidator { regExp: /\d*/ }
-						}
-						Label {
-							text: "Amount of gas"
-						}
-						TextField {
-							id: txGas
-							width: 200
-							validator: RegExpValidator { regExp: /\d*/ }
-							text: "10000"
-							placeholderText: "Gas"
-						}
-						Label {
-							text: "Gas price"
-						}
-						TextField {
-							id: txGasPrice
-							width: 200
-							placeholderText: "Gas price"
-							text: "1000000000000"
-							validator: RegExpValidator { regExp: /\d*/ }
-						}
-					}
-				}
-
-				SplitView {
-					orientation: Qt.Vertical
-					id: inspectorPane
-					height: 500
-
-					SplitView {
-						orientation: Qt.Horizontal
-						height: 150
-
-						TableView {
-							id: stackTableView
-							property var stackModel: ListModel {
-								id: stackModel
-							}
-							height: parent.height
-							width: 300
-							TableViewColumn{ role: "value" ; title: "Temp" ; width: 200 }
-							model: stackModel
-						}
-
-						TableView {
-							id: memoryTableView
-							property var memModel: ListModel {
-								id: memModel
-							}
-							height: parent.height
-							width: parent.width - stackTableView.width
-							TableViewColumn{ id:mnumColmn ; role: "num" ; title: "#" ; width: 50 }
-							TableViewColumn{ role: "value" ; title: "Memory" ; width: 750 }
-							model: memModel
-						}
-					}
-
-					Rectangle {
-						height: 100
-						width: parent.width
-						TableView {
-							id: storageTableView
-							property var memModel: ListModel {
-								id: storageModel
-							}
-							height: parent.height
-							width: parent.width
-							TableViewColumn{ id: key ; role: "key" ; title: "#" ; width: storageTableView.width / 2}
-							TableViewColumn{ role: "value" ; title: "Storage" ; width:  storageTableView.width / 2}
-							model: storageModel
-						}
-					}
-
-					Rectangle {
-						height: 200
-						width: parent.width * 0.66
-						TableView {
-							id: logTableView
-							property var logModel: ListModel {
-								id: logModel
-							}
-							height: parent.height
-							width: parent.width
-							TableViewColumn{ id: message ; role: "message" ; title: "log" ; width: logTableView.width - 2 }
-							model: logModel
-						}
-					}
-
-				}
-			}
-		}
-	}
-
-		function exec() {
-			dbg.execCommand(dbgCommand.text);
-			dbgCommand.text = "";
-		}
-	statusBar: StatusBar {
-		height: 30
-
-
-		TextField {
-			id: dbgCommand
-			y: 1
-			x: asmTableView.width
-			width: 500
-			placeholderText: "Debugger (type 'help')"
-			Keys.onReturnPressed: {
-				exec()
-			}
-		}
-
-		RowLayout {
-			anchors.left: dbgCommand.right
-			anchors.leftMargin: 10
-			spacing: 5
-			y: parent.height / 2 - this.height / 2
-
-			Text {
-				objectName: "stackFrame"
-				font.pixelSize: 10
-				text: "<b>stack ptr</b>: 0"
-			}
-
-			Text {
-				objectName: "stackSize"
-				font.pixelSize: 10
-				text: "<b>stack size</b>: 0"
-			}
-
-			Text {
-				objectName: "memSize"
-				font.pixelSize: 10
-				text: "<b>mem size</b>: 0"
-			}
-		}
-	}
-
-	toolBar: ToolBar {
-		height: 30
-		RowLayout {
-			spacing: 10
-
-			Button {
-				property var enabled: true
-				id: debugStart
-				onClicked: {
-					debugCurrent()
-				}
-				text: "Debug"
-			}
-
-			Button {
-				property var enabled: true
-				id: debugNextButton
-				onClicked: {
-					dbg.next()
-				}
-				text: "Next"
-			}
-
-			Button {
-				id: debugContinueButton
-				onClicked: {
-					dbg.continue()
-				}
-				text: "Continue"
-			}
-		}
-
-
-		ComboBox {
-			id: snippets
-			anchors.right: parent.right
-			model: ListModel {
-				ListElement { text: "Snippets" ; value: "" }
-				ListElement { text: "Call Contract" ; value: "var[2] in;\nvar ret;\n\nin[0] = \"arg1\"\nin[1] = 0xdeadbeef\n\nvar success = call(0x0c542ddea93dae0c2fcb2cf175f03ad80d6be9a0, 0, 7000, in, ret)\n\nreturn ret" }
-			}
-			onCurrentIndexChanged: {
-				if(currentIndex != 0) {
-					var code = snippets.model.get(currentIndex).value;
-					codeEditor.insert(codeEditor.cursorPosition, code)
-				}
-			}
-		}
-
-	}
-
-	function debugCurrent() {
-		dbg.debug(txValue.text, txGas.text, txGasPrice.text, codeEditor.text, rawDataField.text)
-	}
-
-	function setAsm(asm) {
-		asmModel.append({asm: asm})
-	}
-
-	function clearAsm() {
-		asmModel.clear()
-	}
-
-	function setInstruction(num) {
-		asmTableView.selection.clear()
-		asmTableView.selection.select(num)
-		asmTableView.positionViewAtRow(num, ListView.Center)
-	}
-
-	function setMem(mem) {
-		memModel.append({num: mem.num, value: mem.value})
-	}
-	function clearMem(){
-		memModel.clear()
-	}
-
-	function setStack(stack) {
-		stackModel.append({value: stack})
-	}
-	function addDebugMessage(message){
-		debuggerLog.append({value: message})
-	}
-
-	function clearStack() {
-		stackModel.clear()
-	}
-
-	function clearStorage() {
-		storageModel.clear()
-	}
-
-	function setStorage(storage) {
-		storageModel.append({key: storage.key, value: storage.value})
-	}
-
-	function setLog(msg) {
-		// Remove first item once we've reached max log items
-		if(logModel.count > 250) {
-			logModel.remove(0)
-		}
-
-		if(msg.len != 0) {
-			if(logTableView.flickableItem.atYEnd) {
-				logModel.append({message: msg})
-				logTableView.positionViewAtRow(logTableView.rowCount - 1, ListView.Contain)
-			} else {
-				logModel.append({message: msg})
-			}
-		}
-	}
-
-	function clearLog() {
-		logModel.clear()
-	}
+    id: win
+    visible: false
+    title: "IceCREAM"
+    minimumWidth: 1280
+    minimumHeight: 700
+    width: 1290
+    height: 750
+
+    property alias codeText: codeEditor.text
+    property alias dataText: rawDataField.text
+
+    onClosing: {
+        dbg.Stop()
+    }
+
+    menuBar: MenuBar {
+        Menu {
+            title: "Edit"
+            MenuItem {
+                text: "Focus code"
+                shortcut: "Ctrl+1"
+                onTriggered: {
+                    codeEditor.focus = true
+                }
+            }
+            MenuItem {
+                text: "Focus data"
+                shortcut: "Ctrl+2"
+                onTriggered: {
+                    rawDataField.focus = true
+                }
+            }
+
+            MenuItem {
+                text: "Command"
+                shortcut: "Ctrl+l"
+                onTriggered: {
+                    dbgCommand.focus = true
+                }
+            }
+        }
+
+        Menu {
+            title: "Debugger"
+            MenuItem {
+                text: "Run"
+                shortcut: "Ctrl+r"
+                onTriggered: debugCurrent()
+            }
+
+            MenuItem {
+                text: "Stop"
+                onTriggered: dbp.stop()
+            }
+
+            MenuSeparator {}
+
+            MenuItem {
+                text: "Next"
+                shortcut: "Ctrl+n"
+                onTriggered: dbg.next()
+            }
+
+            MenuItem {
+                text: "Continue"
+                shortcut: "Ctrl+g"
+                onTriggered: dbg.continue()
+            }
+        }
+    }
+
+
+    SplitView {
+        anchors.fill: parent
+        property var asmModel: ListModel {
+            id: asmModel
+        }
+
+        TableView {
+            id: asmTableView
+            width: 200
+            headerVisible: false
+            TableViewColumn{ role: "value" ; title: "" ; width: asmTableView.width - 2 }
+            model: asmModel
+            /*
+             alternatingRowColors: false
+             itemDelegate: Item {
+                 Rectangle {
+                     anchors.fill: parent
+                     color: "#DDD"
+                     Text {
+                         anchors {
+                             left: parent.left
+                             right: parent.right
+                             leftMargin: 10
+                             verticalCenter: parent.verticalCenter
+                         }
+                         color: "#333"
+                         elide: styleData.elideMode
+                         text: styleData.value
+                         font.pixelSize: 11
+                         MouseArea {
+                             acceptedButtons: Qt.LeftButton
+                             anchors.fill: parent
+                             onClicked: {
+                                 mouse.accepted = true
+                             }
+                         }
+                     }
+                 }
+             }
+             */
+        }
+
+        Rectangle {
+            color: "#00000000"
+            anchors.left: asmTableView.right
+            anchors.right: parent.right
+            SplitView {
+                orientation: Qt.Vertical
+                anchors.fill: parent
+
+                Rectangle {
+                    color: "#00000000"
+                    height: 330
+                    anchors.left: parent.left
+                    anchors.right: parent.right
+
+                    TextArea {
+                        id: codeEditor
+                        anchors.top: parent.top
+                        anchors.bottom: parent.bottom
+                        anchors.left: parent.left
+                        anchors.right: settings.left
+                        focus: true
+
+                        /*
+                         Timer {
+                             id: compileTimer
+                             interval: 500 ; running: true ;  repeat: true
+                             onTriggered: {
+                                 dbg.autoComp(codeEditor.text)
+                             }
+                         }
+                         */
+                    }
+
+                    Column {
+                        id: settings
+                        spacing: 5
+                        width: 300
+                        height: parent.height
+                        anchors.right: parent.right
+                        anchors.top: parent.top
+                        anchors.bottom: parent.bottom
+
+                        Label {
+                            text: "Arbitrary data"
+                        }
+                        TextArea {
+                            id: rawDataField
+                            anchors.left: parent.left
+                            anchors.right: parent.right
+                            height: 150
+                        }
+
+                        Label {
+                            text: "Amount"
+                        }
+                        TextField {
+                            id: txValue
+                            width: 200
+                            placeholderText: "Amount"
+                            validator: RegExpValidator { regExp: /\d*/ }
+                        }
+                        Label {
+                            text: "Amount of gas"
+                        }
+                        TextField {
+                            id: txGas
+                            width: 200
+                            validator: RegExpValidator { regExp: /\d*/ }
+                            text: "10000"
+                            placeholderText: "Gas"
+                        }
+                        Label {
+                            text: "Gas price"
+                        }
+                        TextField {
+                            id: txGasPrice
+                            width: 200
+                            placeholderText: "Gas price"
+                            text: "1000000000000"
+                            validator: RegExpValidator { regExp: /\d*/ }
+                        }
+                    }
+                }
+
+                SplitView {
+                    orientation: Qt.Vertical
+                    id: inspectorPane
+                    height: 500
+
+                    SplitView {
+                        orientation: Qt.Horizontal
+                        height: 150
+
+                        TableView {
+                            id: stackTableView
+                            property var stackModel: ListModel {
+                                id: stackModel
+                            }
+                            height: parent.height
+                            width: 300
+                            TableViewColumn{ role: "value" ; title: "Temp" ; width: 200 }
+                            model: stackModel
+                        }
+
+                        TableView {
+                            id: memoryTableView
+                            property var memModel: ListModel {
+                                id: memModel
+                            }
+                            height: parent.height
+                            width: parent.width - stackTableView.width
+                            TableViewColumn{ id:mnumColmn ; role: "num" ; title: "#" ; width: 50 }
+                            TableViewColumn{ role: "value" ; title: "Memory" ; width: 750 }
+                            model: memModel
+                        }
+                    }
+
+                    Rectangle {
+                        height: 100
+                        width: parent.width
+                        TableView {
+                            id: storageTableView
+                            property var memModel: ListModel {
+                                id: storageModel
+                            }
+                            height: parent.height
+                            width: parent.width
+                            TableViewColumn{ id: key ; role: "key" ; title: "#" ; width: storageTableView.width / 2}
+                            TableViewColumn{ role: "value" ; title: "Storage" ; width:  storageTableView.width / 2}
+                            model: storageModel
+                        }
+                    }
+
+                    Rectangle {
+                        height: 200
+                        width: parent.width * 0.66
+                        TableView {
+                            id: logTableView
+                            property var logModel: ListModel {
+                                id: logModel
+                            }
+                            height: parent.height
+                            width: parent.width
+                            TableViewColumn{ id: message ; role: "message" ; title: "log" ; width: logTableView.width - 2 }
+                            model: logModel
+                        }
+                    }
+
+                }
+            }
+        }
+    }
+
+    function exec() {
+        dbg.execCommand(dbgCommand.text);
+        dbgCommand.text = "";
+    }
+    statusBar: StatusBar {
+        height: 30
+
+
+        TextField {
+            id: dbgCommand
+            y: 1
+            x: asmTableView.width
+            width: 500
+            placeholderText: "Debugger (type 'help')"
+            Keys.onReturnPressed: {
+                exec()
+            }
+        }
+
+        RowLayout {
+            anchors.left: dbgCommand.right
+            anchors.leftMargin: 10
+            spacing: 5
+            y: parent.height / 2 - this.height / 2
+
+            Text {
+                objectName: "stackFrame"
+                font.pixelSize: 10
+                text: "<b>stack ptr</b>: 0"
+            }
+
+            Text {
+                objectName: "stackSize"
+                font.pixelSize: 10
+                text: "<b>stack size</b>: 0"
+            }
+
+            Text {
+                objectName: "memSize"
+                font.pixelSize: 10
+                text: "<b>mem size</b>: 0"
+            }
+        }
+    }
+
+    toolBar: ToolBar {
+        height: 30
+        RowLayout {
+            spacing: 10
+
+            Button {
+                property var enabled: true
+                id: debugStart
+                onClicked: {
+                    debugCurrent()
+                }
+                text: "Debug"
+            }
+
+            Button {
+                property var enabled: true
+                id: debugNextButton
+                onClicked: {
+                    dbg.next()
+                }
+                text: "Next"
+            }
+
+            Button {
+                id: debugContinueButton
+                onClicked: {
+                    dbg.continue()
+                }
+                text: "Continue"
+            }
+        }
+
+
+        ComboBox {
+            id: snippets
+            anchors.right: parent.right
+            model: ListModel {
+                ListElement { text: "Snippets" ; value: "" }
+                ListElement { text: "Call Contract" ; value: "var[2] in;\nvar ret;\n\nin[0] = \"arg1\"\nin[1] = 0xdeadbeef\n\nvar success = call(0x0c542ddea93dae0c2fcb2cf175f03ad80d6be9a0, 0, 7000, in, ret)\n\nreturn ret" }
+            }
+            onCurrentIndexChanged: {
+                if(currentIndex != 0) {
+                    var code = snippets.model.get(currentIndex).value;
+                    codeEditor.insert(codeEditor.cursorPosition, code)
+                }
+            }
+        }
+
+    }
+
+    function debugCurrent() {
+        dbg.debug(txValue.text, txGas.text, txGasPrice.text, codeEditor.text, rawDataField.text)
+    }
+
+    function setAsm(asm) {
+        asmModel.append({asm: asm})
+    }
+
+    function clearAsm() {
+        asmModel.clear()
+    }
+
+    function setInstruction(num) {
+        asmTableView.selection.clear()
+        asmTableView.selection.select(num)
+        asmTableView.positionViewAtRow(num, ListView.Center)
+    }
+
+    function setMem(mem) {
+        memModel.append({num: mem.num, value: mem.value})
+    }
+    function clearMem(){
+        memModel.clear()
+    }
+
+    function setStack(stack) {
+        stackModel.append({value: stack})
+    }
+    function addDebugMessage(message){
+        debuggerLog.append({value: message})
+    }
+
+    function clearStack() {
+        stackModel.clear()
+    }
+
+    function clearStorage() {
+        storageModel.clear()
+    }
+
+    function setStorage(storage) {
+        storageModel.append({key: storage.key, value: storage.value})
+    }
+
+    function setLog(msg) {
+        // Remove first item once we've reached max log items
+        if(logModel.count > 250) {
+            logModel.remove(0)
+        }
+
+        if(msg.len != 0) {
+            if(logTableView.flickableItem.atYEnd) {
+                logModel.append({message: msg})
+                logTableView.positionViewAtRow(logTableView.rowCount - 1, ListView.Contain)
+            } else {
+                logModel.append({message: msg})
+            }
+        }
+    }
+
+    function clearLog() {
+        logModel.clear()
+    }
 }
 }

+ 149 - 0
mist/assets/ext/html_messaging.js

@@ -0,0 +1,149 @@
+// The magic return variable. The magic return variable will be set during the execution of the QML call.
+(function(window) {
+	function message(type, data) {
+		document.title = JSON.stringify({type: type, data: data});
+
+		return window.____returnData;
+	}
+
+	function isPromise(o) {
+		return typeof o === "object" && o.then
+	}
+
+	window.eth = {
+		_callbacks: {},
+		_onCallbacks: {},
+		prototype: Object(),
+
+		coinbase: function() {
+			return new Promise(function(resolve, reject) {
+				postData({call: "getCoinBase"}, function(coinbase) {
+					resolve(coinbase);
+				});
+			});
+		},
+
+		block: function(numberOrHash) {
+			return new Promise(function(resolve, reject) {
+				var func;
+				if(typeof numberOrHash == "string") {
+					func =  "getBlockByHash";
+				} else {
+					func =  "getBlockByNumber";
+				}
+
+				postData({call: func, args: [numberOrHash]}, function(block) {
+					if(block)
+						resolve(block);
+					else
+						reject("not found");
+
+				});
+			});
+		},
+
+		transact: function(params) {
+			if(params === undefined) {
+				params = {};
+			}
+
+			if(params.endowment !== undefined)
+				params.value = params.endowment;
+			if(params.code !== undefined)
+				params.data = params.code;
+
+
+			var promises = []
+			if(isPromise(params.to)) {
+				promises.push(params.to.then(function(_to) { params.to = _to; }));
+			}
+			if(isPromise(params.from)) {
+				promises.push(params.from.then(function(_from) { params.from = _from; }));
+			}
+
+			if(isPromise(params.data)) {
+				promises.push(params.data.then(function(_code) { params.data = _code; }));
+			} else {
+				if(typeof params.data === "object") {
+					data = "";
+					for(var i = 0; i < params.data.length; i++) {
+						data += params.data[i]
+					}
+				} else {
+					data = params.data;
+				}
+			}
+
+			// Make sure everything is string
+			var fields = ["value", "gas", "gasPrice"];
+			for(var i = 0; i < fields.length; i++) {
+				if(params[fields[i]] === undefined) {
+					params[fields[i]] = "";
+				}
+				params[fields[i]] = params[fields[i]].toString();
+			}
+
+			// Load promises then call the last "transact".
+			return Q.all(promises).then(function() {
+				return new Promise(function(resolve, reject) {
+					postData({call: "transact", args: params}, function(data) {
+						if(data[1])
+							reject(data[0]);
+						else
+							resolve(data[0]);
+					});
+				});
+			})
+		},
+
+		compile: function(code) {
+			return new Promise(function(resolve, reject) {
+				postData({call: "compile", args: [code]}, function(data) {
+					if(data[1])
+						reject(data[0]);
+					else
+						resolve(data[0]);
+				});
+			});
+		},
+
+		key: function() {
+			return new Promise(function(resolve, reject) {
+				postData({call: "getKey"}, function(k) {
+					resolve(k);
+				});
+			});
+		}
+	};
+
+	function postData(data, cb) {
+		data._seed = Math.floor(Math.random() * 1000000)
+		if(cb) {
+			eth._callbacks[data._seed] = cb;
+		}
+
+		if(data.args === undefined) {
+			data.args = [];
+		}
+
+		navigator.qt.postMessage(JSON.stringify(data));
+	}
+
+	navigator.qt.onmessage = function(ev) {
+		var data = JSON.parse(ev.data)
+
+		if(data._event !== undefined) {
+			eth.trigger(data._event, data.data);
+		} else {
+			if(data._seed) {
+				var cb = eth._callbacks[data._seed];
+				if(cb) {
+					cb.call(this, data.data)
+
+					// Remove the "trigger" callback
+					delete eth._callbacks[ev._seed];
+				}
+			}
+		}
+	}
+})(this);

+ 4 - 0
mist/assets/ext/pre.js

@@ -35,3 +35,7 @@ navigator.qt.onmessage = function(ev) {
 		}
 		}
 	}
 	}
 }
 }
+
+if(typeof(Promise) === "undefined") {
+	window.Promise = Q.Promise;
+}

+ 1909 - 0
mist/assets/ext/q.js

@@ -0,0 +1,1909 @@
+// vim:ts=4:sts=4:sw=4:
+/*!
+ *
+ * Copyright 2009-2012 Kris Kowal under the terms of the MIT
+ * license found at http://github.com/kriskowal/q/raw/master/LICENSE
+ *
+ * With parts by Tyler Close
+ * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
+ * at http://www.opensource.org/licenses/mit-license.html
+ * Forked at ref_send.js version: 2009-05-11
+ *
+ * With parts by Mark Miller
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+(function (definition) {
+    // Turn off strict mode for this function so we can assign to global.Q
+    /* jshint strict: false */
+
+    // This file will function properly as a <script> tag, or a module
+    // using CommonJS and NodeJS or RequireJS module formats.  In
+    // Common/Node/RequireJS, the module exports the Q API and when
+    // executed as a simple <script>, it creates a Q global instead.
+
+    // Montage Require
+    if (typeof bootstrap === "function") {
+        bootstrap("promise", definition);
+
+    // CommonJS
+    } else if (typeof exports === "object" && typeof module === "object") {
+        module.exports = definition();
+
+    // RequireJS
+    } else if (typeof define === "function" && define.amd) {
+        define(definition);
+
+    // SES (Secure EcmaScript)
+    } else if (typeof ses !== "undefined") {
+        if (!ses.ok()) {
+            return;
+        } else {
+            ses.makeQ = definition;
+        }
+
+    // <script>
+    } else {
+        Q = definition();
+    }
+
+})(function () {
+"use strict";
+
+var hasStacks = false;
+try {
+    throw new Error();
+} catch (e) {
+    hasStacks = !!e.stack;
+}
+
+// All code after this point will be filtered from stack traces reported
+// by Q.
+var qStartingLine = captureLine();
+var qFileName;
+
+// shims
+
+// used for fallback in "allResolved"
+var noop = function () {};
+
+// Use the fastest possible means to execute a task in a future turn
+// of the event loop.
+var nextTick =(function () {
+    // linked list of tasks (single, with head node)
+    var head = {task: void 0, next: null};
+    var tail = head;
+    var flushing = false;
+    var requestTick = void 0;
+    var isNodeJS = false;
+
+    function flush() {
+        /* jshint loopfunc: true */
+
+        while (head.next) {
+            head = head.next;
+            var task = head.task;
+            head.task = void 0;
+            var domain = head.domain;
+
+            if (domain) {
+                head.domain = void 0;
+                domain.enter();
+            }
+
+            try {
+                task();
+
+            } catch (e) {
+                if (isNodeJS) {
+                    // In node, uncaught exceptions are considered fatal errors.
+                    // Re-throw them synchronously to interrupt flushing!
+
+                    // Ensure continuation if the uncaught exception is suppressed
+                    // listening "uncaughtException" events (as domains does).
+                    // Continue in next event to avoid tick recursion.
+                    if (domain) {
+                        domain.exit();
+                    }
+                    setTimeout(flush, 0);
+                    if (domain) {
+                        domain.enter();
+                    }
+
+                    throw e;
+
+                } else {
+                    // In browsers, uncaught exceptions are not fatal.
+                    // Re-throw them asynchronously to avoid slow-downs.
+                    setTimeout(function() {
+                       throw e;
+                    }, 0);
+                }
+            }
+
+            if (domain) {
+                domain.exit();
+            }
+        }
+
+        flushing = false;
+    }
+
+    nextTick = function (task) {
+        tail = tail.next = {
+            task: task,
+            domain: isNodeJS && process.domain,
+            next: null
+        };
+
+        if (!flushing) {
+            flushing = true;
+            requestTick();
+        }
+    };
+
+    if (typeof process !== "undefined" && process.nextTick) {
+        // Node.js before 0.9. Note that some fake-Node environments, like the
+        // Mocha test runner, introduce a `process` global without a `nextTick`.
+        isNodeJS = true;
+
+        requestTick = function () {
+            process.nextTick(flush);
+        };
+
+    } else if (typeof setImmediate === "function") {
+        // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate
+        if (typeof window !== "undefined") {
+            requestTick = setImmediate.bind(window, flush);
+        } else {
+            requestTick = function () {
+                setImmediate(flush);
+            };
+        }
+
+    } else if (typeof MessageChannel !== "undefined") {
+        // modern browsers
+        // http://www.nonblocking.io/2011/06/windownexttick.html
+        var channel = new MessageChannel();
+        // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create
+        // working message ports the first time a page loads.
+        channel.port1.onmessage = function () {
+            requestTick = requestPortTick;
+            channel.port1.onmessage = flush;
+            flush();
+        };
+        var requestPortTick = function () {
+            // Opera requires us to provide a message payload, regardless of
+            // whether we use it.
+            channel.port2.postMessage(0);
+        };
+        requestTick = function () {
+            setTimeout(flush, 0);
+            requestPortTick();
+        };
+
+    } else {
+        // old browsers
+        requestTick = function () {
+            setTimeout(flush, 0);
+        };
+    }
+
+    return nextTick;
+})();
+
+// Attempt to make generics safe in the face of downstream
+// modifications.
+// There is no situation where this is necessary.
+// If you need a security guarantee, these primordials need to be
+// deeply frozen anyway, and if you don’t need a security guarantee,
+// this is just plain paranoid.
+// However, this **might** have the nice side-effect of reducing the size of
+// the minified code by reducing x.call() to merely x()
+// See Mark Miller’s explanation of what this does.
+// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
+var call = Function.call;
+function uncurryThis(f) {
+    return function () {
+        return call.apply(f, arguments);
+    };
+}
+// This is equivalent, but slower:
+// uncurryThis = Function_bind.bind(Function_bind.call);
+// http://jsperf.com/uncurrythis
+
+var array_slice = uncurryThis(Array.prototype.slice);
+
+var array_reduce = uncurryThis(
+    Array.prototype.reduce || function (callback, basis) {
+        var index = 0,
+            length = this.length;
+        // concerning the initial value, if one is not provided
+        if (arguments.length === 1) {
+            // seek to the first value in the array, accounting
+            // for the possibility that is is a sparse array
+            do {
+                if (index in this) {
+                    basis = this[index++];
+                    break;
+                }
+                if (++index >= length) {
+                    throw new TypeError();
+                }
+            } while (1);
+        }
+        // reduce
+        for (; index < length; index++) {
+            // account for the possibility that the array is sparse
+            if (index in this) {
+                basis = callback(basis, this[index], index);
+            }
+        }
+        return basis;
+    }
+);
+
+var array_indexOf = uncurryThis(
+    Array.prototype.indexOf || function (value) {
+        // not a very good shim, but good enough for our one use of it
+        for (var i = 0; i < this.length; i++) {
+            if (this[i] === value) {
+                return i;
+            }
+        }
+        return -1;
+    }
+);
+
+var array_map = uncurryThis(
+    Array.prototype.map || function (callback, thisp) {
+        var self = this;
+        var collect = [];
+        array_reduce(self, function (undefined, value, index) {
+            collect.push(callback.call(thisp, value, index, self));
+        }, void 0);
+        return collect;
+    }
+);
+
+var object_create = Object.create || function (prototype) {
+    function Type() { }
+    Type.prototype = prototype;
+    return new Type();
+};
+
+var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
+
+var object_keys = Object.keys || function (object) {
+    var keys = [];
+    for (var key in object) {
+        if (object_hasOwnProperty(object, key)) {
+            keys.push(key);
+        }
+    }
+    return keys;
+};
+
+var object_toString = uncurryThis(Object.prototype.toString);
+
+function isObject(value) {
+    return value === Object(value);
+}
+
+// generator related shims
+
+// FIXME: Remove this function once ES6 generators are in SpiderMonkey.
+function isStopIteration(exception) {
+    return (
+        object_toString(exception) === "[object StopIteration]" ||
+        exception instanceof QReturnValue
+    );
+}
+
+// FIXME: Remove this helper and Q.return once ES6 generators are in
+// SpiderMonkey.
+var QReturnValue;
+if (typeof ReturnValue !== "undefined") {
+    QReturnValue = ReturnValue;
+} else {
+    QReturnValue = function (value) {
+        this.value = value;
+    };
+}
+
+// long stack traces
+
+var STACK_JUMP_SEPARATOR = "From previous event:";
+
+function makeStackTraceLong(error, promise) {
+    // If possible, transform the error stack trace by removing Node and Q
+    // cruft, then concatenating with the stack trace of `promise`. See #57.
+    if (hasStacks &&
+        promise.stack &&
+        typeof error === "object" &&
+        error !== null &&
+        error.stack &&
+        error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1
+    ) {
+        var stacks = [];
+        for (var p = promise; !!p; p = p.source) {
+            if (p.stack) {
+                stacks.unshift(p.stack);
+            }
+        }
+        stacks.unshift(error.stack);
+
+        var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n");
+        error.stack = filterStackString(concatedStacks);
+    }
+}
+
+function filterStackString(stackString) {
+    var lines = stackString.split("\n");
+    var desiredLines = [];
+    for (var i = 0; i < lines.length; ++i) {
+        var line = lines[i];
+
+        if (!isInternalFrame(line) && !isNodeFrame(line) && line) {
+            desiredLines.push(line);
+        }
+    }
+    return desiredLines.join("\n");
+}
+
+function isNodeFrame(stackLine) {
+    return stackLine.indexOf("(module.js:") !== -1 ||
+           stackLine.indexOf("(node.js:") !== -1;
+}
+
+function getFileNameAndLineNumber(stackLine) {
+    // Named functions: "at functionName (filename:lineNumber:columnNumber)"
+    // In IE10 function name can have spaces ("Anonymous function") O_o
+    var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine);
+    if (attempt1) {
+        return [attempt1[1], Number(attempt1[2])];
+    }
+
+    // Anonymous functions: "at filename:lineNumber:columnNumber"
+    var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine);
+    if (attempt2) {
+        return [attempt2[1], Number(attempt2[2])];
+    }
+
+    // Firefox style: "function@filename:lineNumber or @filename:lineNumber"
+    var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine);
+    if (attempt3) {
+        return [attempt3[1], Number(attempt3[2])];
+    }
+}
+
+function isInternalFrame(stackLine) {
+    var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);
+
+    if (!fileNameAndLineNumber) {
+        return false;
+    }
+
+    var fileName = fileNameAndLineNumber[0];
+    var lineNumber = fileNameAndLineNumber[1];
+
+    return fileName === qFileName &&
+        lineNumber >= qStartingLine &&
+        lineNumber <= qEndingLine;
+}
+
+// discover own file name and line number range for filtering stack
+// traces
+function captureLine() {
+    if (!hasStacks) {
+        return;
+    }
+
+    try {
+        throw new Error();
+    } catch (e) {
+        var lines = e.stack.split("\n");
+        var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2];
+        var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);
+        if (!fileNameAndLineNumber) {
+            return;
+        }
+
+        qFileName = fileNameAndLineNumber[0];
+        return fileNameAndLineNumber[1];
+    }
+}
+
+function deprecate(callback, name, alternative) {
+    return function () {
+        if (typeof console !== "undefined" &&
+            typeof console.warn === "function") {
+            console.warn(name + " is deprecated, use " + alternative +
+                         " instead.", new Error("").stack);
+        }
+        return callback.apply(callback, arguments);
+    };
+}
+
+// end of shims
+// beginning of real work
+
+/**
+ * Constructs a promise for an immediate reference, passes promises through, or
+ * coerces promises from different systems.
+ * @param value immediate reference or promise
+ */
+function Q(value) {
+    // If the object is already a Promise, return it directly.  This enables
+    // the resolve function to both be used to created references from objects,
+    // but to tolerably coerce non-promises to promises.
+    if (value instanceof Promise) {
+        return value;
+    }
+
+    // assimilate thenables
+    if (isPromiseAlike(value)) {
+        return coerce(value);
+    } else {
+        return fulfill(value);
+    }
+}
+Q.resolve = Q;
+
+/**
+ * Performs a task in a future turn of the event loop.
+ * @param {Function} task
+ */
+Q.nextTick = nextTick;
+
+/**
+ * Controls whether or not long stack traces will be on
+ */
+Q.longStackSupport = false;
+
+/**
+ * Constructs a {promise, resolve, reject} object.
+ *
+ * `resolve` is a callback to invoke with a more resolved value for the
+ * promise. To fulfill the promise, invoke `resolve` with any value that is
+ * not a thenable. To reject the promise, invoke `resolve` with a rejected
+ * thenable, or invoke `reject` with the reason directly. To resolve the
+ * promise to another thenable, thus putting it in the same state, invoke
+ * `resolve` with that other thenable.
+ */
+Q.defer = defer;
+function defer() {
+    // if "messages" is an "Array", that indicates that the promise has not yet
+    // been resolved.  If it is "undefined", it has been resolved.  Each
+    // element of the messages array is itself an array of complete arguments to
+    // forward to the resolved promise.  We coerce the resolution value to a
+    // promise using the `resolve` function because it handles both fully
+    // non-thenable values and other thenables gracefully.
+    var messages = [], progressListeners = [], resolvedPromise;
+
+    var deferred = object_create(defer.prototype);
+    var promise = object_create(Promise.prototype);
+
+    promise.promiseDispatch = function (resolve, op, operands) {
+        var args = array_slice(arguments);
+        if (messages) {
+            messages.push(args);
+            if (op === "when" && operands[1]) { // progress operand
+                progressListeners.push(operands[1]);
+            }
+        } else {
+            nextTick(function () {
+                resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
+            });
+        }
+    };
+
+    // XXX deprecated
+    promise.valueOf = function () {
+        if (messages) {
+            return promise;
+        }
+        var nearerValue = nearer(resolvedPromise);
+        if (isPromise(nearerValue)) {
+            resolvedPromise = nearerValue; // shorten chain
+        }
+        return nearerValue;
+    };
+
+    promise.inspect = function () {
+        if (!resolvedPromise) {
+            return { state: "pending" };
+        }
+        return resolvedPromise.inspect();
+    };
+
+    if (Q.longStackSupport && hasStacks) {
+        try {
+            throw new Error();
+        } catch (e) {
+            // NOTE: don't try to use `Error.captureStackTrace` or transfer the
+            // accessor around; that causes memory leaks as per GH-111. Just
+            // reify the stack trace as a string ASAP.
+            //
+            // At the same time, cut off the first line; it's always just
+            // "[object Promise]\n", as per the `toString`.
+            promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
+        }
+    }
+
+    // NOTE: we do the checks for `resolvedPromise` in each method, instead of
+    // consolidating them into `become`, since otherwise we'd create new
+    // promises with the lines `become(whatever(value))`. See e.g. GH-252.
+
+    function become(newPromise) {
+        resolvedPromise = newPromise;
+        promise.source = newPromise;
+
+        array_reduce(messages, function (undefined, message) {
+            nextTick(function () {
+                newPromise.promiseDispatch.apply(newPromise, message);
+            });
+        }, void 0);
+
+        messages = void 0;
+        progressListeners = void 0;
+    }
+
+    deferred.promise = promise;
+    deferred.resolve = function (value) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        become(Q(value));
+    };
+
+    deferred.fulfill = function (value) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        become(fulfill(value));
+    };
+    deferred.reject = function (reason) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        become(reject(reason));
+    };
+    deferred.notify = function (progress) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        array_reduce(progressListeners, function (undefined, progressListener) {
+            nextTick(function () {
+                progressListener(progress);
+            });
+        }, void 0);
+    };
+
+    return deferred;
+}
+
+/**
+ * Creates a Node-style callback that will resolve or reject the deferred
+ * promise.
+ * @returns a nodeback
+ */
+defer.prototype.makeNodeResolver = function () {
+    var self = this;
+    return function (error, value) {
+        if (error) {
+            self.reject(error);
+        } else if (arguments.length > 2) {
+            self.resolve(array_slice(arguments, 1));
+        } else {
+            self.resolve(value);
+        }
+    };
+};
+
+/**
+ * @param resolver {Function} a function that returns nothing and accepts
+ * the resolve, reject, and notify functions for a deferred.
+ * @returns a promise that may be resolved with the given resolve and reject
+ * functions, or rejected by a thrown exception in resolver
+ */
+Q.Promise = promise; // ES6
+Q.promise = promise;
+function promise(resolver) {
+    if (typeof resolver !== "function") {
+        throw new TypeError("resolver must be a function.");
+    }
+    var deferred = defer();
+    try {
+        resolver(deferred.resolve, deferred.reject, deferred.notify);
+    } catch (reason) {
+        deferred.reject(reason);
+    }
+    return deferred.promise;
+}
+
+promise.race = race; // ES6
+promise.all = all; // ES6
+promise.reject = reject; // ES6
+promise.resolve = Q; // ES6
+
+// XXX experimental.  This method is a way to denote that a local value is
+// serializable and should be immediately dispatched to a remote upon request,
+// instead of passing a reference.
+Q.passByCopy = function (object) {
+    //freeze(object);
+    //passByCopies.set(object, true);
+    return object;
+};
+
+Promise.prototype.passByCopy = function () {
+    //freeze(object);
+    //passByCopies.set(object, true);
+    return this;
+};
+
+/**
+ * If two promises eventually fulfill to the same value, promises that value,
+ * but otherwise rejects.
+ * @param x {Any*}
+ * @param y {Any*}
+ * @returns {Any*} a promise for x and y if they are the same, but a rejection
+ * otherwise.
+ *
+ */
+Q.join = function (x, y) {
+    return Q(x).join(y);
+};
+
+Promise.prototype.join = function (that) {
+    return Q([this, that]).spread(function (x, y) {
+        if (x === y) {
+            // TODO: "===" should be Object.is or equiv
+            return x;
+        } else {
+            throw new Error("Can't join: not the same: " + x + " " + y);
+        }
+    });
+};
+
+/**
+ * Returns a promise for the first of an array of promises to become fulfilled.
+ * @param answers {Array[Any*]} promises to race
+ * @returns {Any*} the first promise to be fulfilled
+ */
+Q.race = race;
+function race(answerPs) {
+    return promise(function(resolve, reject) {
+        // Switch to this once we can assume at least ES5
+        // answerPs.forEach(function(answerP) {
+        //     Q(answerP).then(resolve, reject);
+        // });
+        // Use this in the meantime
+        for (var i = 0, len = answerPs.length; i < len; i++) {
+            Q(answerPs[i]).then(resolve, reject);
+        }
+    });
+}
+
+Promise.prototype.race = function () {
+    return this.then(Q.race);
+};
+
+/**
+ * Constructs a Promise with a promise descriptor object and optional fallback
+ * function.  The descriptor contains methods like when(rejected), get(name),
+ * set(name, value), post(name, args), and delete(name), which all
+ * return either a value, a promise for a value, or a rejection.  The fallback
+ * accepts the operation name, a resolver, and any further arguments that would
+ * have been forwarded to the appropriate method above had a method been
+ * provided with the proper name.  The API makes no guarantees about the nature
+ * of the returned object, apart from that it is usable whereever promises are
+ * bought and sold.
+ */
+Q.makePromise = Promise;
+function Promise(descriptor, fallback, inspect) {
+    if (fallback === void 0) {
+        fallback = function (op) {
+            return reject(new Error(
+                "Promise does not support operation: " + op
+            ));
+        };
+    }
+    if (inspect === void 0) {
+        inspect = function () {
+            return {state: "unknown"};
+        };
+    }
+
+    var promise = object_create(Promise.prototype);
+
+    promise.promiseDispatch = function (resolve, op, args) {
+        var result;
+        try {
+            if (descriptor[op]) {
+                result = descriptor[op].apply(promise, args);
+            } else {
+                result = fallback.call(promise, op, args);
+            }
+        } catch (exception) {
+            result = reject(exception);
+        }
+        if (resolve) {
+            resolve(result);
+        }
+    };
+
+    promise.inspect = inspect;
+
+    // XXX deprecated `valueOf` and `exception` support
+    if (inspect) {
+        var inspected = inspect();
+        if (inspected.state === "rejected") {
+            promise.exception = inspected.reason;
+        }
+
+        promise.valueOf = function () {
+            var inspected = inspect();
+            if (inspected.state === "pending" ||
+                inspected.state === "rejected") {
+                return promise;
+            }
+            return inspected.value;
+        };
+    }
+
+    return promise;
+}
+
+Promise.prototype.toString = function () {
+    return "[object Promise]";
+};
+
+Promise.prototype.then = function (fulfilled, rejected, progressed) {
+    var self = this;
+    var deferred = defer();
+    var done = false;   // ensure the untrusted promise makes at most a
+                        // single call to one of the callbacks
+
+    function _fulfilled(value) {
+        try {
+            return typeof fulfilled === "function" ? fulfilled(value) : value;
+        } catch (exception) {
+            return reject(exception);
+        }
+    }
+
+    function _rejected(exception) {
+        if (typeof rejected === "function") {
+            makeStackTraceLong(exception, self);
+            try {
+                return rejected(exception);
+            } catch (newException) {
+                return reject(newException);
+            }
+        }
+        return reject(exception);
+    }
+
+    function _progressed(value) {
+        return typeof progressed === "function" ? progressed(value) : value;
+    }
+
+    nextTick(function () {
+        self.promiseDispatch(function (value) {
+            if (done) {
+                return;
+            }
+            done = true;
+
+            deferred.resolve(_fulfilled(value));
+        }, "when", [function (exception) {
+            if (done) {
+                return;
+            }
+            done = true;
+
+            deferred.resolve(_rejected(exception));
+        }]);
+    });
+
+    // Progress propagator need to be attached in the current tick.
+    self.promiseDispatch(void 0, "when", [void 0, function (value) {
+        var newValue;
+        var threw = false;
+        try {
+            newValue = _progressed(value);
+        } catch (e) {
+            threw = true;
+            if (Q.onerror) {
+                Q.onerror(e);
+            } else {
+                throw e;
+            }
+        }
+
+        if (!threw) {
+            deferred.notify(newValue);
+        }
+    }]);
+
+    return deferred.promise;
+};
+
+/**
+ * Registers an observer on a promise.
+ *
+ * Guarantees:
+ *
+ * 1. that fulfilled and rejected will be called only once.
+ * 2. that either the fulfilled callback or the rejected callback will be
+ *    called, but not both.
+ * 3. that fulfilled and rejected will not be called in this turn.
+ *
+ * @param value      promise or immediate reference to observe
+ * @param fulfilled  function to be called with the fulfilled value
+ * @param rejected   function to be called with the rejection exception
+ * @param progressed function to be called on any progress notifications
+ * @return promise for the return value from the invoked callback
+ */
+Q.when = when;
+function when(value, fulfilled, rejected, progressed) {
+    return Q(value).then(fulfilled, rejected, progressed);
+}
+
+Promise.prototype.thenResolve = function (value) {
+    return this.then(function () { return value; });
+};
+
+Q.thenResolve = function (promise, value) {
+    return Q(promise).thenResolve(value);
+};
+
+Promise.prototype.thenReject = function (reason) {
+    return this.then(function () { throw reason; });
+};
+
+Q.thenReject = function (promise, reason) {
+    return Q(promise).thenReject(reason);
+};
+
+/**
+ * If an object is not a promise, it is as "near" as possible.
+ * If a promise is rejected, it is as "near" as possible too.
+ * If it’s a fulfilled promise, the fulfillment value is nearer.
+ * If it’s a deferred promise and the deferred has been resolved, the
+ * resolution is "nearer".
+ * @param object
+ * @returns most resolved (nearest) form of the object
+ */
+
+// XXX should we re-do this?
+Q.nearer = nearer;
+function nearer(value) {
+    if (isPromise(value)) {
+        var inspected = value.inspect();
+        if (inspected.state === "fulfilled") {
+            return inspected.value;
+        }
+    }
+    return value;
+}
+
+/**
+ * @returns whether the given object is a promise.
+ * Otherwise it is a fulfilled value.
+ */
+Q.isPromise = isPromise;
+function isPromise(object) {
+    return isObject(object) &&
+        typeof object.promiseDispatch === "function" &&
+        typeof object.inspect === "function";
+}
+
+Q.isPromiseAlike = isPromiseAlike;
+function isPromiseAlike(object) {
+    return isObject(object) && typeof object.then === "function";
+}
+
+/**
+ * @returns whether the given object is a pending promise, meaning not
+ * fulfilled or rejected.
+ */
+Q.isPending = isPending;
+function isPending(object) {
+    return isPromise(object) && object.inspect().state === "pending";
+}
+
+Promise.prototype.isPending = function () {
+    return this.inspect().state === "pending";
+};
+
+/**
+ * @returns whether the given object is a value or fulfilled
+ * promise.
+ */
+Q.isFulfilled = isFulfilled;
+function isFulfilled(object) {
+    return !isPromise(object) || object.inspect().state === "fulfilled";
+}
+
+Promise.prototype.isFulfilled = function () {
+    return this.inspect().state === "fulfilled";
+};
+
+/**
+ * @returns whether the given object is a rejected promise.
+ */
+Q.isRejected = isRejected;
+function isRejected(object) {
+    return isPromise(object) && object.inspect().state === "rejected";
+}
+
+Promise.prototype.isRejected = function () {
+    return this.inspect().state === "rejected";
+};
+
+//// BEGIN UNHANDLED REJECTION TRACKING
+
+// This promise library consumes exceptions thrown in handlers so they can be
+// handled by a subsequent promise.  The exceptions get added to this array when
+// they are created, and removed when they are handled.  Note that in ES6 or
+// shimmed environments, this would naturally be a `Set`.
+var unhandledReasons = [];
+var unhandledRejections = [];
+var trackUnhandledRejections = true;
+
+function resetUnhandledRejections() {
+    unhandledReasons.length = 0;
+    unhandledRejections.length = 0;
+
+    if (!trackUnhandledRejections) {
+        trackUnhandledRejections = true;
+    }
+}
+
+function trackRejection(promise, reason) {
+    if (!trackUnhandledRejections) {
+        return;
+    }
+
+    unhandledRejections.push(promise);
+    if (reason && typeof reason.stack !== "undefined") {
+        unhandledReasons.push(reason.stack);
+    } else {
+        unhandledReasons.push("(no stack) " + reason);
+    }
+}
+
+function untrackRejection(promise) {
+    if (!trackUnhandledRejections) {
+        return;
+    }
+
+    var at = array_indexOf(unhandledRejections, promise);
+    if (at !== -1) {
+        unhandledRejections.splice(at, 1);
+        unhandledReasons.splice(at, 1);
+    }
+}
+
+Q.resetUnhandledRejections = resetUnhandledRejections;
+
+Q.getUnhandledReasons = function () {
+    // Make a copy so that consumers can't interfere with our internal state.
+    return unhandledReasons.slice();
+};
+
+Q.stopUnhandledRejectionTracking = function () {
+    resetUnhandledRejections();
+    trackUnhandledRejections = false;
+};
+
+resetUnhandledRejections();
+
+//// END UNHANDLED REJECTION TRACKING
+
+/**
+ * Constructs a rejected promise.
+ * @param reason value describing the failure
+ */
+Q.reject = reject;
+function reject(reason) {
+    var rejection = Promise({
+        "when": function (rejected) {
+            // note that the error has been handled
+            if (rejected) {
+                untrackRejection(this);
+            }
+            return rejected ? rejected(reason) : this;
+        }
+    }, function fallback() {
+        return this;
+    }, function inspect() {
+        return { state: "rejected", reason: reason };
+    });
+
+    // Note that the reason has not been handled.
+    trackRejection(rejection, reason);
+
+    return rejection;
+}
+
+/**
+ * Constructs a fulfilled promise for an immediate reference.
+ * @param value immediate reference
+ */
+Q.fulfill = fulfill;
+function fulfill(value) {
+    return Promise({
+        "when": function () {
+            return value;
+        },
+        "get": function (name) {
+            return value[name];
+        },
+        "set": function (name, rhs) {
+            value[name] = rhs;
+        },
+        "delete": function (name) {
+            delete value[name];
+        },
+        "post": function (name, args) {
+            // Mark Miller proposes that post with no name should apply a
+            // promised function.
+            if (name === null || name === void 0) {
+                return value.apply(void 0, args);
+            } else {
+                return value[name].apply(value, args);
+            }
+        },
+        "apply": function (thisp, args) {
+            return value.apply(thisp, args);
+        },
+        "keys": function () {
+            return object_keys(value);
+        }
+    }, void 0, function inspect() {
+        return { state: "fulfilled", value: value };
+    });
+}
+
+/**
+ * Converts thenables to Q promises.
+ * @param promise thenable promise
+ * @returns a Q promise
+ */
+function coerce(promise) {
+    var deferred = defer();
+    nextTick(function () {
+        try {
+            promise.then(deferred.resolve, deferred.reject, deferred.notify);
+        } catch (exception) {
+            deferred.reject(exception);
+        }
+    });
+    return deferred.promise;
+}
+
+/**
+ * Annotates an object such that it will never be
+ * transferred away from this process over any promise
+ * communication channel.
+ * @param object
+ * @returns promise a wrapping of that object that
+ * additionally responds to the "isDef" message
+ * without a rejection.
+ */
+Q.master = master;
+function master(object) {
+    return Promise({
+        "isDef": function () {}
+    }, function fallback(op, args) {
+        return dispatch(object, op, args);
+    }, function () {
+        return Q(object).inspect();
+    });
+}
+
+/**
+ * Spreads the values of a promised array of arguments into the
+ * fulfillment callback.
+ * @param fulfilled callback that receives variadic arguments from the
+ * promised array
+ * @param rejected callback that receives the exception if the promise
+ * is rejected.
+ * @returns a promise for the return value or thrown exception of
+ * either callback.
+ */
+Q.spread = spread;
+function spread(value, fulfilled, rejected) {
+    return Q(value).spread(fulfilled, rejected);
+}
+
+Promise.prototype.spread = function (fulfilled, rejected) {
+    return this.all().then(function (array) {
+        return fulfilled.apply(void 0, array);
+    }, rejected);
+};
+
+/**
+ * The async function is a decorator for generator functions, turning
+ * them into asynchronous generators.  Although generators are only part
+ * of the newest ECMAScript 6 drafts, this code does not cause syntax
+ * errors in older engines.  This code should continue to work and will
+ * in fact improve over time as the language improves.
+ *
+ * ES6 generators are currently part of V8 version 3.19 with the
+ * --harmony-generators runtime flag enabled.  SpiderMonkey has had them
+ * for longer, but under an older Python-inspired form.  This function
+ * works on both kinds of generators.
+ *
+ * Decorates a generator function such that:
+ *  - it may yield promises
+ *  - execution will continue when that promise is fulfilled
+ *  - the value of the yield expression will be the fulfilled value
+ *  - it returns a promise for the return value (when the generator
+ *    stops iterating)
+ *  - the decorated function returns a promise for the return value
+ *    of the generator or the first rejected promise among those
+ *    yielded.
+ *  - if an error is thrown in the generator, it propagates through
+ *    every following yield until it is caught, or until it escapes
+ *    the generator function altogether, and is translated into a
+ *    rejection for the promise returned by the decorated generator.
+ */
+Q.async = async;
+function async(makeGenerator) {
+    return function () {
+        // when verb is "send", arg is a value
+        // when verb is "throw", arg is an exception
+        function continuer(verb, arg) {
+            var result;
+
+            // Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only
+            // engine that has a deployed base of browsers that support generators.
+            // However, SM's generators use the Python-inspired semantics of
+            // outdated ES6 drafts.  We would like to support ES6, but we'd also
+            // like to make it possible to use generators in deployed browsers, so
+            // we also support Python-style generators.  At some point we can remove
+            // this block.
+
+            if (typeof StopIteration === "undefined") {
+                // ES6 Generators
+                try {
+                    result = generator[verb](arg);
+                } catch (exception) {
+                    return reject(exception);
+                }
+                if (result.done) {
+                    return Q(result.value);
+                } else {
+                    return when(result.value, callback, errback);
+                }
+            } else {
+                // SpiderMonkey Generators
+                // FIXME: Remove this case when SM does ES6 generators.
+                try {
+                    result = generator[verb](arg);
+                } catch (exception) {
+                    if (isStopIteration(exception)) {
+                        return Q(exception.value);
+                    } else {
+                        return reject(exception);
+                    }
+                }
+                return when(result, callback, errback);
+            }
+        }
+        var generator = makeGenerator.apply(this, arguments);
+        var callback = continuer.bind(continuer, "next");
+        var errback = continuer.bind(continuer, "throw");
+        return callback();
+    };
+}
+
+/**
+ * The spawn function is a small wrapper around async that immediately
+ * calls the generator and also ends the promise chain, so that any
+ * unhandled errors are thrown instead of forwarded to the error
+ * handler. This is useful because it's extremely common to run
+ * generators at the top-level to work with libraries.
+ */
+Q.spawn = spawn;
+function spawn(makeGenerator) {
+    Q.done(Q.async(makeGenerator)());
+}
+
+// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.
+/**
+ * Throws a ReturnValue exception to stop an asynchronous generator.
+ *
+ * This interface is a stop-gap measure to support generator return
+ * values in older Firefox/SpiderMonkey.  In browsers that support ES6
+ * generators like Chromium 29, just use "return" in your generator
+ * functions.
+ *
+ * @param value the return value for the surrounding generator
+ * @throws ReturnValue exception with the value.
+ * @example
+ * // ES6 style
+ * Q.async(function* () {
+ *      var foo = yield getFooPromise();
+ *      var bar = yield getBarPromise();
+ *      return foo + bar;
+ * })
+ * // Older SpiderMonkey style
+ * Q.async(function () {
+ *      var foo = yield getFooPromise();
+ *      var bar = yield getBarPromise();
+ *      Q.return(foo + bar);
+ * })
+ */
+Q["return"] = _return;
+function _return(value) {
+    throw new QReturnValue(value);
+}
+
+/**
+ * The promised function decorator ensures that any promise arguments
+ * are settled and passed as values (`this` is also settled and passed
+ * as a value).  It will also ensure that the result of a function is
+ * always a promise.
+ *
+ * @example
+ * var add = Q.promised(function (a, b) {
+ *     return a + b;
+ * });
+ * add(Q(a), Q(B));
+ *
+ * @param {function} callback The function to decorate
+ * @returns {function} a function that has been decorated.
+ */
+Q.promised = promised;
+function promised(callback) {
+    return function () {
+        return spread([this, all(arguments)], function (self, args) {
+            return callback.apply(self, args);
+        });
+    };
+}
+
+/**
+ * sends a message to a value in a future turn
+ * @param object* the recipient
+ * @param op the name of the message operation, e.g., "when",
+ * @param args further arguments to be forwarded to the operation
+ * @returns result {Promise} a promise for the result of the operation
+ */
+Q.dispatch = dispatch;
+function dispatch(object, op, args) {
+    return Q(object).dispatch(op, args);
+}
+
+Promise.prototype.dispatch = function (op, args) {
+    var self = this;
+    var deferred = defer();
+    nextTick(function () {
+        self.promiseDispatch(deferred.resolve, op, args);
+    });
+    return deferred.promise;
+};
+
+/**
+ * Gets the value of a property in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of property to get
+ * @return promise for the property value
+ */
+Q.get = function (object, key) {
+    return Q(object).dispatch("get", [key]);
+};
+
+Promise.prototype.get = function (key) {
+    return this.dispatch("get", [key]);
+};
+
+/**
+ * Sets the value of a property in a future turn.
+ * @param object    promise or immediate reference for object object
+ * @param name      name of property to set
+ * @param value     new value of property
+ * @return promise for the return value
+ */
+Q.set = function (object, key, value) {
+    return Q(object).dispatch("set", [key, value]);
+};
+
+Promise.prototype.set = function (key, value) {
+    return this.dispatch("set", [key, value]);
+};
+
+/**
+ * Deletes a property in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of property to delete
+ * @return promise for the return value
+ */
+Q.del = // XXX legacy
+Q["delete"] = function (object, key) {
+    return Q(object).dispatch("delete", [key]);
+};
+
+Promise.prototype.del = // XXX legacy
+Promise.prototype["delete"] = function (key) {
+    return this.dispatch("delete", [key]);
+};
+
+/**
+ * Invokes a method in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of method to invoke
+ * @param value     a value to post, typically an array of
+ *                  invocation arguments for promises that
+ *                  are ultimately backed with `resolve` values,
+ *                  as opposed to those backed with URLs
+ *                  wherein the posted value can be any
+ *                  JSON serializable object.
+ * @return promise for the return value
+ */
+// bound locally because it is used by other methods
+Q.mapply = // XXX As proposed by "Redsandro"
+Q.post = function (object, name, args) {
+    return Q(object).dispatch("post", [name, args]);
+};
+
+Promise.prototype.mapply = // XXX As proposed by "Redsandro"
+Promise.prototype.post = function (name, args) {
+    return this.dispatch("post", [name, args]);
+};
+
+/**
+ * Invokes a method in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of method to invoke
+ * @param ...args   array of invocation arguments
+ * @return promise for the return value
+ */
+Q.send = // XXX Mark Miller's proposed parlance
+Q.mcall = // XXX As proposed by "Redsandro"
+Q.invoke = function (object, name /*...args*/) {
+    return Q(object).dispatch("post", [name, array_slice(arguments, 2)]);
+};
+
+Promise.prototype.send = // XXX Mark Miller's proposed parlance
+Promise.prototype.mcall = // XXX As proposed by "Redsandro"
+Promise.prototype.invoke = function (name /*...args*/) {
+    return this.dispatch("post", [name, array_slice(arguments, 1)]);
+};
+
+/**
+ * Applies the promised function in a future turn.
+ * @param object    promise or immediate reference for target function
+ * @param args      array of application arguments
+ */
+Q.fapply = function (object, args) {
+    return Q(object).dispatch("apply", [void 0, args]);
+};
+
+Promise.prototype.fapply = function (args) {
+    return this.dispatch("apply", [void 0, args]);
+};
+
+/**
+ * Calls the promised function in a future turn.
+ * @param object    promise or immediate reference for target function
+ * @param ...args   array of application arguments
+ */
+Q["try"] =
+Q.fcall = function (object /* ...args*/) {
+    return Q(object).dispatch("apply", [void 0, array_slice(arguments, 1)]);
+};
+
+Promise.prototype.fcall = function (/*...args*/) {
+    return this.dispatch("apply", [void 0, array_slice(arguments)]);
+};
+
+/**
+ * Binds the promised function, transforming return values into a fulfilled
+ * promise and thrown errors into a rejected one.
+ * @param object    promise or immediate reference for target function
+ * @param ...args   array of application arguments
+ */
+Q.fbind = function (object /*...args*/) {
+    var promise = Q(object);
+    var args = array_slice(arguments, 1);
+    return function fbound() {
+        return promise.dispatch("apply", [
+            this,
+            args.concat(array_slice(arguments))
+        ]);
+    };
+};
+Promise.prototype.fbind = function (/*...args*/) {
+    var promise = this;
+    var args = array_slice(arguments);
+    return function fbound() {
+        return promise.dispatch("apply", [
+            this,
+            args.concat(array_slice(arguments))
+        ]);
+    };
+};
+
+/**
+ * Requests the names of the owned properties of a promised
+ * object in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @return promise for the keys of the eventually settled object
+ */
+Q.keys = function (object) {
+    return Q(object).dispatch("keys", []);
+};
+
+Promise.prototype.keys = function () {
+    return this.dispatch("keys", []);
+};
+
+/**
+ * Turns an array of promises into a promise for an array.  If any of
+ * the promises gets rejected, the whole array is rejected immediately.
+ * @param {Array*} an array (or promise for an array) of values (or
+ * promises for values)
+ * @returns a promise for an array of the corresponding values
+ */
+// By Mark Miller
+// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled
+Q.all = all;
+function all(promises) {
+    return when(promises, function (promises) {
+        var countDown = 0;
+        var deferred = defer();
+        array_reduce(promises, function (undefined, promise, index) {
+            var snapshot;
+            if (
+                isPromise(promise) &&
+                (snapshot = promise.inspect()).state === "fulfilled"
+            ) {
+                promises[index] = snapshot.value;
+            } else {
+                ++countDown;
+                when(
+                    promise,
+                    function (value) {
+                        promises[index] = value;
+                        if (--countDown === 0) {
+                            deferred.resolve(promises);
+                        }
+                    },
+                    deferred.reject,
+                    function (progress) {
+                        deferred.notify({ index: index, value: progress });
+                    }
+                );
+            }
+        }, void 0);
+        if (countDown === 0) {
+            deferred.resolve(promises);
+        }
+        return deferred.promise;
+    });
+}
+
+Promise.prototype.all = function () {
+    return all(this);
+};
+
+/**
+ * Waits for all promises to be settled, either fulfilled or
+ * rejected.  This is distinct from `all` since that would stop
+ * waiting at the first rejection.  The promise returned by
+ * `allResolved` will never be rejected.
+ * @param promises a promise for an array (or an array) of promises
+ * (or values)
+ * @return a promise for an array of promises
+ */
+Q.allResolved = deprecate(allResolved, "allResolved", "allSettled");
+function allResolved(promises) {
+    return when(promises, function (promises) {
+        promises = array_map(promises, Q);
+        return when(all(array_map(promises, function (promise) {
+            return when(promise, noop, noop);
+        })), function () {
+            return promises;
+        });
+    });
+}
+
+Promise.prototype.allResolved = function () {
+    return allResolved(this);
+};
+
+/**
+ * @see Promise#allSettled
+ */
+Q.allSettled = allSettled;
+function allSettled(promises) {
+    return Q(promises).allSettled();
+}
+
+/**
+ * Turns an array of promises into a promise for an array of their states (as
+ * returned by `inspect`) when they have all settled.
+ * @param {Array[Any*]} values an array (or promise for an array) of values (or
+ * promises for values)
+ * @returns {Array[State]} an array of states for the respective values.
+ */
+Promise.prototype.allSettled = function () {
+    return this.then(function (promises) {
+        return all(array_map(promises, function (promise) {
+            promise = Q(promise);
+            function regardless() {
+                return promise.inspect();
+            }
+            return promise.then(regardless, regardless);
+        }));
+    });
+};
+
+/**
+ * Captures the failure of a promise, giving an oportunity to recover
+ * with a callback.  If the given promise is fulfilled, the returned
+ * promise is fulfilled.
+ * @param {Any*} promise for something
+ * @param {Function} callback to fulfill the returned promise if the
+ * given promise is rejected
+ * @returns a promise for the return value of the callback
+ */
+Q.fail = // XXX legacy
+Q["catch"] = function (object, rejected) {
+    return Q(object).then(void 0, rejected);
+};
+
+Promise.prototype.fail = // XXX legacy
+Promise.prototype["catch"] = function (rejected) {
+    return this.then(void 0, rejected);
+};
+
+/**
+ * Attaches a listener that can respond to progress notifications from a
+ * promise's originating deferred. This listener receives the exact arguments
+ * passed to ``deferred.notify``.
+ * @param {Any*} promise for something
+ * @param {Function} callback to receive any progress notifications
+ * @returns the given promise, unchanged
+ */
+Q.progress = progress;
+function progress(object, progressed) {
+    return Q(object).then(void 0, void 0, progressed);
+}
+
+Promise.prototype.progress = function (progressed) {
+    return this.then(void 0, void 0, progressed);
+};
+
+/**
+ * Provides an opportunity to observe the settling of a promise,
+ * regardless of whether the promise is fulfilled or rejected.  Forwards
+ * the resolution to the returned promise when the callback is done.
+ * The callback can return a promise to defer completion.
+ * @param {Any*} promise
+ * @param {Function} callback to observe the resolution of the given
+ * promise, takes no arguments.
+ * @returns a promise for the resolution of the given promise when
+ * ``fin`` is done.
+ */
+Q.fin = // XXX legacy
+Q["finally"] = function (object, callback) {
+    return Q(object)["finally"](callback);
+};
+
+Promise.prototype.fin = // XXX legacy
+Promise.prototype["finally"] = function (callback) {
+    callback = Q(callback);
+    return this.then(function (value) {
+        return callback.fcall().then(function () {
+            return value;
+        });
+    }, function (reason) {
+        // TODO attempt to recycle the rejection with "this".
+        return callback.fcall().then(function () {
+            throw reason;
+        });
+    });
+};
+
+/**
+ * Terminates a chain of promises, forcing rejections to be
+ * thrown as exceptions.
+ * @param {Any*} promise at the end of a chain of promises
+ * @returns nothing
+ */
+Q.done = function (object, fulfilled, rejected, progress) {
+    return Q(object).done(fulfilled, rejected, progress);
+};
+
+Promise.prototype.done = function (fulfilled, rejected, progress) {
+    var onUnhandledError = function (error) {
+        // forward to a future turn so that ``when``
+        // does not catch it and turn it into a rejection.
+        nextTick(function () {
+            makeStackTraceLong(error, promise);
+            if (Q.onerror) {
+                Q.onerror(error);
+            } else {
+                throw error;
+            }
+        });
+    };
+
+    // Avoid unnecessary `nextTick`ing via an unnecessary `when`.
+    var promise = fulfilled || rejected || progress ?
+        this.then(fulfilled, rejected, progress) :
+        this;
+
+    if (typeof process === "object" && process && process.domain) {
+        onUnhandledError = process.domain.bind(onUnhandledError);
+    }
+
+    promise.then(void 0, onUnhandledError);
+};
+
+/**
+ * Causes a promise to be rejected if it does not get fulfilled before
+ * some milliseconds time out.
+ * @param {Any*} promise
+ * @param {Number} milliseconds timeout
+ * @param {Any*} custom error message or Error object (optional)
+ * @returns a promise for the resolution of the given promise if it is
+ * fulfilled before the timeout, otherwise rejected.
+ */
+Q.timeout = function (object, ms, error) {
+    return Q(object).timeout(ms, error);
+};
+
+Promise.prototype.timeout = function (ms, error) {
+    var deferred = defer();
+    var timeoutId = setTimeout(function () {
+        if (!error || "string" === typeof error) {
+            error = new Error(error || "Timed out after " + ms + " ms");
+            error.code = "ETIMEDOUT";
+        }
+        deferred.reject(error);
+    }, ms);
+
+    this.then(function (value) {
+        clearTimeout(timeoutId);
+        deferred.resolve(value);
+    }, function (exception) {
+        clearTimeout(timeoutId);
+        deferred.reject(exception);
+    }, deferred.notify);
+
+    return deferred.promise;
+};
+
+/**
+ * Returns a promise for the given value (or promised value), some
+ * milliseconds after it resolved. Passes rejections immediately.
+ * @param {Any*} promise
+ * @param {Number} milliseconds
+ * @returns a promise for the resolution of the given promise after milliseconds
+ * time has elapsed since the resolution of the given promise.
+ * If the given promise rejects, that is passed immediately.
+ */
+Q.delay = function (object, timeout) {
+    if (timeout === void 0) {
+        timeout = object;
+        object = void 0;
+    }
+    return Q(object).delay(timeout);
+};
+
+Promise.prototype.delay = function (timeout) {
+    return this.then(function (value) {
+        var deferred = defer();
+        setTimeout(function () {
+            deferred.resolve(value);
+        }, timeout);
+        return deferred.promise;
+    });
+};
+
+/**
+ * Passes a continuation to a Node function, which is called with the given
+ * arguments provided as an array, and returns a promise.
+ *
+ *      Q.nfapply(FS.readFile, [__filename])
+ *      .then(function (content) {
+ *      })
+ *
+ */
+Q.nfapply = function (callback, args) {
+    return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfapply = function (args) {
+    var deferred = defer();
+    var nodeArgs = array_slice(args);
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.fapply(nodeArgs).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * Passes a continuation to a Node function, which is called with the given
+ * arguments provided individually, and returns a promise.
+ * @example
+ * Q.nfcall(FS.readFile, __filename)
+ * .then(function (content) {
+ * })
+ *
+ */
+Q.nfcall = function (callback /*...args*/) {
+    var args = array_slice(arguments, 1);
+    return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfcall = function (/*...args*/) {
+    var nodeArgs = array_slice(arguments);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.fapply(nodeArgs).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * Wraps a NodeJS continuation passing function and returns an equivalent
+ * version that returns a promise.
+ * @example
+ * Q.nfbind(FS.readFile, __filename)("utf-8")
+ * .then(console.log)
+ * .done()
+ */
+Q.nfbind =
+Q.denodeify = function (callback /*...args*/) {
+    var baseArgs = array_slice(arguments, 1);
+    return function () {
+        var nodeArgs = baseArgs.concat(array_slice(arguments));
+        var deferred = defer();
+        nodeArgs.push(deferred.makeNodeResolver());
+        Q(callback).fapply(nodeArgs).fail(deferred.reject);
+        return deferred.promise;
+    };
+};
+
+Promise.prototype.nfbind =
+Promise.prototype.denodeify = function (/*...args*/) {
+    var args = array_slice(arguments);
+    args.unshift(this);
+    return Q.denodeify.apply(void 0, args);
+};
+
+Q.nbind = function (callback, thisp /*...args*/) {
+    var baseArgs = array_slice(arguments, 2);
+    return function () {
+        var nodeArgs = baseArgs.concat(array_slice(arguments));
+        var deferred = defer();
+        nodeArgs.push(deferred.makeNodeResolver());
+        function bound() {
+            return callback.apply(thisp, arguments);
+        }
+        Q(bound).fapply(nodeArgs).fail(deferred.reject);
+        return deferred.promise;
+    };
+};
+
+Promise.prototype.nbind = function (/*thisp, ...args*/) {
+    var args = array_slice(arguments, 0);
+    args.unshift(this);
+    return Q.nbind.apply(void 0, args);
+};
+
+/**
+ * Calls a method of a Node-style object that accepts a Node-style
+ * callback with a given array of arguments, plus a provided callback.
+ * @param object an object that has the named method
+ * @param {String} name name of the method of object
+ * @param {Array} args arguments to pass to the method; the callback
+ * will be provided by Q and appended to these arguments.
+ * @returns a promise for the value or error
+ */
+Q.nmapply = // XXX As proposed by "Redsandro"
+Q.npost = function (object, name, args) {
+    return Q(object).npost(name, args);
+};
+
+Promise.prototype.nmapply = // XXX As proposed by "Redsandro"
+Promise.prototype.npost = function (name, args) {
+    var nodeArgs = array_slice(args || []);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * Calls a method of a Node-style object that accepts a Node-style
+ * callback, forwarding the given variadic arguments, plus a provided
+ * callback argument.
+ * @param object an object that has the named method
+ * @param {String} name name of the method of object
+ * @param ...args arguments to pass to the method; the callback will
+ * be provided by Q and appended to these arguments.
+ * @returns a promise for the value or error
+ */
+Q.nsend = // XXX Based on Mark Miller's proposed "send"
+Q.nmcall = // XXX Based on "Redsandro's" proposal
+Q.ninvoke = function (object, name /*...args*/) {
+    var nodeArgs = array_slice(arguments, 2);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    Q(object).dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+    return deferred.promise;
+};
+
+Promise.prototype.nsend = // XXX Based on Mark Miller's proposed "send"
+Promise.prototype.nmcall = // XXX Based on "Redsandro's" proposal
+Promise.prototype.ninvoke = function (name /*...args*/) {
+    var nodeArgs = array_slice(arguments, 1);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * If a function would like to support both Node continuation-passing-style and
+ * promise-returning-style, it can end its internal promise chain with
+ * `nodeify(nodeback)`, forwarding the optional nodeback argument.  If the user
+ * elects to use a nodeback, the result will be sent there.  If they do not
+ * pass a nodeback, they will receive the result promise.
+ * @param object a result (or a promise for a result)
+ * @param {Function} nodeback a Node.js-style callback
+ * @returns either the promise or nothing
+ */
+Q.nodeify = nodeify;
+function nodeify(object, nodeback) {
+    return Q(object).nodeify(nodeback);
+}
+
+Promise.prototype.nodeify = function (nodeback) {
+    if (nodeback) {
+        this.then(function (value) {
+            nextTick(function () {
+                nodeback(null, value);
+            });
+        }, function (error) {
+            nextTick(function () {
+                nodeback(error);
+            });
+        });
+    } else {
+        return this;
+    }
+};
+
+// All code before this point will be filtered from stack traces.
+var qEndingLine = captureLine();
+
+return Q;
+
+});
+

+ 13 - 0
mist/assets/ext/qml_messaging.js

@@ -0,0 +1,13 @@
+function HandleMessage(data) {
+	var message;
+	try { message = JSON.parse(data) } catch(e) {};
+
+	if(message) {
+		switch(message.type) {
+			case "coinbase":
+				return eth.coinBase();
+			case "block":
+				return eth.blockByNumber(0);
+		}
+	}
+}

+ 21 - 8
mist/assets/qml/webapp.qml

@@ -7,6 +7,8 @@ import QtQuick.Layouts 1.0;
 import QtQuick.Window 2.1;
 import QtQuick.Window 2.1;
 import Ethereum 1.0
 import Ethereum 1.0
 
 
+import "../ext/qml_messaging.js" as Messaging
+
 //ApplicationWindow {
 //ApplicationWindow {
 Rectangle {
 Rectangle {
 	id: window
 	id: window
@@ -14,10 +16,6 @@ Rectangle {
 	property var iconSource: "../browser.png"
 	property var iconSource: "../browser.png"
 	property var menuItem
 	property var menuItem
 
 
-	//width: 1000
-	//height: 800
-	//minimumHeight: 300
-
 	property alias url: webview.url
 	property alias url: webview.url
 	property alias webView: webview
 	property alias webView: webview
 
 
@@ -97,7 +95,6 @@ Rectangle {
 				bottom: parent.bottom
 				bottom: parent.bottom
 				top: navBar.bottom
 				top: navBar.bottom
 			}
 			}
-			onTitleChanged: { window.title = title }
 
 
 			property var cleanPath: false
 			property var cleanPath: false
 			onNavigationRequested: {
 			onNavigationRequested: {
@@ -113,7 +110,7 @@ Rectangle {
 						uri.replace(reg, function(match, pre, domain, path) {
 						uri.replace(reg, function(match, pre, domain, path) {
 							uri = pre;
 							uri = pre;
 
 
-							var lookup = ui.lookupDomain(domain.substring(0, domain.length - 4));
+							var lookup = eth.lookupDomain(domain.substring(0, domain.length - 4));
 							var ip = [];
 							var ip = [];
 							for(var i = 0, l = lookup.length; i < l; i++) {
 							for(var i = 0, l = lookup.length; i < l; i++) {
 								ip.push(lookup.charCodeAt(i))
 								ip.push(lookup.charCodeAt(i))
@@ -138,11 +135,22 @@ Rectangle {
 				}
 				}
 			}
 			}
 
 
+			function sendMessage(data) {
+				//this.experimental.evaluateJavaScript("window.____returnData="+JSON.stringify(data));
+				webview.experimental.postMessage(JSON.stringify(data))
+			}
+
+			onTitleChanged: {
+				var data = Messaging.HandleMessage(title);
+				if(data) {
+					sendMessage(data)
+				}
+			}
 
 
 			experimental.preferences.javascriptEnabled: true
 			experimental.preferences.javascriptEnabled: true
 			experimental.preferences.navigatorQtObjectEnabled: true
 			experimental.preferences.navigatorQtObjectEnabled: true
 			experimental.preferences.developerExtrasEnabled: true
 			experimental.preferences.developerExtrasEnabled: true
-			experimental.userScripts: ["../ext/pre.js", "../ext/big.js", "../ext/string.js", "../ext/ethereum.js"]
+			experimental.userScripts: ["../ext/q.js", "../ext/pre.js", "../ext/big.js", "../ext/string.js", "../ext/html_messaging.js"]
 			experimental.onMessageReceived: {
 			experimental.onMessageReceived: {
 				console.log("[onMessageReceived]: ", message.data)
 				console.log("[onMessageReceived]: ", message.data)
 				// TODO move to messaging.js
 				// TODO move to messaging.js
@@ -150,6 +158,10 @@ Rectangle {
 
 
 				try {
 				try {
 					switch(data.call) {
 					switch(data.call) {
+						case "compile":
+						postData(data._seed, eth.compile(data.args[0]))
+						break
+
 						case "getCoinBase":
 						case "getCoinBase":
 						postData(data._seed, eth.coinBase())
 						postData(data._seed, eth.coinBase())
 
 
@@ -191,7 +203,8 @@ Rectangle {
 						case "transact":
 						case "transact":
 						require(5)
 						require(5)
 
 
-						var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5])
+						var tx = eth.transact(data.args)
+						console.log("transactx", tx)
 						postData(data._seed, tx)
 						postData(data._seed, tx)
 
 
 						break
 						break

+ 7 - 3
mist/debugger.go

@@ -94,9 +94,7 @@ func (self *DebuggerWindow) ClearLog() {
 }
 }
 
 
 func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, dataStr string) {
 func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, dataStr string) {
-	if !self.Db.done {
-		self.Db.Q <- true
-	}
+	self.Stop()
 
 
 	defer func() {
 	defer func() {
 		if r := recover(); r != nil {
 		if r := recover(); r != nil {
@@ -186,6 +184,12 @@ func (self *DebuggerWindow) Continue() {
 	self.Next()
 	self.Next()
 }
 }
 
 
+func (self *DebuggerWindow) Stop() {
+	if !self.Db.done {
+		self.Db.Q <- true
+	}
+}
+
 func (self *DebuggerWindow) ExecCommand(command string) {
 func (self *DebuggerWindow) ExecCommand(command string) {
 	if len(command) > 0 {
 	if len(command) > 0 {
 		cmd := strings.Split(command, " ")
 		cmd := strings.Split(command, " ")

+ 9 - 0
mist/ui_lib.go

@@ -248,3 +248,12 @@ func (self *UiLib) Transact(object map[string]interface{}) (*ethpipe.JSReceipt,
 		dataStr,
 		dataStr,
 	)
 	)
 }
 }
+
+func (self *UiLib) Compile(code string) (string, error) {
+	bcode, err := ethutil.Compile(code, false)
+	if err != nil {
+		return err.Error(), err
+	}
+
+	return ethutil.Bytes2Hex(bcode), err
+}