function installFromURL(url) {
        var STATE_DOWNLOADING = 1,
            STATE_INSTALLING = 2,
            STATE_SUCCEEDED = 3,
            STATE_FAILED = 4;

        var d = new $.Deferred();
        var state = STATE_DOWNLOADING;

        var downloadId = (_uniqueId++);
        download(url, downloadId)
            .done(function (downloadResult) {
                state = STATE_INSTALLING;

                installFromPath(downloadResult.localPath, downloadResult.filenameHint)
                    .done(function (result) {
                        var installationStatus = result.installationStatus;

                        state = STATE_SUCCEEDED;
                        result.localPath = downloadResult.localPath;
                        result.keepFile = false;

                        if (installationStatus === InstallationStatuses.INSTALLED) {
                            // Delete temp file
                            FileSystem.getFileForPath(downloadResult.localPath).unlink();
                        }

                        d.resolve(result);
                    })
                    .fail(function (err) {
                        // File IO errors, internal error in install()/validate(), or extension startup crashed
                        state = STATE_FAILED;
                        FileSystem.getFileForPath(downloadResult.localPath).unlink();
                        d.reject(err);  // TODO: needs to be err.message ?
                    });
            })
            .fail(function (err) {
                // Download error (the Node-side download code cleans up any partial ZIP file)
                state = STATE_FAILED;
                d.reject(err);
            });

        return {
            promise: d.promise(),
            cancel: function () {
                if (state === STATE_DOWNLOADING) {
                    // This will trigger download()'s fail() handler with CANCELED as the err code
                    cancelDownload(downloadId);
                }
                // Else it's too late to cancel; we'll continue on through the done() chain and emit
                // a success result (calling done() handlers) if all else goes well.
            }
        };
    }
On success, resolves with an extension metadata object; at that point, the extension has already
started running in Brackets. On failure (including validation errors), rejects with an error object.

An error object consists of either a string error code OR an array where the first entry is the error
code and the remaining entries are further info. The error code string is one of either
ExtensionsDomain.Errors or Package.Errors. Use formatError() to convert an error object to a friendly,
localized error message.

The returned cancel() function will *attempt* to cancel installation, but it is not guaranteed to
succeed. If cancel() succeeds, the Promise is rejected with a CANCELED error code. If we're unable
to cancel, the Promise is resolved or rejected normally, as if cancel() had never been called.

@return {{promise: $.Promise, cancel: function():boolean}}