Bug 858326 - Uplift Add-on SDK changeset 99d7f27c7e

This commit is contained in:
Wes Kocher 2013-04-04 16:30:51 -07:00
parent c6eafdb377
commit 5da2ab51d0
34 changed files with 1355 additions and 73 deletions

View File

@ -157,6 +157,7 @@ We'd like to thank our many Jetpack project contributors! They include:
* slash
* Markus Stange
* Dan Stevens
* [J. Ryan Stinnett](https://github.com/jryans)
* [Mihai Sucan](https://github.com/mihaisucan)
<!--end-->

View File

@ -1,4 +1,3 @@
<!-- 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/. -->
@ -58,8 +57,8 @@ In this case files are specified by a URL typically constructed using the
pageMod.PageMod({
include: "*.mozilla.org",
contentScriptFile: [self.data.url("jquery-1.7.min.js"),
self.data.url("my-script.js")]
contentScriptFile: [data.url("jquery-1.7.min.js"),
data.url("my-script.js")]
});
<div class="warning">

View File

@ -169,6 +169,12 @@ This is an optional property.
@prop [onReady] {function}
A callback function that will be registered for 'ready' event.
This is an optional property.
@prop [onLoad] {function}
A callback function that will be registered for 'load' event.
This is an optional property.
@prop [onPageShow] {function}
A callback function that will be registered for 'pageshow' event.
This is an optional property.
@prop [onActivate] {function}
A callback function that will be registered for 'activate' event.
This is an optional property.
@ -332,6 +338,47 @@ content can be used.
Listeners are passed the tab object.
</api>
<api name="load">
@event
This event is emitted when the page for the tab's content is loaded. It is
equivalent to the `load` event for the given content page.
A single tab will emit this event every time the page is loaded: so it will be
emitted again if the tab's location changes or the content is reloaded.
After this event has been emitted, all properties relating to the tab's
content can be used.
This is fired after the `ready` event on DOM content pages and can be used
for pages that do not have a `DOMContentLoaded` event, like images.
@argument {Tab}
Listeners are passed the tab object.
</api>
<api name="pageshow">
@event
This event is emitted when the page for the tab's content is potentially
from the cache. It is equivilent to the [pageshow](https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/pageshow) event for the given
content page.
After this event has been emitted, all properties relating to the tab's
content can be used.
While the `ready` and `load` events will not be fired when a user uses the back
or forward buttons to navigate history, the `pageshow` event will be fired.
If the `persisted` argument is true, then the contents were loaded from the
bfcache.
@argument {Tab}
Listeners are passed the tab object.
@argument {persisted}
Listeners are passed a boolean value indicating whether or not the page
was loaded from the [bfcache](https://developer.mozilla.org/en-US/docs/Working_with_BFCache) or not.
</api>
<api name="activate">
@event

View File

@ -16,6 +16,8 @@ const { when: unload } = require('../system/unload');
const { loadReason } = require('../self');
const { rootURI } = require("@loader/options");
const globals = require('../system/globals');
const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
getService(Ci.nsIAppShellService);
const NAME2TOPIC = {
'Firefox': 'sessionstore-windows-restored',
@ -70,8 +72,18 @@ function wait(reason, options) {
}
function startup(reason, options) {
if (reason === 'startup')
// Try accessing hidden window to guess if we are running during firefox
// startup, so that we should wait for session restore event before
// running the addon
let initialized = false;
try {
appShellService.hiddenDOMWindow;
initialized = true;
}
catch(e) {}
if (reason === 'startup' || !initialized) {
return wait(reason, options);
}
// Inject globals ASAP in order to have console API working ASAP
Object.defineProperties(options.loader.globals, descriptor(globals));

View File

@ -0,0 +1,20 @@
/* 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 { events } = require("../window/events");
const { filter } = require("../event/utils");
const { isBrowser } = require("../window/utils");
// TODO: `isBrowser` detects weather window is a browser by checking
// `windowtype` attribute, which means that all 'open' events will be
// filtered out since document is not loaded yet. Maybe we can find a better
// 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);

View File

@ -270,6 +270,12 @@ TestRunner.prototype = {
this.waitUntilCallback = null;
if (this.test.passed == 0 && this.test.failed == 0) {
this._logTestFailed("empty test");
if ("testMessage" in this.console) {
this.console.testMessage(false, false, this.test.name, "Empty test");
}
else {
this.console.error("fail:", "Empty test")
}
this.failed++;
this.test.failed++;
}
@ -414,6 +420,12 @@ TestRunner.prototype = {
function tiredOfWaiting() {
self._logTestFailed("timed out");
if ("testMessage" in self.console) {
self.console.testMessage(false, false, self.test.name, "Timed out");
}
else {
self.console.error("fail:", "Timed out")
}
if (self.waitUntilCallback) {
self.waitUntilCallback(true);
self.waitUntilCallback = null;

View File

@ -95,7 +95,8 @@ function emit(target, type, message /*, ...*/) {
*/
emit.lazy = function lazy(target, type, message /*, ...*/) {
let args = Array.slice(arguments, 2);
let listeners = observers(target, type).slice();
let state = observers(target, type);
let listeners = state.slice();
let index = 0;
let count = listeners.length;
@ -103,7 +104,11 @@ emit.lazy = function lazy(target, type, message /*, ...*/) {
// into a console.
if (count === 0 && type === 'error') console.exception(message);
while (index < count) {
try { yield listeners[index].apply(target, args); }
try {
let listener = listeners[index];
// Dispatch only if listener is still registered.
if (~state.indexOf(listener)) yield listener.apply(target, args);
}
catch (error) {
// If exception is not thrown by a error listener and error listener is
// registered emit `error` event. Otherwise dump exception to the console.

View File

@ -0,0 +1,26 @@
/* 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"
};
let { emit, on, off } = require("./core");
// Simple utility function takes event target, event type and optional
// `options.capture` and returns node style event stream that emits "data"
// events every time event of that type occurs on the given `target`.
function open(target, type, options) {
let output = {};
let capture = options && options.capture ? true : false;
target.addEventListener(type, function(event) {
emit(output, "data", event);
}, capture);
return output;
}
exports.open = open;

View File

@ -75,6 +75,9 @@ const EventTarget = Class({
// than intended. This way we make sure all arguments are passed and only
// one listener is removed at most.
off(this, type, listener);
},
off: function(type, listener) {
off(this, type, listener)
}
});
exports.EventTarget = EventTarget;

View File

@ -0,0 +1,101 @@
/* 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"
};
let { emit, on, off } = require("./core");
// This module provides set of high order function for working with event
// streams (streams in a NodeJS style that dispatch data, end and error
// events).
// Function takes a `target` object and returns set of implicit references
// (non property references) it keeps. This basically allows defining
// references between objects without storing the explicitly. See transform for
// more details.
let refs = (function() {
let refSets = new WeakMap();
return function refs(target) {
if (!refSets.has(target)) refSets.set(target, new Set());
return refSets.get(target);
}
})();
function transform(f, input) {
let output = {};
// Since event listeners don't prevent `input` to be GC-ed we wanna presrve
// it until `output` can be GC-ed. There for we add implicit reference which
// is removed once `input` ends.
refs(output).add(input);
function next(data) emit(output, "data", data);
on(input, "error", function(error) emit(output, "error", error));
on(input, "end", function() {
refs(output).delete(input);
emit(output, "end");
});
on(input, "data", function(data) f(data, next));
return output;
}
// 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) {
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)
exports.map = map;
// High order function that takes `input` stream of streams and merges them
// into single event stream. Like flatten but time based rather than order
// based.
function merge(inputs) {
let output = {};
let open = 1;
let state = [];
output.state = state;
refs(output).add(inputs);
function end(input) {
open = open - 1;
refs(output).delete(input);
if (open === 0) emit(output, "end");
}
function error(e) emit(output, "error", e);
function forward(input) {
state.push(input);
open = open + 1;
on(input, "end", function() end(input));
on(input, "error", error);
on(input, "data", function(data) emit(output, "data", data));
}
// If `inputs` is an array treat it as a stream.
if (Array.isArray(inputs)) {
inputs.forEach(forward)
end(inputs)
}
else {
on(inputs, "end", function() end(inputs));
on(inputs, "error", error);
on(inputs, "data", forward);
}
return output;
}
exports.merge = merge;
function expand(f, inputs) merge(map(f, inputs))
exports.expand = expand;

View File

@ -16,6 +16,7 @@ const { EventTarget } = require("./event/target");
const { Class } = require("./core/heritage");
const { XMLHttpRequest } = require("./net/xhr");
const apiUtils = require("./deprecated/api-utils");
const { isValidURI } = require("./url.js");
const response = ns();
const request = ns();
@ -24,8 +25,7 @@ const request = ns();
// reuse it.
const { validateOptions, validateSingleOption } = new OptionsValidator({
url: {
//XXXzpao should probably verify that url is a valid url as well
is: ["string"]
ok: isValidURI
},
headers: {
map: function (v) v || {},

View File

@ -0,0 +1,60 @@
/* 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";
// This module provides temporary shim until Bug 843901 is shipped.
// It basically registers tab event listeners on all windows that get
// opened and forwards them through observer notifications.
module.metadata = {
"stability": "experimental"
};
const { Ci } = require("chrome");
const { windows, isInteractive } = require("../window/utils");
const { events } = require("../browser/events");
const { open } = require("../event/dom");
const { filter, map, merge, expand } = require("../event/utils");
// Module provides event stream (in nodejs style) that emits data events
// for all the tab events that happen in running firefox. At the moment
// it does it by registering listeners on all browser windows and then
// forwarding events when they occur to a stream. This will become obsolete
// once Bug 843901 is fixed, and we'll just leverage observer notifications.
// Set of tab events that this module going to aggregate and expose.
const TYPES = ["TabOpen","TabClose","TabSelect","TabMove","TabPinned",
"TabUnpinned"];
// Utility function that given a browser `window` returns stream of above
// defined tab events for all tabs on the given window.
function tabEventsFor(window) {
// Map supported event types to a streams of those events on the given
// `window` and than merge these streams into single form stream off
// all events.
let channels = TYPES.map(function(type) open(window, type));
return merge(channels);
}
// Filter DOMContentLoaded events from all the browser events.
let readyEvents = filter(function(e) e.type === "DOMContentLoaded", events);
// Map DOMContentLoaded events to it's target browser windows.
let futureWindows = map(function(e) e.target, readyEvents);
// 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);
// 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
// tab events for them too.
let interactiveWindows = windows("navigator:browser", { includePrivate: true }).
filter(isInteractive);
let eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));
// Finally merge stream of tab events from future windows and current windows
// to cover all tab events on all windows that will open.
exports.events = merge([eventsFromInteractive, eventsFromFuture]);

View File

@ -20,6 +20,8 @@ function Options(options) {
onOpen: { is: ["undefined", "function"] },
onClose: { is: ["undefined", "function"] },
onReady: { is: ["undefined", "function"] },
onLoad: { is: ["undefined", "function"] },
onPageShow: { is: ["undefined", "function"] },
onActivate: { is: ["undefined", "function"] },
onDeactivate: { is: ["undefined", "function"] }
});

View File

@ -12,6 +12,8 @@ const TAB_PREFIX = "Tab";
const EVENTS = {
ready: "DOMContentLoaded",
load: "load", // Used for non-HTML content
pageshow: "pageshow", // Used for cached content
open: "TabOpen",
close: "TabClose",
activate: "TabSelect",

View File

@ -6,6 +6,7 @@
const { Trait } = require("../deprecated/traits");
const { EventEmitter } = require("../deprecated/events");
const { defer } = require("../lang/functional");
const { has } = require("../util/array");
const { EVENTS } = require("./events");
const { getThumbnailURIForWindow } = require("../content/thumbnail");
const { getFaviconURIForLocation } = require("../io/data");
@ -33,6 +34,8 @@ const TabTrait = Trait.compose(EventEmitter, {
window: null,
constructor: function Tab(options) {
this._onReady = this._onReady.bind(this);
this._onLoad = this._onLoad.bind(this);
this._onPageShow = this._onPageShow.bind(this);
this._tab = options.tab;
// TODO: Remove this dependency
let window = this.window = options.window || require('../windows').BrowserWindow({ window: getOwnerWindow(this._tab) });
@ -40,14 +43,19 @@ const TabTrait = Trait.compose(EventEmitter, {
// Setting event listener if was passed.
for each (let type in EVENTS) {
let listener = options[type.listener];
if (listener)
if (listener) {
this.on(type.name, options[type.listener]);
if ('ready' != type.name) // window spreads this event.
}
// window spreads this event.
if (!has(['ready', 'load', 'pageshow'], (type.name)))
window.tabs.on(type.name, this._onEvent.bind(this, type.name));
}
this.on(EVENTS.close.name, this.destroy.bind(this));
this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true);
this._browser.addEventListener(EVENTS.load.dom, this._onLoad, true);
this._browser.addEventListener(EVENTS.pageshow.dom, this._onPageShow, true);
if (options.isPinned)
this.pin();
@ -65,8 +73,11 @@ const TabTrait = Trait.compose(EventEmitter, {
if (this._tab) {
let browser = this._browser;
// The tab may already be removed from DOM -or- not yet added
if (browser)
if (browser) {
browser.removeEventListener(EVENTS.ready.dom, this._onReady, true);
browser.removeEventListener(EVENTS.load.dom, this._onLoad, true);
browser.removeEventListener(EVENTS.pageshow.dom, this._onPageShow, true);
}
this._tab = null;
TABS.splice(TABS.indexOf(this), 1);
}
@ -74,13 +85,35 @@ const TabTrait = Trait.compose(EventEmitter, {
/**
* Internal listener that emits public event 'ready' when the page of this
* tab is loaded.
* tab is loaded, from DOMContentLoaded
*/
_onReady: function _onReady(event) {
// IFrames events will bubble so we need to ignore those.
if (event.target == this._contentDocument)
this._emit(EVENTS.ready.name, this._public);
},
/**
* Internal listener that emits public event 'load' when the page of this
* tab is loaded, for triggering on non-HTML content, bug #671305
*/
_onLoad: function _onLoad(event) {
// IFrames events will bubble so we need to ignore those.
if (event.target == this._contentDocument) {
this._emit(EVENTS.load.name, this._public);
}
},
/**
* Internal listener that emits public event 'pageshow' when the page of this
* tab is loaded from cache, bug #671305
*/
_onPageShow: function _onPageShow(event) {
// IFrames events will bubble so we need to ignore those.
if (event.target == this._contentDocument) {
this._emit(EVENTS.pageshow.name, this._public, event.persisted);
}
},
/**
* Internal tab event router. Window will emit tab related events for all it's
* tabs, this listener will propagate all the events for this tab to it's

View File

@ -303,3 +303,53 @@ function getTabForBrowser(browser) {
}
exports.getTabForBrowser = getTabForBrowser;
function pin(tab) {
let gBrowser = getTabBrowserForTab(tab);
// TODO: Implement Fennec support
if (gBrowser) gBrowser.pinTab(tab);
}
exports.pin = pin;
function unpin(tab) {
let gBrowser = getTabBrowserForTab(tab);
// TODO: Implement Fennec support
if (gBrowser) gBrowser.unpinTab(tab);
}
exports.unpin = unpin;
function isPinned(tab) !!tab.pinned
exports.isPinned = isPinned;
function reload(tab) {
let gBrowser = getTabBrowserForTab(tab);
// Firefox
if (gBrowser) gBrowser.unpinTab(tab);
// Fennec
else if (tab.browser) tab.browser.reload();
}
exports.reload = reload
function getIndex(tab) {
let gBrowser = getTabBrowserForTab(tab);
// Firefox
if (gBrowser) {
let document = getBrowserForTab(tab).contentDocument;
return gBrowser.getBrowserIndexForDocument(document);
}
// Fennec
else {
let window = getWindowHoldingTab(tab)
let tabs = window.BrowserApp.tabs;
for (let i = tabs.length; i >= 0; i--)
if (tabs[i] === tab) return i;
}
}
exports.getIndex = getIndex;
function move(tab, index) {
let gBrowser = getTabBrowserForTab(tab);
// Firefox
if (gBrowser) gBrowser.moveTabTo(tab, index);
// TODO: Implement fennec support
}
exports.move = move;

View File

@ -26,7 +26,8 @@ function runTests(findAndRunTests) {
stdout.write("No tests were run\n");
exit(0);
} else {
printFailedTests(tests, cfxArgs.verbose, stdout.write);
if (cfxArgs.verbose || cfxArgs.parseable)
printFailedTests(tests, stdout.write);
exit(1);
}
};
@ -50,10 +51,7 @@ function runTests(findAndRunTests) {
}, 0);
}
function printFailedTests(tests, verbose, print) {
if (!verbose)
return;
function printFailedTests(tests, print) {
let iterationNumber = 0;
let singleIteration = tests.testRuns.length == 1;
let padding = singleIteration ? "" : " ";

View File

@ -234,3 +234,12 @@ const DataURL = Class({
});
exports.DataURL = DataURL;
let isValidURI = exports.isValidURI = function (uri) {
try {
newURI(uri);
} catch(e) {
return false;
}
return true;
}

View File

@ -0,0 +1,80 @@
/* 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 { Ci } = require("chrome");
const events = require("../system/events");
const { on, off, emit } = require("../event/core");
const { windows } = require("../window/utils");
// Object represents event channel on which all top level window events
// will be dispatched, allowing users to react to those evens.
const channel = {};
exports.events = channel;
const types = {
domwindowopened: "open",
domwindowclosed: "close",
}
// Utility function to query observer notification subject to get DOM window.
function nsIDOMWindow($) $.QueryInterface(Ci.nsIDOMWindow);
// Utility function used as system event listener that is invoked every time
// top level window is open. This function does two things:
// 1. Registers event listeners to track when document becomes interactive and
// when it's done loading. This will become obsolete once Bug 843910 is
// fixed.
// 2. Forwards event to an exported event stream.
function onOpen(event) {
observe(nsIDOMWindow(event.subject));
dispatch(event);
}
// Function registers single shot event listeners for relevant window events
// that forward events to exported event stream.
function observe(window) {
function listener(event) {
if (event.target === window.document) {
window.removeEventListener(event.type, listener, true);
emit(channel, "data", { type: event.type, target: window });
}
}
// Note: we do not remove listeners on unload since on add-on unload we
// nuke add-on sandbox that should allow GC-ing listeners. This also has
// positive effects on add-on / firefox unloads.
window.addEventListener("DOMContentLoaded", listener, true);
window.addEventListener("load", listener, true);
// TODO: Also add focus event listener so that can be forwarded to event
// stream. It can be part of Bug 854982.
}
// Utility function that takes system notification event and forwards it to a
// channel in restructured form.
function dispatch({ type: topic, subject }) {
emit(channel, "data", {
topic: topic,
type: types[topic],
target: nsIDOMWindow(subject)
});
}
// In addition to observing windows that are open we also observe windows
// that are already already opened in case they're in process of loading.
let opened = windows(null, { includePrivate: true });
opened.forEach(observe);
// Register system event listeners to forward messages on exported event
// stream. Note that by default only weak refs are kept by system events
// module so they will be GC-ed once add-on unloads and no manual cleanup
// is required. Also note that listeners are intentionally not inlined since
// to avoid premature GC-ing. Currently refs are kept by module scope and there
// for they remain alive.
events.on("domwindowopened", onOpen);
events.on("domwindowclosed", dispatch);

View File

@ -275,6 +275,15 @@ function windows(type, options) {
}
exports.windows = windows;
/**
* Check if the given window is interactive.
* i.e. if its "DOMContentLoaded" event has already been fired.
* @params {nsIDOMWindow} window
*/
function isInteractive(window)
window.document.readyState === "interactive" || isDocumentLoaded(window)
exports.isInteractive = isInteractive;
/**
* Check if the given window is completely loaded.
* i.e. if its "load" event has already been fired and all possible DOM content

View File

@ -52,6 +52,8 @@ const WindowTabTracker = Trait.compose({
// Binding all methods used as event listeners to the instance.
this._onTabReady = this._emitEvent.bind(this, "ready");
this._onTabLoad = this._emitEvent.bind(this, "load");
this._onTabPageShow = this._emitEvent.bind(this, "pageshow");
this._onTabOpen = this._onTabEvent.bind(this, "open");
this._onTabClose = this._onTabEvent.bind(this, "close");
this._onTabActivate = this._onTabEvent.bind(this, "activate");
@ -109,17 +111,23 @@ const WindowTabTracker = Trait.compose({
return;
// Setting up an event listener for ready events.
if (type === "open")
if (type === "open") {
wrappedTab.on("ready", this._onTabReady);
wrappedTab.on("load", this._onTabLoad);
wrappedTab.on("pageshow", this._onTabPageShow);
}
this._emitEvent(type, wrappedTab);
}
},
_emitEvent: function _emitEvent(type, tab) {
_emitEvent: function _emitEvent(type, tag) {
// Slices additional arguments and passes them into exposed
// listener like other events (for pageshow)
let args = Array.slice(arguments);
// Notifies combined tab list that tab was added / removed.
tabs._emit(type, tab);
tabs._emit.apply(tabs, args);
// Notifies contained tab list that window was added / removed.
this._tabs._emit(type, tab);
this._tabs._emit.apply(this._tabs, args);
}
});
exports.WindowTabTracker = WindowTabTracker;

View File

@ -0,0 +1,59 @@
/* 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 { on, once, off, emit, count } = require("sdk/event/core");
function scenario(setup) {
return function(unit) {
return function(assert) {
let actual = [];
let input = {};
unit(input, function(output, events, expected, message) {
let result = setup(output, expected, actual);
events.forEach(function(event) emit(input, "data", event));
assert.deepEqual(actual, result, message);
});
}
}
}
exports.emits = scenario(function(output, expected, actual) {
on(output, "data", function(data) actual.push(this, data));
return expected.reduce(function($$, $) $$.concat(output, $), []);
});
exports.registerOnce = scenario(function(output, expected, actual) {
function listener(data) actual.push(data);
on(output, "data", listener);
on(output, "data", listener);
on(output, "data", listener);
return expected;
});
exports.ignoreNew = scenario(function(output, expected, actual) {
on(output, "data", function(data) {
actual.push(data + "#1");
on(output, "data", function(data) {
actual.push(data + "#2");
});
});
return expected.map(function($) $ + "#1");
});
exports.FIFO = scenario(function(target, expected, actual) {
on(target, "data", function($) actual.push($ + "#1"));
on(target, "data", function($) actual.push($ + "#2"));
on(target, "data", function($) actual.push($ + "#3"));
return expected.reduce(function(result, value) {
return result.concat(value + "#1", value + "#2", value + "#3");
}, []);
});

View File

@ -8,6 +8,11 @@ const { Loader } = require('sdk/test/loader');
const timer = require('sdk/timers');
const { StringBundle } = require('sdk/deprecated/app-strings');
const base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
"AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
"N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
"bWRR9AAAAABJRU5ErkJggg%3D%3D";
// TEST: tabs.activeTab getter
exports.testActiveTab_getter = function(test) {
test.waitUntilDone();
@ -178,10 +183,26 @@ exports.testTabProperties = function(test) {
test.assertEqual(tab.index, 1, "index of the new tab matches");
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
closeBrowserWindow(window, function() test.done());
onReadyOrLoad(window);
},
onLoad: function(tab) {
test.assertEqual(tab.title, "foo", "title of the new tab matches");
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assert(tab.favicon, "favicon of the new tab is not empty");
test.assertEqual(tab.style, null, "style of the new tab matches");
test.assertEqual(tab.index, 1, "index of the new tab matches");
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
onReadyOrLoad(window);
}
});
});
let count = 0;
function onReadyOrLoad (window) {
if (count++)
closeBrowserWindow(window, function() test.done());
}
};
// TEST: tab properties
@ -961,6 +982,113 @@ exports['test unique tab ids'] = function(test) {
next(0);
}
// related to Bug 671305
exports.testOnLoadEventWithDOM = function(test) {
test.waitUntilDone();
openBrowserWindow(function(window, browser) {
let tabs = require('sdk/tabs');
let count = 0;
tabs.on('load', function onLoad(tab) {
test.assertEqual(tab.title, 'tab', 'tab passed in as arg, load called');
if (!count++) {
tab.reload();
}
else {
// end of test
tabs.removeListener('load', onLoad);
test.pass('onLoad event called on reload');
closeBrowserWindow(window, function() test.done());
}
});
// open a about: url
tabs.open({
url: 'data:text/html;charset=utf-8,<title>tab</title>',
inBackground: true
});
});
};
// related to Bug 671305
exports.testOnLoadEventWithImage = function(test) {
test.waitUntilDone();
openBrowserWindow(function(window, browser) {
let tabs = require('sdk/tabs');
let count = 0;
tabs.on('load', function onLoad(tab) {
if (!count++) {
tab.reload();
}
else {
// end of test
tabs.removeListener('load', onLoad);
test.pass('onLoad event called on reload with image');
closeBrowserWindow(window, function() test.done());
}
});
// open a image url
tabs.open({
url: base64png,
inBackground: true
});
});
};
exports.testOnPageShowEvent = function (test) {
test.waitUntilDone();
let firstUrl = 'about:home';
let secondUrl = 'about:newtab';
openBrowserWindow(function(window, browser) {
let tabs = require('sdk/tabs');
let wait = 500;
let counter = 1;
tabs.on('pageshow', function setup(tab, persisted) {
if (counter === 1)
test.assert(!persisted, 'page should not be cached on initial load');
if (wait > 5000) {
test.fail('Page was not cached after 5s')
closeBrowserWindow(window, function() test.done());
}
if (tab.url === firstUrl) {
// If first page has persisted, pass
if (persisted) {
tabs.removeListener('pageshow', setup);
test.pass('pageshow event called on history.back()');
closeBrowserWindow(window, function() test.done());
}
// On the first run, or if the page wasn't cached
// the first time due to not waiting long enough,
// try again with a longer delay (this is terrible
// and ugly)
else {
counter++;
timer.setTimeout(function () {
tab.url = secondUrl;
wait *= 2;
}, wait);
}
}
else {
tab.attach({
contentScript: 'setTimeout(function () { window.history.back(); }, 0)'
});
}
});
tabs.open({
url: firstUrl
});
});
};
/******************* helpers *********************/
// Helper for getting the active window

View File

@ -0,0 +1,105 @@
/* 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 { Loader } = require("sdk/test/loader");
const { open, getMostRecentBrowserWindow, getOuterId } = require("sdk/window/utils");
const { setTimeout } = require("sdk/timers");
exports["test browser events"] = function(assert, done) {
let loader = Loader(module);
let { events } = loader.require("sdk/browser/events");
let { on, off } = loader.require("sdk/event/core");
let actual = [];
on(events, "data", function handler(e) {
actual.push(e);
if (e.type === "load") window.close();
if (e.type === "close") {
// Unload the module so that all listeners set by observer are removed.
let [ ready, load, close ] = actual;
assert.equal(ready.type, "DOMContentLoaded");
assert.equal(ready.target, window, "window ready");
assert.equal(load.type, "load");
assert.equal(load.target, window, "window load");
assert.equal(close.type, "close");
assert.equal(close.target, window, "window load");
// Note: If window is closed right after this GC won't have time
// to claim loader and there for this listener, there for it's safer
// to remove listener.
off(events, "data", handler);
loader.unload();
done();
}
});
// Open window and close it to trigger observers.
let window = open();
};
exports["test browser events ignore other wins"] = function(assert, done) {
let loader = Loader(module);
let { events: windowEvents } = loader.require("sdk/window/events");
let { events: browserEvents } = loader.require("sdk/browser/events");
let { on, off } = loader.require("sdk/event/core");
let actualBrowser = [];
let actualWindow = [];
function browserEventHandler(e) actualBrowser.push(e)
on(browserEvents, "data", browserEventHandler);
on(windowEvents, "data", function handler(e) {
actualWindow.push(e);
// Delay close so that if "load" is also emitted on `browserEvents`
// `browserEventHandler` will be invoked.
if (e.type === "load") setTimeout(window.close);
if (e.type === "close") {
assert.deepEqual(actualBrowser, [], "browser events were not triggered");
let [ open, ready, load, close ] = actualWindow;
assert.equal(open.type, "open");
assert.equal(open.target, window, "window is open");
assert.equal(ready.type, "DOMContentLoaded");
assert.equal(ready.target, window, "window ready");
assert.equal(load.type, "load");
assert.equal(load.target, window, "window load");
assert.equal(close.type, "close");
assert.equal(close.target, window, "window load");
// Note: If window is closed right after this GC won't have time
// to claim loader and there for this listener, there for it's safer
// to remove listener.
off(windowEvents, "data", handler);
off(browserEvents, "data", browserEventHandler);
loader.unload();
done();
}
});
// Open window and close it to trigger observers.
let window = open("data:text/html,not a browser");
};
if (require("sdk/system/xul-app").is("Fennec")) {
module.exports = {
"test Unsupported Test": function UnsupportedTest (assert) {
assert.pass(
"Skipping this test until Fennec support is implemented." +
"See bug 793071");
}
}
}
require("test").run(exports);

View File

@ -68,6 +68,19 @@ exports['test no side-effects in emit'] = function(assert) {
emit(target, 'message');
};
exports['test can remove next listener'] = function(assert) {
let target = { name: 'target' };
function fail() assert.fail('Listener should be removed');
on(target, 'data', function() {
assert.pass('first litener called');
off(target, 'data', fail);
});
on(target, 'data', fail);
emit(target, 'data', 'hello');
};
exports['test order of propagation'] = function(assert) {
let actual = [];
let target = { name: 'target' };

View File

@ -115,7 +115,7 @@ exports['test remove a listener'] = function(assert) {
})
});
target.removeListener('message'); // must do nothing.
target.off('message'); // must do nothing.
emit(target, 'message');
assert.deepEqual([ 1 ], actual, 'first listener called');
emit(target, 'message');

View File

@ -0,0 +1,169 @@
/* 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 { on, emit } = require("sdk/event/core");
const { filter, map, merge, expand } = require("sdk/event/utils");
const $ = require("./event/helpers");
function isEven(x) !(x % 2)
function inc(x) x + 1
exports["test filter events"] = function(assert) {
let input = {};
let evens = filter(isEven, input);
let actual = [];
on(evens, "data", function(e) actual.push(e));
[1, 2, 3, 4, 5, 6, 7].forEach(function(x) emit(input, "data", x));
assert.deepEqual(actual, [2, 4, 6], "only even numbers passed through");
};
exports["test filter emits"] = $.emits(function(input, assert) {
let output = filter(isEven, input);
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],
"listener can be registered only once");
});
exports["test filter ignores new"] = $.ignoreNew(function(input, assert) {
assert(filter(isEven, input), [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],
"listeners are invoked in fifo order")
});
exports["test map events"] = function(assert) {
let input = {};
let incs = map(inc, input);
let actual = [];
on(incs, "data", function(e) actual.push(e));
[1, 2, 3, 4].forEach(function(x) emit(input, "data", x));
assert.deepEqual(actual, [2, 3, 4, 5], "all numbers were incremented");
};
exports["test map emits"] = $.emits(function(input, assert) {
let output = map(inc, input);
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],
"listener can be registered only once");
});
exports["test map ignores new"] = $.ignoreNew(function(input, assert) {
assert(map(inc, input), [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],
"listeners are invoked in fifo order")
});
exports["test merge stream[stream]"] = function(assert) {
let a = {}, b = {}, c = {};
let inputs = {};
let actual = [];
on(merge(inputs), "data", function($) actual.push($))
emit(inputs, "data", a);
emit(a, "data", "a1");
emit(inputs, "data", b);
emit(b, "data", "b1");
emit(a, "data", "a2");
emit(inputs, "data", c);
emit(c, "data", "c1");
emit(c, "data", "c2");
emit(b, "data", "b2");
emit(a, "data", "a3");
assert.deepEqual(actual, ["a1", "b1", "a2", "c1", "c2", "b2", "a3"],
"all inputs data merged into one");
};
exports["test merge array[stream]"] = function(assert) {
let a = {}, b = {}, c = {};
let inputs = {};
let actual = [];
on(merge([a, b, c]), "data", function($) actual.push($))
emit(a, "data", "a1");
emit(b, "data", "b1");
emit(a, "data", "a2");
emit(c, "data", "c1");
emit(c, "data", "c2");
emit(b, "data", "b2");
emit(a, "data", "a3");
assert.deepEqual(actual, ["a1", "b1", "a2", "c1", "c2", "b2", "a3"],
"all inputs data merged into one");
};
exports["test merge emits"] = $.emits(function(input, assert) {
let evens = filter(isEven, input)
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 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 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 output = merge([input, evens])
assert(output, [1, 2, 3, 4], [1, 2, 2, 3, 4, 4],
"listeners are invoked in fifo order")
});
exports["test expand"] = function(assert) {
let a = {}, b = {}, c = {};
let inputs = {};
let actual = [];
on(expand(function($) $(), inputs), "data", function($) actual.push($))
emit(inputs, "data", function() a);
emit(a, "data", "a1");
emit(inputs, "data", function() b);
emit(b, "data", "b1");
emit(a, "data", "a2");
emit(inputs, "data", function() c);
emit(c, "data", "c1");
emit(c, "data", "c2");
emit(b, "data", "b2");
emit(a, "data", "a3");
assert.deepEqual(actual, ["a1", "b1", "a2", "c1", "c2", "b2", "a3"],
"all inputs data merged into one");
}
require('test').run(exports);

View File

@ -9,7 +9,7 @@ if (xulApp.versionInRange(xulApp.platformVersion, "16.0a1", "*")) {
new function tests() {
const { indexedDB, IDBKeyRange, DOMException, IDBCursor, IDBTransaction,
IDBOpenDBRequest, IDBVersionChangeEvent, IDBDatabase, IDBIndex,
IDBOpenDBRequest, IDBVersionChangeEvent, IDBDatabase, IDBIndex,
IDBObjectStore, IDBRequest
} = require("sdk/indexed-db");

View File

@ -30,7 +30,7 @@ exports.testOptionsValidator = function(test) {
Request({
url: null
});
}, 'The option "url" must be one of the following types: string');
}, 'The option "url" is invalid.');
// Next we'll have a Request that doesn't throw from c'tor, but from a setter.
let req = Request({
@ -38,8 +38,8 @@ exports.testOptionsValidator = function(test) {
onComplete: function () {}
});
test.assertRaises(function () {
req.url = null;
}, 'The option "url" must be one of the following types: string');
req.url = 'www.mozilla.org';
}, 'The option "url" is invalid.');
// The url shouldn't have changed, so check that
test.assertEqual(req.url, "http://playground.zpao.com/jetpack/request/text.php");
}

View File

@ -6,6 +6,7 @@
const {Cc, Ci, Cu, Cm, components} = require('chrome');
Cu.import("resource://gre/modules/AddonManager.jsm", this);
const xulApp = require("sdk/system/xul-app");
exports.testSelf = function(test) {
var self = require("sdk/self");
@ -30,8 +31,12 @@ exports.testSelf = function(test) {
test.assert(self.name == "addon-sdk", "self.name is addon-sdk");
// loadReason may change here, as we change the way tests addons are installed
test.assertEqual(self.loadReason, "startup",
"self.loadReason is always `startup` on test runs");
// Bug 854937 fixed loadReason and is now install
let testLoadReason = xulApp.versionInRange(xulApp.platformVersion,
"23.0a1", "*") ? "install"
: "startup";
test.assertEqual(self.loadReason, testLoadReason,
"self.loadReason is either startup or install on test runs");
test.assertEqual(self.isPrivateBrowsingSupported, false,
'usePrivateBrowsing property is false by default');

View File

@ -0,0 +1,175 @@
/* 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 { Loader } = require("sdk/test/loader");
const utils = require("sdk/tabs/utils");
const { open, close } = require("sdk/window/helpers");
const { getMostRecentBrowserWindow } = require("sdk/window/utils");
const { events } = require("sdk/tab/events");
const { on, off } = require("sdk/event/core");
const { resolve } = require("sdk/core/promise");
let isFennec = require("sdk/system/xul-app").is("Fennec");
function test(scenario, currentWindow) {
let useActiveWindow = isFennec || currentWindow;
return function(assert, done) {
let actual = [];
function handler(event) actual.push(event)
let win = useActiveWindow ? resolve(getMostRecentBrowserWindow()) :
open(null, {
features: { private: true, toolbar:true, chrome: true }
});
let window = null;
win.then(function(w) {
window = w;
on(events, "data", handler);
return scenario(assert, window, actual);
}).then(function() {
off(events, "data", handler);
return useActiveWindow ? null : close(window);
}).then(done, assert.fail);
}
}
exports["test current window"] = test(function(assert, window, events) {
// Just making sure that tab events work for already opened tabs not only
// for new windows.
let tab = utils.openTab(window, 'data:text/plain,open');
utils.closeTab(tab);
let [open, select, close] = events;
assert.equal(open.type, "TabOpen");
assert.equal(open.target, tab);
assert.equal(select.type, "TabSelect");
assert.equal(select.target, tab);
assert.equal(close.type, "TabClose");
assert.equal(close.target, tab);
});
exports["test open"] = test(function(assert, window, events) {
let tab = utils.openTab(window, 'data:text/plain,open');
let [open, select] = events;
assert.equal(open.type, "TabOpen");
assert.equal(open.target, tab);
assert.equal(select.type, "TabSelect");
assert.equal(select.target, tab);
});
exports["test open -> close"] = test(function(assert, window, events) {
// First tab is useless we just open it so that closing second tab won't
// close window on some platforms.
let _ = utils.openTab(window, 'daat:text/plain,ignore');
let tab = utils.openTab(window, 'data:text/plain,open-close');
utils.closeTab(tab);
let [_open, _select, open, select, close] = events;
assert.equal(open.type, "TabOpen");
assert.equal(open.target, tab);
assert.equal(select.type, "TabSelect");
assert.equal(select.target, tab);
assert.equal(close.type, "TabClose");
assert.equal(close.target, tab);
});
exports["test open -> open -> select"] = test(function(assert, window, events) {
let tab1 = utils.openTab(window, 'data:text/plain,Tab-1');
let tab2 = utils.openTab(window, 'data:text/plain,Tab-2');
utils.activateTab(tab1, window);
let [open1, select1, open2, select2, select3] = events;
// Open first tab
assert.equal(open1.type, "TabOpen", "first tab opened")
assert.equal(open1.target, tab1, "event.target is first tab")
assert.equal(select1.type, "TabSelect", "first tab seleceted")
assert.equal(select1.target, tab1, "event.target is first tab")
// Open second tab
assert.equal(open2.type, "TabOpen", "second tab opened");
assert.equal(open2.target, tab2, "event.target is second tab");
assert.equal(select2.type, "TabSelect", "second tab seleceted");
assert.equal(select2.target, tab2, "event.target is second tab");
// Select first tab
assert.equal(select3.type, "TabSelect", "tab seleceted");
assert.equal(select3.target, tab1, "event.target is first tab");
});
exports["test open -> pin -> unpin"] = test(function(assert, window, events) {
let tab = utils.openTab(window, 'data:text/plain,pin-unpin');
utils.pin(tab);
utils.unpin(tab);
let [open, select, move, pin, unpin] = events;
assert.equal(open.type, "TabOpen");
assert.equal(open.target, tab);
assert.equal(select.type, "TabSelect");
assert.equal(select.target, tab);
if (isFennec) {
assert.pass("Tab pin / unpin is not supported by Fennec");
}
else {
assert.equal(move.type, "TabMove");
assert.equal(move.target, tab);
assert.equal(pin.type, "TabPinned");
assert.equal(pin.target, tab);
assert.equal(unpin.type, "TabUnpinned");
assert.equal(unpin.target, tab);
}
});
exports["test open -> open -> move "] = test(function(assert, window, events) {
let tab1 = utils.openTab(window, 'data:text/plain,Tab-1');
let tab2 = utils.openTab(window, 'data:text/plain,Tab-2');
utils.move(tab1, 2);
let [open1, select1, open2, select2, move] = events;
// Open first tab
assert.equal(open1.type, "TabOpen", "first tab opened");
assert.equal(open1.target, tab1, "event.target is first tab");
assert.equal(select1.type, "TabSelect", "first tab seleceted")
assert.equal(select1.target, tab1, "event.target is first tab");
// Open second tab
assert.equal(open2.type, "TabOpen", "second tab opened");
assert.equal(open2.target, tab2, "event.target is second tab");
assert.equal(select2.type, "TabSelect", "second tab seleceted");
assert.equal(select2.target, tab2, "event.target is second tab");
if (isFennec) {
assert.pass("Tab index changes not supported on Fennec yet")
}
else {
// Move first tab
assert.equal(move.type, "TabMove", "tab moved");
assert.equal(move.target, tab1, "event.target is first tab");
}
});
require("test").run(exports);

View File

@ -114,57 +114,48 @@ exports.testWaitUntilErrorInCallback = function(test) {
}
exports.testWaitUntilTimeoutInCallback = function(test) {
test.waitUntilDone(1000);
test.waitUntilDone();
let expected = [];
let message = 0;
if (require("@test/options").parseable) {
expected.push(["print", "TEST-START | wait4ever\n"]);
expected.push(["error", "fail:", "Timed out"]);
expected.push(["error", "test assertion never became true:\n", "assertion failed, value is false\n"]);
expected.push(["print", "TEST-END | wait4ever\n"]);
}
else {
expected.push(["info", "executing 'wait4ever'"]);
expected.push(["error", "fail:", "Timed out"]);
expected.push(["error", "test assertion never became true:\n", "assertion failed, value is false\n"]);
}
function checkExpected(name, args) {
if (expected.length == 0 || expected[0][0] != name) {
test.fail("Saw an unexpected console." + name + "() call " + args);
return;
}
message++;
let expectedArgs = expected.shift().slice(1);
for (let i = 0; i < expectedArgs.length; i++)
test.assertEqual(args[i], expectedArgs[i], "Should have seen the right message in argument " + i + " of message " + message);
if (expected.length == 0)
test.done();
}
let runner = new (require("sdk/deprecated/unit-test").TestRunner)({
console: {
calls: 0,
error: function(msg) {
this.calls++;
if (this.calls == 2) {
test.assertEqual(arguments[0], "test assertion never became true:\n");
test.assertEqual(arguments[1], "assertion failed, value is false\n");
// We could additionally check that arguments[1] contains the correct
// stack, but it would be difficult to do so given that it contains
// resource: URLs with a randomly generated string embedded in them
// (the ID of the test addon created to run the tests). And in any
// case, checking the arguments seems sufficient.
test.done();
}
else {
test.fail("We got unexpected console.error() calls from waitUntil" +
" assertion callback: '" + arguments[1] + "'");
}
error: function() {
checkExpected("error", Array.slice(arguments));
},
info: function (msg) {
this.calls++;
if (require("@test/options").parseable) {
test.fail("We got unexpected console.info() calls: " + msg)
}
else if (this.calls == 1) {
test.assertEqual(arguments[0], "executing 'wait4ever'");
}
else {
test.fail("We got unexpected console.info() calls: " + msg);
}
info: function () {
checkExpected("info", Array.slice(arguments));
},
trace: function () {},
exception: function () {},
print: function (str) {
this.calls++;
if (!require("@test/options").parseable) {
test.fail("We got unexpected console.print() calls: " + str)
}
else if (this.calls == 1) {
test.assertEqual(str, "TEST-START | wait4ever\n");
}
else if (this.calls == 3) {
test.assertEqual(str, "TEST-END | wait4ever\n");
}
else {
test.fail("We got unexpected console.print() calls: " + str);
}
print: function () {
checkExpected("print", Array.slice(arguments));
}
}
});

View File

@ -242,3 +242,107 @@ exports.testDataURLparseBase64 = function (test) {
test.assertEqual(dataURL.toString(), "data:text/plain;base64," + encodeURIComponent(b64text));
}
exports.testIsValidURI = function (test) {
validURIs().forEach(function (aUri) {
test.assertEqual(url.isValidURI(aUri), true, aUri + ' is a valid URL');
});
};
exports.testIsInvalidURI = function (test) {
invalidURIs().forEach(function (aUri) {
test.assertEqual(url.isValidURI(aUri), false, aUri + ' is an invalid URL');
});
};
function validURIs() {
return [
'http://foo.com/blah_blah',
'http://foo.com/blah_blah/',
'http://foo.com/blah_blah_(wikipedia)',
'http://foo.com/blah_blah_(wikipedia)_(again)',
'http://www.example.com/wpstyle/?p=364',
'https://www.example.com/foo/?bar=baz&amp;inga=42&amp;quux',
'http://✪df.ws/123',
'http://userid:password@example.com:8080',
'http://userid:password@example.com:8080/',
'http://userid@example.com',
'http://userid@example.com/',
'http://userid@example.com:8080',
'http://userid@example.com:8080/',
'http://userid:password@example.com',
'http://userid:password@example.com/',
'http://142.42.1.1/',
'http://142.42.1.1:8080/',
'http://➡.ws/䨹',
'http://⌘.ws',
'http://⌘.ws/',
'http://foo.com/blah_(wikipedia)#cite-1',
'http://foo.com/blah_(wikipedia)_blah#cite-1',
'http://foo.com/unicode_(✪)_in_parens',
'http://foo.com/(something)?after=parens',
'http://☺.damowmow.com/',
'http://code.google.com/events/#&amp;product=browser',
'http://j.mp',
'ftp://foo.bar/baz',
'http://foo.bar/?q=Test%20URL-encoded%20stuff',
'http://مثال.إختبار',
'http://例子.测试',
'http://उदाहरण.परीक्षा',
'http://-.~_!$&amp;\'()*+,;=:%40:80%2f::::::@example.com',
'http://1337.net',
'http://a.b-c.de',
'http://223.255.255.254',
// Also want to validate data-uris, localhost
'http://localhost:8432/some-file.js',
'data:text/plain;base64,',
'data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E',
'data:text/html;charset=utf-8,'
];
}
// Some invalidURIs are valid according to the regex used,
// can be improved in the future, but better to pass some
// invalid URLs than prevent valid URLs
function invalidURIs () {
return [
// 'http://',
// 'http://.',
// 'http://..',
// 'http://../',
// 'http://?',
// 'http://??',
// 'http://??/',
// 'http://#',
// 'http://##',
// 'http://##/',
// 'http://foo.bar?q=Spaces should be encoded',
'not a url',
'//',
'//a',
'///a',
'///',
// 'http:///a',
'foo.com',
'http:// shouldfail.com',
':// should fail',
// 'http://foo.bar/foo(bar)baz quux',
// 'http://-error-.invalid/',
// 'http://a.b--c.de/',
// 'http://-a.b.co',
// 'http://a.b-.co',
// 'http://0.0.0.0',
// 'http://10.1.1.0',
// 'http://10.1.1.255',
// 'http://224.1.1.1',
// 'http://1.1.1.1.1',
// 'http://123.123.123',
// 'http://3628126748',
// 'http://.www.foo.bar/',
// 'http://www.foo.bar./',
// 'http://.www.foo.bar./',
// 'http://10.1.1.1',
// 'http://10.1.1.254'
];
}

View File

@ -0,0 +1,56 @@
/* 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 { Loader } = require("sdk/test/loader");
const { open, getMostRecentBrowserWindow, getOuterId } = require("sdk/window/utils");
exports["test browser events"] = function(assert, done) {
let loader = Loader(module);
let { events } = loader.require("sdk/window/events");
let { on, off } = loader.require("sdk/event/core");
let actual = [];
on(events, "data", function handler(e) {
actual.push(e);
if (e.type === "load") window.close();
if (e.type === "close") {
let [ open, ready, load, close ] = actual;
assert.equal(open.type, "open")
assert.equal(open.target, window, "window is open")
assert.equal(ready.type, "DOMContentLoaded")
assert.equal(ready.target, window, "window ready")
assert.equal(load.type, "load")
assert.equal(load.target, window, "window load")
assert.equal(close.type, "close")
assert.equal(close.target, window, "window load")
// Note: If window is closed right after this GC won't have time
// to claim loader and there for this listener. It's better to remove
// remove listener here to avoid race conditions.
off(events, "data", handler);
loader.unload();
done();
}
});
// Open window and close it to trigger observers.
let window = open();
};
if (require("sdk/system/xul-app").is("Fennec")) {
module.exports = {
"test Unsupported Test": function UnsupportedTest (assert) {
assert.pass(
"Skipping this test until Fennec support is implemented." +
"See bug 793071");
}
}
}
require("test").run(exports);