diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js index 381893f9..842fc894 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -257,8 +257,9 @@ var Bookmarks = Module("bookmarks", { let engine = hasOwnProperty(this.searchEngines, engineName) && this.searchEngines[engineName]; if (engine && engine.supportsResponseType(responseType)) var queryURI = engine.getSubmission(query, responseType).uri.spec; + if (!queryURI) - return (callback || util.identity)([]); + return promises.fail(); function parse(req) JSON.parse(req.responseText)[1].filter(isString); return this.makeSuggestions(queryURI, parse, callback); @@ -271,25 +272,25 @@ var Bookmarks = Module("bookmarks", { * @param {string} url The URL to fetch. * @param {function(XMLHttpRequest):[string]} parser The function which * parses the response. + * @returns {Promise} */ - makeSuggestions: function makeSuggestions(url, parser, callback) { - function process(req) { + makeSuggestions: function makeSuggestions(url, parser) { + let deferred = Promise.defer(); + + let req = util.fetchUrl(url); + req.then(function process(req) { let results = []; try { results = parser(req); } catch (e) { - util.reportError(e); + return deferred.reject(e); } - if (callback) - return callback(results); - return results; - } + deferred.resolve(results); + }, Cu.reportError); - let req = util.httpGet(url, callback && process); - if (callback) - return req; - return process(req); + promises.oncancel(deferred, r => promises.cancel(req, reason)); + return deferred.promise; }, suggestionProviders: {}, @@ -536,7 +537,7 @@ var Bookmarks = Module("bookmarks", { "Delete a bookmark", function (args) { if (args.bang) - commandline.input(_("bookmark.prompt.deleteAll") + " ", + commandline.input(_("bookmark.prompt.deleteAll") + " ").then( function (resp) { if (resp && resp.match(/^y(es)?$/i)) { bookmarks.remove(Object.keys(bookmarkcache.bookmarks)); @@ -628,7 +629,7 @@ var Bookmarks = Module("bookmarks", { }, completion: function initCompletion() { - completion.bookmark = function bookmark(context, tags, extra = {}) { + completion.bookmark = function bookmark(context, tags, extra={}) { context.title = ["Bookmark", "Title"]; context.format = bookmarks.format; iter(extra).forEach(function ([k, v]) { @@ -721,11 +722,12 @@ var Bookmarks = Module("bookmarks", { ctxt.hasItems = ctxt.completions.length; ctxt.incomplete = true; - ctxt.cache.request = bookmarks.getSuggestions(name, ctxt.filter, function (compl) { + ctxt.cache.request = bookmarks.getSuggestions(name, ctxt.filter); + ctxt.cache.request.then(function (compl) { ctxt.incomplete = false; ctxt.completions = array.uniq(ctxt.completions.filter(c => compl.indexOf(c) >= 0) .concat(compl), true); - }); + }, Cu.reportError); }); }; diff --git a/common/content/commandline.js b/common/content/commandline.js index 1a9ccb17..584ae3bf 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2013 Kris Maglione +// Copyright (c) 2008-2014 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -845,7 +845,6 @@ var CommandLine = Module("commandline", { * pop at any time to close the prompt. * * @param {string} prompt The input prompt to use. - * @param {function(string)} callback * @param {Object} extra * @... {function} onChange - A function to be called with the current * input every time it changes. @@ -856,15 +855,16 @@ var CommandLine = Module("commandline", { * @... {string} default - The initial value that will be returned * if the user presses straightaway. @default "" */ - input: function _input(prompt, callback, extra = {}) { - CommandPromptMode(prompt, update({ onSubmit: callback }, extra)).open(); - }, + input: promises.withCallbacks(function _input([callback, reject], prompt, extra={}, thing={}) { + if (callable(extra)) + // Deprecated. + [callback, extra] = [extra, thing]; + + CommandPromptMode(prompt, update({ onSubmit: callback, onCancel: reject }, extra)).open(); + }), readHeredoc: function readHeredoc(end) { - let args; - commandline.inputMultiline(end, function (res) { args = res; }); - util.waitFor(() => args !== undefined); - return args; + return util.waitFor(commandline.inputMultiline(end)); }, /** @@ -873,10 +873,10 @@ var CommandLine = Module("commandline", { * callback with that string as a parameter. * * @param {string} end - * @param {function(string)} callback + * @returns {Promise} */ // FIXME: Buggy, especially when pasting. - inputMultiline: function inputMultiline(end, callback) { + inputMultiline: promises.withCallbacks(function inputMultiline([callback], end) { let cmd = this.command; let self = { end: "\n" + end + "\n", @@ -902,7 +902,7 @@ var CommandLine = Module("commandline", { this._autosizeMultilineInputWidget(); this.timeout(function () { dactyl.focus(this.widgets.multilineInput); }, 10); - }, + }), get commandMode() this.commandSession && isinstance(modes.main, modes.COMMAND_LINE), @@ -1384,7 +1384,7 @@ var CommandLine = Module("commandline", { * @default {@link #selected} * @returns {object} */ - getItem: function getItem(tuple = this.selected) + getItem: function getItem(tuple=this.selected) tuple && tuple[0] && tuple[0].items[tuple[1]], /** @@ -1499,7 +1499,7 @@ var CommandLine = Module("commandline", { * @default false * @private */ - select: function select(idx, count = 1, fromTab = false) { + select: function select(idx, count=1, fromTab=false) { switch (idx) { case this.UP: case this.DOWN: diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 93b24e0f..a3443e56 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -538,7 +538,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * @param {boolean} silent Whether the command should be echoed on the * command line. */ - execute: function execute(str, modifiers = {}, silent = false) { + execute: function execute(str, modifiers={}, silent=false) { // skip comments and blank lines if (/^\s*("|$)/.test(str)) return; @@ -890,13 +890,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * tabs. * @returns {boolean} */ - open: function open(urls, params = {}, force = false) { + open: function open(urls, params={}, force=false) { if (typeof urls == "string") urls = dactyl.parseURLs(urls); if (urls.length > prefs.get("browser.tabs.maxOpenBeforeWarn", 20) && !force) - return commandline.input(_("dactyl.prompt.openMany", urls.length) + " ", - function (resp) { + return commandline.input(_("dactyl.prompt.openMany", urls.length) + " ") + .then(function (resp) { if (resp && resp.match(/^y(es)?$/i)) dactyl.open(urls, params, true); }); @@ -1184,7 +1184,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { return []; } }, - wrapCallback: function wrapCallback(callback, self = this) { + wrapCallback: function wrapCallback(callback, self=this) { let save = ["forceOpen"]; let saved = save.map(p => dactyl[p]); return function wrappedCallback() { diff --git a/common/content/editor.js b/common/content/editor.js index 39e9a857..c7f5d59a 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -386,8 +386,8 @@ var Editor = Module("editor", XPCOM(Ci.nsIEditActionListener, ModuleBase), { let keepFocus = modes.stack.some(m => isinstance(m.main, modes.COMMAND_LINE)); if (!forceEditing && textBox && textBox.type == "password") { - commandline.input(_("editor.prompt.editPassword") + " ", - function (resp) { + commandline.input(_("editor.prompt.editPassword") + " ") + .then(function (resp) { if (resp && resp.match(/^y(es)?$/i)) editor.editFieldExternally(true); }); @@ -424,7 +424,7 @@ var Editor = Module("editor", XPCOM(Ci.nsIEditActionListener, ModuleBase), { column = 1 + pre.replace(/[^]*\n/, "").length; let origGroup = DOM(textBox).highlight.toString(); - let cleanup = util.yieldable(function cleanup(error) { + let cleanup = promises.task(function cleanup(error) { if (timer) timer.cancel(); @@ -443,9 +443,11 @@ var Editor = Module("editor", XPCOM(Ci.nsIEditActionListener, ModuleBase), { DOM(textBox).highlight.remove("EditorEditing"); if (!keepFocus) dactyl.focus(textBox); + for (let group in values(blink.concat(blink, ""))) { highlight.highlightNode(textBox, origGroup + " " + group); - yield 100; + + yield promises.sleep(100); } } }); diff --git a/common/content/events.js b/common/content/events.js index 2460e6ed..be6220b1 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -212,7 +212,7 @@ var Events = Module("events", { /** * Wraps an event listener to ensure that errors are reported. */ - wrapListener: function wrapListener(method, self = this) { + wrapListener: function wrapListener(method, self=this) { method.wrapper = wrappedListener; wrappedListener.wrapped = method; function wrappedListener(event) { diff --git a/common/content/hints.js b/common/content/hints.js index 019f753e..79a6fd62 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2013 Kris Maglione +// Copyright (c) 2008-2014 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -12,7 +12,7 @@ var HintSession = Class("HintSession", CommandMode, { get extendedMode() modes.HINTS, - init: function init(mode, opts = {}) { + init: function init(mode, opts={}) { init.supercall(this); if (!opts.window) @@ -1054,11 +1054,11 @@ var Hints = Module("hints", { return null; }, //}}} - open: function open(mode, opts = {}) { + open: function open(mode, opts={}) { this._extendedhintCount = opts.count; mappings.pushCommand(); - commandline.input(["Normal", mode], null, { + commandline.input(["Normal", mode], { autocomplete: false, completer: function (context) { context.compare = () => 0; diff --git a/common/content/history.js b/common/content/history.js index 62aacf1b..5b2c6d3f 100644 --- a/common/content/history.js +++ b/common/content/history.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2013 Kris Maglione +// Copyright (c) 2008-2014 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -13,7 +13,7 @@ var History = Module("history", { get service() services.history, - get: function get(filter, maxItems, sort = this.SORT_DEFAULT) { + get: function get(filter, maxItems, sort=this.SORT_DEFAULT) { if (isString(filter)) filter = { searchTerms: filter }; diff --git a/common/content/mappings.js b/common/content/mappings.js index e6e47681..8109a579 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -187,7 +187,7 @@ var MapHive = Class("MapHive", Contexts.Hive, { * @param {Object} extra An optional extra configuration hash. * @optional */ - add: function (modes, keys, description, action, extra = {}) { + add: function (modes, keys, description, action, extra={}) { modes = Array.concat(modes); if (!modes.every(util.identity)) throw TypeError(/*L*/"Invalid modes: " + modes); @@ -817,7 +817,7 @@ var Mappings = Module("mappings", { }); }, completion: function initCompletion(dactyl, modules, window) { - completion.userMapping = function userMapping(context, modes_ = [modes.NORMAL], hive = mappings.user) { + completion.userMapping = function userMapping(context, modes_=[modes.NORMAL], hive=mappings.user) { context.keys = { text: function (m) m.names[0], description: function (m) m.description + ": " + m.action }; context.completions = hive.iterate(modes_); diff --git a/common/content/marks.js b/common/content/marks.js index cfa27bf3..8bb37916 100644 --- a/common/content/marks.js +++ b/common/content/marks.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2013 Kris Maglione +// Copyright (c) 2008-2014 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -34,7 +34,7 @@ var Marks = Module("marks", { get localURI() buffer.focusedFrame.document.documentURI.replace(/#.*/, ""), - Mark: function Mark(params = {}) { + Mark: function Mark(params={}) { let win = buffer.focusedFrame; let doc = win.document; diff --git a/common/content/modes.js b/common/content/modes.js index cab9600e..897eacf7 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -505,7 +505,7 @@ var Modes = Module("modes", { return StackElement; })(), cacheId: 0, - boundProperty: function BoundProperty(desc = {}) { + boundProperty: function BoundProperty(desc={}) { let id = this.cacheId++; let value; diff --git a/common/content/tabs.js b/common/content/tabs.js index b9674ca9..500d0eea 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -406,7 +406,7 @@ var Tabs = Module("tabs", { * @param {number} count How many tabs to remove. * @param {boolean} focusLeftTab Focus the tab to the left of the removed tab. */ - remove: function remove(tab, count = 1, focusLeftTab = false) { + remove: function remove(tab, count=1, focusLeftTab=false) { let res = this.count > count; let tabs = this.visibleTabs; diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 8543e5da..76a4f5fc 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -161,6 +161,7 @@ defineModule("base", { this.lazyRequire("cache", ["cache"]); this.lazyRequire("config", ["config"]); this.lazyRequire("messages", ["_", "Messages"]); +this.lazyRequire("promises", ["Task", "promises"]); this.lazyRequire("services", ["services"]); this.lazyRequire("storage", ["File"]); this.lazyRequire("util", ["FailedAssertion", "util"]); @@ -915,16 +916,16 @@ Class.Memoize = function Memoize(getter, wait) } }); - util.yieldable(function () { + Task.spawn(function () { let wait; for (var res in getter.call(obj)) { if (wait !== undefined) - yield wait; + yield promises.sleep(wait); wait = res; } Class.replaceProperty(obj, key, res); done = true; - })(); + }); return this[key]; }; @@ -1147,7 +1148,7 @@ let stub = Class.Property({ */ var ErrorBase = Class("ErrorBase", Error, { level: 2, - init: function EB_init(message, level = 0) { + init: function EB_init(message, level=0) { let error = Error(message); update(this, error); this.stack = error.stack; @@ -1321,7 +1322,7 @@ var StructBase = Class("StructBase", Array, { }); var Timer = Class("Timer", { - init: function init(minInterval, maxInterval, callback, self = this) { + init: function init(minInterval, maxInterval, callback, self=this) { this._timer = services.Timer(); this.callback = callback; this.self = self; diff --git a/common/modules/buffer.jsm b/common/modules/buffer.jsm index 6da0ed2b..e196344b 100644 --- a/common/modules/buffer.jsm +++ b/common/modules/buffer.jsm @@ -85,10 +85,10 @@ var Buffer = Module("Buffer", { * @param {string} pref The name of the preference to return. * @returns {Promise} */ - get: promises.withCallback(function get(callback, pref) { + get: promises.withCallbacks(function get([resolve], pref) { services.contentPrefs.getByDomainAndName( self.uri.host, pref, self.loadContext, - callback); + resolve); }), /** @@ -97,10 +97,10 @@ var Buffer = Module("Buffer", { * @param {string} pref The preference to set. * @param {string} value The value to store. */ - set: promises.withCallback(function set(callback, pref, value) { + set: promises.withCallbacks(function set([resolve], pref, value) { services.contentPrefs.set( self.uri.host, pref, value, self.loadContext, - callback); + resolve); }), /** @@ -108,9 +108,9 @@ var Buffer = Module("Buffer", { * * @param {string} pref The preference to clear. */ - clear: promises.withCallback(function clear(callback, pref) { + clear: promises.withCallbacks(function clear([resolve], pref) { services.contentPrefs.removeByDomainAndName( - self.uri.domain, pref, self.loadContext, callback); + self.uri.domain, pref, self.loadContext, resolve); }), })), @@ -834,7 +834,7 @@ var Buffer = Module("Buffer", { * @param {number} count The multiple of 'scroll' lines to scroll. * @optional */ - scrollByScrollSize: function scrollByScrollSize(direction, count = 1) { + scrollByScrollSize: function scrollByScrollSize(direction, count=1) { let { options } = this.modules; direction = direction ? 1 : -1; diff --git a/common/modules/commands.jsm b/common/modules/commands.jsm index ade668fd..afb54b7e 100644 --- a/common/modules/commands.jsm +++ b/common/modules/commands.jsm @@ -155,7 +155,7 @@ var Command = Class("Command", { * @param {Args} args The Args object passed to {@link #action}. * @param {Object} modifiers Any modifiers to be passed to {@link #action}. */ - execute: function execute(args, modifiers = {}) { + execute: function execute(args, modifiers={}) { const { dactyl } = this.modules; let context = args.context; @@ -558,7 +558,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, { * @param {boolean} replace Replace an existing command of the same name. * @optional */ - add: function add(specs, description, action, extra = {}, replace = false) { + add: function add(specs, description, action, extra={}, replace=false) { const { commands, contexts } = this.modules; if (!extra.definedAt) @@ -597,7 +597,7 @@ var CommandHive = Class("CommandHive", Contexts.Hive, { return name; }, - _add: function _add(names, description, action, extra = {}, replace = false) { + _add: function _add(names, description, action, extra={}, replace=false) { const { contexts } = this.modules; extra.definedAt = contexts.getCaller(Components.stack.caller.caller); return this.add.apply(this, arguments); @@ -969,7 +969,7 @@ var Commands = Module("commands", { * Args object. * @returns {Args} */ - parseArgs: function parseArgs(str, params = {}) { + parseArgs: function parseArgs(str, params={}) { const self = this; function getNextArg(str, _keepQuotes=keepQuotes) { @@ -1777,7 +1777,7 @@ var Commands = Module("commands", { } }); -let quote = function quote(q, list, map = Commands.quoteMap) { +let quote = function quote(q, list, map=Commands.quoteMap) { let re = RegExp("[" + list + "]", "g"); function quote(str) (q + String.replace(str, re, $0 => ($0 in map ? map[$0] : ("\\" + $0))) + q); diff --git a/common/modules/completion.jsm b/common/modules/completion.jsm index 7d4e04f7..3cfddce4 100644 --- a/common/modules/completion.jsm +++ b/common/modules/completion.jsm @@ -33,7 +33,7 @@ lazyRequire("template", ["template"]); * @constructor */ var CompletionContext = Class("CompletionContext", { - init: function cc_init(editor, name = "", offset = 0) { + init: function cc_init(editor, name="", offset=0) { let self = this; if (editor instanceof this.constructor) { let parent = editor; diff --git a/common/modules/config.jsm b/common/modules/config.jsm index 44d3d252..c460bcb0 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -132,6 +132,7 @@ var ConfigBase = Class("ConfigBase", { "options", "overlay", "prefs", + ["promises", "Promise", "Task", "promises"], "protocol", "sanitizer", "services", diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 5d913c51..04cd0dc3 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -15,6 +15,7 @@ defineModule("io", { lazyRequire("config", ["config"]); lazyRequire("contexts", ["Contexts", "contexts"]); +lazyRequire("promises", ["Promise"]); lazyRequire("storage", ["File", "storage"]); lazyRequire("styles", ["styles"]); lazyRequire("template", ["template"]); @@ -318,7 +319,7 @@ var IO = Module("io", { * @default "" * @returns {File} */ - createTempFile: function createTempFile(ext = "txt", label = "") { + createTempFile: function createTempFile(ext="txt", label="") { let file = services.directory.get("TmpD", Ci.nsIFile); file.append(config.name + label + "." + ext); file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, octal(666)); @@ -446,26 +447,29 @@ var IO = Module("io", { let process = services.Process(file.file); process.run(false, args.map(String), args.length); - try { - if (callable(blocking)) - var timer = services.Timer( - function () { - if (!process.isRunning) { - timer.cancel(); - util.trapErrors(blocking, self, process.exitValue); - } - }, - 100, services.Timer.TYPE_REPEATING_SLACK); - else if (blocking) - while (process.isRunning) - util.threadYield(false, true); - } - catch (e) { - process.kill(); - throw e; + + let deferred = Promise.defer(); + + if (callable(blocking)) + // Deprecated. + deferred.promise.then(blocking); + else if (blocking) { + // Deprecated? + while (process.isRunning) + util.threadYield(false, true); + return process.exitValue; } - return process.exitValue; + let timer = services.Timer( + function () { + if (!process.isRunning) { + timer.cancel(); + deferred.resolve(process.exitValue); + } + }, + 100, services.Timer.TYPE_REPEATING_SLACK); + + return deferred.promise; }, // TODO: when https://bugzilla.mozilla.org/show_bug.cgi?id=68702 is diff --git a/common/modules/messages.jsm b/common/modules/messages.jsm index c4b0dea8..1b65009d 100644 --- a/common/modules/messages.jsm +++ b/common/modules/messages.jsm @@ -11,7 +11,7 @@ defineModule("messages", { var Messages = Module("messages", { - init: function init(name = "messages") { + init: function init(name="messages") { let self = this; this.name = name; @@ -107,7 +107,7 @@ var Messages = Module("messages", { let { Buffer, commands, hints, io, mappings, modes, options, sanitizer } = overlay.activeModules; file = io.File(file); - function properties(base, iter_, prop = "description") iter(function _properties() { + function properties(base, iter_, prop="description") iter(function _properties() { function key(...args) [base, obj.identifier || obj.name].concat(args).join(".").replace(/[\\:=]/g, "\\$&"); for (var obj in iter_) { diff --git a/common/modules/promises.jsm b/common/modules/promises.jsm index 25aba2f5..a1b832f9 100644 --- a/common/modules/promises.jsm +++ b/common/modules/promises.jsm @@ -9,10 +9,86 @@ defineModule("promises", { require: [] }); +lazyRequire("services", ["services"]); + lazyRequire("resource://gre/modules/Promise.jsm", ["Promise"]); lazyRequire("resource://gre/modules/Task.jsm", ["Task"]); +function withCallbacks(fn) { + return function wrapper(...args) { + let deferred = Promise.defer(); + function resolve(arg) { deferred.resolve(arg); } + function reject(arg) { deferred.reject(arg); } + fn.apply(this, [[resolve, reject, deferred]].concat(args)); + return deferred.promise; + } +} + var Promises = Module("Promises", { + _cancel: WeakMap(), + + /** + * Allows promises to be canceled.. + * + * @param {Promise} promise The promise to cancel. + * @param {*} arg Argument to be passed to the cancellation + * function. + */ + cancel: function cancel(promise, reason) { + let cleanup = this._cancel.get(promise); + if (cleanup) { + cleanup[0](promise); + cleanup[1].reject(reason); + } + this._cancel.delete(promise); + }, + + /** + * Registers a cleanup function for the given deferred promise. + * + * @param {Deferred} promise The promise to cancel. + * @param {function} fn The cleanup function. + */ + oncancel: function oncancel(deferred, fn) { + this._cancel.set(deferred.promise, [fn, deferred]); + }, + + /** + * Returns a promise which resolves after a brief delay. + */ + delay: withCallbacks(function delay([accept]) { + let { mainThread } = services.threading; + mainThread.dispatch(accept, mainThread.DISPATCH_NORMAL); + }), + + /** + * Returns a promise which resolves with the given argument. + */ + accept: function fail(arg) { + let deferred = Promise.defer(); + deferred.resolve(arg); + return deferred.promise; + }, + + /** + * Returns a promise which fails with the given argument. + */ + fail: function fail(arg) { + let deferred = Promise.defer(); + deferred.reject(arg); + return deferred.promise; + }, + + /** + * Returns a promise which resolves after the given number of + * milliseconds. + * + * @param {number} delay The number of milliseconds to wait. + */ + sleep: withCallbacks(function sleep([callback], delay) { + this.timeout(callback, delay); + }), + /** * Wraps the given function so that each call spawns a Task. * @@ -26,21 +102,45 @@ var Promises = Module("Promises", { }, /** - * Wraps the given function so that its first argument is a - * callback which, when called, resolves the returned promise. + * Returns a promise which resolves when the function *test* returns + * true, or *timeout* milliseconds have expired. + * + * @param {function} test The predicate on which to wait. + * @param {Number} timeout The maximum number of milliseconds to + * wait. + * @optional + * @param {number} pollInterval The poll interval, in milliseconds. + * @default 10 + */ + waitFor: withCallbacks(function waitFor([accept, reject], test, timeout=null, pollInterval=10) { + let end = timeout && Date.now() + timeout, result; + + let timer = services.Timer( + () => { + try { + var result = test(); + } + catch (e) { + timer.cancel(); + reject(e); + } + if (result) { + timer.cancel(); + accept(result); + } + }, + pollInterval, services.Timer.TYPE_REPEATING_SLACK); + }), + + /** + * Wraps the given function so that its first argument is an array + * of success and failure callbacks which, when called, resolve the + * returned promise. * * @param {function} fn The function to wrap. * @returns {Promise} */ - withCallback: function withCallback(fn) { - return function wrapper(...args) { - let deferred = Promise.defer(); - function callback(arg) { - deferred.resolve(arg); - } - return fn.apply(this, [callback].concat(args)); - } - }, + withCallbacks: withCallbacks, }); endModule(); diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index 31756070..12cf60fa 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -382,7 +382,7 @@ var Styles = Module("Styles", { return val; }, - completeSite: function (context, content, group = styles.user) { + completeSite: function (context, content, group=styles.user) { context.anchored = false; try { context.fork("current", 0, this, function (context) { diff --git a/common/modules/template.jsm b/common/modules/template.jsm index 17c04cf3..4aa2c888 100644 --- a/common/modules/template.jsm +++ b/common/modules/template.jsm @@ -467,7 +467,7 @@ var Template = Module("Template", { ["td", { style: style[i] || "" }, d])])]; }, - usage: function usage(iter, format = {}) { + usage: function usage(iter, format={}) { let desc = format.description || (item => this.linkifyHelp(item.description)); let help = format.help || (item => item.name); let sourceLink = (frame) => { diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 23a4088a..b05fc199 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -10,7 +10,7 @@ try { defineModule("util", { exports: ["DOM", "$", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"], - require: ["dom", "services"] + require: ["dom", "promises", "services"] }); lazyRequire("overlay", ["overlay"]); @@ -750,10 +750,10 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * * @returns {XMLHttpRequest} */ - httpGet: function httpGet(url, callback, self) { - let params = callback; - if (!isObject(params)) - params = { callback: params && ((...args) => callback.apply(self, args)) }; + httpGet: function httpGet(url, params, self) { + if (callable(params)) + // Deprecated. + params = { callback: params.bind(self) }; try { let xmlhttp = services.Xmlhttp(); @@ -761,8 +761,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), let async = params.callback || params.onload || params.onerror; if (async) { - xmlhttp.addEventListener("load", function handler(event) { util.trapErrors(params.onload || params.callback, params, xmlhttp, event); }, false); - xmlhttp.addEventListener("error", function handler(event) { util.trapErrors(params.onerror || params.callback, params, xmlhttp, event); }, false); + xmlhttp.addEventListener("load", event => { util.trapErrors(params.onload || params.callback, params, xmlhttp, event); }, false); + xmlhttp.addEventListener("error", event => { util.trapErrors(params.onerror || params.callback, params, xmlhttp, event); }, false); } if (isObject(params.params)) { @@ -810,6 +810,22 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } }, + /** + * Like #httpGet, but returns a promise rather than accepting + * callbacks. + * + * @param {string} url The URL to fetch. + * @param {object} params Parameter object, as in #httpGet. + */ + fetchUrl: promises.withCallbacks(function fetchUrl([accept, reject, deferred], url, params) { + params = update({}, params); + params.onload = accept; + params.onerror = reject; + + let req = this.httpGet(url, params); + promises.oncancel(deferred, req.cancel); + }), + /** * The identity function. * @@ -1555,7 +1571,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * Waits for the function *test* to return true, or *timeout* * milliseconds to expire. * - * @param {function} test The predicate on which to wait. + * @param {function|Promise} test The predicate on which to wait. * @param {object} self The 'this' object for *test*. * @param {Number} timeout The maximum number of milliseconds to * wait. @@ -1565,6 +1581,15 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * thrown. */ waitFor: function waitFor(test, self, timeout, interruptable) { + if (!callable(test)) { + let done = false; + var promise = test, + retVal; + promise.then((arg) => { retVal = arg; done = true; }, + (arg) => { retVal = arg; done = true; }); + test = () => done; + } + let end = timeout && Date.now() + timeout, result; let timer = services.Timer(function () {}, 10, services.Timer.TYPE_REPEATING_SLACK); @@ -1575,7 +1600,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), finally { timer.cancel(); } - return result; + return promise ? retVal: result; }, /** @@ -1595,7 +1620,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * @returns {function} A new function which may not execute * synchronously. */ - yieldable: function yieldable(func) + yieldable: deprecated("Task.spawn", function yieldable(func) function magic() { let gen = func.apply(this, arguments); (function next() { @@ -1604,7 +1629,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } catch (e if e instanceof StopIteration) {}; })(); - }, + }), /** * Wraps a callback function such that its errors are not lost. This