Bug 1001074 - Uplift Add-on SDK to Firefox r=me

This commit is contained in:
Erik Vold 2014-04-24 12:32:56 -07:00
parent b4f08e51ba
commit a0ae90c65d
34 changed files with 1227 additions and 800 deletions

View File

@ -81,8 +81,11 @@ exports.set = function(aData, aDataType) {
options.datatype = dataURL.mimeType;
options.data = dataURL.data;
}
catch (e if e.name === "URIError") {
// Not a valid Data URL
catch (e) {
// Ignore invalid URIs
if (e.name !== "URIError") {
throw e;
}
}
}

View File

@ -12,7 +12,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
const self = require("../self");
const prefs = require("../preferences/service");
const { merge } = require("../util/object");
const { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
const { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
const DEFAULT_LOG_LEVEL = "error";
const ADDON_LOG_LEVEL_PREF = "extensions." + self.id + ".sdk.console.logLevel";
@ -44,12 +44,13 @@ let branch = Cc["@mozilla.org/preferences-service;1"].
branch.addObserver(ADDON_LOG_LEVEL_PREF, logLevelObserver, true);
branch.addObserver(SDK_LOG_LEVEL_PREF, logLevelObserver, true);
function PlainTextConsole(print) {
function PlainTextConsole(print, innerID) {
let consoleOptions = {
prefix: self.name + ": ",
maxLogLevel: logLevel,
dump: print
dump: print,
innerID: innerID
};
let console = new ConsoleAPI(consoleOptions);

View File

@ -19,6 +19,7 @@ const { sandbox, evaluate, load } = require('../loader/sandbox');
const { merge } = require('../util/object');
const { getTabForContentWindow } = require('../tabs/utils');
const { getInnerId } = require('../window/utils');
const { PlainTextConsole } = require('../console/plain-text');
// WeakMap of sandboxes so we can access private values
const sandboxes = new WeakMap();
@ -197,8 +198,10 @@ const WorkerSandbox = Class({
// script
merge(model, result);
let console = new PlainTextConsole(null, getInnerId(window));
// Handle messages send by this script:
setListeners(this);
setListeners(this, console);
// Inject `addon` global into target document if document is trusted,
// `addon` in document is equivalent to `self` in content script.
@ -304,7 +307,7 @@ function importScripts (workerSandbox, ...urls) {
}
}
function setListeners (workerSandbox) {
function setListeners (workerSandbox, console) {
let { worker } = modelFor(workerSandbox);
// console.xxx calls
workerSandbox.on('console', function consoleListener (kind, ...args) {

View File

@ -1,243 +1,29 @@
;(function(id, factory) { // Module boilerplate :(
if (typeof(define) === 'function') { // RequireJS
define(factory);
} else if (typeof(require) === 'function') { // CommonJS
factory.call(this, require, exports, module);
} else if (String(this).indexOf('BackstagePass') >= 0) { // JSM
this[factory.name] = {};
try {
this.console = this['Components'].utils
.import('resource://gre/modules/devtools/Console.jsm', {}).console;
}
catch (ex) {
// Avoid failures on different toolkit configurations.
}
factory(function require(uri) {
var imports = {};
this['Components'].utils.import(uri, imports);
return imports;
}, this[factory.name], { uri: __URI__, id: id });
this.EXPORTED_SYMBOLS = [factory.name];
} else if (~String(this).indexOf('Sandbox')) { // Sandbox
factory(function require(uri) {}, this, { id: id });
} else { // Browser or alike
var globals = this;
factory(function require(id) {
return globals[id];
}, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
}
}).call(this, 'promise/core', function Promise(require, exports, module) {
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
'use strict';
/*
* Uses `Promise.jsm` as a core implementation, with additional sugar
* from previous implementation, with inspiration from `Q` and `when`
*
* https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm
* https://github.com/cujojs/when
* https://github.com/kriskowal/q
*/
const PROMISE_URI = 'resource://gre/modules/Promise.jsm';
getEnvironment.call(this, function ({ require, exports, module, Cu }) {
const { defer, resolve, all, reject, race } = Cu.import(PROMISE_URI, {}).Promise;
module.metadata = {
"stability": "unstable"
'stability': 'unstable'
};
/**
* Internal utility: Wraps given `value` into simplified promise, successfully
* fulfilled to a given `value`. Note the result is not a complete promise
* implementation, as its method `then` does not returns anything.
*/
function fulfilled(value) {
return { then: function then(fulfill) { fulfill(value); } };
}
/**
* Internal utility: Wraps given input into simplified promise, pre-rejected
* with a given `reason`. Note the result is not a complete promise
* implementation, as its method `then` does not returns anything.
*/
function rejected(reason) {
return { then: function then(fulfill, reject) { reject(reason); } };
}
/**
* Internal utility: Returns `true` if given `value` is a promise. Value is
* assumed to be a promise if it implements method `then`.
*/
function isPromise(value) {
return value && typeof(value.then) === 'function';
}
/**
* Creates deferred object containing fresh promise & methods to either resolve
* or reject it. The result is an object with the following properties:
* - `promise` Eventual value representation implementing CommonJS [Promises/A]
* (http://wiki.commonjs.org/wiki/Promises/A) API.
* - `resolve` Single shot function that resolves enclosed `promise` with a
* given `value`.
* - `reject` Single shot function that rejects enclosed `promise` with a given
* `reason`.
*
* An optional `prototype` argument is used as a prototype of the returned
* `promise` allowing one to implement additional API. If prototype is not
* passed then it falls back to `Object.prototype`.
*
* ## Example
*
* function fetchURI(uri, type) {
* var deferred = defer();
* var request = new XMLHttpRequest();
* request.open("GET", uri, true);
* request.responseType = type;
* request.onload = function onload() {
* deferred.resolve(request.response);
* }
* request.onerror = function(event) {
* deferred.reject(event);
* }
* request.send();
*
* return deferred.promise;
* }
*/
function defer(prototype) {
// Define FIFO queue of observer pairs. Once promise is resolved & all queued
// observers are forwarded to `result` and variable is set to `null`.
var observers = [];
// Promise `result`, which will be assigned a resolution value once promise
// is resolved. Note that result will always be assigned promise (or alike)
// object to take care of propagation through promise chains. If result is
// `null` promise is not resolved yet.
var result = null;
prototype = (prototype || prototype === null) ? prototype : Object.prototype;
// Create an object implementing promise API.
var promise = Object.create(prototype, {
then: { value: function then(onFulfill, onError) {
var deferred = defer(prototype);
function resolve(value) {
// If `onFulfill` handler is provided resolve `deferred.promise` with
// result of invoking it with a resolution value. If handler is not
// provided propagate value through.
try {
deferred.resolve(onFulfill ? onFulfill(value) : value);
}
// `onFulfill` may throw exception in which case resulting promise
// is rejected with thrown exception.
catch(error) {
if (exports._reportErrors && typeof(console) === 'object')
console.error(error);
// Note: Following is equivalent of `deferred.reject(error)`,
// we use this shortcut to reduce a stack.
deferred.resolve(rejected(error));
}
}
function reject(reason) {
try {
if (onError) deferred.resolve(onError(reason));
else deferred.resolve(rejected(reason));
}
catch(error) {
if (exports._reportErrors && typeof(console) === 'object')
console.error(error);
deferred.resolve(rejected(error));
}
}
// If enclosed promise (`this.promise`) observers queue is still alive
// enqueue a new observer pair into it. Note that this does not
// necessary means that promise is pending, it may already be resolved,
// but we still have to queue observers to guarantee an order of
// propagation.
if (observers) {
observers.push({ resolve: resolve, reject: reject });
}
// Otherwise just forward observer pair right to a `result` promise.
else {
result.then(resolve, reject);
}
return deferred.promise;
}}
})
var deferred = {
promise: promise,
/**
* Resolves associated `promise` to a given `value`, unless it's already
* resolved or rejected. Note that resolved promise is not necessary a
* successfully fulfilled. Promise may be resolved with a promise `value`
* in which case `value` promise's fulfillment / rejection will propagate
* up to a promise resolved with `value`.
*/
resolve: function resolve(value) {
if (!result) {
// Store resolution `value` in a `result` as a promise, so that all
// the subsequent handlers can be simply forwarded to it. Since
// `result` will be a promise all the value / error propagation will
// be uniformly taken care of.
result = isPromise(value) ? value : fulfilled(value);
// Forward already registered observers to a `result` promise in the
// order they were registered. Note that we intentionally dequeue
// observer at a time until queue is exhausted. This makes sure that
// handlers registered as side effect of observer forwarding are
// queued instead of being invoked immediately, guaranteeing FIFO
// order.
while (observers.length) {
var observer = observers.shift();
result.then(observer.resolve, observer.reject);
}
// Once `observers` queue is exhausted we `null`-ify it, so that
// new handlers are forwarded straight to the `result`.
observers = null;
}
},
/**
* Rejects associated `promise` with a given `reason`, unless it's already
* resolved / rejected. This is just a (better performing) convenience
* shortcut for `deferred.resolve(reject(reason))`.
*/
reject: function reject(reason) {
// Note that if promise is resolved that does not necessary means that it
// is successfully fulfilled. Resolution value may be a promise in which
// case its result propagates. In other words if promise `a` is resolved
// with promise `b`, `a` is either fulfilled or rejected depending
// on weather `b` is fulfilled or rejected. Here `deferred.promise` is
// resolved with a promise pre-rejected with a given `reason`, there for
// `deferred.promise` is rejected with a given `reason`. This may feel
// little awkward first, but doing it this way greatly simplifies
// propagation through promise chains.
deferred.resolve(rejected(reason));
}
};
return deferred;
}
exports.defer = defer;
/**
* Returns a promise resolved to a given `value`. Optionally a second
* `prototype` argument may be provided to be used as a prototype for the
* returned promise.
*/
function resolve(value, prototype) {
var deferred = defer(prototype);
deferred.resolve(value);
return deferred.promise;
}
exports.resolve = resolve;
/**
* Returns a promise rejected with a given `reason`. Optionally a second
* `prototype` argument may be provided to be used as a prototype for the
* returned promise.
*/
function reject(reason, prototype) {
var deferred = defer(prototype);
deferred.reject(reason);
return deferred.promise;
}
exports.reject = reject;
var promised = (function() {
let promised = (function() {
// Note: Define shortcuts and utility functions here in order to avoid
// slower property accesses and unnecessary closure creations on each
// call of this popular function.
@ -247,15 +33,14 @@ var promised = (function() {
// Utility function that does following:
// execute([ f, self, args...]) => f.apply(self, args)
function execute(args) { return call.apply(call, args) }
function execute (args) call.apply(call, args)
// Utility function that takes promise of `a` array and maybe promise `b`
// as arguments and returns promise for `a.concat(b)`.
function promisedConcat(promises, unknown) {
return promises.then(function(values) {
return resolve(unknown).then(function(value) {
return values.concat([ value ]);
});
return promises.then(function (values) {
return resolve(unknown)
.then(function (value) values.concat([value]));
});
}
@ -280,11 +65,53 @@ var promised = (function() {
// finally map that to promise of `f.apply(this, args...)`
then(execute);
};
}
};
})();
exports.promised = promised;
var all = promised(Array);
exports.promised = promised;
exports.all = all;
exports.defer = defer;
exports.resolve = resolve;
exports.reject = reject;
exports.race = race;
exports.Promise = Promise;
});
function getEnvironment (callback) {
let Cu, _exports, _module, _require;
// CommonJS / SDK
if (typeof(require) === 'function') {
Cu = require('chrome').Cu;
_exports = exports;
_module = module;
_require = require;
}
// JSM
else if (String(this).indexOf('BackstagePass') >= 0) {
Cu = this['Components'].utils;
_exports = this.Promise = {};
_module = { uri: __URI__, id: 'promise/core' };
_require = uri => {
let imports = {};
Cu.import(uri, imports);
return imports;
};
this.EXPORTED_SYMBOLS = ['Promise'];
// mozIJSSubScriptLoader.loadSubscript
} else if (~String(this).indexOf('Sandbox')) {
Cu = this['Components'].utils;
_exports = this;
_module = { id: 'promise/core' };
_require = uri => {};
}
callback({
Cu: Cu,
exports: _exports,
module: _module,
require: _require
});
}

View File

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
"use strict";
module.metadata = {
"stability": "unstable"
};
const { dispatcher } = require("../util/dispatcher");
// Define `modelFor` accessor function that can be implemented
// for different types of views. Since view's we'll be dealing
// with types that don't really play well with `instanceof`
// operator we're gonig to use `dispatcher` that is slight
// extension over polymorphic dispatch provided by method.
// This allows models to extend implementations of this by
// providing predicates:
//
// modelFor.when($ => $ && $.nodeName === "tab", findTabById($.id))
const modelFor = dispatcher("modelFor");
exports.modelFor = modelFor;

View File

@ -15,27 +15,24 @@ const { merge } = require("../util/object");
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
/**
* Open a channel synchronously for the URI given, with an optional charset, and
* returns a resolved promise if succeed; rejected promise otherwise.
* Reads a URI and returns a promise.
*
* @param uri {string} The URI to read
* @param [options] {object} This parameter can have any or all of the following
* fields: `charset`. By default the `charset` is set to 'UTF-8'.
*
* @returns {promise} The promise that will be resolved with the content of the
* URL given.
*
* @example
* let promise = readURI('resource://gre/modules/NetUtil.jsm', {
* charset: 'US-ASCII'
* });
*/
function readSync(uri, charset) {
let { promise, resolve, reject } = defer();
function readURI(uri, options) {
options = options || {};
let charset = options.charset || 'UTF-8';
try {
resolve(readURISync(uri, charset));
}
catch (e) {
reject("Failed to read: '" + uri + "' (Error Code: " + e.result + ")");
}
return promise;
}
/**
* Open a channel synchronously for the URI given, with an optional charset, and
* returns a promise.
*/
function readAsync(uri, charset) {
let channel = NetUtil.newChannel(uri, charset, null);
let { promise, resolve, reject } = defer();
@ -59,34 +56,6 @@ function readAsync(uri, charset) {
return promise;
}
/**
* Reads a URI and returns a promise. If the `sync` option is set to `true`, the
* promise will be resolved synchronously.
*
* @param uri {string} The URI to read
* @param [options] {object} This parameter can have any or all of the following
* fields: `sync`, `charset`. By default the `charset` is set to 'UTF-8'.
*
* @returns {promise} The promise that will be resolved with the content of the
* URL given.
*
* @example
* let promise = readURI('resource://gre/modules/NetUtil.jsm', {
* sync: true,
* charset: 'US-ASCII'
});
*/
function readURI(uri, options) {
options = merge({
charset: "UTF-8",
sync: false
}, options);
return options.sync
? readSync(uri, options.charset)
: readAsync(uri, options.charset);
}
exports.readURI = readURI;
/**

View File

@ -45,15 +45,16 @@ exports.notify = function notifications_notify(options) {
try {
notifyWithOpts(notify);
}
catch (err if err instanceof Ci.nsIException &&
err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
console.warn("The notification icon named by " + valOpts.iconURL +
" does not exist. A default icon will be used instead.");
delete valOpts.iconURL;
notifyWithOpts(notify);
}
catch (err) {
notifyWithOpts(notifyUsingConsole);
if (err instanceof Ci.nsIException && err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
console.warn("The notification icon named by " + valOpts.iconURL +
" does not exist. A default icon will be used instead.");
delete valOpts.iconURL;
notifyWithOpts(notify);
}
else {
notifyWithOpts(notifyUsingConsole);
}
}
};

View File

@ -367,11 +367,16 @@ function style(panel) {
document.getAnonymousElementByAttribute(panel, "class",
"panel-inner-arrowcontent");
let color = window.getComputedStyle(node).getPropertyValue("color");
let { color, fontFamily, fontSize, fontWeight } = window.getComputedStyle(node);
let style = contentDocument.createElement("style");
style.id = "sdk-panel-style";
style.textContent = "body { color: " + color + "; }";
style.textContent = "body { " +
"color: " + color + ";" +
"font-family: " + fontFamily + ";" +
"font-weight: " + fontWeight + ";" +
"font-size: " + fontSize + ";" +
"}";
let container = contentDocument.head ? contentDocument.head :
contentDocument.documentElement;

View File

@ -49,8 +49,9 @@ exports.TreeNode = TreeNode;
/*
* Descends down from `node` applying `fn` to each in order.
* Can be asynchronous if `fn` returns a promise. `fn` is passed
* one argument, the current node, `curr`
* `fn` can return values or promises -- if promise returned,
* children are not processed until resolved. `fn` is passed
* one argument, the current node, `curr`.
*/
function walk (curr, fn) {
return promised(fn)(curr).then(val => {

View File

@ -137,7 +137,8 @@ exports.isSet = isSet;
function reset(name) {
try {
prefSvc.clearUserPref(name);
} catch (e if e.result == Cr.NS_ERROR_UNEXPECTED) {
}
catch (e) {
// The pref service throws NS_ERROR_UNEXPECTED when the caller tries
// to reset a pref that doesn't exist or is already set to its default
// value. This interface fails silently in those cases, so callers
@ -145,6 +146,9 @@ function reset(name) {
// resetting first or trap exceptions after the fact. It passes through
// other exceptions, however, so callers know about them, since we don't
// know what other exceptions might be thrown and what they might mean.
if (e.result != Cr.NS_ERROR_UNEXPECTED) {
throw e;
}
}
}
exports.reset = reset;

View File

@ -8,13 +8,16 @@ module.metadata = {
"stability": "stable"
};
const { Cc, Ci } = require("chrome");
const { Cc, Ci, Cu } = require("chrome");
const file = require("./io/file");
const prefs = require("./preferences/service");
const jpSelf = require("./self");
const timer = require("./timers");
const unload = require("./system/unload");
const { emit, on, off } = require("./event/core");
const { defer } = require('./core/promise');
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes
@ -35,6 +38,57 @@ Object.defineProperties(exports, {
}
});
function getHash(data) {
let { promise, resolve } = defer();
let crypto = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
crypto.init(crypto.MD5);
let listener = {
onStartRequest: function() { },
onDataAvailable: function(request, context, inputStream, offset, count) {
crypto.updateFromStream(inputStream, count);
},
onStopRequest: function(request, context, status) {
resolve(crypto.finish(false));
}
};
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let stream = converter.convertToInputStream(data);
let pump = Cc["@mozilla.org/network/input-stream-pump;1"].
createInstance(Ci.nsIInputStreamPump);
pump.init(stream, -1, -1, 0, 0, true);
pump.asyncRead(listener, null);
return promise;
}
function writeData(filename, data) {
let { promise, resolve, reject } = defer();
let stream = file.open(filename, "w");
try {
stream.writeAsync(data, err => {
if (err)
reject(err);
else
resolve();
});
}
catch (err) {
// writeAsync closes the stream after it's done, so only close on error.
stream.close();
reject(err);
}
return promise;
}
// A generic JSON store backed by a file on disk. This should be isolated
// enough to move to its own module if need be...
function JsonStore(options) {
@ -43,11 +97,9 @@ function JsonStore(options) {
this.writePeriod = options.writePeriod;
this.onOverQuota = options.onOverQuota;
this.onWrite = options.onWrite;
this.hash = null;
unload.ensure(this);
this.writeTimer = timer.setInterval(this.write.bind(this),
this.writePeriod);
this.startTimer();
}
JsonStore.prototype = {
@ -81,11 +133,18 @@ JsonStore.prototype = {
undefined;
},
startTimer: function JsonStore_startTimer() {
timer.setTimeout(() => {
this.write().then(this.startTimer.bind(this));
}, this.writePeriod);
},
// Removes the backing file and all empty subdirectories.
purge: function JsonStore_purge() {
try {
// This'll throw if the file doesn't exist.
file.remove(this.filename);
this.hash = null;
let parentPath = this.filename;
do {
parentPath = file.dirname(parentPath);
@ -105,31 +164,25 @@ JsonStore.prototype = {
// errors cause tests to fail. Supporting "known" errors in the test
// harness appears to be non-trivial. Maybe later.
this.root = JSON.parse(str);
let self = this;
getHash(str).then(hash => this.hash = hash);
}
catch (err) {
this.root = {};
this.hash = null;
}
},
// If the store is under quota, writes the root to the backing file.
// Otherwise quota observers are notified and nothing is written.
write: function JsonStore_write() {
if (this.quotaUsage > 1)
this.onOverQuota(this);
else
this._write();
},
// Cleans up on unload. If unloading because of uninstall, the store is
// purged; otherwise it's written.
unload: function JsonStore_unload(reason) {
timer.clearInterval(this.writeTimer);
timer.clearTimeout(this.writeTimer);
this.writeTimer = null;
if (reason === "uninstall")
this.purge();
else
this._write();
this.write();
},
// True if the root is an empty object.
@ -148,32 +201,40 @@ JsonStore.prototype = {
// Writes the root to the backing file, notifying write observers when
// complete. If the store is over quota or if it's empty and the store has
// never been written, nothing is written and write observers aren't notified.
_write: function JsonStore__write() {
write: Task.async(function JsonStore_write() {
// Don't write if the root is uninitialized or if the store is empty and the
// backing file doesn't yet exist.
if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename)))
return;
let data = JSON.stringify(this.root);
// If the store is over quota, don't write. The current under-quota state
// should persist.
if (this.quotaUsage > 1)
if ((this.quota > 0) && (data.length > this.quota)) {
this.onOverQuota(this);
return;
}
// Hash the data to compare it to any previously written data
let hash = yield getHash(data);
if (hash == this.hash)
return;
// Finally, write.
let stream = file.open(this.filename, "w");
try {
stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) {
if (err)
console.error("Error writing simple storage file: " + this.filename);
else if (this.onWrite)
this.onWrite(this);
}.bind(this));
yield writeData(this.filename, data);
this.hash = hash;
if (this.onWrite)
this.onWrite(this);
}
catch (err) {
// writeAsync closes the stream after it's done, so only close on error.
stream.close();
console.error("Error writing simple storage file: " + this.filename);
console.error(err);
}
}
})
};

View File

@ -1,10 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
"use strict";
module.metadata = {
'stability': 'stable'
"stability": "unstable",
"engines": {
"Firefox": "*",
"Fennec": "*"
}
};
module.exports = require('./tabs/tabs');
const { modelFor } = require("./model/core");
const { viewFor } = require("./view/core");
const { isTab } = require("./tabs/utils");
if (require("./system/xul-app").is("Fennec")) {
module.exports = require("./windows/tabs-fennec").tabs;
}
else {
module.exports = require("./tabs/tabs-firefox");
}
const tabs = module.exports;
// Implement `modelFor` function for the Tab instances.
// Finds a right model by iterating over all tab models
// and finding one that wraps given `view`.
modelFor.when(isTab, view => {
for (let model of tabs) {
if (viewFor(model) === view)
return model;
}
return null;
});

View File

@ -1,19 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
module.metadata = {
'stability': 'unstable',
'engines': {
'Firefox': '*',
'Fennec': '*'
}
};
if (require('../system/xul-app').name == 'Fennec') {
module.exports = require('../windows/tabs-fennec').tabs;
}
else {
module.exports = require('./tabs-firefox');
}

View File

@ -20,6 +20,31 @@ const { isGlobalPBSupported } = require('../private-browsing/utils');
// Bug 834961: ignore private windows when they are not supported
function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported });
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// Define predicate functions that can be used to detech weather
// we deal with fennec tabs or firefox tabs.
// Predicate to detect whether tab is XUL "Tab" node.
const isXULTab = tab =>
tab instanceof Ci.nsIDOMNode &&
tab.nodeName === "tab" &&
tab.namespaceURI === XUL_NS;
exports.isXULTab = isXULTab;
// Predicate to detecet whether given tab is a fettec tab.
// Unfortunately we have to guess via duck typinng of:
// http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/browser.js#2583
const isFennecTab = tab =>
tab &&
tab.QueryInterface &&
Ci.nsIBrowserTab &&
tab.QueryInterface(Ci.nsIBrowserTab) === tab;
exports.isFennecTab = isFennecTab;
const isTab = x => isXULTab(x) || isFennecTab(x);
exports.isTab = isTab;
function activateTab(tab, window) {
let gBrowser = getTabBrowserForTab(tab);

View File

@ -8,7 +8,7 @@ const { resolveURI, Require,
unload, override, descriptor } = require('../../toolkit/loader');
const { ensure } = require('../system/unload');
const addonWindow = require('../addon/window');
const { PlainTextConsole } = require("sdk/console/plain-text");
const { PlainTextConsole } = require('sdk/console/plain-text');
let defaultGlobals = override(require('../system/globals'), {
console: console
@ -43,33 +43,43 @@ function CustomLoader(module, globals, packaging, overrides={}) {
};
exports.Loader = CustomLoader;
function HookedPlainTextConsole(hook, print, innerID) {
this.log = hook.bind(null, "log", innerID);
this.info = hook.bind(null, "info", innerID);
this.warn = hook.bind(null, "warn", innerID);
this.error = hook.bind(null, "error", innerID);
this.debug = hook.bind(null, "debug", innerID);
this.exception = hook.bind(null, "exception", innerID);
this.time = hook.bind(null, "time", innerID);
this.timeEnd = hook.bind(null, "timeEnd", innerID);
this.__exposedProps__ = {
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
exception: "rw", time: "rw", timeEnd: "rw"
};
}
// Creates a custom loader instance whose console module is hooked in order
// to avoid printing messages to the console, and instead, expose them in the
// returned `messages` array attribute
exports.LoaderWithHookedConsole = function (module, callback) {
let messages = [];
function hook(msg) {
messages.push({type: this, msg: msg});
function hook(type, innerID, msg) {
messages.push({ type: type, msg: msg, innerID: innerID });
if (callback)
callback(this, msg);
callback(type, msg, innerID);
}
return {
loader: CustomLoader(module, {
console: {
log: hook.bind("log"),
info: hook.bind("info"),
warn: hook.bind("warn"),
error: hook.bind("error"),
debug: hook.bind("debug"),
exception: hook.bind("exception"),
time: hook.bind("time"),
timeEnd: hook.bind("timeEnd"),
__exposedProps__: {
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
exception: "rw", time: "rw", timeEnd: "rw"
console: new HookedPlainTextConsole(hook, null, null)
}, override(require("@loader/options"), {
modules: {
'sdk/console/plain-text': {
PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
}
}
}),
})),
messages: messages
};
}
@ -94,25 +104,19 @@ exports.LoaderWithHookedConsole2 = function (module, callback) {
// console message type and message and if it returns false the message will
// not be logged normally
exports.LoaderWithFilteredConsole = function (module, callback) {
function hook(msg) {
if (callback && callback(this, msg) == false)
function hook(type, innerID, msg) {
if (callback && callback(type, msg, innerID) == false)
return;
console[this](msg);
console[type](msg);
}
return CustomLoader(module, {
console: {
log: hook.bind("log"),
info: hook.bind("info"),
warn: hook.bind("warn"),
error: hook.bind("error"),
debug: hook.bind("debug"),
exception: hook.bind("exception"),
time: hook.bind("time"),
timeEnd: hook.bind("timeEnd"),
__exposedProps__: {
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
exception: "rw", time: "rw", timeEnd: "rw"
console: new HookedPlainTextConsole(hook, null, null)
}, override(require("@loader/options"), {
modules: {
'sdk/console/plain-text': {
PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
}
}
});
}));
}

View File

@ -28,12 +28,14 @@ function newURI(uriStr, base) {
let baseURI = base ? ios.newURI(base, null, null) : null;
return ios.newURI(uriStr, null, baseURI);
}
catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
throw new Error("malformed URI: " + uriStr);
}
catch (e if (e.result == Cr.NS_ERROR_FAILURE ||
e.result == Cr.NS_ERROR_ILLEGAL_VALUE)) {
throw new Error("invalid URI: " + uriStr);
catch (e) {
if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
throw new Error("malformed URI: " + uriStr);
}
if (e.result == Cr.NS_ERROR_FAILURE ||
e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
throw new Error("invalid URI: " + uriStr);
}
}
}
@ -41,9 +43,12 @@ function resolveResourceURI(uri) {
var resolved;
try {
resolved = resProt.resolveURI(uri);
} catch (e if e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
throw new Error("resource does not exist: " + uri.spec);
};
}
catch (e) {
if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
throw new Error("resource does not exist: " + uri.spec);
}
}
return resolved;
}
@ -63,8 +68,11 @@ let toFilename = exports.toFilename = function toFilename(url) {
try {
channel = channel.QueryInterface(Ci.nsIFileChannel);
return channel.file.path;
} catch (e if e.result == Cr.NS_NOINTERFACE) {
throw new Error("chrome url isn't on filesystem: " + url);
}
catch (e) {
if (e.result == Cr.NS_NOINTERFACE) {
throw new Error("chrome url isn't on filesystem: " + url);
}
}
}
if (uri.scheme == "file") {
@ -84,17 +92,32 @@ function URL(url, base) {
var userPass = null;
try {
userPass = uri.userPass ? uri.userPass : null;
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
}
catch (e) {
if (e.result != Cr.NS_ERROR_FAILURE) {
throw e;
}
}
var host = null;
try {
host = uri.host;
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
}
catch (e) {
if (e.result != Cr.NS_ERROR_FAILURE) {
throw e;
}
}
var port = null;
try {
port = uri.port == -1 ? null : uri.port;
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
}
catch (e) {
if (e.result != Cr.NS_ERROR_FAILURE) {
throw e;
}
}
let uriData = [uri.path, uri.path.length, {}, {}, {}, {}, {}, {}];
URLParser.parsePath.apply(URLParser, uriData);
@ -262,16 +285,21 @@ let getTLD = exports.getTLD = function getTLD (url) {
let tld = null;
try {
tld = tlds.getPublicSuffix(uri);
} catch (e if
e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS ||
e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {}
}
catch (e) {
if (e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS &&
e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
throw e;
}
}
return tld;
};
let isValidURI = exports.isValidURI = function (uri) {
try {
newURI(uri);
} catch(e) {
}
catch(e) {
return false;
}
return true;

View File

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
"use strict";
module.metadata = {
"stability": "experimental"
};
const method = require("method/core");
// Utility function that is just an enhancement over `method` to
// allow predicate based dispatch in addition to polymorphic
// dispatch. Unfortunately polymorphic dispatch does not quite
// cuts it in the world of XPCOM where no types / classes exist
// and all the XUL nodes share same type / prototype.
// Probably this is more generic and belongs some place else, but
// we can move it later once this will be relevant.
let dispatcher = hint => {
const base = method(hint);
// Make a map for storing predicate, implementation mappings.
let implementations = new Map();
// Dispatcher function goes through `predicate, implementation`
// pairs to find predicate that matches first argument and
// returns application of arguments on the associated
// `implementation`. If no matching predicate is found delegates
// to a `base` polymorphic function.
let dispatch = (value, ...rest) => {
for (let [predicate, implementation] of implementations) {
if (predicate(value))
return implementation(value, ...rest);
}
return base(value, ...rest);
};
// Expose base API.
dispatch.define = base.define;
dispatch.implement = base.implement;
dispatch.toString = base.toString;
// Add a `when` function to allow extending function via
// predicates.
dispatch.when = (predicate, implementation) => {
if (implementations.has(predicate))
throw TypeError("Already implemented for the given predicate");
implementations.set(predicate, implementation);
};
return dispatch;
};
exports.dispatcher = dispatcher;

View File

@ -11,9 +11,26 @@ module.metadata = {
}
};
const { isBrowser } = require('./window/utils');
const { modelFor } = require('./model/core');
const { viewFor } = require('./view/core');
if (require('./system/xul-app').is('Fennec')) {
module.exports = require('./windows/fennec');
}
else {
module.exports = require('./windows/firefox');
}
const browsers = module.exports.browserWindows;
//
modelFor.when(isBrowser, view => {
for (let model of browsers) {
if (viewFor(model) === view)
return model;
}
return null;
});

View File

@ -147,13 +147,13 @@ exports.testTabsGetFaviconPromiseFailure = function (assert, done) {
exports.testRejects = function (assert, done) {
getFavicon({})
.then(invalidResolve(assert), validReject(assert, 'Object'))
.then(getFavicon(null))
.then(() => getFavicon(null))
.then(invalidResolve(assert), validReject(assert, 'null'))
.then(getFavicon(undefined))
.then(() => getFavicon(undefined))
.then(invalidResolve(assert), validReject(assert, 'undefined'))
.then(getFavicon([]))
.then(() => getFavicon([]))
.then(invalidResolve(assert), validReject(assert, 'Array'))
.then(done);
.catch(assert.fail).then(done);
};
function invalidResolve (assert) {

View File

@ -31,26 +31,25 @@ exports['test construct tree'] = function (assert) {
assert.equal(tree.get(4).get(2), null, 'node.get descends from itself fails if not descendant');
};
exports['test walk'] = function (assert) {
exports['test walk'] = function (assert, done) {
let resultsAll = [];
let resultsNode = [];
let tree = TreeNode(1);
tree.add([2, 3, 4]);
tree.get(2).add([2.1, 2.2]);
tree.walk(function (node) {
resultsAll.push(node.value);
});
[1, 2, 2.1, 2.2, 3, 4].forEach(function (num) {
assert.ok(~resultsAll.indexOf(num), 'function applied to each node from root');
});
let resultsNode = [];
tree.get(2).walk(function (node) resultsNode.push(node.value));
[2, 2.1, 2.2].forEach(function (num) {
assert.ok(~resultsNode.indexOf(num), 'function applied to each node from node');
});
}).then(() => {
[1, 2, 2.1, 2.2, 3, 4].forEach(num => {
assert.ok(~resultsAll.indexOf(num), 'function applied to each node from root');
});
return tree.get(2).walk(node => resultsNode.push(node.value));
}).then(() => {
[2, 2.1, 2.2].forEach(function (num) {
assert.ok(~resultsNode.indexOf(num), 'function applied to each node from node');
});
}).catch(assert.fail).then(done);
};
exports['test async walk'] = function (assert, done) {

View File

@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
"use strict";
const { dispatcher } = require("sdk/util/dispatcher");
exports["test dispatcher API"] = assert => {
const dispatch = dispatcher();
assert.equal(typeof(dispatch), "function",
"dispatch is a function");
assert.equal(typeof(dispatch.define), "function",
"dispatch.define is a function");
assert.equal(typeof(dispatch.implement), "function",
"dispatch.implement is a function");
assert.equal(typeof(dispatch.when), "function",
"dispatch.when is a function");
};
exports["test dispatcher"] = assert => {
const isDuck = dispatcher();
const quacks = x => x && typeof(x.quack) === "function";
const Duck = function() {};
const Goose = function() {};
const True = _ => true;
const False = _ => false;
isDuck.define(Goose, False);
isDuck.define(Duck, True);
isDuck.when(quacks, True);
assert.equal(isDuck(new Goose()), false,
"Goose ain't duck");
assert.equal(isDuck(new Duck()), true,
"Ducks are ducks");
assert.equal(isDuck({ quack: () => "Quaaaaaack!" }), true,
"It's a duck if it quacks");
assert.throws(() => isDuck({}), /Type does not implements method/, "not implemneted");
isDuck.define(Object, False);
assert.equal(isDuck({}), false,
"Ain't duck if it does not quacks!");
};
exports["test redefining fails"] = assert => {
const isPM = dispatcher();
const isAfternoon = time => time.getHours() > 12;
isPM.when(isAfternoon, _ => true);
assert.equal(isPM(new Date(Date.parse("Jan 23, 1985, 13:20:00"))), true,
"yeap afternoon");
assert.equal(isPM({ getHours: _ => 17 }), true,
"seems like afternoon");
assert.throws(() => isPM.when(isAfternoon, x => x > 12 && x < 24),
/Already implemented for the given predicate/,
"can't redefine on same predicate");
};
require("sdk/test").run(exports);

View File

@ -29,18 +29,6 @@ exports["test async readURI"] = function(assert, done) {
assert.equal(content, "", "The URL content is not load yet");
}
exports["test sync readURI"] = function(assert) {
let content = "";
readURI(data.url("test-net-url.txt"), { sync: true }).then(function(data) {
content = data;
}, function() {
assert.fail("should not reject");
})
assert.equal(content, utf8text, "The URL content is loaded properly");
}
exports["test readURISync"] = function(assert) {
let content = readURISync(data.url("test-net-url.txt"));
@ -62,21 +50,6 @@ exports["test async readURI with ISO-8859-1 charset"] = function(assert, done) {
assert.equal(content, "", "The URL content is not load yet");
}
exports["test sync readURI with ISO-8859-1 charset"] = function(assert) {
let content = "";
readURI(data.url("test-net-url.txt"), {
sync: true,
charset: "ISO-8859-1"
}).then(function(data) {
content = data;
}, function() {
assert.fail("should not reject");
})
assert.equal(content, latin1text, "The URL content is loaded properly");
}
exports["test readURISync with ISO-8859-1 charset"] = function(assert) {
let content = readURISync(data.url("test-net-url.txt"), "ISO-8859-1");
@ -93,14 +66,6 @@ exports["test async readURI with not existing file"] = function(assert, done) {
})
}
exports["test sync readURI with not existing file"] = function(assert) {
readURI(data.url("test-net-url-fake.txt"), { sync: true }).then(function(data) {
assert.fail("should not resolve");
}, function(reason) {
assert.ok(reason.indexOf("Failed to read:") === 0);
})
}
exports["test readURISync with not existing file"] = function(assert) {
assert.throws(function() {
readURISync(data.url("test-net-url-fake.txt"));
@ -122,18 +87,6 @@ exports["test async readURI with data URI"] = function(assert, done) {
assert.equal(content, "", "The URL content is not load yet");
}
exports["test sync readURI with data URI"] = function(assert) {
let content = "";
readURI(dataURIutf8, { sync: true }).then(function(data) {
content = data;
}, function() {
assert.fail("should not reject");
})
assert.equal(content, utf8text, "The URL content is loaded properly");
}
exports["test readURISync with data URI"] = function(assert) {
let content = readURISync(dataURIutf8);
@ -155,21 +108,6 @@ exports["test async readURI with data URI and ISO-8859-1 charset"] = function(as
assert.equal(content, "", "The URL content is not load yet");
}
exports["test sync readURI with data URI and ISO-8859-1 charset"] = function(assert) {
let content = "";
readURI(dataURIlatin1, {
sync: true,
charset: "ISO-8859-1"
}).then(function(data) {
content = unescape(data);
}, function() {
assert.fail("should not reject");
})
assert.equal(content, latin1text, "The URL content is loaded properly");
}
exports["test readURISync with data URI and ISO-8859-1 charset"] = function(assert) {
let content = unescape(readURISync(dataURIlatin1, "ISO-8859-1"));
@ -197,16 +135,4 @@ exports["test async readURI with chrome URI"] = function(assert, done) {
assert.equal(content, "", "The URL content is not load yet");
}
exports["test sync readURI with chrome URI"] = function(assert) {
let content = "";
readURI(chromeURI, { sync: true }).then(function(data) {
content = data;
}, function() {
assert.fail("should not reject");
})
assert.equal(content, readURISync(chromeURI), "The URL content is loaded properly");
}
require("test").run(exports)

View File

@ -24,6 +24,7 @@ const { isTabPBSupported, isWindowPBSupported, isGlobalPBSupported } = require('
const promise = require("sdk/core/promise");
const { pb } = require('./private-browsing/helper');
const { URL } = require("sdk/url");
const { LoaderWithHookedConsole } = require('sdk/test/loader');
const { waitUntil } = require("sdk/test/utils");
const data = require("./fixtures");
@ -1531,6 +1532,41 @@ exports.testDetachOnUnload = function(assert, done) {
})
}
exports.testConsole = function(assert, done) {
let innerID;
const TEST_URL = 'data:text/html;charset=utf-8,console';
const { loader } = LoaderWithHookedConsole(module, onMessage);
const { PageMod } = loader.require('sdk/page-mod');
const system = require("sdk/system/events");
let seenMessage = false;
function onMessage(type, msg, msgID) {
seenMessage = true;
innerID = msgID;
}
let mod = PageMod({
include: TEST_URL,
contentScriptWhen: "ready",
contentScript: Isolate(function() {
console.log("Hello from the page mod");
self.port.emit("done");
}),
onAttach: function(worker) {
worker.port.on("done", function() {
let window = getTabContentWindow(tab);
let id = getInnerId(window);
assert.ok(seenMessage, "Should have seen the console message");
assert.equal(innerID, id, "Should have seen the right inner ID");
closeTab(tab);
done();
});
},
});
let tab = openTab(getMostRecentBrowserWindow(), TEST_URL);
}
exports.testSyntaxErrorInContentScript = function(assert, done) {
const url = "data:text/html;charset=utf-8,testSyntaxErrorInContentScript";
let hitError = null;

View File

@ -454,8 +454,13 @@ function isDestroyed(page) {
try {
page.postMessage("foo");
}
catch (err if err.message == ERR_DESTROYED) {
return true;
catch (err) {
if (err.message == ERR_DESTROYED) {
return true;
}
else {
throw err;
}
}
return false;
}

View File

@ -901,7 +901,7 @@ exports['test passing DOM node as first argument'] = function (assert, done) {
let widgetNode = document.getElementById(widgetId);
all(warned.promise, shown.promise).
all([warned.promise, shown.promise]).
then(loader.unload).
then(done, assert.fail)

View File

@ -230,6 +230,31 @@ exports.testPlainTextConsoleBoundMethods = function(assert) {
restorePrefs();
};
exports.testConsoleInnerID = function(assert) {
let Console = require("sdk/console/plain-text").PlainTextConsole;
let { log, info, warn, error, debug, exception, trace } = new Console(function() {}, "test ID");
let messages = [];
function onMessage({ subject }) {
let message = subject.wrappedJSObject;
messages.push({ msg: message.arguments[0], type: message.level, innerID: message.innerID });
}
const system = require("sdk/system/events");
system.on("console-api-log-event", onMessage);
log("Test log");
warn("Test warning");
error("Test error");
assert.equal(messages.length, 3, "Should see 3 log events");
assert.deepEqual(messages[0], { msg: "Test log", type: "log", innerID: "test ID" }, "Should see the right event");
assert.deepEqual(messages[1], { msg: "Test warning", type: "warn", innerID: "test ID" }, "Should see the right event");
assert.deepEqual(messages[2], { msg: "Test error", type: "error", innerID: "test ID" }, "Should see the right event");
system.off("console-api-log-event", onMessage);
};
function restorePrefs() {
if (HAS_ORIGINAL_ADDON_LOG_LEVEL)
prefs.set(ADDON_LOG_LEVEL_PREF, ORIGINAL_ADDON_LOG_LEVEL);

View File

@ -4,277 +4,235 @@
'use strict';
var core = require('sdk/core/promise'),
defer = core.defer, resolve = core.resolve, reject = core.reject, all = core.all,
promised = core.promised;
var { setTimeout } = require('sdk/timers');
const { Cc, Cu, Ci } = require('chrome');
const { setTimeout } = require('sdk/timers');
const { prefixURI, name } = require('@loader/options');
const addonPromiseURI = prefixURI + name + '/lib/sdk/core/promise.js';
const builtPromiseURI = 'resource://gre/modules/commonjs/sdk/core/promise.js';
let { Promise, defer, resolve, reject, all, promised } = require('sdk/core/promise');
exports['test all observers are notified'] = function(assert, done) {
var expected = 'Taram pam param!'
var deferred = defer()
var pending = 10, i = 0
let expected = 'Taram pam param!';
let deferred = defer();
let pending = 10, i = 0;
function resolved(value) {
assert.equal(value, expected, 'value resolved as expected: #' + pending)
if (!--pending) done()
assert.equal(value, expected, 'value resolved as expected: #' + pending);
if (!--pending) done();
}
while (i++ < pending) deferred.promise.then(resolved)
while (i++ < pending) deferred.promise.then(resolved);
deferred.resolve(expected)
}
deferred.resolve(expected);
};
exports['test exceptions dont stop notifications'] = function(assert, done) {
var threw = false, boom = Error('Boom!')
var deferred = defer()
let threw = false, boom = Error('Boom!');
let deferred = defer();
var promise2 = deferred.promise.then(function() {
threw = true
throw boom
})
let promise2 = deferred.promise.then(function() {
threw = true;
throw boom;
});
deferred.promise.then(function() {
assert.ok(threw, 'observer is called even though previos one threw')
assert.ok(threw, 'observer is called even though previos one threw');
promise2.then(function() {
assert.fail('should not resolve')
assert.fail('should not resolve');
}, function(reason) {
assert.equal(reason, boom, 'rejects to thrown error')
done()
})
})
assert.equal(reason, boom, 'rejects to thrown error');
done();
});
});
deferred.resolve('go!')
}
deferred.resolve('go!');
};
exports['test subsequent resolves are ignored'] = function(assert, done) {
var deferred = defer()
deferred.resolve(1)
deferred.resolve(2)
deferred.reject(3)
let deferred = defer();
deferred.resolve(1);
deferred.resolve(2);
deferred.reject(3);
deferred.promise.then(function(actual) {
assert.equal(actual, 1, 'resolves to first value')
assert.equal(actual, 1, 'resolves to first value');
}, function() {
assert.fail('must not reject')
})
assert.fail('must not reject');
});
deferred.promise.then(function(actual) {
assert.equal(actual, 1, 'subsequent resolutions are ignored')
done()
assert.equal(actual, 1, 'subsequent resolutions are ignored');
done();
}, function() {
assert.fail('must not reject')
})
}
assert.fail('must not reject');
});
};
exports['test subsequent rejections are ignored'] = function(assert, done) {
var deferred = defer()
deferred.reject(1)
deferred.resolve(2)
deferred.reject(3)
let deferred = defer();
deferred.reject(1);
deferred.resolve(2);
deferred.reject(3);
deferred.promise.then(function(actual) {
assert.fail('must not resolve')
assert.fail('must not resolve');
}, function(actual) {
assert.equal(actual, 1, 'must reject to first')
})
assert.equal(actual, 1, 'must reject to first');
});
deferred.promise.then(function(actual) {
assert.fail('must not resolve')
assert.fail('must not resolve');
}, function(actual) {
assert.equal(actual, 1, 'must reject to first')
done()
})
}
assert.equal(actual, 1, 'must reject to first');
done();
});
};
exports['test error recovery'] = function(assert, done) {
var boom = Error('Boom!')
var deferred = defer()
let boom = Error('Boom!');
let deferred = defer();
deferred.promise.then(function() {
assert.fail('rejected promise should not resolve')
assert.fail('rejected promise should not resolve');
}, function(reason) {
assert.equal(reason, boom, 'rejection reason delivered')
return 'recovery'
assert.equal(reason, boom, 'rejection reason delivered');
return 'recovery';
}).then(function(value) {
assert.equal(value, 'recovery', 'error handled by a handler')
done()
})
deferred.reject(boom)
}
assert.equal(value, 'recovery', 'error handled by a handler');
done();
});
deferred.reject(boom);
};
exports['test error recovery with promise'] = function(assert, done) {
var deferred = defer()
let deferred = defer();
deferred.promise.then(function() {
assert.fail('must reject')
assert.fail('must reject');
}, function(actual) {
assert.equal(actual, 'reason', 'rejected')
var deferred = defer()
deferred.resolve('recovery')
return deferred.promise
assert.equal(actual, 'reason', 'rejected');
let deferred = defer();
deferred.resolve('recovery');
return deferred.promise;
}).then(function(actual) {
assert.equal(actual, 'recovery', 'recorvered via promise')
var deferred = defer()
deferred.reject('error')
return deferred.promise
assert.equal(actual, 'recovery', 'recorvered via promise');
let deferred = defer();
deferred.reject('error');
return deferred.promise;
}).then(null, function(actual) {
assert.equal(actual, 'error', 'rejected via promise')
var deferred = defer()
deferred.reject('end')
return deferred.promise
assert.equal(actual, 'error', 'rejected via promise');
let deferred = defer();
deferred.reject('end');
return deferred.promise;
}).then(null, function(actual) {
assert.equal(actual, 'end', 'rejeced via promise')
done()
})
assert.equal(actual, 'end', 'rejeced via promise');
done();
});
deferred.reject('reason')
}
deferred.reject('reason');
};
exports['test propagation'] = function(assert, done) {
var d1 = defer(), d2 = defer(), d3 = defer()
let d1 = defer(), d2 = defer(), d3 = defer();
d1.promise.then(function(actual) {
assert.equal(actual, 'expected', 'resolves to expected value')
done()
})
assert.equal(actual, 'expected', 'resolves to expected value');
done();
});
d1.resolve(d2.promise)
d2.resolve(d3.promise)
d3.resolve('expected')
}
d1.resolve(d2.promise);
d2.resolve(d3.promise);
d3.resolve('expected');
};
exports['test chaining'] = function(assert, done) {
var boom = Error('boom'), brax = Error('braxXXx')
var deferred = defer()
let boom = Error('boom'), brax = Error('braxXXx');
let deferred = defer();
deferred.promise.then().then().then(function(actual) {
assert.equal(actual, 2, 'value propagates unchanged')
return actual + 2
assert.equal(actual, 2, 'value propagates unchanged');
return actual + 2;
}).then(null, function(reason) {
assert.fail('should not reject')
assert.fail('should not reject');
}).then(function(actual) {
assert.equal(actual, 4, 'value propagates through if not handled')
throw boom
assert.equal(actual, 4, 'value propagates through if not handled');
throw boom;
}).then(function(actual) {
assert.fail('exception must reject promise')
assert.fail('exception must reject promise');
}).then().then(null, function(actual) {
assert.equal(actual, boom, 'reason propagates unchanged')
throw brax
assert.equal(actual, boom, 'reason propagates unchanged');
throw brax;
}).then().then(null, function(actual) {
assert.equal(actual, brax, 'reason changed becase of exception')
return 'recovery'
assert.equal(actual, brax, 'reason changed becase of exception');
return 'recovery';
}).then(function(actual) {
assert.equal(actual, 'recovery', 'recovered from error')
done()
})
deferred.resolve(2)
}
assert.equal(actual, 'recovery', 'recovered from error');
done();
});
deferred.resolve(2);
};
exports['test reject'] = function(assert, done) {
var expected = Error('boom')
let expected = Error('boom');
reject(expected).then(function() {
assert.fail('should reject')
assert.fail('should reject');
}, function(actual) {
assert.equal(actual, expected, 'rejected with expected reason')
}).then(function() {
done()
})
}
assert.equal(actual, expected, 'rejected with expected reason');
}).then(done, assert.fail);
};
exports['test resolve to rejected'] = function(assert, done) {
var expected = Error('boom')
var deferred = defer()
let expected = Error('boom');
let deferred = defer();
deferred.promise.then(function() {
assert.fail('should reject')
assert.fail('should reject');
}, function(actual) {
assert.equal(actual, expected, 'rejected with expected failure')
}).then(function() {
done()
})
assert.equal(actual, expected, 'rejected with expected failure');
}).then(done, assert.fail);
deferred.resolve(reject(expected))
}
deferred.resolve(reject(expected));
};
exports['test resolve'] = function(assert, done) {
var expected = 'value'
let expected = 'value';
resolve(expected).then(function(actual) {
assert.equal(actual, expected, 'resolved as expected')
}).then(function() {
done()
})
}
exports['test resolve with prototype'] = function(assert, done) {
var seventy = resolve(70, {
subtract: function subtract(y) {
return this.then(function(x) { return x - y })
}
})
seventy.subtract(17).then(function(actual) {
assert.equal(actual, 70 - 17, 'resolves to expected')
done()
})
}
assert.equal(actual, expected, 'resolved as expected');
}).catch(assert.fail).then(done);
};
exports['test promised with normal args'] = function(assert, done) {
var sum = promised(function(x, y) { return x + y })
let sum = promised((x, y) => x + y );
sum(7, 8).then(function(actual) {
assert.equal(actual, 7 + 8, 'resolves as expected')
done()
})
}
assert.equal(actual, 7 + 8, 'resolves as expected');
}).catch(assert.fail).then(done);
};
exports['test promised with promise args'] = function(assert, done) {
var sum = promised(function(x, y) { return x + y })
var deferred = defer()
let sum = promised((x, y) => x + y );
let deferred = defer();
sum(11, deferred.promise).then(function(actual) {
assert.equal(actual, 11 + 24, 'resolved as expected')
done()
})
assert.equal(actual, 11 + 24, 'resolved as expected');
}).catch(assert.fail).then(done);
deferred.resolve(24)
}
exports['test promised with prototype'] = function(assert, done) {
var deferred = defer()
var numeric = {}
numeric.subtract = promised(function(y) { return this - y }, numeric)
var sum = promised(function(x, y) { return x + y }, numeric)
sum(7, 70).
subtract(14).
subtract(deferred.promise).
subtract(5).
then(function(actual) {
assert.equal(actual, 7 + 70 - 14 - 23 - 5, 'resolved as expected')
done()
})
deferred.resolve(23)
}
deferred.resolve(24);
};
exports['test promised error handleing'] = function(assert, done) {
var expected = Error('boom')
var f = promised(function() {
throw expected
})
let expected = Error('boom');
let f = promised(function() {
throw expected;
});
f().then(function() {
assert.fail('should reject')
assert.fail('should reject');
}, function(actual) {
assert.equal(actual, expected, 'rejected as expected')
done()
})
}
assert.equal(actual, expected, 'rejected as expected');
}).catch(assert.fail).then(done);
};
exports['test errors in promise resolution handlers are propagated'] = function(assert, done) {
var expected = Error('Boom');
@ -289,56 +247,67 @@ exports['test errors in promise resolution handlers are propagated'] = function(
}).then(done, assert.fail);
resolve({});
}
};
exports['test return promise form promised'] = function(assert, done) {
var f = promised(function() {
return resolve(17)
})
let f = promised(function() {
return resolve(17);
});
f().then(function(actual) {
assert.equal(actual, 17, 'resolves to a promise resolution')
done()
})
}
assert.equal(actual, 17, 'resolves to a promise resolution');
}).catch(assert.fail).then(done);
};
exports['test promised returning failure'] = function(assert, done) {
var expected = Error('boom')
var f = promised(function() {
return reject(expected)
})
let expected = Error('boom');
let f = promised(function() {
return reject(expected);
});
f().then(function() {
assert.fail('must reject')
assert.fail('must reject');
}, function(actual) {
assert.equal(actual, expected, 'rejects with expected reason')
done()
})
}
assert.equal(actual, expected, 'rejects with expected reason');
}).catch(assert.fail).then(done);
};
exports['test promised are greedy'] = function(assert, done) {
var runs = 0
var f = promised(function() { ++runs })
var promise = f()
assert.equal(runs, 1, 'promised runs task right away')
done()
}
/*
* Changed for compliance in Bug 881047, promises are now always async
*/
exports['test promises are always async'] = function (assert, done) {
let runs = 0;
resolve(1)
.then(val => ++runs)
.catch(assert.fail).then(done);
assert.equal(runs, 0, 'resolutions are called in following tick');
};
/*
* Changed for compliance in Bug 881047, promised's are now non greedy
*/
exports['test promised are not greedy'] = function(assert, done) {
let runs = 0;
promised(() => ++runs)()
.catch(assert.fail).then(done);
assert.equal(runs, 0, 'promised does not run task right away');
};
exports['test arrays should not flatten'] = function(assert, done) {
var a = defer()
var b = defer()
let a = defer();
let b = defer();
var combine = promised(function(str, arr) {
assert.equal(str, 'Hello', 'Array was not flattened')
assert.deepEqual(arr, [ 'my', 'friend' ])
})
let combine = promised(function(str, arr) {
assert.equal(str, 'Hello', 'Array was not flattened');
assert.deepEqual(arr, [ 'my', 'friend' ]);
});
combine(a.promise, b.promise).then(done)
combine(a.promise, b.promise).catch(assert.fail).then(done);
a.resolve('Hello')
b.resolve([ 'my', 'friend' ])
}
a.resolve('Hello');
b.resolve([ 'my', 'friend' ]);
};
exports['test `all` for all promises'] = function (assert, done) {
all([
@ -366,7 +335,7 @@ exports['test `all` aborts upon first reject'] = function (assert, done) {
});
function delayedResolve () {
var deferred = defer();
let deferred = defer();
setTimeout(deferred.resolve, 50);
return deferred.promise;
}
@ -404,4 +373,78 @@ exports['test `all` with multiple rejected'] = function (assert, done) {
});
};
require("test").run(exports)
exports['test Promise constructor resolve'] = function (assert, done) {
var isAsync = true;
new Promise(function (resolve, reject) {
resolve(5);
}).then(x => {
isAsync = false;
assert.equal(x, 5, 'Promise constructor resolves correctly');
}).catch(assert.fail).then(done);
assert.ok(isAsync, 'Promise constructor runs async');
};
exports['test Promise constructor reject'] = function (assert, done) {
new Promise(function (resolve, reject) {
reject(new Error('deferred4life'));
}).then(assert.fail, (err) => {
assert.equal(err.message, 'deferred4life', 'Promise constructor rejects correctly');
}).catch(assert.fail).then(done);
};
exports['test JSM Load and API'] = function (assert, done) {
// Use addon URL when loading from cfx/local:
// resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js
// Use built URL when testing on try, etc.
// resource://gre/modules/commonjs/sdk/core/promise.js
try {
var { Promise } = Cu.import(addonPromiseURI, {});
} catch (e) {
var { Promise } = Cu.import(builtPromiseURI, {});
}
testEnvironment(Promise, assert, done, 'JSM');
};
exports['test mozIJSSubScriptLoader exporting'] = function (assert, done) {
let { Services } = Cu.import('resource://gre/modules/Services.jsm', {});
let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
let Promise = new Cu.Sandbox(systemPrincipal);
let loader = Cc['@mozilla.org/moz/jssubscript-loader;1']
.getService(Ci.mozIJSSubScriptLoader);
// Use addon URL when loading from cfx/local:
// resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js
// Use built URL when testing on try, etc.
// resource://gre/modules/commonjs/sdk/core/promise.js
try {
loader.loadSubScript(addonPromiseURI, Promise);
} catch (e) {
loader.loadSubScript(builtPromiseURI, Promise);
}
testEnvironment(Promise, assert, done, 'mozIJSSubScript');
};
function testEnvironment ({all, resolve, defer, reject, promised}, assert, done, type) {
all([resolve(5), resolve(10), 925]).then(val => {
assert.equal(val[0], 5, 'promise#all works ' + type);
assert.equal(val[1], 10, 'promise#all works ' + type);
assert.equal(val[2], 925, 'promise#all works ' + type);
return resolve(1000);
}).then(value => {
assert.equal(value, 1000, 'promise#resolve works ' + type);
return reject('testing reject');
}).then(null, reason => {
assert.equal(reason, 'testing reject', 'promise#reject works ' + type);
let deferred = defer();
setTimeout(() => deferred.resolve('\\m/'), 10);
return deferred.promise;
}).then(value => {
assert.equal(value, '\\m/', 'promise#defer works ' + type);
return promised(x => x * x)(5);
}).then(value => {
assert.equal(value, 25, 'promise#promised works ' + type);
}).then(done, assert.fail);
}
require("sdk/test").run(exports);

View File

@ -6,6 +6,7 @@ const file = require("sdk/io/file");
const prefs = require("sdk/preferences/service");
const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
let {Cc,Ci} = require("chrome");
@ -18,6 +19,7 @@ let storeFile = Cc["@mozilla.org/file/directory_service;1"].
storeFile.append("jetpack");
storeFile.append(id);
storeFile.append("simple-storage");
file.mkpath(storeFile.path);
storeFile.append("store.json");
let storeFilename = storeFile.path;
@ -33,12 +35,13 @@ exports.testSetGet = function (assert, done) {
// Load the module again and make sure the value stuck.
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
manager(loader).jsonStore.onWrite = function (storage) {
file.remove(storeFilename);
done();
};
assert.equal(ss.storage.foo, val, "Value should persist");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
file.remove(storeFilename);
done();
};
let val = "foo";
ss.storage.foo = val;
@ -104,6 +107,23 @@ exports.testEmpty = function (assert) {
assert.ok(!file.exists(storeFilename), "Store file should not exist");
};
exports.testStorageDataRecovery = function(assert) {
const data = {
a: true,
b: [3, 13],
c: "guilty!",
d: { e: 1, f: 2 }
};
let stream = file.open(storeFilename, "w");
stream.write(JSON.stringify(data));
stream.close();
let loader = Loader(module);
let ss = loader.require("sdk/simple-storage");
assert.deepEqual(ss.storage, data, "Recovered data should be the same as written");
file.remove(storeFilename);
loader.unload();
}
exports.testMalformed = function (assert) {
let stream = file.open(storeFilename, "w");
stream.write("i'm not json");
@ -116,6 +136,7 @@ exports.testMalformed = function (assert) {
break;
}
assert.ok(empty, "Malformed storage should cause root to be empty");
file.remove(storeFilename);
loader.unload();
};
@ -141,10 +162,11 @@ exports.testQuotaExceededHandle = function (assert, done) {
assert.equal(ss.storage.x, 4, "x value should be correct");
assert.equal(ss.storage.y, 5, "y value should be correct");
manager(loader).jsonStore.onWrite = function (storage) {
prefs.reset(QUOTA_PREF);
done();
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
prefs.reset(QUOTA_PREF);
done();
};
loader.unload();
});
@ -178,6 +200,9 @@ exports.testQuotaExceededNoHandle = function (assert, done) {
assert.equal(ss.storage, val,
"Over-quota value should not have been written, " +
"old value should have persisted: " + ss.storage);
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
prefs.reset(QUOTA_PREF);
done();
@ -232,6 +257,184 @@ exports.testUninstall = function (assert, done) {
loader.unload();
};
exports.testChangeInnerArray = function(assert, done) {
prefs.set(WRITE_PERIOD_PREF, 10);
let expected = {
x: [5, 7],
y: [7, 28],
z: [6, 2]
};
// Load the module, set a value.
let loader = Loader(module);
let ss = loader.require("sdk/simple-storage");
manager(loader).jsonStore.onWrite = function (storage) {
assert.ok(file.exists(storeFilename), "Store file should exist");
// Load the module again and check the result
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Add a property
ss.storage.x.push(["bar"]);
expected.x.push(["bar"]);
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Modify a property
ss.storage.y[0] = 42;
expected.y[0] = 42;
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Delete a property
delete ss.storage.z[1];
delete expected.z[1];
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Modify the new inner-object
ss.storage.x[2][0] = "baz";
expected.x[2][0] = "baz";
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
// Load the module again and check the result
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
loader.unload();
file.remove(storeFilename);
prefs.reset(WRITE_PERIOD_PREF);
done();
};
};
};
};
};
ss.storage = {
x: [5, 7],
y: [7, 28],
z: [6, 2]
};
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
loader.unload();
};
exports.testChangeInnerObject = function(assert, done) {
prefs.set(WRITE_PERIOD_PREF, 10);
let expected = {
x: {
a: 5,
b: 7
},
y: {
c: 7,
d: 28
},
z: {
e: 6,
f: 2
}
};
// Load the module, set a value.
let loader = Loader(module);
let ss = loader.require("sdk/simple-storage");
manager(loader).jsonStore.onWrite = function (storage) {
assert.ok(file.exists(storeFilename), "Store file should exist");
// Load the module again and check the result
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Add a property
ss.storage.x.g = {foo: "bar"};
expected.x.g = {foo: "bar"};
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Modify a property
ss.storage.y.c = 42;
expected.y.c = 42;
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Delete a property
delete ss.storage.z.f;
delete expected.z.f;
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Modify the new inner-object
ss.storage.x.g.foo = "baz";
expected.x.g.foo = "baz";
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
// Load the module again and check the result
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
loader.unload();
file.remove(storeFilename);
prefs.reset(WRITE_PERIOD_PREF);
done();
};
};
};
};
};
ss.storage = {
x: {
a: 5,
b: 7
},
y: {
c: 7,
d: 28
},
z: {
e: 6,
f: 2
}
};
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
loader.unload();
};
exports.testSetNoSetRead = function (assert, done) {
// Load the module, set a value.
let loader = Loader(module);
@ -250,12 +453,13 @@ exports.testSetNoSetRead = function (assert, done) {
// Load the module a third time and make sure the value stuck.
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
manager(loader).jsonStore.onWrite = function (storage) {
file.remove(storeFilename);
done();
};
assert.equal(ss.storage.foo, val, "Value should persist");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
file.remove(storeFilename);
done();
};
let val = "foo";
ss.storage.foo = val;
@ -276,12 +480,13 @@ function setGetRoot(assert, done, val, compare) {
// Load the module again and make sure the value stuck.
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
manager(loader).jsonStore.onWrite = function () {
file.remove(storeFilename);
done();
};
assert.ok(compare(ss.storage, val), "Value should persist");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
file.remove(storeFilename);
done();
};
ss.storage = val;
assert.ok(compare(ss.storage, val), "Value read should be value set");

View File

@ -2,13 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const tabs = require("sdk/tabs"); // From addon-kit
const windowUtils = require("sdk/deprecated/window-utils");
const { getTabForWindow } = require('sdk/tabs/helpers');
const app = require("sdk/system/xul-app");
const { viewFor } = require("sdk/view/core");
const { getTabId } = require("sdk/tabs/utils");
const { modelFor } = require("sdk/model/core");
const { getTabId, isTab } = require("sdk/tabs/utils");
const { defer } = require("sdk/lang/functional");
// The primary test tab
@ -152,6 +154,22 @@ exports["test viewFor(tab)"] = (assert, done) => {
}));
tabs.open({ url: "about:mozilla" });
}
};
exports["test modelFor(xulTab)"] = (assert, done) => {
tabs.open({
url: "about:mozilla",
onReady: tab => {
const view = viewFor(tab);
assert.ok(view, "view is returned");
assert.ok(isTab(view), "view is underlaying tab");
assert.equal(getTabId(view), tab.id, "tab has a same id");
assert.equal(modelFor(view), tab, "modelFor(view) is SDK tab");
tab.close(defer(done));
}
});
};
require("test").run(exports);

View File

@ -48,12 +48,12 @@ exports["test LoaderWithHookedConsole"] = function (assert) {
console.debug("5th");
console.exception("6th");
assert.equal(messages.length, 6, "Got all console messages");
assert.deepEqual(messages[0], {type: "log", msg: "1st"}, "Got log");
assert.deepEqual(messages[1], {type: "error", msg: "2nd"}, "Got error");
assert.deepEqual(messages[2], {type: "warn", msg: "3rd"}, "Got warn");
assert.deepEqual(messages[3], {type: "info", msg: "4th"}, "Got info");
assert.deepEqual(messages[4], {type: "debug", msg: "5th"}, "Got debug");
assert.deepEqual(messages[5], {type: "exception", msg: "6th"}, "Got exception");
assert.deepEqual(messages[0], {type: "log", msg: "1st", innerID: null}, "Got log");
assert.deepEqual(messages[1], {type: "error", msg: "2nd", innerID: null}, "Got error");
assert.deepEqual(messages[2], {type: "warn", msg: "3rd", innerID: null}, "Got warn");
assert.deepEqual(messages[3], {type: "info", msg: "4th", innerID: null}, "Got info");
assert.deepEqual(messages[4], {type: "debug", msg: "5th", innerID: null}, "Got debug");
assert.deepEqual(messages[5], {type: "exception", msg: "6th", innerID: null}, "Got exception");
assert.equal(count, 6, "Called for all messages");
};

View File

@ -48,9 +48,14 @@ exports.testFromExceptionWithString = function(assert) {
try {
throw "foob";
assert.fail("an exception should've been thrown");
} catch (e if e == "foob") {
var tb = traceback.fromException(e);
assert.equal(tb.length, 0);
} catch (e) {
if (e == "foob") {
var tb = traceback.fromException(e);
assert.equal(tb.length, 0);
}
else {
throw e;
}
}
};
@ -65,11 +70,16 @@ exports.testFromExceptionWithError = function(assert) {
try {
throwError();
assert.fail("an exception should've been thrown");
} catch (e if e instanceof Error) {
var tb = traceback.fromException(e);
} catch (e) {
if (e instanceof Error) {
var tb = traceback.fromException(e);
var xulApp = require("sdk/system/xul-app");
assert.equal(tb.slice(-1)[0].name, "throwError");
var xulApp = require("sdk/system/xul-app");
assert.equal(tb.slice(-1)[0].name, "throwError");
}
else {
throw e;
}
}
};
@ -77,9 +87,14 @@ exports.testFromExceptionWithNsIException = function(assert) {
try {
throwNsIException();
assert.fail("an exception should've been thrown");
} catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) {
var tb = traceback.fromException(e);
assert.equal(tb[tb.length - 1].name, "throwNsIException");
} catch (e) {
if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
var tb = traceback.fromException(e);
assert.equal(tb[tb.length - 1].name, "throwNsIException");
}
else {
throw e;
}
}
};

View File

@ -9,6 +9,7 @@ module.metadata = {
}
};
const { Cu } = require("chrome");
const { Frame } = require("sdk/ui/frame");
const { Toolbar } = require("sdk/ui/toolbar");
const { Loader } = require("sdk/test/loader");
@ -16,15 +17,29 @@ const { identify } = require("sdk/ui/id");
const { setTimeout } = require("sdk/timers");
const { getMostRecentBrowserWindow, open } = require("sdk/window/utils");
const { ready, loaded, close } = require("sdk/window/helpers");
const { defer } = require("sdk/core/promise");
const { defer, all } = require("sdk/core/promise");
const { send } = require("sdk/event/utils");
const { object } = require("sdk/util/sequence");
const { OutputPort } = require("sdk/output/system");
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const output = new OutputPort({ id: "toolbar-change" });
const wait = (toolbar, event) => {
const wait = (toolbar, event, times) => {
let { promise, resolve } = defer();
toolbar.once(event, resolve);
if (times) {
let resolveArray = [];
let counter = 0;
toolbar.on(event, function onEvent (e) {
resolveArray.push(e);
if (++counter === times) {
toolbar.off(event, onEvent);
resolve(resolveArray);
}
});
}
else {
toolbar.once(event, resolve);
}
return promise;
};
@ -36,7 +51,9 @@ const stateEventsFor = frame =>
const isAttached = ({id}, window=getMostRecentBrowserWindow()) =>
!!window.document.getElementById(id);
exports["test frame API"] = function*(assert) {
// Use `Task.spawn` instead of `Task.async` because the returned function does not contain
// a length for the test harness to determine whether the test should be executed
exports["test frame API"] = function* (assert) {
const url = "data:text/html,frame-api";
assert.throws(() => new Frame(),
/The `options.url`/,
@ -86,8 +103,7 @@ exports["test frame API"] = function*(assert) {
f3.destroy();
};
exports["test frame in toolbar"] = function*(assert) {
exports["test frame in toolbar"] = function* (assert) {
const assertEvent = (event, type) => {
assert.ok(event, "`" + type + "` event was dispatched");
assert.equal(event.type, type, "event.type is: " + type);
@ -116,7 +132,6 @@ exports["test frame in toolbar"] = function*(assert) {
const [a2, r2, l2] = stateEventsFor(f1);
const w2 = open();
assertEvent((yield a2), "attach");
assert.ok(isAttached(f1, w2), "frame is in the window#2");
assertEvent((yield r2), "ready");
@ -126,7 +141,6 @@ exports["test frame in toolbar"] = function*(assert) {
const d1 = wait(f1, "detach");
yield close(w2);
assertEvent((yield d1), "detach");
assert.pass("frame detached when window is closed");
@ -138,7 +152,7 @@ exports["test frame in toolbar"] = function*(assert) {
};
exports["test host to content messaging"] = function*(assert) {
exports["test host to content messaging"] = function* (assert) {
const url = "data:text/html,<script>new " + function() {
window.addEventListener("message", (event) => {
if (event.data === "ping!")
@ -159,7 +173,7 @@ exports["test host to content messaging"] = function*(assert) {
};
exports["test content to host messaging"] = function*(assert) {
exports["test content to host messaging"] = function* (assert) {
const url = "data:text/html,<script>new " + function() {
window.addEventListener("message", (event) => {
if (event.data === "pong!")
@ -182,10 +196,11 @@ exports["test content to host messaging"] = function*(assert) {
t1.destroy();
yield wait(t1, "detach");
};
exports["test direct messaging"] = function*(assert) {
exports["test direct messaging"] = function* (assert) {
const url = "data:text/html,<script>new " + function() {
var n = 0;
window.addEventListener("message", (event) => {
@ -208,31 +223,30 @@ exports["test direct messaging"] = function*(assert) {
yield wait(f1, "ready");
assert.pass("document loaded in window#2");
let messages = wait(f1, "message", 2);
f1.postMessage("inc", f1.origin);
f1.postMessage("print", f1.origin);
const e1 = yield wait(f1, "message");
const [e1, e2] = yield messages;
assert.deepEqual(e1.data, {n: 1}, "received message from window#1");
const e2 = yield wait(f1, "message");
assert.deepEqual(e2.data, {n: 1}, "received message from window#2");
let message = wait(f1, "message");
e1.source.postMessage("inc", e1.origin);
e1.source.postMessage("print", e1.origin);
const e3 = yield wait(f1, "message");
const e3 = yield message;
assert.deepEqual(e3.data, {n: 2}, "state changed in window#1");
let message = wait(f1, "message");
e2.source.postMessage("print", e2.origin);
const e4 = yield wait(f1, "message");
yield message;
assert.deepEqual(e2.data, {n:1}, "window#2 didn't received inc message");
yield close(w2);
t1.destroy();
yield wait(t1, "detach");
};
require("sdk/test").run(exports);

View File

@ -829,7 +829,7 @@ exports.testShowingInOneWindowDoesNotAffectOtherWindows = function(assert, done)
}, assert.fail);
}
exports.testHidingAHiddenSidebarRejects = function(assert) {
exports.testHidingAHiddenSidebarRejects = function(assert, done) {
const { Sidebar } = require('sdk/ui/sidebar');
let testName = 'testHidingAHiddenSidebarRejects';
let url = 'data:text/html;charset=utf-8,'+testName;
@ -1385,7 +1385,7 @@ exports.testEventListeners = function(assert, done) {
onHide.resolve();
});
all(constructorOnShow.promise,
all([constructorOnShow.promise,
constructorOnAttach.promise,
constructorOnReady.promise,
constructorOnHide.promise,
@ -1396,7 +1396,7 @@ exports.testEventListeners = function(assert, done) {
onShow.promise,
onAttach.promise,
onReady.promise,
onHide.promise).then(function() {
onHide.promise]).then(function() {
assert.equal(eventListenerOrder.join(), [
'onAttach',
'once attach',
@ -1436,31 +1436,22 @@ exports.testAttachDoesNotEmitWhenShown = function(assert, done) {
}
if (++count == 1) {
setTimeout(function() {
let shown = false;
let endShownTest = false;
sidebar.once('show', function() {
assert.pass('shown was emitted');
shown = !endShownTest && true;
});
setImmediate(function() {
let shownFired = 0;
let onShow = () => shownFired++;
sidebar.on('show', onShow);
sidebar.show().then(function() {
assert.pass('calling hide');
sidebar.hide();
}).then(function() {
endShownTest = true;
setTimeout(function() {
sidebar.show().then(function() {
assert.ok(!shown, 'show did not emit');
sidebar.hide().then(function() {
sidebar.destroy();
done();
}).then(null, assert.fail);
})
})
}).then(null, assert.fail);
sidebar.show()
.then(() => assert.equal(shownFired, 0, 'shown should not be fired again when already showing from after attach'))
.then(sidebar.hide.bind(sidebar))
.then(sidebar.show.bind(sidebar))
.then(() => assert.equal(shownFired, 1, 'shown was emitted when `show` called after being hidden'))
.then(sidebar.show.bind(sidebar))
.then(() => {
assert.equal(shownFired, 1, 'shown was not emitted again if already being shown');
sidebar.off('show', onShow);
sidebar.destroy();
}).catch(assert.fail).then(done);
});
}
}

View File

@ -5,7 +5,10 @@
const { Loader } = require('sdk/test/loader');
const { browserWindows } = require('sdk/windows');
const { viewFor } = require('sdk/view/core');
const { modelFor } = require('sdk/model/core');
const { Ci } = require("chrome");
const { isBrowser, getWindowTitle } = require("sdk/window/utils");
// TEST: browserWindows Iterator
exports.testBrowserWindowsIterator = function(assert) {
@ -57,4 +60,37 @@ exports.testWindowActivateMethod_simple = function(assert) {
'Active tab is active after window.activate() call');
};
exports["test getView(window)"] = function(assert, done) {
browserWindows.once("open", window => {
const view = viewFor(window);
assert.ok(view instanceof Ci.nsIDOMWindow, "view is a window");
assert.ok(isBrowser(view), "view is a browser window");
assert.equal(getWindowTitle(view), window.title,
"window has a right title");
window.close(done);
});
browserWindows.open({ url: "data:text/html;charset=utf-8,<title>yo</title>" });
};
exports["test modelFor(window)"] = function(assert, done) {
browserWindows.once("open", window => {
const view = viewFor(window);
assert.ok(view instanceof Ci.nsIDOMWindow, "view is a window");
assert.ok(isBrowser(view), "view is a browser window");
assert.ok(modelFor(view) === window, "modelFor(browserWindow) is SDK window");
window.close(done);
});
browserWindows.open({ url: "data:text/html;charset=utf-8,<title>yo</title>" });
};
require('sdk/test').run(exports);