diff --git a/addon-sdk/source/app-extension/bootstrap.js b/addon-sdk/source/app-extension/bootstrap.js index 60dc6afb547d..77be051b0c81 100644 --- a/addon-sdk/source/app-extension/bootstrap.js +++ b/addon-sdk/source/app-extension/bootstrap.js @@ -235,6 +235,7 @@ function startup(data, reasonCode) { stopOnError: options.stopOnError, verbose: options.verbose, parseable: options.parseable, + checkMemory: options.check_memory, } } }); diff --git a/addon-sdk/source/doc/dev-guide-source/credits.md b/addon-sdk/source/doc/dev-guide-source/credits.md index a02758b90158..8dfaad656bd9 100644 --- a/addon-sdk/source/doc/dev-guide-source/credits.md +++ b/addon-sdk/source/doc/dev-guide-source/credits.md @@ -76,6 +76,7 @@ We'd like to thank our many Jetpack project contributors! They include: * Hrishikesh Kale * Wes Kocher * Lajos Koszti +* Kusanagi Kouichi * [Vladimir Kukushkin](https://github.com/kukushechkin) ### L ### @@ -129,6 +130,7 @@ We'd like to thank our many Jetpack project contributors! They include: * Dan Stevens * [J. Ryan Stinnett](https://github.com/jryans) * [Mihai Sucan](https://github.com/mihaisucan) +* Sunny ([darkowlzz](https://github.com/darkowlzz)) ### T ### diff --git a/addon-sdk/source/doc/module-source/sdk/lang/functional.md b/addon-sdk/source/doc/module-source/sdk/lang/functional.md index 9b63948aab44..42d2092ce368 100644 --- a/addon-sdk/source/doc/module-source/sdk/lang/functional.md +++ b/addon-sdk/source/doc/module-source/sdk/lang/functional.md @@ -94,26 +94,26 @@ Returns the value that is returned by `callee`. Returns the return value of `callee`. - + @function -[Curries](http://en.wikipedia.org/wiki/Currying) the given function with the arguments given. +Takes a function and bind values to one or more arguments, returning a new function of smaller arity. - let { curry } = require("sdk/lang/functional"); + let { partial } = require("sdk/lang/functional"); let add = function add (x, y) { return x + y; } - let addOne = curry(add, 1); + let addOne = partial(add, 1); addOne(5); // 6 addOne(10); // 11 - curry(add, addOne(20))(2); // 23 + partial(add, addOne(20))(2); // 23 @param fn {function} - Function to be curried. + Function on which partial application is to be performed. @param arguments... {mixed} Additional arguments @returns {function} - The curried function. + The partial function. diff --git a/addon-sdk/source/doc/module-source/sdk/lang/type.md b/addon-sdk/source/doc/module-source/sdk/lang/type.md new file mode 100644 index 000000000000..418f13877afc --- /dev/null +++ b/addon-sdk/source/doc/module-source/sdk/lang/type.md @@ -0,0 +1,346 @@ + + +The `lang/type` module provides simple helper functions for working with type +detection. + + +@function +Returns `true` if `value` is [`undefined`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/undefined), `false` otherwise. + + let { isUndefined } = require('sdk/lang/type'); + + var foo; + isUndefined(foo); // true + isUndefined(0); // false + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is `undefined`. + + + +@function +Returns `true` if `value` is [`null`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/null), `false` otherwise. + + let { isNull } = require('sdk/lang/type'); + + isNull(null); // true + isNull(false); // false + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is `null`. + + + +@function +Returns `true` if `value` is a [`String`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String), +`false` otherwise. Uses [`typeof`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/typeof) +operator to check type, and will only properly detect string primitives: +for example, a string created with `new String()` will always return false. + + let { isString } = require('sdk/lang/type'); + + isString('my string'); // true + isString(100); // false + isString('100'); // true + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is a `String`. + + + +@function +Returns `true` if `value` is a [`Number`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number), +`false` otherwise. Uses [`typeof`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/typeof) +operator to check type, and will only properly detect number primitives: +for example, a number created with `new Number()` will always return false. + + let { isNumber } = require('sdk/lang/type'); + + isNumber(3.1415); // true + isNumber(100); // true + isNumber('100'); // false + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is a `Number`. + + + +@function +Returns `true` if `value` is a [`RegExp`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp), `false` otherwise. + + let { isRegExp } = require('sdk/lang/type'); + + isRegExp(/[^\.]*\.js$/); // true + isRegExp(new RegExp('substring')); // true + isRegExp(1000); // false + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is a `RegExp`. + + + +@function +Returns `true` if `value` is a [`Date`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date), `false` otherwise. + + let { isDate } = require('sdk/lang/type'); + + isDate(new Date()); // true + isDate('3/1/2013'); // false + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is a `Date`. + + + +@function +Returns `true` if `value` is a [`Function`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function), `false` otherwise. + + let { isFunction } = require('sdk/lang/type'); + + let fn = function () {}; + isFunction(fn); // true; + isFunction(otherFn); // true; + isFunction(function () {}); // true; + + function otherFn () {} + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is a `Function`. + + + +@function +Returns `true` if `value` is an [`Object`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object) and not null, `false` otherwise. + + let { isObject } = require('sdk/lang/type'); + + isObject({}); // true + isObject(new Class()); // true + isObject(null); // false + isObject(5); // false + + function Class () {} + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is an `Object`. + + + +@function +Returns `true` if `value` is an [`Array`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array), `false` otherwise. Uses native +[`Array.isArray`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray). + + let { isArray } = require('sdk/lang/type'); + + isArray([]); // true + isArray({}); // false + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is an `Array`. + + + +@function +Returns `true` if `value` is an array-like [`arguments`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments) object, +`false` otherwise. + + let { isArguments } = require('sdk/lang/type'); + + function run () { + isArguments(arguments); // true + isArguments([]); // false + isArguments(Array.slice(arguments)); // false + } + run(1, 2, 3); + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is an `arguments` object. + + + +@function +Returns `true` if `value` is a primitive value: that is, any of [`null`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/null), [`undefined`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/undefined), [`number`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/number), +[`boolean`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/boolean), or [`string`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/string). Returns `false` if `value` is not a primitive value. + + let { isPrimitive } = require('sdk/lang/type'); + + isPrimitive(3); // true + isPrimitive('foo'); // true + isPrimitive({}); // false + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is a primitive. + + + +@function +Returns `true` if `value` is a direct descendant of `Object.prototype` or `null`. +Similar to jQuery's [`isPlainObject`](http://api.jquery.com/jQuery.isPlainObject/). + + let { isFlat } = require('sdk/lang/type'); + + isFlat({}); // true + isFlat(new Type()); // false + + function Type () {} + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is a direct descendant of `Object.prototype` or `null`. + + + +@function +Returns `true` if `value` is an [`Object`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object) with no properties and `false` otherwise. + + let { isEmpty } = require('sdk/lang/type'); + + isEmpty({}); // true + isEmpty({ init: false }); // false + +@param value {object} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is an `Object` with no properties. + + + +@function +Returns `true` if `value` is a string, number, boolean, null, array of JSON-serializable values, or an object whose property values are themselves JSON-serializable. Returns `false` otherwise. + + let { isJSON } = require('sdk/lang/type'); + + isJSON({ value: 42 }); // true + isJSON({ fn: function () {} ); // false + +@param value {mixed} + The variable to check. + +@returns {boolean} + Boolean indicating if `value` is an `Array`/flat `Object` containing only + atomic values and other flat objects. + + + +@function +Returns `true` if `value` is an instance of a given `Type`. This is similar to the [`instanceof`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/instanceof) operator. +The difference is that the `Type` constructor can be from a scope that has +a different top level object: for example, it could be from a different iframe, +module or sandbox. + + let { instanceOf } = require('sdk/lang/type'); + + instanceOf(new Class(), Class); // true + function Class() {} + +@param value {object} + The variable to check. + +@param Type {object} + The constructor to compare to `value` + +@returns {boolean} + Boolean indicating if `value` is an instance of `Type`. + + + +@function +Returns the textual representation of `value`, containing property descriptors and types +of properties contained within the object. + + let { source } = require('sdk/lang/type'); + + var obj = { + name: undefined, + twitter: '@horse_js', + tweets: [ + { id: 100, text: 'What happens to you if you break the monad laws?' }, + { id: 101, text: 'JAVASCRIPT DUBSTEP GENERATOR' } + ] + }; + + console.log(source(obj)); + // Prints the below + /* + { // [object Object] + // writable configurable enumerable + name: undefined, + // writable configurable enumerable + twitter: "@horse_js", + // writable configurable enumerable + tweets: [ + { // [object Object] + // writable configurable enumerable + id: 100, + // writable configurable enumerable + text: "What happens to you if you break the monad laws?", + "__proto__": { // [object Object] + + } + }, + { // [object Object] + // writable configurable enumerable + id: 101, + // writable configurable enumerable + text: "JAVASCRIPT DUBSTEP GENERATOR", + "__proto__": { // [object Object] + + } + } + ], + "__proto__": { // [object Object] + + } + } + */ + +@param value {mixed} + The source object to create a textual representation of. + +@param indent {string} + Optional. `String` to be used as indentation in output. 4 spaces by default. + +@param limit {number} + Optional. Number of properties to display per object. + +@returns {string} + The textual representation of `value`. + diff --git a/addon-sdk/source/doc/module-source/sdk/tabs.md b/addon-sdk/source/doc/module-source/sdk/tabs.md index fa9c59268953..29938775ab18 100644 --- a/addon-sdk/source/doc/module-source/sdk/tabs.md +++ b/addon-sdk/source/doc/module-source/sdk/tabs.md @@ -192,6 +192,11 @@ registration. Tabs emit all the events described in the Events section. Listeners are passed the `Tab` object that triggered the event. + +@property {string} +The unique id for the tab. This property is read-only. + + @property {string} The title of the tab (usually the title of the page currently loaded in the tab) diff --git a/addon-sdk/source/lib/sdk/browser/events.js b/addon-sdk/source/lib/sdk/browser/events.js index b538608efaf5..250cbd045e07 100644 --- a/addon-sdk/source/lib/sdk/browser/events.js +++ b/addon-sdk/source/lib/sdk/browser/events.js @@ -17,4 +17,4 @@ const { isBrowser } = require("../window/utils"); // implementation for `isBrowser`. Either way it's not really needed yet // neither window tracker provides this event. -exports.events = filter(function({target}) isBrowser(target), events); +exports.events = filter(events, function({target}) isBrowser(target)); diff --git a/addon-sdk/source/lib/sdk/console/plain-text.js b/addon-sdk/source/lib/sdk/console/plain-text.js index 445cebd82932..3ab3b6f247c1 100644 --- a/addon-sdk/source/lib/sdk/console/plain-text.js +++ b/addon-sdk/source/lib/sdk/console/plain-text.js @@ -13,7 +13,7 @@ const self = require("../self"); const traceback = require("./traceback") const prefs = require("../preferences/service"); const { merge } = require("../util/object"); -const { curry } = require("../lang/functional"); +const { partial } = require("../lang/functional"); const LEVELS = { "all": Number.MIN_VALUE, @@ -102,13 +102,13 @@ function PlainTextConsole(print) { } merge(this, { - log: curry(message, print, "info"), - info: curry(message, print, "info"), - warn: curry(message, print, "warn"), - error: curry(message, print, "error"), - debug: curry(message, print, "debug"), - exception: curry(errorMessage, print), - trace: curry(traceMessage, print), + log: partial(message, print, "info"), + info: partial(message, print, "info"), + warn: partial(message, print, "warn"), + error: partial(message, print, "error"), + debug: partial(message, print, "debug"), + exception: partial(errorMessage, print), + trace: partial(traceMessage, print), dir: function dir() {}, group: function group() {}, diff --git a/addon-sdk/source/lib/sdk/core/disposable.js b/addon-sdk/source/lib/sdk/core/disposable.js index a15e2d72926a..162fc4f33526 100644 --- a/addon-sdk/source/lib/sdk/core/disposable.js +++ b/addon-sdk/source/lib/sdk/core/disposable.js @@ -19,8 +19,7 @@ function initialize(instance) { // Create an event handler that will dispose instance on unload. function handler(event) { if (event.subject.wrappedJSObject === unloadSubject) { - dispose(instance); - instance.dispose(); + instance.destroy(); } } @@ -66,8 +65,10 @@ let Disposable = Class({ destroy: function destroy() { // Destroying disposable removes unload handler so that attempt to dispose // won't be made at unload & delegates to dispose. - dispose(this); - this.dispose(); + if (disposables.has(this)) { + dispose(this); + this.dispose(); + } } }); exports.Disposable = Disposable; diff --git a/addon-sdk/source/lib/sdk/event/utils.js b/addon-sdk/source/lib/sdk/event/utils.js index 1603c0eabd6f..3ab2c140a7ff 100644 --- a/addon-sdk/source/lib/sdk/event/utils.js +++ b/addon-sdk/source/lib/sdk/event/utils.js @@ -25,7 +25,7 @@ let refs = (function() { } })(); -function transform(f, input) { +function transform(input, f) { let output = {}; // Since event listeners don't prevent `input` to be GC-ed we wanna presrve @@ -46,16 +46,16 @@ function transform(f, input) { // High order event transformation function that takes `input` event channel // and returns transformation containing only events on which `p` predicate // returns `true`. -function filter(predicate, input) { - return transform(function(data, next) { +function filter(input, predicate) { + return transform(input, function(data, next) { if (predicate(data)) next(data) - }, input); + }); } exports.filter = filter; // High order function that takes `input` and returns input of it's values // mapped via given `f` function. -function map(f, input) transform(function(data, next) next(f(data)), input) +function map(input, f) transform(input, function(data, next) next(f(data))) exports.map = map; // High order function that takes `input` stream of streams and merges them @@ -97,7 +97,7 @@ function merge(inputs) { } exports.merge = merge; -function expand(f, inputs) merge(map(f, inputs)) +function expand(inputs, f) merge(map(inputs, f)) exports.expand = expand; function pipe(from, to) on(from, "*", emit.bind(emit, to)) diff --git a/addon-sdk/source/lib/sdk/lang/functional.js b/addon-sdk/source/lib/sdk/lang/functional.js index 3595a27043b5..2f763a220852 100644 --- a/addon-sdk/source/lib/sdk/lang/functional.js +++ b/addon-sdk/source/lib/sdk/lang/functional.js @@ -13,6 +13,7 @@ module.metadata = { }; const { setTimeout } = require("../timers"); +const { deprecateFunction } = require("../util/deprecate"); /** * Takes `lambda` function and returns a method. When returned method is @@ -55,14 +56,15 @@ function invoke(callee, params, self) callee.apply(self, params); exports.invoke = invoke; /** - * Curries a function with the arguments given. + * Takes a function and bind values to one or more arguments, returning a new + * function of smaller arity. * * @param {Function} fn - * The function to curry + * The function to partial * - * @returns The function curried + * @returns The new function with binded values */ -function curry(fn) { +function partial(fn) { if (typeof fn !== "function") throw new TypeError(String(fn) + " is not a function"); @@ -70,7 +72,12 @@ function curry(fn) { return function() fn.apply(this, args.concat(Array.slice(arguments))); } -exports.curry = curry; +exports.partial = partial; + +exports.curry = deprecateFunction(partial, + 'curry is deprecated, ' + + 'please use require("sdk/lang/functional").partial instead.' +); /** * Returns the composition of a list of functions, where each function consumes diff --git a/addon-sdk/source/lib/sdk/loader/cuddlefish.js b/addon-sdk/source/lib/sdk/loader/cuddlefish.js index 51355cc75d8a..53dd2f6395fb 100644 --- a/addon-sdk/source/lib/sdk/loader/cuddlefish.js +++ b/addon-sdk/source/lib/sdk/loader/cuddlefish.js @@ -63,9 +63,15 @@ function incompatibility(module) { let applications = Object.keys(engines); - applications.forEach(xulappModule.is); - - let versionRange = engines[xulappModule.name]; + let versionRange; + applications.forEach(function(name) { + if (xulappModule.is(name)) { + versionRange = engines[name]; + // Continue iteration. We want to ensure the module doesn't + // contain a typo in the applications' name or some unknown + // application - `is` function throws an exception in that case. + } + }); if (typeof(versionRange) === "string") { if (xulappModule.satisfiesVersion(versionRange)) diff --git a/addon-sdk/source/lib/sdk/net/xhr.js b/addon-sdk/source/lib/sdk/net/xhr.js index 7fc829970a38..e20ff7102629 100644 --- a/addon-sdk/source/lib/sdk/net/xhr.js +++ b/addon-sdk/source/lib/sdk/net/xhr.js @@ -1,7 +1,6 @@ /* 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 = { @@ -64,7 +63,8 @@ var getRequestCount = exports.getRequestCount = function getRequestCount() { }; var XMLHttpRequest = exports.XMLHttpRequest = function XMLHttpRequest() { - var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + let self = this; + let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(Ci.nsIXMLHttpRequest); // For the sake of simplicity, don't tie this request to any UI. req.mozBackgroundRequest = true; @@ -73,31 +73,25 @@ var XMLHttpRequest = exports.XMLHttpRequest = function XMLHttpRequest() { this._req = req; this._orsc = null; + this._cleanup = this._cleanup.bind(this); requests.push(this); - var self = this; - - this._boundCleanup = function _boundCleanup() { - self._cleanup(); - }; - - TERMINATE_EVENTS.forEach( - function(name) { - self._req.addEventListener(name, self._boundCleanup, false); - }); + TERMINATE_EVENTS.forEach(function(name) { + self._req.addEventListener(name, self._cleanup, false); + }); }; XMLHttpRequest.prototype = { _cleanup: function _cleanup() { this.onreadystatechange = null; - var index = requests.indexOf(this); + + let index = requests.indexOf(this); if (index != -1) { - var self = this; - TERMINATE_EVENTS.forEach( - function(name) { - self._req.removeEventListener(name, self._boundCleanup, false); - }); + let self = this; + TERMINATE_EVENTS.forEach(function(name) { + self._req.removeEventListener(name, self._cleanup, false); + }); requests.splice(index, 1); } }, @@ -105,11 +99,11 @@ XMLHttpRequest.prototype = { this._req.abort(); this._cleanup(); }, - addEventListener: function addEventListener() { - throw new Error("not implemented"); + addEventListener: function addEventListener(name, func) { + this._req.addEventListener(name, func); }, - removeEventListener: function removeEventListener() { - throw new Error("not implemented"); + removeEventListener: function removeEventListener(name, func) { + this._req.removeEventListener(name, func); }, set upload(newValue) { throw new Error("not implemented"); @@ -128,12 +122,15 @@ XMLHttpRequest.prototype = { this._req.onreadystatechange = function() { try { self._orsc.apply(self, arguments); - } catch (e) { + } + catch (e) { console.exception(e); } }; - } else + } + else { this._req.onreadystatechange = null; + } } }; diff --git a/addon-sdk/source/lib/sdk/panel.js b/addon-sdk/source/lib/sdk/panel.js index c60bbce95e2f..68be56735734 100644 --- a/addon-sdk/source/lib/sdk/panel.js +++ b/addon-sdk/source/lib/sdk/panel.js @@ -32,9 +32,6 @@ const { filter, pipe } = require("./event/utils"); const { getNodeView, getActiveView } = require("./view/core"); const { isNil, isObject } = require("./lang/type"); -if (isPrivateBrowsingSupported && isWindowPBSupported) - throw Error('The panel module cannot be used with per-window private browsing at the moment, see Bug 816257'); - let isArray = Array.isArray; let assetsURI = require("./self").data.url(); @@ -251,24 +248,24 @@ const Panel = Class({ exports.Panel = Panel; // Filter panel events to only panels that are create by this module. -let panelEvents = filter(function({target}) panelFor(target), events); +let panelEvents = filter(events, function({target}) panelFor(target)); // Panel events emitted after panel has being shown. -let shows = filter(function({type}) type === "sdk-panel-shown", panelEvents); +let shows = filter(panelEvents, function({type}) type === "sdk-panel-shown"); // Panel events emitted after panel became hidden. -let hides = filter(function({type}) type === "sdk-panel-hidden", panelEvents); +let hides = filter(panelEvents, function({type}) type === "sdk-panel-hidden"); // Panel events emitted after content inside panel is ready. For different // panels ready may mean different state based on `contentScriptWhen` attribute. // Weather given event represents readyness is detected by `getAttachEventType` // helper function. -let ready = filter(function({type, target}) - getAttachEventType(modelFor(panelFor(target))) === type, panelEvents); +let ready = filter(panelEvents, function({type, target}) + getAttachEventType(modelFor(panelFor(target))) === type); // Panel events emitted after content document in the panel has changed. -let change = filter(function({type}) type === "sdk-panel-content-changed", - panelEvents); +let change = filter(panelEvents, function({type}) + type === "sdk-panel-content-changed"); // Forward panel show / hide events to panel's own event listeners. on(shows, "data", function({target}) emit(panelFor(target), "show")); diff --git a/addon-sdk/source/lib/sdk/tab/events.js b/addon-sdk/source/lib/sdk/tab/events.js index 31debe17cf86..4a9f3b3aeb98 100644 --- a/addon-sdk/source/lib/sdk/tab/events.js +++ b/addon-sdk/source/lib/sdk/tab/events.js @@ -39,13 +39,13 @@ function tabEventsFor(window) { } // Filter DOMContentLoaded events from all the browser events. -let readyEvents = filter(function(e) e.type === "DOMContentLoaded", events); +let readyEvents = filter(events, function(e) e.type === "DOMContentLoaded"); // Map DOMContentLoaded events to it's target browser windows. -let futureWindows = map(function(e) e.target, readyEvents); +let futureWindows = map(readyEvents, function(e) e.target); // Expand all browsers that will become interactive to supported tab events // on these windows. Result will be a tab events from all tabs of all windows // that will become interactive. -let eventsFromFuture = expand(tabEventsFor, futureWindows); +let eventsFromFuture = expand(futureWindows, tabEventsFor); // Above covers only windows that will become interactive in a future, but some // windows may already be interactive so we pick those and expand to supported diff --git a/addon-sdk/source/lib/sdk/test/harness.js b/addon-sdk/source/lib/sdk/test/harness.js index 3d41987f13fd..97216fb9eb5e 100644 --- a/addon-sdk/source/lib/sdk/test/harness.js +++ b/addon-sdk/source/lib/sdk/test/harness.js @@ -57,6 +57,9 @@ var results = { testRuns: [] }; +// A list of the compartments and windows loaded after startup +var startLeaks; + // JSON serialization of last memory usage stats; we keep it stringified // so we don't actually change the memory usage stats (in terms of objects) // of the JSRuntime we're profiling. @@ -162,9 +165,32 @@ function reportMemoryUsage() { var gWeakrefInfo; -function showResults() { +function checkMemory() { memory.gc(); + setTimeout(function () { + memory.gc(); + setTimeout(function () { + let leaks = getPotentialLeaks(); + let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) { + return !(url in startLeaks.compartments); + }); + let windowURLs = Object.keys(leaks.windows).filter(function(url) { + return !(url in startLeaks.windows); + }); + + for (let url of compartmentURLs) + console.warn("LEAKED", leaks.compartments[url]); + + for (let url of windowURLs) + console.warn("LEAKED", leaks.windows[url]); + + showResults(); + }); + }); +} + +function showResults() { if (gWeakrefInfo) { gWeakrefInfo.forEach( function(info) { @@ -227,7 +253,7 @@ function cleanup() { console.exception(e); }; - setTimeout(showResults, 1); + setTimeout(require('@test/options').checkMemory ? checkMemory : showResults, 1); // dump the coverobject if (Object.keys(coverObject).length){ @@ -245,6 +271,123 @@ function cleanup() { } } +function getPotentialLeaks() { + memory.gc(); + + // Things we can assume are part of the platform and so aren't leaks + let WHITELIST_BASE_URLS = [ + "chrome://", + "resource:///", + "resource://app/", + "resource://gre/", + "resource://gre-resources/", + "resource://pdf.js/", + "resource://pdf.js.components/", + "resource://services-common/", + "resource://services-crypto/", + "resource://services-sync/" + ]; + + let ioService = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + let uri = ioService.newURI("chrome://global/content/", "UTF-8", null); + let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]. + getService(Ci.nsIChromeRegistry); + uri = chromeReg.convertChromeURL(uri); + let spec = uri.spec; + let pos = spec.indexOf("!/"); + WHITELIST_BASE_URLS.push(spec.substring(0, pos + 2)); + + let compartmentRegexp = new RegExp("^explicit/js-non-window/compartments/non-window-global/compartment\\((.+)\\)/"); + let compartmentDetails = new RegExp("^([^,]+)(?:, (.+?))?(?: \\(from: (.*)\\))?$"); + let windowRegexp = new RegExp("^explicit/window-objects/top\\((.*)\\)/active"); + let windowDetails = new RegExp("^(.*), id=.*$"); + + function isPossibleLeak(item) { + if (!item.location) + return false; + + for (let whitelist of WHITELIST_BASE_URLS) { + if (item.location.substring(0, whitelist.length) == whitelist) + return false; + } + + return true; + } + + let compartments = {}; + let windows = {}; + function logReporter(process, path, kind, units, amount, description) { + let matches = compartmentRegexp.exec(path); + if (matches) { + if (matches[1] in compartments) + return; + + let details = compartmentDetails.exec(matches[1]); + if (!details) { + console.error("Unable to parse compartment detail " + matches[1]); + return; + } + + let item = { + path: matches[1], + principal: details[1], + location: details[2] ? details[2].replace("\\", "/", "g") : undefined, + source: details[3] ? details[3].split(" -> ").reverse() : undefined, + toString: function() this.location + }; + + if (!isPossibleLeak(item)) + return; + + compartments[matches[1]] = item; + return; + } + + matches = windowRegexp.exec(path); + if (matches) { + if (matches[1] in windows) + return; + + let details = windowDetails.exec(matches[1]); + if (!details) { + console.error("Unable to parse window detail " + matches[1]); + return; + } + + let item = { + path: matches[1], + location: details[1].replace("\\", "/", "g"), + source: [details[1].replace("\\", "/", "g")], + toString: function() this.location + }; + + if (!isPossibleLeak(item)) + return; + + windows[matches[1]] = item; + } + } + + let mgr = Cc["@mozilla.org/memory-reporter-manager;1"]. + getService(Ci.nsIMemoryReporterManager); + + let enm = mgr.enumerateReporters(); + while (enm.hasMoreElements()) { + let reporter = enm.getNext().QueryInterface(Ci.nsIMemoryReporter); + logReporter(reporter.process, reporter.path, reporter.kind, reporter.units, + reporter.amount, reporter.description); + } + + let enm = mgr.enumerateMultiReporters(); + while (enm.hasMoreElements()) { + let mr = enm.getNext().QueryInterface(Ci.nsIMemoryMultiReporter); + mr.collectReports(logReporter, null); + } + + return { compartments: compartments, windows: windows }; +} + function nextIteration(tests) { if (tests) { results.passed += tests.passed; @@ -440,6 +583,12 @@ var runTests = exports.runTests = function runTests(options) { global: {} // useful for storing things like coverage testing. }); + // Load these before getting initial leak stats as they will still be in + // memory when we check later + require("../deprecated/unit-test"); + require("../deprecated/unit-test-finder"); + startLeaks = getPotentialLeaks(); + nextIteration(); } catch (e) { let frames = fromException(e).reverse().reduce(function(frames, frame) { diff --git a/addon-sdk/source/lib/sdk/test/loader.js b/addon-sdk/source/lib/sdk/test/loader.js index 9efbf71d59e2..39a49686a979 100644 --- a/addon-sdk/source/lib/sdk/test/loader.js +++ b/addon-sdk/source/lib/sdk/test/loader.js @@ -6,6 +6,7 @@ const { Loader, resolveURI, Require, unload, override, descriptor } = require('../loader/cuddlefish'); +const { ensure } = require('../system/unload'); const addonWindow = require('../addon/window'); const { PlainTextConsole } = require("sdk/console/plain-text"); @@ -19,7 +20,7 @@ function CustomLoader(module, globals, packaging) { }); let loader = Loader(options); - return Object.create(loader, descriptor({ + let wrapper = Object.create(loader, descriptor({ require: Require(loader, module), sandbox: function(id) { let requirement = loader.resolve(id, module.id); @@ -30,6 +31,8 @@ function CustomLoader(module, globals, packaging) { unload(loader, reason); } })); + ensure(wrapper); + return wrapper; }; exports.Loader = CustomLoader; diff --git a/addon-sdk/source/python-lib/cuddlefish/__init__.py b/addon-sdk/source/python-lib/cuddlefish/__init__.py index a70ac5720851..bce03e1df3b3 100644 --- a/addon-sdk/source/python-lib/cuddlefish/__init__.py +++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py @@ -228,6 +228,12 @@ parser_groups = ( metavar=None, default=False, cmds=['sdocs'])), + (("", "--check-memory",), dict(dest="check_memory", + help="attempts to detect leaked compartments after a test run", + action="store_true", + default=False, + cmds=['test', 'testpkgs', 'testaddons', + 'testall'])), ] ), @@ -660,7 +666,7 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, # a Mozilla application (which includes running tests). use_main = False - inherited_options = ['verbose', 'enable_e10s', 'parseable'] + inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory'] enforce_timeouts = False if command == "xpi": diff --git a/addon-sdk/source/test/addons/private-browsing-supported/test-panel.js b/addon-sdk/source/test/addons/private-browsing-supported/test-panel.js index 453916ae4034..242cfd3a7bf1 100644 --- a/addon-sdk/source/test/addons/private-browsing-supported/test-panel.js +++ b/addon-sdk/source/test/addons/private-browsing-supported/test-panel.js @@ -1,16 +1,83 @@ 'use strict'; -const { isWindowPBSupported } = require('sdk/private-browsing/utils'); +const { open, focus, close } = require('sdk/window/helpers'); +const { isPrivate } = require('sdk/private-browsing'); +const { defer } = require('sdk/core/promise'); -if (isWindowPBSupported) { - exports.testRequirePanel = function (assert) { - try { - require('panel'); +const BROWSER = 'chrome://browser/content/browser.xul'; + +exports.testRequirePanel = function(assert) { + require('panel'); + assert.ok('the panel module should not throw an error'); +}; + +exports.testShowPanelInPrivateWindow = function(assert, done) { + let panel = require('sdk/panel').Panel({ + contentURL: "data:text/html;charset=utf-8," + }); + + testShowPanel(assert, panel). + then(makeEmptyPrivateBrowserWindow). + then(focus). + then(function(window) { + assert.equal(isPrivate(window), true, 'opened window is private'); + assert.pass('private window was focused'); + return window; + }). + then(function(window) { + let { promise, resolve } = defer(); + + assert.ok(!panel.isShowing, 'the panel is not showing [1]'); + + panel.once('show', function() { + assert.ok(panel.isShowing, 'the panel is showing'); + + panel.once('hide', function() { + assert.ok(!panel.isShowing, 'the panel is not showing [2]'); + + resolve(window); + }); + + panel.hide(); + }); + + panel.show(); + + return promise; + }). + then(close). + then(done, assert.fail.bind(assert)); +}; + + +function makeEmptyPrivateBrowserWindow(options) { + options = options || {}; + return open(BROWSER, { + features: { + chrome: true, + toolbar: true, + private: true } - catch(e) { - assert.ok(e.message.match(/Bug 816257/), 'Bug 816257 is mentioned'); - return; - } - assert.fail('the panel module should throw an error'); - } + }); +} + +function testShowPanel(assert, panel) { + let { promise, resolve } = defer(); + + assert.ok(!panel.isShowing, 'the panel is not showing [1]'); + + panel.once('show', function() { + assert.ok(panel.isShowing, 'the panel is showing'); + + panel.once('hide', function() { + assert.ok(!panel.isShowing, 'the panel is not showing [2]'); + + resolve(null); + }); + + panel.hide(); + }) + panel.show(); + + return promise; } diff --git a/addon-sdk/source/test/private-browsing/helper.js b/addon-sdk/source/test/private-browsing/helper.js index 71f0d30d103e..cf4d5a0162c5 100644 --- a/addon-sdk/source/test/private-browsing/helper.js +++ b/addon-sdk/source/test/private-browsing/helper.js @@ -10,7 +10,7 @@ const { loader } = LoaderWithHookedConsole(module); const pb = loader.require('sdk/private-browsing'); const pbUtils = loader.require('sdk/private-browsing/utils'); const xulApp = require("sdk/system/xul-app"); -const { openDialog, getMostRecentBrowserWindow } = require('sdk/window/utils'); +const { open: openWindow, getMostRecentBrowserWindow } = require('sdk/window/utils'); const { openTab, getTabContentWindow, getActiveTab, setTabURL, closeTab } = require('sdk/tabs/utils'); const promise = require("sdk/core/promise"); const windowHelpers = require('sdk/window/helpers'); @@ -58,6 +58,7 @@ exports.openWebpage = function openWebpage(url, enablePrivate) { let rawTab = openTab(chromeWindow, url, { isPrivate: enablePrivate }); + return { ready: promise.resolve(getTabContentWindow(rawTab)), close: function () { @@ -68,8 +69,10 @@ exports.openWebpage = function openWebpage(url, enablePrivate) { }; } else { - let win = openDialog({ - private: enablePrivate + let win = openWindow(null, { + features: { + private: enablePrivate + } }); let deferred = promise.defer(); @@ -77,7 +80,8 @@ exports.openWebpage = function openWebpage(url, enablePrivate) { // that the window is really ready events.on("browser-delayed-startup-finished", function onReady({subject}) { if (subject == win) { - events.off("browser-delayed-startup-finished", onReady, true); + events.off("browser-delayed-startup-finished", onReady); + deferred.resolve(win); let rawTab = getActiveTab(win); setTabURL(rawTab, url); diff --git a/addon-sdk/source/test/tabs/test-fennec-tabs.js b/addon-sdk/source/test/tabs/test-fennec-tabs.js index e186713e5c7d..0a4c3544ea1c 100644 --- a/addon-sdk/source/test/tabs/test-fennec-tabs.js +++ b/addon-sdk/source/test/tabs/test-fennec-tabs.js @@ -192,33 +192,6 @@ exports.testTabLocation = function(test) { }); }; -// TEST: tab.reload() -exports.testTabReload = function(test) { - test.waitUntilDone(); - - let url = "data:text/html;charset=utf-8,"; - - tabs.open({ - url: url, - onReady: function onReady(tab) { - tab.removeListener('ready', onReady); - - tab.once( - 'ready', - function onReload() { - test.pass("the tab was loaded again"); - test.assertEqual(tab.url, url, "the tab has the same URL"); - - // end test - tab.close(function() test.done()); - } - ); - - tab.reload(); - } - }); -}; - // TEST: tab.move() exports.testTabMove = function(test) { test.waitUntilDone(); diff --git a/addon-sdk/source/test/tabs/test-firefox-tabs.js b/addon-sdk/source/test/tabs/test-firefox-tabs.js index 607952399ac1..8168bdc933dd 100644 --- a/addon-sdk/source/test/tabs/test-firefox-tabs.js +++ b/addon-sdk/source/test/tabs/test-firefox-tabs.js @@ -300,39 +300,6 @@ exports.testTabClose = function(test) { }); }; -// TEST: tab.reload() -exports.testTabReload = function(test) { - test.waitUntilDone(); - openBrowserWindow(function(window, browser) { - let tabs = require("sdk/tabs"); - let url = "data:text/html;charset=utf-8,"; - - tabs.open({ url: url, onReady: function onReady(tab) { - tab.removeListener("ready", onReady); - - browser.addEventListener( - "load", - function onLoad() { - browser.removeEventListener("load", onLoad, true); - - browser.addEventListener( - "load", - function onReload() { - browser.removeEventListener("load", onReload, true); - test.pass("the tab was loaded again"); - test.assertEqual(tab.url, url, "the tab has the same URL"); - closeBrowserWindow(window, function() test.done()); - }, - true - ); - tab.reload(); - }, - true - ); - }}); - }); -}; - // TEST: tab.move() exports.testTabMove = function(test) { test.waitUntilDone(); diff --git a/addon-sdk/source/test/test-context-menu.js b/addon-sdk/source/test/test-context-menu.js index 1ca3877d8797..5e6109dd0bdc 100644 --- a/addon-sdk/source/test/test-context-menu.js +++ b/addon-sdk/source/test/test-context-menu.js @@ -9,6 +9,7 @@ let { Cc, Ci } = require("chrome"); const { Loader } = require('sdk/test/loader'); const timer = require("sdk/timers"); +const { merge } = require("sdk/util/object"); // These should match the same constants in the module. const ITEM_CLASS = "addon-context-menu-item"; @@ -1944,6 +1945,7 @@ exports.testParentMenu = function (test) { }); }; + // Existing context menu modifications should apply to new windows. exports.testNewWindow = function (test) { test = new TestHelper(test); @@ -1981,6 +1983,73 @@ exports.testNewWindowMultipleModules = function (test) { }; +// Existing context menu modifications should not apply to new private windows. +exports.testNewPrivateWindow = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ label: "item" }); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + popup.hidePopup(); + + test.withNewPrivateWindow(function () { + test.showMenu(null, function (popup) { + test.checkMenu([], [], []); + test.done(); + }); + }); + }); +}; + + +// Existing context menu modifications should apply to new private windows when +// private browsing support is enabled. +exports.testNewPrivateEnabledWindow = function (test) { + test = new TestHelper(test); + let loader = test.newPrivateLoader(); + + let item = new loader.cm.Item({ label: "item" }); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + popup.hidePopup(); + + test.withNewPrivateWindow(function () { + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); + }); + }); +}; + + +// Existing context menu modifications should apply to new private windows when +// private browsing support is enabled unless unloaded. +exports.testNewPrivateEnabledWindowUnloaded = function (test) { + test = new TestHelper(test); + let loader = test.newPrivateLoader(); + + let item = new loader.cm.Item({ label: "item" }); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + popup.hidePopup(); + + loader.unload(); + + test.withNewPrivateWindow(function () { + test.showMenu(null, function (popup) { + test.checkMenu([], [], []); + test.done(); + }); + }); + }); +}; + + // Items in the context menu should be sorted according to locale. exports.testSorting = function (test) { test = new TestHelper(test); @@ -3339,6 +3408,37 @@ TestHelper.prototype = { return wrapper; }, + // As above but the loader has private-browsing support enabled. + newPrivateLoader: function() { + let base = require("@loader/options"); + + // Clone current loader's options adding the private-browsing permission + let options = merge({}, base, { + metadata: merge({}, base.metadata || {}, { + permissions: merge({}, base.metadata.permissions || {}, { + 'private-browsing': true + }) + }) + }); + + const self = this; + let loader = Loader(module, null, options); + let wrapper = { + loader: loader, + cm: loader.require("sdk/context-menu"), + globalScope: loader.sandbox("sdk/context-menu"), + unload: function () { + loader.unload(); + let idx = self.loaders.indexOf(wrapper); + if (idx < 0) + throw new Error("Test error: tried to unload nonexistent loader"); + self.loaders.splice(idx, 1); + } + }; + this.loaders.push(wrapper); + return wrapper; + }, + // Returns true if the count crosses the overflow threshold. shouldOverflow: function (count) { return count > @@ -3405,6 +3505,15 @@ TestHelper.prototype = { this.browserWindow = win; }, + // Opens a new private browser window. The window will be closed + // automatically when done() is called. + withNewPrivateWindow: function (onloadCallback) { + let win = this.browserWindow.OpenBrowserWindow({private: true}); + this.delayedEventListener(win, "load", onloadCallback, true); + this.oldBrowserWindow = this.browserWindow; + this.browserWindow = win; + }, + // Opens a new tab with our test page in the current window. The tab will // be closed automatically when done() is called. withTestDoc: function (onloadCallback) { diff --git a/addon-sdk/source/test/test-disposable.js b/addon-sdk/source/test/test-disposable.js index 34c4bd109951..8b457c4d8e8f 100644 --- a/addon-sdk/source/test/test-disposable.js +++ b/addon-sdk/source/test/test-disposable.js @@ -188,4 +188,38 @@ exports["test disposables that throw"] = function(assert) { assert.equal(disposals, 0, "no disposal if constructor threw"); } +exports["test multiple destroy"] = function(assert) { + let loader = Loader(module); + let { Disposable } = loader.require("sdk/core/disposable"); + + let disposals = 0 + + let Foo = Class({ + extends: Disposable, + dispose: function dispose() { + disposals = disposals + 1 + } + }) + + let foo1 = Foo(); + let foo2 = Foo(); + let foo3 = Foo(); + + assert.equal(disposals, 0, "no disposals yet"); + + foo1.destroy(); + assert.equal(disposals, 1, "disposed properly"); + foo1.destroy(); + assert.equal(disposals, 1, "didn't attempt to dispose twice"); + + foo2.destroy(); + assert.equal(disposals, 2, "other instances still dispose fine"); + foo2.destroy(); + assert.equal(disposals, 2, "but not twice"); + + loader.unload(); + + assert.equal(disposals, 3, "unload only disposed the remaining instance"); +} + require('test').run(exports); diff --git a/addon-sdk/source/test/test-event-utils.js b/addon-sdk/source/test/test-event-utils.js index f17f97686d43..b0163600a293 100644 --- a/addon-sdk/source/test/test-event-utils.js +++ b/addon-sdk/source/test/test-event-utils.js @@ -13,7 +13,7 @@ function inc(x) x + 1 exports["test filter events"] = function(assert) { let input = {}; - let evens = filter(isEven, input); + let evens = filter(input, isEven); let actual = []; on(evens, "data", function(e) actual.push(e)); @@ -23,28 +23,28 @@ exports["test filter events"] = function(assert) { }; exports["test filter emits"] = $.emits(function(input, assert) { - let output = filter(isEven, input); + let output = filter(input, isEven); assert(output, [1, 2, 3, 4, 5], [2, 4], "this is `output` & evens passed"); });; exports["test filter reg once"] = $.registerOnce(function(input, assert) { - assert(filter(isEven, input), [1, 2, 3, 4, 5, 6], [2, 4, 6], + assert(filter(input, isEven), [1, 2, 3, 4, 5, 6], [2, 4, 6], "listener can be registered only once"); }); exports["test filter ignores new"] = $.ignoreNew(function(input, assert) { - assert(filter(isEven, input), [1, 2, 3], [2], + assert(filter(input, isEven), [1, 2, 3], [2], "new listener is ignored") }); exports["test filter is FIFO"] = $.FIFO(function(input, assert) { - assert(filter(isEven, input), [1, 2, 3, 4], [2, 4], + assert(filter(input, isEven), [1, 2, 3, 4], [2, 4], "listeners are invoked in fifo order") }); exports["test map events"] = function(assert) { let input = {}; - let incs = map(inc, input); + let incs = map(input, inc); let actual = []; on(incs, "data", function(e) actual.push(e)); @@ -54,22 +54,22 @@ exports["test map events"] = function(assert) { }; exports["test map emits"] = $.emits(function(input, assert) { - let output = map(inc, input); + let output = map(input, inc); assert(output, [1, 2, 3], [2, 3, 4], "this is `output` & evens passed"); });; exports["test map reg once"] = $.registerOnce(function(input, assert) { - assert(map(inc, input), [1, 2, 3], [2, 3, 4], + assert(map(input, inc), [1, 2, 3], [2, 3, 4], "listener can be registered only once"); }); exports["test map ignores new"] = $.ignoreNew(function(input, assert) { - assert(map(inc, input), [1], [2], + assert(map(input, inc), [1], [2], "new listener is ignored") }); exports["test map is FIFO"] = $.FIFO(function(input, assert) { - assert(map(inc, input), [1, 2, 3, 4], [2, 3, 4, 5], + assert(map(input, inc), [1, 2, 3, 4], [2, 3, 4, 5], "listeners are invoked in fifo order") }); @@ -115,28 +115,28 @@ exports["test merge array[stream]"] = function(assert) { }; exports["test merge emits"] = $.emits(function(input, assert) { - let evens = filter(isEven, input) + let evens = filter(input, isEven) let output = merge([evens, input]); assert(output, [1, 2, 3], [1, 2, 2, 3], "this is `output` & evens passed"); }); exports["test merge reg once"] = $.registerOnce(function(input, assert) { - let evens = filter(isEven, input) + let evens = filter(input, isEven) let output = merge([input, evens]); assert(output, [1, 2, 3, 4], [1, 2, 2, 3, 4, 4], "listener can be registered only once"); }); exports["test merge ignores new"] = $.ignoreNew(function(input, assert) { - let evens = filter(isEven, input) + let evens = filter(input, isEven) let output = merge([input, evens]) assert(output, [1], [1], "new listener is ignored") }); exports["test marge is FIFO"] = $.FIFO(function(input, assert) { - let evens = filter(isEven, input) + let evens = filter(input, isEven) let output = merge([input, evens]) assert(output, [1, 2, 3, 4], [1, 2, 2, 3, 4, 4], @@ -148,7 +148,7 @@ exports["test expand"] = function(assert) { let inputs = {}; let actual = []; - on(expand(function($) $(), inputs), "data", function($) actual.push($)) + on(expand(inputs, function($) $()), "data", function($) actual.push($)) emit(inputs, "data", function() a); emit(a, "data", "a1"); diff --git a/addon-sdk/source/test/test-functional.js b/addon-sdk/source/test/test-functional.js index 81d36e986071..92e796cdb32c 100644 --- a/addon-sdk/source/test/test-functional.js +++ b/addon-sdk/source/test/test-functional.js @@ -2,9 +2,11 @@ * 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/. */ + const { setTimeout } = require('sdk/timers'); const utils = require('sdk/lang/functional'); -const { invoke, defer, curry, compose, memoize, once, delay, wrap } = utils; +const { invoke, defer, partial, compose, memoize, once, delay, wrap } = utils; +const { LoaderWithHookedConsole } = require('sdk/test/loader'); exports['test forwardApply'] = function(assert) { function sum(b, c) this.a + b + c @@ -29,17 +31,33 @@ exports['test deferred function'] = function(assert, done) { nextTurn = true; }; +exports['test partial function'] = function(assert) { + function sum(b, c) this.a + b + c; + + let foo = { a : 5 }; + + foo.sum7 = partial(sum, 7); + foo.sum8and4 = partial(sum, 8, 4); + + assert.equal(foo.sum7(2), 14, 'partial one arguments works'); + + assert.equal(foo.sum8and4(), 17, 'partial both arguments works'); +}; + exports['test curry function'] = function(assert) { + let { loader, messages } = LoaderWithHookedConsole(module); + let { curry } = loader.require('sdk/lang/functional'); + function sum(b, c) this.a + b + c; let foo = { a : 5 }; foo.sum7 = curry(sum, 7); - foo.sum8and4 = curry(sum, 8, 4); - assert.equal(foo.sum7(2), 14, 'curry one arguments works'); + assert.equal(messages.length, 1, "only one error is dispatched"); + assert.ok(messages[0].msg.indexOf('curry is deprecated') > -1); - assert.equal(foo.sum8and4(), 17, 'curry both arguments works'); + loader.unload(); }; exports['test compose'] = function(assert) { diff --git a/addon-sdk/source/test/test-page-mod.js b/addon-sdk/source/test/test-page-mod.js index 55fcea33eccc..af2f270e3555 100644 --- a/addon-sdk/source/test/test-page-mod.js +++ b/addon-sdk/source/test/test-page-mod.js @@ -1058,6 +1058,8 @@ exports.testEvents = function(test) { exports["test page-mod on private tab"] = function (test) { test.waitUntilDone(); + let fail = test.fail.bind(test); + let privateUri = "data:text/html;charset=utf-8," + "